[Python-ideas] Properties for classes possible?

Eric Snow ericsnowcurrently at gmail.com
Sat Aug 29 23:21:38 CEST 2015

On Thu, Aug 20, 2015 at 12:54 AM, Thomas Güttler
<guettliml at thomas-guettler.de> wrote:
> I think it would be great to have properties for classes in Python2 and
> Python3

As always there's a rich history to which we can turn: on the mailing
lists and the issue tracker.  The addition of a "class property" is
not a new idea, which implies it *might* be worth pursuing, but only
if the previous obstacles/objections are resolved. [1]

Furthermore, composition of abc.abstractmethod and
property/classmethod/staticmethod was added not that long ago.  The
evolution of that composition provides some context for what is
appropriate here. [2]  Note that first we added abc.abstractmethod,
then abc.abstractclassmethod, and then proper composition
(abc.abstractmethod + classmethod).

Though it's subtly different than the abstractmethod case, I suggest
we avoid adding "classproperty" and skip straight to getting the
composition approach working in a similar way to abstractmethod [3]:

class Spam:
   def eggs(cls):
        return 42

[Note that the alternate composition order doesn't work out since
property is a descriptor that resolves strictly against instances.
Would that be obvious enough or a point of confusion?]

Unfortunately, there is a problem with applying classmethod onto property.
Obviously a property isn't a function such that "classmethod" is an
accurately described modifier.  This is more concretely problematic
because the classmethod implementation directly wraps the decorated
object in a method object. [4]  In Python it would look like this:

class classmethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, obj, cls):
        return types.MethodType(self.func, cls)

I expect that this is an optimization over calling self.func.__get__,
which optimization was likely supported by the sensible assumption
that only functions would be passed to classmethod.  The naive
implementation of classmethod would look more like this:

class classmethod:
    def __init__(self, func):
        self.func = func
    def __get__(self, obj, cls):
        return self.func.__get__(cls, type(cls))

If that were the actual implementation then we wouldn't be having this
conversation. :)  So to get composition to work correctly we have 3

1. switch to the less efficient, naive implementation of classmethod.__get__
2. add "classproperty", which does the right thing
3. add "classresolved" (or similarly named), which resolves wrapped
descriptors to the class rather than the instance

I advocate for #3 over the others.  It provides for broader
application while not impacting the optimizations in classmethod (and
it has a more accurate name).  It would work similarly to the naive
classmethod implementation (and work as a less efficient replacement
for classmethod):

class classresolved:
    def __init__(self, wrapped):
        self.wrapped = wrapped
    def __get__(self, obj, cls):
            getter = self.wrapped.__get__
        except AttributeError:
            return self.wrapped
        return getter(cls, type(cls))

Note that wrapped data descriptors (like property) would behave as
non-data descriptors since classresolved is itself a non-data
descriptor.  The case for making it a data descriptor can be treated
separately, but I don't think that case is as strong.

All this leads to some broader observations about useful, generic
descriptors in the stdlib.  I'll open a new thread for that

> There are some "patterns" to get this working:
> http://stackoverflow.com/questions/5189699/how-can-i-make-a-class-property-in-python
>   http://stackoverflow.com/questions/128573/using-property-on-classmethods
> ... but an official solution would be more "zen of python".
> Do you think properties for classes would be useful?

I think so.  Here are use cases off the top of my head:

* read-only class attrs
* dynamically generated class attrs
* lazily generated class attrs
* class attrs that track access
* class attrs that interact with a class registry
* class attrs that replace themselves on the class upon first use
* ...

So basically stick in nearly all the use cases for properties, but
applied to classes.  The main difference is that a "class property"
would be a non-data descriptor.  Pursuing a data descriptor approach
is debatably overreaching and potentially problematic.

Note that normally bound class attrs still meet most needs, so any
solution here should be cognizant of the possibility of providing an
attractive nuisance here (and avoid it!).

> If it works for classes, then it could be used for modules, too?

Module attribute access does not involve the descriptor protocol.
There are ways to work around that to support descriptors (e.g. the
module-replaces-itself-in-sys-modules trick), but that is an
orthogonal issue.  Furthermore, using some other mechanism than the
descriptor protocol to achieve module "properties" isn't worth it
("special cases aren't special enough...").


[1] Some examples from the history of the idea:

(oct2005) https://mail.python.org/pipermail/python-list/2005-October/321426.html
an attempt to implement classproperty

(jan2011) https://mail.python.org/pipermail/python-ideas/2011-January/008950.html
Enumeration of many permutations of decorators; proposal to add
classproperty; Guido in favor; Michael shows a simple implementation

(feb2014) http://bugs.python.org/issue20659
"To get the behaviour you're requesting, you need to use a custom
metaclass and define the property there."

[2] Changes relative to abc.abstractmethod:

(apr2009) http://bugs.python.org/issue5867
Compose abc.abstractmethod and classmethod (changed to
abc.abstractclassmethod)...Guido said "I object to making changes to
the classmethod implementation."

(mar2011) http://bugs.python.org/issue11610
Compose abc.abstractmethod and property/classmethod/staticmethod/

[3] https://docs.python.org/3/library/abc.html#abc.abstractmethod
[4] https://hg.python.org/cpython/file/default/Objects/funcobject.c

More information about the Python-ideas mailing list