1

I've been attempting to get __get__ __set__ and __delete__ magic method working with decorators put onto the class's method (not the instance). I am entirely unable to get anything other than __get__ to work / be called. I have tried about every combination / arrangement of code but the most recent looks like this:

def my_decorator5(func: Callable[..., R]) -> R:
    """ Decorate func while preserving the signature. """

    print(f"{'*'*40} my_decorator main")

    class Wrap(object):

        def __init__(self, clsmethod) -> None:
            print(f"{'*'*40} is __init__")
            self.value = None
            self.func = func
            self.clsmethod = clsmethod
            self.func_name = func.__name__

        def __get__(self, obj, cls) -> R:
            print(f"{'*'*40} {cls} is getting")
            owner = cls
            self.value = func(cls)
            return cast(R, self.value)

        def __set__(self, cls, newval) -> None:
            print(f"{'*'*40} {cls} is setting")
            self.value = newval

        def __set_name__(self, cls, newval) -> None:
            print(f"{'*'*40} {self} is __set_name__")
            print(f"{'*'*40} {cls} is __set_name__")
            print(f"{'*'*40} {newval} is __set_name__")

        def __delete__(self, cls) -> None:
            print(f"{'*'*40} {cls} is deleting")
            delattr(self, "value")

        def __delattr__(self, key) -> None:
            print(f"{'*'*40} {self} is __delattr__")

I have test code :

class foo3:

    @my_decorator5
    def foo3_str(self) -> str:
        return "Original foo3 str"



print(f"\n{'*'*80}\n{'*'*35} STARTING {'*'*35}\n{'*'*80}\n")


print(f"\n--{dir(foo3.foo3_str)}--\n")
print(f"\n--{foo3.foo3_str.__class__}--\n")
print(f"\n--{dir(foo3.__dict__['foo3_str'])}--\n")
print(f"\n--\tatrr: {foo3.foo3_str.__class__}\n\t__dict__{foo3.__dict__['foo3_str'].__class__}\n\t{foo3.__dict__['foo3_str'].__set__(1,2)}--\n")

print(">>>> getting  >>>>")
print(f"foo3_str ==> {foo3.foo3_str}")
print(f"<<<< got foo3_str <<<<")
print("\n")

print(f"\n--\tatrr: {foo3.foo3_str.__class__}\n\t__dict__{foo3.__dict__['foo3_str'].__class__}--\n")

print(">>>> settting >>>>")
foo3.foo3_str = "Set foo3 str"
print("<<<< set foo3_str <<<<")

print(f"\n--\tatrr: {foo3.foo3_str.__class__}\n\t__dict__{foo3.__dict__['foo3_str'].__class__}--\n")


print("\n")
print(f"foo3_str ==> {foo3.foo3_str}")
print("\n")

print(f">>>> deleting foo3_str")
del foo3.foo3_str
print(f"<<<< deleted foo3_str <<<<")
print("\n")

print(">>>> getting")
try:
    print(f"foo3_str ==> {foo3.foo3_str}")

    foo3.foo3_str = "Eric"
except AttributeError:
    print("...foo3.foo3_str AttributeError")
print(" <<<< getting foo3_str")

My output with this is:

**************************************** my_decorator main
**************************************** is __init__
**************************************** <__main__.my_decorator5.<locals>.Wrap object at 0x000002D185BA6470> is __set_name__
**************************************** <class '__main__.foo3'> is __set_name__
**************************************** foo3_str is __set_name__

********************************************************************************
*********************************** STARTING ***********************************
********************************************************************************

**************************************** <class '__main__.foo3'> is getting

--['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']--

**************************************** <class '__main__.foo3'> is getting

--<class 'str'>--


--['__class__', '__delattr__', '__delete__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__set__', '__set_name__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'clsmethod', 'func', 'func_name', 'value']--

**************************************** <class '__main__.foo3'> is getting
**************************************** 1 is setting

--      atrr: <class 'str'>
        __dict__<class '__main__.my_decorator5.<locals>.Wrap'>
        None--

>>>> getting  >>>>
**************************************** <class '__main__.foo3'> is getting
foo3_str ==> Original foo3 str
<<<< got foo3_str <<<<


**************************************** <class '__main__.foo3'> is getting

--      atrr: <class 'str'>
        __dict__<class '__main__.my_decorator5.<locals>.Wrap'>--

>>>> settting >>>>
<<<< set foo3_str <<<<

--      atrr: <class 'str'>
        __dict__<class 'str'>--



foo3_str ==> Set foo3 str


>>>> deleting foo3_str
<<<< deleted foo3_str <<<<


>>>> getting
...foo3.foo3_str AttributeError
 >> getting foo3_str
**************************************** my_decorator main

When the line

foo3.foo3_str = "Set foo3 str"

I would have expected to see

**************************************** ... is setting

But instead the __set__ function is entirely ignored and the class's dict is set to an actual str type.

Is it possible to have get , set , delete on a decorator for a class property , or without an instance will only the __get__ and never the __set__ and __detele__ be run?

4
  • Your decorator wraps the function instance, not the class. Write the decorator for the class instead (or perhaps a metaclass). Commented Jul 11, 2018 at 2:36
  • Are you using Python 2 or Python 3. If using Python 2, your foo3 class would need to be derived from object for descriptors to work fully. Commented Jul 11, 2018 at 3:02
  • f-strings would indicate Python >=3.6 Commented Jul 11, 2018 at 5:08
  • Correct, Python 3.6 Commented Jul 11, 2018 at 17:06

1 Answer 1

3

A descriptor's __set__ method is only called if you assign to its name in an instance. If you assign to the class directly, you'll overwrite the descriptor (without __set__ being called). In contrast, the __get__ method gets called for lookups on either the class or an instance. Some descriptors (such as property) are programmed to to return themselves when called on the class, so it may not be as evident that their __get__ method did in fact run.

If you want to use descriptors to control access to a class variable, you need to put your descriptor in a metaclass (the class of the class object). Here's a basic example, using a property to control a class variable named foo:

class Meta(type):
    _foo = "original foo value"

    @property
    def foo(cls):
        print("getting foo")
        return cls._foo

    @foo.setter
    def foo(cls, value):
        print("setting foo")
        cls._foo = value

class Klass(metaclass=Meta):
    pass

# this invokes the property methods
print(Klass.foo)
Klass.foo = "new foo value"
print(Klass.foo)

# this won't work, the property is not accessible via an instance of Klass
obj = Klass()
obj.foo         # raises an AttributeError
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.