What is a DynamicClassAttribute and how do I use 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 resultclass access raises an
AttributeError
; when this happens Python then searches every parent class (via themro
) 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 ofEnumMeta
__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 withDynamicClassAttribute
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.
New Version:
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:
Old Version
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"