Search code examples
pythonooppropertiespycharmpython-descriptors

Is there a way to set an instance variable without calling a descriptors __set__ method?


I have made a custom descriptor which I've assigned to a value in my class. However I would like to set the instance variable initially without using the descriptors __set__ method.

class Descriptor:
    def __set_name__(self, owner, name):
        self.private_name = '_' + name

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        return getattr(instance, self.private_name)

    def __set__(self, instance, value):
        if value == 'bye':
            raise ValueError('blah')
        self.toggle(instance)
        setattr(instance, self.private_name, value)
   
    @staticmethod
    def toggle(instance):
        pass

class Foo:
    bar = Descriptor()

    def __init__(self, bar):
        # no need to call descriptor initially for some reason ok? but is still called (unnecessary)
        self.bar = bar

foo = Foo('hi')
foo.bar = 'bye'  # descriptor needed here and with every subsequent change to bar.

Is there a way to work around this and set self.bar directly?
I have this question as originally I was using the @property descriptor, which when used allowed both the descriptor method and the private variable to be called, which was exactly what I needed. Since @property is a descriptor there must be a sensible way to do this (Even though the reason this works with @property is that the private variable is defined within the scope of my class, rather than the descriptor class as done with custom descriptors which allows for both to be modified within the class, but only the public version outside the scope of the class (I know private variables are not truly private but that's beside the point).) For example this same code using @property:

class Foo:
    def __init__(self, bar):
        # no need to call setter initially
        self._bar = bar

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, value):
        self._bar = value

foo = Foo('hi')
foo.bar = 'bye'  # property setter called.

However in this case, the getters and setters take up way more room when more instance variables are introduced, and the toggle method from the original decorator has to be rewritten for each new setter added (which is what caused me to rewrite the @properties as a custom descriptor class as it seemed like a place to use oop to not have to repeat myself).


Solution

  • None of that scope stuff you were thinking about actually matters. Your property sets self._bar, and you bypass it by setting self._bar directly. Your custom descriptor does the exact same thing, just through setattr, so you still bypass it by setting the instance's _bar attribute directly:

    class Foo
        bar = Descriptor()
        def __init__(self, bar):
            self._bar = bar