As of Python 3.4, there is a descriptor called DynamicClassAttribute
. The documentation states:
types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)
Route attribute access on a class to
__getattr__
.This is a descriptor, used to define attributes that act differently when accessed through an instance and through a class. Instance access remains normal, but access to an attribute through a class will be routed to the class’s
__getattr__
method; this is done by raisingAttributeError
.This allows one to have properties active on an instance, and have virtual attributes on the class with the same name (see Enum for an example).
New in version 3.4.
It is apparently used in the enum module:
# DynamicClassAttribute is used to provide access to the `name` and # `value` properties of enum members while keeping some measure of # protection from modification, while still allowing for an enumeration # to have members named `name` and `value`. This works because enumeration # members are not set directly on the enum class -- __getattr__ is # used to look them up. @DynamicClassAttribute def name(self): """The name of the Enum member.""" return self._name_ @DynamicClassAttribute def value(self): """The value of the Enum member.""" return self._value_
I realise that enums are a little special, but I don't understand how this relates to the DynamicClassAttribute
. What does it mean that those attributes are dynamic, how is this different from a normal property, and how do I use a DynamicClassAttribute
to my advantage?
I was a bit disappointed with the previous answer so I decided to rewrite it a bit:
First have a look at the source code of DynamicClassAttribute
and you'll probably notice, that it looks very much like the normal property
. Except for the __get__
-method:
def __get__(self, instance, ownerclass=None): if instance is None: # Here is the difference, the normal property just does: return self if self.__isabstractmethod__: return self raise AttributeError() elif self.fget is None: raise AttributeError("unreadable attribute") return self.fget(instance)
So what this means is that if you want to access a DynamicClassAttribute
(that isn't abstract) on the class it raises an AttributeError
instead of returning self
. For instances if instance:
evaluates to True
and the __get__
is identical to property.__get__
.
For normal classes that just resolves in a visible AttributeError
when calling the attribute:
from types import DynamicClassAttribute class Fun(): @DynamicClassAttribute def has_fun(self): return False Fun.has_fun
AttributeError - Traceback (most recent call last)
that for itself is not very helpful until you take a look at the "Class attribute lookup" procedure when using metaclass
es (I found a nice image of this in this blog). Because in case that an attribute raises an AttributeError
and that class has a metaclass python looks at the metaclass.__getattr__
method and sees if that can resolve the attribute. To illustrate this with a minimal example:
from types import DynamicClassAttribute # Metaclass class Funny(type): def __getattr__(self, value): print('search in meta') # Normally you would implement here some ifs/elifs or a lookup in a dictionary # but I'll just return the attribute return Funny.dynprop # Metaclasses dynprop: dynprop = 'Meta' class Fun(metaclass=Funny): def __init__(self, value): self._dynprop = value @DynamicClassAttribute def dynprop(self): return self._dynprop
And here comes the "dynamic" part. If you call the dynprop
on the class it will search in the meta and return the meta's dynprop
:
Fun.dynprop
which prints:
search in meta 'Meta'
So we invoked the metaclass.__getattr__
and returned the original attribute (which was defined with the same name as the new property).
While for instances the dynprop
of the Fun
-instance is returned:
Fun('Not-Meta').dynprop
we get the overriden attribute:
'Not-Meta'
My conclusion from this is, that DynamicClassAttribute
is important if you want to allow subclasses to have an attribute with the same name as used in the metaclass. You'll shadow it on instances but it's still accessible if you call it on the class.
I did go into the behaviour of Enum
in the old version so I left it in here:
The DynamicClassAttribute
is just useful (I'm not really sure on that point) if you suspect there could be naming conflicts between an attribute that is set on a subclass and a property on the base-class.
You'll need to know at least some basics about metaclasses, because this will not work without using metaclasses (a nice explanation on how class attributes are called can be found in this blog post) because the attribute lookup is slightly different with metaclasses.
Suppose you have:
class Funny(type): dynprop = 'Very important meta attribute, do not override' class Fun(metaclass=Funny): def __init__(self, value): self._stub = value @property def dynprop(self): return 'Haha, overridden it with {}'.format(self._stub)
and then call:
Fun.dynprop
property at 0x1b3d9fd19a8
and on the instance we get:
Fun(2).dynprop
'Haha, overridden it with 2'
bad ... it's lost. But wait we can use the metaclass
special lookup: Let's implement an __getattr__
(fallback) and implement the dynprop
as DynamicClassAttribute
. Because according to it's documentation that's its purpose - to fallback to the __getattr__
if it's called on the class:
from types import DynamicClassAttribute class Funny(type): def __getattr__(self, value): print('search in meta') return Funny.dynprop dynprop = 'Meta' class Fun(metaclass=Funny): def __init__(self, value): self._dynprop = value @DynamicClassAttribute def dynprop(self): return self._dynprop
now we access the class-attribute:
Fun.dynprop
which prints:
search in meta 'Meta'
So we invoked the metaclass.__getattr__
and returned the original attribute (which was defined with the same name as the new property).
And for instances:
Fun('Not-Meta').dynprop
we get the overriden attribute:
'Not-Meta'
Well that's not too bad considering we can reroute using metaclasses to previously defined but overriden attributes without creating an instance. This example is the opposite that is done with Enum
, where you define attributes on the subclass:
from enum import Enum class Fun(Enum): name = 'me' age = 28 hair = 'brown'
and want to access these afterwards defined attributes by default.
Fun.name # <Fun.name: 'me'>
but you also want to allow accessing the name
attribute that was defined as DynamicClassAttribute
(which returns which name the variable actually has):
Fun('me').name # 'name'
because otherwise how could you access the name of 28
?
Fun.hair.age # <Fun.age: 28> # BUT: Fun.hair.name # returns 'hair'
See the difference? Why does the second one don't return <Fun.name: 'me'>
? That's because of this use of DynamicClassAttribute
. So you can shadow the original property but "release" it again later. This behaviour is the reverse of that shown in my example and requires at least the usage of __new__
and __prepare__
. But for that you need to know how that exactly works and is explained in a lot of blogs and stackoverflow-answers that can explain it much better than I can so I'll skip going into that much depth (and I'm not sure if I could solve it in short order).
Actual use-cases might be sparse but given time one can propably think of some...
Very nice discussion on the documentation of DynamicClassAttribute
: "we added it because we needed it"
What is a DynamicClassAttribute
A DynamicClassAttribute
is a descriptor that is similar to property
. Dynamic
is part of the name because you get different results based on whether you access it via the class or via the instance:
instance access is identical to property
and simply runs whatever method was decorated, returning its result
class access raises an AttributeError
; when this happens Python then searches every parent class (via the mro
) looking for that attribute -- when it doesn't find it, it calls the class' metaclass's __getattr__
for one last shot at finding the attribute. __getattr__
can, of course, do whatever it wants -- in the case of EnumMeta
__getattr__
looks in the class' _member_map_
to see if the requested attribute is there, and returns it if it is. As a side note: all that searching had a severe performance impact, which is why we ended up putting all members that did not have name conflicts with DynamicClassAttribute
s in the Enum class' __dict__
after all.
and how do I use it?
You use it just like you would property
-- the only difference is that you use it when creating a base class for other Enums. As an example, the Enum
from aenum
1 has three reserved names:
name
value
values
values
is there to support Enum members with multiple values. That class is effectively:
class Enum(metaclass=EnumMeta): @DynamicClassAttribute def name(self): return self._name_ @DynamicClassAttribute def value(self): return self._value_ @DynamicClassAttribute def values(self): return self._values_
and now any aenum.Enum
can have a values
member without messing up Enum.<member>.values
.
1 Disclosure: I am the author of the Python stdlib Enum
, the enum34
backport, and the Advanced Enumeration (aenum
) library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With