Home > Enterprise >  Python - Descriptor - Broken class
Python - Descriptor - Broken class

Time:02-15

I follow the tutorial out of the docs and an example by fluent Python. In the book they teach me how to avoid the AttributeError by get, (e.g., when you do z = Testing.x) and I wanted to do something simliar with the set method. But it seems like, it lead to a broken class with no error.

To be more specific about the issue:

  1. With outcommented line Testing.x = 1 it invoke the __set__ methods.
  2. With uncommented line #Testing.x = 1 it does not invoke the __set__ methods.

Can someone teach me why it behaves this way?

import abc

class Descriptor:
    def __init__(self):
        cls = self.__class__
        self.storage_name = cls.__name__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        print(instance,self.storage_name)
        setattr(instance, self.storage_name, value)

class Validator(Descriptor):

    def __set__(self, instance, value):
        value = self.validate(instance, value)
        super().__set__(instance, value)

    @abc.abstractmethod
    def validate(self, instance, value):
        """return validated value or raise ValueError"""

class NonNegative(Validator):
    
    def validate(self, instance, value):
        if value <= 0:
            raise ValueError(f'{value!r} must be > 0')
        return value

class Testing:
    x = NonNegative()
    def __init__(self,number):
        self.x = number

#Testing.x = 1
t = Testing(1)
t.x = 1

CodePudding user response:

The line

Testing.x = 1

replaces the descriptor you've set as a class attribute for Testing with an integer.

Since the descriptor is no more, self.x = ... or t.x = ... is just an assignment that doesn't involve a descriptor.


As an aside, surely you've noticed there is no true x attribute anymore with your descriptor, and you can't use more than one instance of the same descriptor without conflicts?

class Testing:
    x = NonNegative()
    y = NonNegative()

    def __init__(self, number):
        self.x = number


t = Testing(2345)
t.x = 1234
t.y = 5678
print(vars(t))

prints out

{'NonNegative': 5678}
  • Related