2

Property decorators are discussed at length in the documentation and the machinery which resolves them is discussed as well, but only for getters. I have been entirely unsuccessful in making a setter work for an uninstantiated class. Simple example:

My Property Decorator

class ClassyProp(object):

    """ Classmethod + property """

    def __init__(self, input_func):
        log(f"ClassProp.__init__({input_func})")
        self.func = input_func

    def __get__(self, obj, cls=None):
        log(f"ClassProp.__get__({obj}, {cls})")
        return self.func

    def __set__(self, obj, value):
        log("__ClassyProp__.__set__()")

    def setter(self, fset):
        log("__ClassyProp__.setter()")

    def fset(*args):
        log("__ClassyProp__.fset()")

Test Class

class foo(object):
    """ Test class """

    def __init__(self):
        pass

    @ClassyProp
    def f(cls):
        """ Test property_lazy_static get , set """
        pass

Run

print("======== ObjFoo ========")

ObjFoo = foo()
print(ObjFoo.f)
ObjFoo.f = 18
print(ObjFoo.f)

print("======== class foo ========")

print(foo.f)
foo.f = 15
print(foo.f)

Yields

[      00s] ClassProp.__init__(<function foo.f at 0x7f4330c88268>)
======== ObjFoo ========
[      00s] ClassProp.__get__(<__main__.foo object at 0x7f4351522748>, <class '__main__.foo'>)
<function foo.f at 0x7f4330c88268>
[      00s] __ClassyProp__.__set__()
[      00s] ClassProp.__get__(<__main__.foo object at 0x7f4351522748>, <class '__main__.foo'>)
<function foo.f at 0x7f4330c88268>
======== class foo ========
[      00s] ClassProp.__get__(None, <class '__main__.foo'>)
<function foo.f at 0x7f4330c88268>
15

NOTE: If I had ClassyProp return values it wouldn't say the function id, does say proper values for get, but I just wanted print outs for the purpose of debugging.

As you can see, the getter AND setter is called properly for the Instance of ObjFoo. But not for the uninstantiated class foo. Is having a property setter simply not possible on classes? The documentation states (I added breaks):

For objects, the machinery is in
- object.getattribute() which transforms b.x into
- type(b).dict['x'].get(b, type(b))

The implementation works through a precedence chain that
- gives data descriptors priority over instance variables,
- instance variables priority over non-data descriptors, and
- assigns lowest priority to getattr() if provided.

and

For classes, the machinery is in:
- type.getattribute() which transforms B.x into
- B.dict['x'].get(None, B)

With no mention of __set__ for either. (get obviously is working for both the class and the instance)

So I am left wondering now, is this possible? I see nothing in the documentation suggesting it isn't, but all testing suggests this.

ehiller
  • 1,346
  • 17
  • 32
  • "I am left wondering now, is this possible? " What is possible? Intercepting a set on a class property? – Ivan Nov 21 '17 at 17:15
  • 1
    That ^ thread is a little outdated, but it answers your question nonetheless. I recommend taking a look at [this](https://stackoverflow.com/a/1800999/1222951) answer. Just keep in mind that the syntax for using a metaclass has changed in python 3 (You now have to do `class foo(metaclass=MyMetaClass):`). – Aran-Fey Nov 21 '17 at 17:28
  • Also, while it doesn't explicitly answer your question about how descriptors are resolved, [here's](https://docs.python.org/3/reference/datamodel.html#invoking-descriptors) the relevant documentation. They use `__get__` as an example, but I think it works the same way for `__set__` and `__delete__`. – Aran-Fey Nov 21 '17 at 17:36
  • Despite the other threading having being set for Python 2.2, it is still all true. You will need a metaclass, the answers there provide several examples. Just be sure to use the proper Python 3 syntax to use the metaclasses in your class. – jsbueno Nov 21 '17 at 18:35

0 Answers0