Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a DynamicClassAttribute and how do I use it?

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 raising AttributeError.

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?

like image 442
gerrit Avatar asked Mar 16 '16 16:03

gerrit


2 Answers

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 metaclasses (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"

like image 116
MSeifert Avatar answered Oct 02 '22 23:10

MSeifert


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 DynamicClassAttributes 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 aenum1 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.

like image 38
Ethan Furman Avatar answered Oct 03 '22 00:10

Ethan Furman