Class with read-only attributes - python

I want to create a class with attributes that can be __setattr__-ed by its methods internally, so an attempt like self.attr = value would raise an AttributeError. This is what I have so far:
class MyClass():
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def __repr__(self):
return '%r class with a=%s, b=%s, c=%s' % (self, self.a, self.b, self.c)
def __setattr__(self,attr,value):
raise AttributeError('%r is read-only' % self)
def setattr_(self,attr,value):
self.attr = value
>>> obj = MyClass(1,2,3)
>>> obj.setattr_(a,4) # obj.a = 4
AttributeError: 'obj' is read-only # __setattr__ method also applies internally

This is a use case for properties. Properties without a setter are read-only. In the following, a and b are read-only, while c is not.
class MyClass:
def __init__(self, a, b, c):
self._a = a
self.b = b
self._c = c
# a is a read-only property
#property
def a(self):
return self._a
# b is an ordinary attribute
# c is a property you can set
#property
def c(self):
return self._c
#c.setter
def c(self, value):
self._c = value
Since you have defined only getters for the a, attempts to
change its value will fail. Attempts to change b will succeed as expected. Attempts to change c will succeed as
if it were a regular attribute.
>>> obj = MyClass(1,2,3)
>>> obj.a = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> obj.b = 5
>>> obj.c = 6
>>> obj.c
6

You can use properties in Python for this type of tasks. First, you make your attribute 'private' by adding two underscores, then you create a getter method with the #property decorator:
class MyClass:
def __init__(self, a, b, c):
self.__a, self.__b, self.__c = a, b, c
#property
def a(self):
return self.__a
#property
def b(self):
return self.__b
#property
def c(self):
return self.__c
Now, you can use your class like this:
>>> my_object = MyClass('foo', 'bar', 'bar')
>>> print(my_object.b)
bar
>>> my_object.b = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Note
I wrote 'private' because you can still access it if you really want:
>>> my_object._MyClass__b = 42
>>> print(my_object.b)
42
This has to do with the Zen of Python: "We’re all consenting adults here".

Please use the properties.
Anyway, it is good to understand the internals, here is a working code based on your question. Just to play with.
Once you redefine __setattr__ to fail, there is no way to set an attribute in that class. But there is still a working __setattr__ left in the parent class.
class MyClass():
def __init__(self, a, b, c):
self.setattr_('a', a)
self.setattr_('b', b)
self.setattr_('c', c)
def __setattr__(self,attr,value):
raise AttributeError('%r is read-only' % self)
def setattr_(self,attr,value):
super().__setattr__(attr, value)
obj = MyClass(1,2,3)
obj.setattr_('a',4) # note that a is a name (string)

Related

How do classes inherited from namedtuple maintain access to parent properties with a redefined __init__?

Minimum Reproducible Example:
from collections import namedtuple
class Test(namedtuple('Test', ['a','b'])):
def __init__(self, a, b):
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND'))
OUTPUT:
FIRSTSECOND
I thought when an __init__ function is defined, it overwrites the parent implementation. If that is the case, how do self.a and self.b exist with the correct values? If I forego the a and b parameters in __init__, I get a TypeError: __init__() takes 1 positional argument but 3 were given. I need to provide the parameters, but they're not being set explicitly in __init__ either, and I have no called to super().
self.a and self.b are set by the named tuple's __new__ method before __init__ is called. This is because a named tuple is immutable (aside from the ability to add additional attributes, as Test.__init__ does), so trying to set a and b after the tuple is created would fail. Instead, the values are passed to __new__ so that the values are available when the tuple is being created.
Here's an example of __new__ being overriden to swap the a and b values.
class Test(namedtuple('Test', ['a','b'])):
def __new__(cls, a, b, **kwargs):
return super().__new__(cls, b, a, **kwargs)
def __init__(self, a, b):
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND')) # outputs SECONDFIRST
Trying to do the same with __init__ would fail:
class Test(namedtuple('Test', ['a','b'])):
def __init__(self, a, b):
self.a, self.b = b, a
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND')) # outputs SECONDFIRST
results in
Traceback (most recent call last):
File "/Users/chepner/advent-of-code-2020/tmp.py", line 11, in <module>
print(Test('FIRST', 'SECOND'))
File "/Users/chepner/advent-of-code-2020/tmp.py", line 5, in __init__
self.a, self.b = b, a
AttributeError: can't set attribute
To make c immutable as well (while keeping it distinct from the tuple itself), use a property.
class Test(namedtuple('Test', ['a','b'])):
#property
def c(self):
return self.a + self.b
def __str__(self):
return self.c
Note that c is not visible or accessible when treating an instance of Test as a regular tuple:
>>> x = Test("First", "Second")
>>> x
Test(a='First', b='Second')
>>> len(x)
2
>>> tuple(x)
('First', 'Second')
>>> x[0]
'First'
>>> x[1]
'Second'
>>> x[2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>> x.c = "foo"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

encapsulating class calls/modifies things in the dir of original class

I have a class A encapsulating a class B instance and additional stuff. The following is a toy example.
class B(object):
def __init__(self):
self.b = 2
def square(self):
return self.b * self.b
class A(object):
def __init__(self, x):
self.b = B()
a = A(1)
print(a.b.square())
Any time an A instance wants to call a method in B, I always need to do things like 'a.b'. My hope is to get rid of '.b' for user convenience. The following codes do the job.
class B(object):
def __init__(self):
self.b = 2
def square(self):
return self.b * self.b
class A(object):
def __init__(self, x):
self.b = B()
def square(self):
return self.b.square()
a = A(1)
print(a.square())
The problem is that class B is from outside library and there are lots of and different types of things in the dir. I couldn't do it one by one manually like above. Any magical ways to handle that?
Any magical ways to handle that?
It's python, of course there are! You can use __getattr__ function to proxy unknown calls to b:
class B(object):
def shadowed(self):
print('B.shadowed')
def unshadowed(self):
print('B.unshadowed')
class A(object):
def __init__(self):
self._b = B()
def shadowed(self):
print('A.shadowed')
def __getattr__(self, name):
return getattr(self._b, name)
test = A()
test.shadowed()
test.unshadowed()
test.unknown()
Result:
A.shadowed
B.unshadowed
Traceback (most recent call last):
File "/Users/Andrew/Desktop/test.py", line 23, in <module>
test.unknown()
File "/Users/Andrew/Desktop/test.py", line 17, in __getattr__
return getattr(self._b, name)
AttributeError: 'B' object has no attribute 'unknown'
__getattr__ is called when the object doesn't have attribute that's being asked for.

pythonic class instance attribute calculated from other attributes

I have a class instance with attributes that are calculated from other attributes. The attributes will change throughout the life of the instance. All attributes are not necessarily defined when the object is initialized.
what is the pythonic way to calculate attributes from other attributes?
This is a simple example, the calculations have numerous input variables ("a" below) and calculations ("b" & "c").
a = something
b = function of a (a+5)
c = function of a and b (a*b)
I've tried numerous implementations. Here is a decent one to communicate my intention.
class CalcAttr(object):
def __init__(self):
self._a = None
self._b = None
self._c = None
#property
def a(self):
return self._a
#a.setter
def a(self,value):
self._a = value
#property
def b(self):
self.calc_b()
return self._b
#property
def c(self):
self.calc_c()
return self._c
def calc_b(self):
self._b = self._a + 5
def calc_c(self):
self._c = self._a * self._b
def test():
abc = CalcAttr()
a = 5
return abc.c
Note: t.c works if I first call t.b first.
> >>> t=abc.test()
> >>> t.c Traceback (most recent call last): File "<stdin>", line 1, in <module> File "abc.py", line 22, in c
> self.calc_c() File "abc.py", line 29, in calc_c
> self._c = int(self._a) * int(self._b) TypeError: int() argument must be a string or a number, not 'NoneType'
> >>> t.b 10
> >>> t.c 50
> >>>
Keep in mind most of the real calculations are dependent on multiple attribures (5-10 input variables & as many calculated ones).
My next iteration will include a "calculate_model" function that will populate all calculated attributes after checking that all inputs are defined. Maybe that will be the pyhonic answer?
Thanks!
Update - working solution
I created a method that calculates each attribute in order:
def calc_model(self):
self.calc_b()
self.calc_c()
Each calculated attribute calls that method
#property
def c(self):
self.calc_model()
return self._c
I'm not sure if this is proper, but it works as desired...
If I understand your question correctly, you should compute b and c in their getters. You should also probably require that the user passes a value for a in the initializer, since b and c can't be computed without a. Also, it doesn't seem like there is much of a reason to keep _a, _b, and _c around -- unless b and c are expensive to compute and you'd like to cache them.
For example:
class CalcAttr(object):
def __init__(self, a):
self.a = a
#property
def b(self):
return self.a + 5
#property
def c(self):
return self.a * self.b
Such that
>>> x = CalcAttr(42)
>>> x.c
1974
I understand what #jme suggested in the accepted answer is more elegant, but I still try to fix the original example and get it to work. Here is the code.
class CalcAttr(object):
def __init__(self):
self._a = None
self._b = None
self._c = None
#property
def a(self):
return self._a
#a.setter
def a(self,value):
self._a = value
#property
def b(self):
self.calc_b()
return self._b
#property
def c(self):
self.calc_c()
return self._c
def calc_b(self):
self._b = self._a + 5
def calc_c(self):
self._c = self.a * self.b
def test():
abc = CalcAttr()
abc.a = 5
return abc.c
test()
The code will work and 50 is the resulted value.

inheriting from list and other object

I have a class A defining basics behaviour of my object and a class B inheriting from list and C inheriting from str
class A(object):
def __init__(self, a):
self.a = a
class B(list, A):
def __init__(self, inputs, a):
A.__init__(self, a)
return list.__init__(self, [inputs])
class C(str, A):
def __new__(self, input, a):
return str.__new__(self, input)
def __init__(self, inputs, a):
A.__init__(self, a)
def __init__(self, input, a):
A.__init__(self, a)
What I'd like is that the user build object B or C which behaves like a list or a str, those classes just have metadata usefull for our application but not for the user ... using class B is easy, if I want to change the values, I can clear it or append new values ... but how can I modify the value of a C object. I checked setattr but this one required an attribute name ...
thanks,
Jerome
This works:
>>> class A(object):
... def __init__(self, a):
... self.a = a
...
>>> class B(list, A):
... def __init__(self, inputs, a):
... A.__init__(self, a)
... return list.__init__(self, [inputs])
...
>>> class C(str, A):
... def __new__(self, input, a):
... return str.__new__(self, input)
... def __init__(self, inputs, a):
... A.__init__(self, a)
...
>>> c = C('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __new__() takes exactly 3 arguments (2 given)
>>> c = C('foo', 1)
>>> c
'foo'
>>> c.a
1
>>> c.a = 2
>>> c.a
2
>>>
You can change the metadata on a C instance. Like str, you can't change the value of the characters it contains.
If you want a mutable string, you're going to have to create that in pure python. However, given that everyone else gets by without that, consider whether you can use the built in facilities, such as TextStream.
You can't. Strings are immutable - you cannot change their value once created. Lists are mutable, which is why you can change their value (contents) after creation.

Using super() in a property's setter method when using the #property decorator raises an AttributeError

I am a little confused by the behavior when attempting to overwrite a property in a subclass.
The first example sets up two classes, Parent and Child. Parent inherits from object, while Child inherits from Parent. The property a is defined using the property decorator. When child.a's setter method is called, an AttributeError is raised.
In the second example, by using the property() function rather than the decorator, everything works as would be expected.
Can anyone shed some light on why the behavior differs? Also, yes, I know that the __init__ definition in Child is not needed.
Example 1 - Using #property
class Parent(object):
def __init__(self):
self._a = 'a'
#property
def a(self):
return self._a
#a.setter
def a(self, val):
self._a = val
class Child(Parent):
def __init__(self):
super(Child, self).__init__()
#property
def a(self):
return super(Child, self).a
#a.setter
def a(self, val):
val += 'Child'
super(Child, self).a = val
p = Parent()
c = Child()
print p.a, c.a
p.a = 'b'
c.a = 'b'
print p.a, c.a
Example 1 return - Raises an attribute error
a a
Traceback (most recent call last):
File "testsuper.py", line 26, in <module>
c.a = 'b'
File "testsuper.py", line 20, in a
super(Child, self).a = val
AttributeError: 'super' object has no attribute 'a'
Example 2 - Using property()
class Parent(object):
def __init__(self):
self._a = 'a'
def _get_a(self):
return self._a
def _set_a(self, val):
self._a = val
a = property(_get_a, _set_a)
class Child(Parent):
def __init__(self):
super(Child, self).__init__()
def _get_a(self):
return super(Child, self)._get_a()
def _set_a(self, val):
val = val+'Child'
super(Child, self)._set_a(val)
a = property(_get_a, _set_a)
p = Parent()
c = Child()
print p.a, c.a
p.a = 'b'
c.a = 'b'
print p.a, c.a
Example 2 return - Works correctly
a a
b bChild
super() returns a proxy object, not a superclass, and it doesn't support the function __set__().
And you can see more details here Python super and setting parent class property and here http://bugs.python.org/issue14965.

Categories