tyleax
tyleax

Reputation: 1780

Python - Can same property logic be applied to multiple attributes?

Is there a way to apply the same property logic to a set of attributes in a class? For example, I want to apply the same @attr1.setter decorator to attr2, attr3, and attr4 without having to define the property for each attribute.

class Sample:
    def __init__(self):
        self.attr1 = None
        self.attr2 = None
        self.attr3 = None
        self.attr4 = None

    @property
    def attr1(self):
        return self.__attr1

    @attr1.setter
    def attr1(self, val):
        if val < 0:
            self.__attr1 = 0
        else:
            self.__attr1 = val

Upvotes: 4

Views: 695

Answers (2)

slybloty
slybloty

Reputation: 6506

You could override the __getattr__ and __setattr__ to behave the way you want them. This way you don't need to define any private variables nor initialize any of the member variables either.

class Sample:
    def __getattr__(self, attr):
        return self.__dict__.get(attr)

    def __setattr__(self, attr, val):
        if val is not None and val < 0:
            self.__dict__[attr] = 0
        else:
            self.__dict__[attr] = val

s = Sample()

print(s.attr1) # None
s.attr1 = 10
print(s.attr1) # 10
s.attr1 = -10
print(s.attr1) # 0
s.attr1 = None
print(s.attr1) # None

Upvotes: 1

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95873

Just create your own descriptor for this:

class MyDescriptor:
    def __set_name__(self, owner, name):
        self.name = f'_{name}'
    def __get__(self, instance, owner):
        return getattr(instance, self.name)
    def __set__(self, instance, val):
        if val is None:
            setattr(instance, self.name, None)
        elif val < 0:
            setattr(instance, self.name, 0)
        else:
            setattr(instance, self.name, val)

class Sample:
    attr1 = MyDescriptor()
    attr2 = MyDescriptor()
    attr3 = MyDescriptor()
    attr4 = MyDescriptor()
    def __init__(self):
        self.attr1 = None
        self.attr2 = None
        self.attr3 = None
        self.attr4 = None

Now, in action:

In [3]: s = Sample()

In [4]: s.attr1 = -99

In [5]: s.attr1
Out[5]: 0

In [6]: s.attr2

In [7]: s.attr2 = 10

In [8]: s.attr2
Out[8]: 10

In [9]: s.attr2 = -1

In [10]: s.attr2
Out[10]: 0

See the Descriptor HOWTO and some more relevant documentation

Note, I incorporated the possibility of None in your setter logic (your code would have raised a TypeError on initialization of an instance, because the setter checks if None < 0). Also note, you probably don't want to be using double-underscore name-mangling (which doesn't mean private), so I used the conventional single-underscore to denote a variable not part of the public api. Using double-underscore name-mangling complicates things here.

Upvotes: 1

Related Questions