immutable objects in Python that can have weak references - python

I've been subclassing tuple or using namedtuple blissfully for a few years, but now I have a use case where I need a class that can be used as a weak referent. And today I learned tuples don't support weak references.
Is there another way to create an immutable object in Python with a fixed set of attributes? I don't need the numeric indexing or variable width of a tuple.
class SimpleThingWithMethods(object):
def __init__(self, n, x):
# I just need to store n and x as read-only attributes
... ??? ...
I guess this raises the obvious question of why immutable; "Pythonic" code usually just assumes we're all adults here and no one in their right mind would reach into a class and muck with its values if it risks ruining the class invariants. In my case I have a class in a library and I am worried about accidental modification of objects by end-users. The people I work with sometimes make incorrect assumptions about my code and start doing things I did not expect, so it's much cleaner if I can raise an error if they accidentally modify my code.
I'm not so worried about bulletproof immutability; if someone really nefarious wants to go and modify things, ok, fine, they're on their own. I just want to make it hard to accidentally modify my objects.

well, this isn't a great answer but it looks like I can modify the answer in https://stackoverflow.com/a/4828492/44330 --- essentially overriding __setattr__ and __delattr__ to meet my needs at least against accidental modification. (but not as nice as subclassing tuple)
class Point(object):
__slots__ = ('x','y','__weakref__')
def __init__(self, x, y):
object.__setattr__(self, "x", x)
object.__setattr__(self, "y", y)
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return self.x.__hash__() * 31 + self.y.__hash__()
Implementing #Elazar's idea:
class Point(object):
__slots__ = ('x','y','__weakref__')
def __new__(cls, x, y):
thing = object.__new__(cls)
object.__setattr__(thing, "x", x)
object.__setattr__(thing, "y", y)
return thing
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return self.x.__hash__() * 31 + self.y.__hash__()

If you don't worry about isinstance checks, you can strengthen you answer:
def Point(x, y):
class Point(object):
__slots__ = ('x','y','__weakref__')
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
def __eq__(self, other):
return x == other.x and y == other.y
def __hash__(self):
return x.__hash__() * 31 + y.__hash__()
p = Point()
object.__setattr__(p, "x", x)
object.__setattr__(p, "y", y)
return p
I don't really recommend it (every invocation creates a class!), just wanted to note the possibility.
It is also possible to go javascript all the way, and supply __getattr__ that will access the local variables. But that will also slow down access, in addition to creation. Now we don't need these slots at all:
class MetaImmutable:
def __setattr__(self, name, val):
raise TypeError
def Point(x, y):
class Point(object):
__metaclass__ = MetaImmutable
__slots__ = ('__weakref__',)
def __getattr__(self, name):
if name == 'x': return x
if name == 'y': return y
raise TypeError
#property
def x(self): return x
#property
def y(self): return y
def __eq__(self, other):
return x == other.x and y == other.y
def __hash__(self):
return x.__hash__() * 31 + y.__hash__()
return Point()
Test it:
>>> p = Point(1, 2)
>>> p.y
2
>>> p.z
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __getattr__
TypeError
>>> p.z = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> object.__setattr__(p, 'z', 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'
>>> from weakref import ref
>>> ref(p)().x
1
>>> type(p).x = property(lambda self: 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __setattr__
TypeError
And finally, you can still break it:
>>> type.__setattr__(type(p), 'x', property(lambda self: 5))
>>> p.x
5
Again, nothing here is recommended. Use #Jasons implementation.

What about using encapsulation and abstraction on the parameter (getter?):
class SimpleThingWithMethods(object):
def __init__(self, n, x):
self._n = n
self._x = x
def x(self):
return self._x
def n(self):
return self._n
SimpleThingWithMethods(2,3).x()
=> 3

Related

Why I can't fetch an attribute inside object's __setattr__ method?

I have a class with attributes that I could changed after check the others attributes, so I coded this class:
class MyClass():
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
print(self.x) # doesn't work
self.__dict__[name] = value
if __name__ == '__main__':
myObj = MyClass(1)
myObj.x = 2
But I got this error
AttributeError: 'MyClass' object has no attribute 'x'
If I don't have print(self.x) the attribute is rewrite but I need to check the other attributes for changing another one.
I tried self.__dict__[name] and getattr(self, name) but I got the same error.
The full error message is informative:
Traceback (most recent call last):
File "example_code.py", line 11, in <module>
myObj = MyClass(1)
File "example_code.py", line 3, in __init__
self.x = x
File "example_code.py", line 6, in __setattr__
print(self.x) # doesn't work
AttributeError: 'MyClass' object has no attribute 'x'
You're seeing an error on the initialization of MyClass, which in turn calls self.x = x, which in turn is calling into your custom __setattr__ implementation to do the work. At this point it's trying to print x, but this is before x assigned to the class, since you haven't done that work yet.
There are a few ways to work around this, the most direct is probably to verify your class actually has the attribute before you try to access it:
class MyClass():
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
if hasattr(self, 'x'):
print(self.x) # works now
self.__dict__[name] = value
if __name__ == '__main__':
myObj = MyClass(1)
myObj.x = 2

Way to use self in method default argument?

For a method of a class I want the following behaviour
>>class A:
>> def __init__(self, x):
>> self.x = x
>> def func(self, x = self.x):
>> print(x)
>>a = A(5)
>>a.func(2)
2
>>a.func()
5
But I get this error for the declaration of func():
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in A
NameError: name 'self' is not defined
Is there a recommended way to achieve this behaviour?
Use a sentinel value; None typically suffices.
class A:
def __init__(self, x):
self.x = x
def func(self, x=None):
if x is None:
x = self.x
print(x)
If, for whatever reason, None could be a valid argument, you can create your own sentinel.
_sentinel = object()
class A:
def __init__(self, x):
self.x = x
def func(self, x=_sentinel):
if x is _sentinel:
x = self.x
print(x)
You cannot refer to self in a function declaration, since at that point self indeed doesn't exist (as the error says). The idiomatic way is:
def func(self, x = None):
if x is None:
x = self.x
print(x)
Or perhaps:
def func(self, x = None):
print(x or self.x)
(Though note that falsey isn't the same as None and may hence behave differently.)

Avoid unauthorized value assignment to a python class attribute

Assume a class like this, where attribute x has to be either an integer or a float:
class foo(object):
def __init__(self,x):
if not isinstance(x,float) and not isinstance(x,int):
raise TypeError('x has to be a float or integer')
else:
self.x = x
Assigning a non-integer and non-float to x will return an error when instantiating the class:
>>> f = foo(x = 't')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
TypeError: x has to be a float or integer
But the direct assignment of x does not return any errors:
>>> f = foo(x = 3)
>>> f.x = 't'
>>>
How can I make python raise an error in the latter case?
You could use the Descriptor protocol, although the syntax is a little bit more complicated:
from types import IntType, LongType, FloatType
AllowedTypes = IntType, LongType, FloatType
class NumberDescriptor(object):
def __init__(self, name):
self._name = name
def __set__(self, instance, value):
if not isinstance(value, AllowedTypes):
raise TypeError("%s must be an int/float" % self._name)
instance.__dict__[self._name] = value
class A(object):
x = NumberDescriptor("x")
def __init__(self, x):
self.x = x
if __name__ == "__main__":
a1 = A(1)
print a1.x
a2 = A(1.4)
print a2.x
#a2 = A("1")
a2.x = 4
print a2.x
a1.x = "2"
print a1.x
Use a property:
class Foo(object):
def __init__(self, x):
self.x = x
#property
def x(self):
return self._x
#x.setter
def x(self, x):
if not isinstance(x,float) and not isinstance(x,int):
raise TypeError('x has to be a float or integer')
self._x = x
If you find yourself needing to do this a lot you might want to look into Traits or Traitlets.

Mixin class to trace attribute requests - __attribute__ recursion

I'm trying to create a class which must be superclass of others, tracing their attribute requests. I thought of using "getattribute" which gets all attribute requests, but it generates recursion:
class Mixin(object):
def __getattribute__ (self, attr):
print self, "getting", attr
return self.__dict__[attr]
I know why I get recursion: it's for the self.dict call which recalls getattribute recursively. I've tryied to change last line in "return object.__getattribute__(self,attr)" like suggested in other posts but recursion is recalled.
Try this:
class Mixin(object):
def __getattribute__ (self, attr):
print self, "getting", attr
return object.__getattribute__(self, attr)
If you are still getting recursion problems, it is caused by code you haven't shown us
>>> class Mixin(object):
... def __getattribute__ (self, attr):
... print self, "getting", attr
... return object.__getattribute__(self, attr)
...
>>> Mixin().__str__
<__main__.Mixin object at 0x00B47870> getting __str__
<method-wrapper '__str__' of Mixin object at 0x00B47870>
>>> Mixin().foobar
<__main__.Mixin object at 0x00B47670> getting foobar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattribute__
AttributeError: 'Mixin' object has no attribute 'foobar'
>>>
And here is the result when combined with Bob's Mylist
>>> class Mylist(Mixin):
... def __init__ (self, lista):
... if not type (lista) == type (""):
... self.value = lista[:]
... def __add__ (self,some):
... return self.value + some
... def __getitem__ (self,item):
... return self.value[item]
... def __getslice__ (self, beg, end):
... return self.value[beg:end]
...
>>> a=Mylist([1,2])
>>> a.value
<__main__.Mylist object at 0x00B47A90> getting value
[1, 2]
This is the code:
from Es123 import Mixin
class Mylist(Mixin):
def __init__ (self, lista):
if not type (lista) == type (""):
self.value = lista[:]
def __add__ (self,some):
return self.value + some
def __getitem__ (self,item):
return self.value[item]
def __getslice__ (self, beg, end):
return self.value[beg:end]
a = Mylist ([1,2])
a.value
Then python returns "RuntimeError: maximum recursion depth exceeded"

Why does #foo.setter in Python not work for me?

So, I'm playing with decorators in Python 2.6, and I'm having some trouble getting them to work. Here is my class file:
class testDec:
#property
def x(self):
print 'called getter'
return self._x
#x.setter
def x(self, value):
print 'called setter'
self._x = value
What I thought this meant is to treat x like a property, but call these functions on get and set. So, I fired up IDLE and checked it:
>>> from testDec import testDec
from testDec import testDec
>>> t = testDec()
t = testDec()
>>> t.x
t.x
called getter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "testDec.py", line 18, in x
return self._x
AttributeError: testDec instance has no attribute '_x'
>>> t.x = 5
t.x = 5
>>> t.x
t.x
5
Clearly the first call works as expected, since I call the getter, and there is no default value, and it fails. OK, good, I understand. However, the call to assign t.x = 5 seems to create a new property x, and now the getter doesn't work!
What am I missing?
You seem to be using classic old-style classes in python 2. In order for properties to work correctly you need to use new-style classes instead (in python 2 you must inherit from object). Just declare your class as MyClass(object):
class testDec(object):
#property
def x(self):
print 'called getter'
return self._x
#x.setter
def x(self, value):
print 'called setter'
self._x = value
It works:
>>> k = testDec()
>>> k.x
called getter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/devel/class_test.py", line 6, in x
return self._x
AttributeError: 'testDec' object has no attribute '_x'
>>> k.x = 5
called setter
>>> k.x
called getter
5
>>>
Another detail that might cause problems is that both methods need the same name for the property to work. If you define the setter with a different name like this it won't work:
#x.setter
def x_setter(self, value):
...
And one more thing that is not completely easy to spot at first, is the order: The getter must be defined first. If you define the setter first, you get name 'x' is not defined error.
Just a note for other people who stumble here looking for this exception: both functions need to have the same name. Naming the methods as follows will result in an exception:
#property
def x(self): pass
#x.setter
def x_setter(self, value): pass
Instead give both methods the same name
#property
def x(self): pass
#x.setter
def x(self, value): pass
It is also important to note that the order of the declaration matters. The getter must be defined before the setter in the file or else you will get a NameError: name 'x' is not defined
You need to use new-style classes which you do by deriving your class from object:
class testDec(object):
....
Then it should work.
In case anybody comes here from google, in addition to the above answers I would like to add that this needs careful attention when invoking the setter from the __init__ method of your class based on this answer
Specifically:
class testDec(object):
def __init__(self, value):
print 'We are in __init__'
self.x = value # Will call the setter. Note just x here
#self._x = value # Will not call the setter
#property
def x(self):
print 'called getter'
return self._x # Note the _x here
#x.setter
def x(self, value):
print 'called setter'
self._x = value # Note the _x here
t = testDec(17)
print t.x
Output:
We are in __init__
called setter
called getter
17

Categories

Resources