Search code examples
pythonclasssetattr

Python classes, difference between setting an attribute and using __setattr__()


I'm trying to set attributes to a class of which I don't know the name a-priori. I also want to avoid users to write to that attribute, so I use a property factory with getters and setters which returns a property object. However, when calling the property object, I get the reference to that object, instead of whatever the getter should be returning.

So I try to do this:

def property_factory(name):
    def getter(self):
        return self.__getattribute__(name)

    def setter(self, value):
        raise Exception('Cannot set a value')

    return property(getter, setter)

# This is just a read_file placeholder
class read_file(object):
    def __init__(self):
        self.name = 'myName'
        self.value = 'myValue'

    def __iter__(self):
        return self

class a(object):
    list1 = read_file()

    def __init__(self):

        list1 = read_file()

        self.__setattr__('_' + list1.name, list1.value)
        # this doesn't work:
        self.__setattr__(list1.name, property_factory('_' + list1.name))
        
        # this actually does work, but with the wrong attribute name:

    notMyName = property_factory('_' + list1.name)

Then I get this:

In [38]: b = a()

In [39]: b.myName
Out[39]: <property at 0x2883d454450>

In [40]: b.notMyName
Out[40]: 'myValue'

In [41]: b.notMyName = 'true'
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In[41], line 1
----> 1 b.notMyName = 'true'

Cell In[37], line 6, in property_factory.<locals>.setter(self, value)
      5 def setter(self, value):
----> 6     raise Exception('Cannot set a value')

Exception: Cannot set a value

What I want is this:

In [39]: b.myName
Out[40]: 'myValue'

In [41]: b.MyName = 'true'
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In[41], line 1
----> 1 b.MyName = 'true'

Cell In[37], line 6, in property_factory.<locals>.setter(self, value)
      5 def setter(self, value):
----> 6     raise Exception('Cannot set a value')

Exception: Cannot set a value

How do I do this?


Solution

  • Why do you want to do this? I've always gone back to this answer whenever I have an idea that uses the notion of dynamically-named attributes -- which is essentially what you're trying to do here if I'm not mistaken (with added read-only "protection" applied only to the keys in list1). Do you need to use a property factory? You could do something like this:

    class A(object):
        list1 = read_file()
        def __init__(self):
            self.__dict__[A.list1.name] = A.list1.value
        def __setattr__(self, name, value):
            if name == A.list1.name:
                raise Exception('Cannot set a value for this key!')
    

    Now at least this works:

    >>> b = A()
    >>> b.myName
    'myValue'
    >>> b.myName = 'true'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 7, in __setattr__
    Exception: Cannot set a value for this key!
    

    However both methods will be susecptable to the following:

    >>> b.__dict__['myName'] = 'true'
    >>> b.myName
    'true'
    

    Obviously there's a lot of optimization to be done here, adding sentinels, name mangling, etc, plus I'd need a lot more information regarding ultimately what you're trying to achieve and why -- but is this getting a little closer to what you want? I'll delete this answer (or tidy) if necessary, too long for a comment. Also, typo:

    In [41]: b.MyName = 'true'
    

    Should be myName.