views:

42

answers:

2

I'd like to have some data descriptors as part of a class. Meaning that I'd like class attributes to actually be properties, whose access is handled by class methods.

It seems that Python doesn't directly support this, but that it can be implemented by subclassing the type class. So adding a property to a subclass of type will cause its instances to have descriptors for that property. Its instances are classes. Thus, class descriptors.

Is this advisable? Are there any gotchas I should watch out for?

+1  A: 

Is this what you mean by "class attributes to actually be properties, whose access is handled by class methods"?

You can use a decorator property to make an accessor appear to be an actual data member. Then, you can use the x.setter decorator to make a setter for that attribute.

Be sure to inherit from object, or this won't work.

class Foo(object):
    def __init__(self):
            self._hiddenx = 3
    @property
    def x(self):
            return self._hiddenx + 10
    @x.setter
    def x(self, value):
            self._hiddenx = value

p = Foo()

p.x #13

p.x = 4
p.x #14
orangeoctopus
No, I want to be able to do `Foo.x` to invoke `x(cls)`.
intuited
+1  A: 

It is convention (usually), for a descriptor, when accessed on a class, to return the descriptor object itself. This is what property does; if you access a property object on a class, you get the property object back (because that's what it's __get__ method chooses to do). But that's a convention; you don't have to do it that way.

So, if you only need to have a getter descriptor on your class, and you don't mind that a an attempt to set will overwrite the descriptor, you can do something like this with no metaclass programming:

def classproperty_getter_only(f):
    class NonDataDescriptor(object):
        def __get__(self, instance, icls):
            return f(icls)
    return NonDataDescriptor()

class Foo(object):

    @classproperty_getter_only
    def flup(cls):
        return 'hello from', cls

print Foo.flup
print Foo().flup

for

('hello from', <class '__main__.Foo'>)
('hello from', <class '__main__.Foo'>)

If you want a full fledged data descriptor, or want to use the built-in property object, then you're right you can use a metaclass and put it there (realizing that this attribute will be totally invisible from instances of your class; metaclasses are not examined when doing attribute lookup on an instance of a class).

Is it advisable? I don't think so. I wouldn't do what you're describing casually in production code; I would only consider it if I had a very compelling reason to do so (and I can't think of such a scenario off the top of my head). Metaclasses are very powerful, but they aren't well understood by all programmers, and are somewhat harder to reason about, so their use makes your code harder to maintain. I think this sort of design would be frowned upon by the python community at large.

Matt Anderson
Great response, thanks. When you say *metaclass*, are you referring to what I was describing, ie subclassing `type`? This terminology seems ambiguous, since it's distinct from using the `__metaclass__` attribute, which as I understand it only governs class *creation*. Is there a way to distinguish between these two concepts? Or is subclassing `type` so rarely done that there's little need?
intuited
To my knowledge, subclassing `type` is really only useful if you use it as a `__metaclass__`. The relationship between a class and it's metaclass is completely analogous to the relationship between an object instance and its class. The class of a class. When you do `print MyClass`, resulting in an implicit `str(MyClass)` (the text we print), the method that actually gets called to produce that text is `type.__str__` (or the `__str__` method on your custom metaclass). Metaclass customization *typically* does its thing during class construction, but can be customized in other ways.
Matt Anderson
Okay I get it now. I was actually calling the metaclass as with a 3-argument call to `type` to create the class. I just didn't understand what `__metaclass__` does. I guess I was confused by the documentation's assertion that its effects take place at class construction. But then the same is true of the class declaration itself :) Thanks again.
intuited