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.
Related
I'm writing a Python class A with a method square() that returns a new instance of that class with its first attribute squared. For example:
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
I would like to use this method in a subclass B so that it returns an instance of B with x squared but all additional attributes of B unchanged (i. e. taken from the instance). I can get it to work by overwriting square() like this:
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def square(self):
return self.__class__(self.x**2, self.y)
If I don't overwrite the square() method, this little code example will fail because I need to pass a value for y in the constructor of B:
#test.py
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
#def square(self):
# return self.__class__(self.x**2, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
$ python test.py
9
Traceback (most recent call last):
File "test.py", line 20, in <module>
b2 = b.square()
File "test.py", line 6, in square
return self.__class__(self.x**2)
TypeError: __init__() takes exactly 3 arguments (2 given)
Overwriting the method once isn't a problem. But A potentially has multiple methods similar to square() and there might be more sub(sub)classes. If possible, I would like to avoid overwriting all those methods in all those subclasses.
So my question is this:
Can I somehow implement the method square() in A so that it returns a new instance of the current subclass with x squared and all other attributes it needs for the constructor taken from self (kept constant)? Or do I have to go ahead and overwrite square() for each subclass?
Thanks in advance!
I'd suggest implementing .__copy__() (and possibly .__deepcopy__ as well) methods for both classes.
Then your squared can be simple method:
def squared(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
It will work with inheritance, assuming all child classes have correctly implemented __copy__ method.
EDIT: fixed typo with call to copy()
Full working example:
#test.py
from copy import copy
class A:
def __init__(self, x):
self.x = x
def square(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
def __copy__(self):
return A(self.x)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def __copy__(self):
return B(self.x, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
check if the object contains y then return the right class instance:
class A:
x: int
def __init__(self, x):
self.x = x
def square(self):
if hasattr(self, 'y'):
return self.__class__(self.x ** 2, self.y)
return self.__class__(self.x**2)
class B(A):
y: int
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
# def square(self):
# return self.__class__(self.x**2, self.y)
In python, the #property and #val.setter is very helpful. For example:
from types import FunctionType
class Test:
def __init__(self):
self.a = 1
#property
def A(self):
print('get A')
return self.a
#A.setter
def A(self, val):
print('set A')
self.a = val
t = Test()
print(t.A)
t.A = 3
print(t.A)
It works.
Now, I want to create setProperty and getProperty for many variable, so I want to dynamic create those functions.
My code is:
from types import FunctionType
class Test:
def __init__(self):
self.a = 1
code = compile('#property\ndef A(self): print("get A")\nreturn self.a', '', 'exec')
FunctionType(code.co_consts[0], globals(), "A")
code = compile('#A.setter\ndef A(self, val): print("set A")\nself.a=val', '', 'exec')
FunctionType(code.co_consts[0], globals(), "A")
t = Test()
print(t.A)
t.A = 3
print(t.A)
And it reports a bug:
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/medpro/test.py", line 23, in <module>
t = Test()
File "C:/Users/Administrator/Desktop/medpro/test.py", line 7, in __init__
code = compile('#property\ndef A(self): print("get A")\nreturn self.a', '', 'exec')
File "", line 3
SyntaxError: 'return' outside function
Then, I remove print("get A"), and another bug is reported:
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/medpro/test.py", line 24, in <module>
print(t.A)
AttributeError: 'Test' object has no attribute 'A'
You can, but do not have to use property as decorator, consider following example from property docs
class C:
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
To dynamically add descriptors, in this case read & write, you can first create a decorator which take as arguments the attributes identifiers and add them to your target class.
class DynamicDescriptors:
def __init__(self, id_attributes: tuple):
self.ids = id_attributes
def __call__(self, cls):
for attr in self.ids:
# getter
setattr(cls, attr, property(lambda cls_self: getattr(cls, attr)))
# setter
setattr(cls, attr, getattr(cls, attr).setter(lambda cls_self, v: setattr(cls, attr, v)))
return cls
dynamic_attrs = ('a', 'b', 'c')
#DynamicDescriptors(dynamic_attrs)
class Test:
pass
# access from instance
t = Test()
t.a = 'a'
print(t.a)
t.b = 'b'
print(t.b)
t.c = 'c'
print(t.c)
# access from class
Test.a = 10
print(Test.a)
# property object
print(Test.b)
Output
a
b
c
10
<property object at 0x7f5ff9d12c70>
EDIT (reimplementation) without decorator + support for print (or custom implementation)
There are a plenty of different ways to achieve the goal without decorator. I like to separate the tasks, so I add a classmethod which add the descriptors and the attributes are given as class attribute.
Note by personal choice I assign to a descriptor a a private name __a. Since adding the descriptor dynamically and since the private name mangling happens at compilation time the attribute __a should be called as _ClassName__a, see docs 1, 2. For non private name attribute no need for that.
class Test:
dynamic_attrs = ('a', 'b', 'c')
#classmethod
def dynamic_descriptors(cls, *id_attributes):
def prop_factory(attr):
def getter(attr):
def __wrapper(self):
p_attr = f'_{type(self).__name__}__{attr}' # private name
v_attr = getattr(self, p_attr)
print(f'getter {attr}: {v_attr}')
return v_attr
return __wrapper
def setter(attr):
def __wrapper(self, v):
p_attr = f'_{type(self).__name__}__{attr}' # private name
old_attr = getattr(self, p_attr) if hasattr(self, p_attr) else 'None'
setattr(self, p_attr, v)
print(f'setter {attr}: {old_attr} -> {v}')
return __wrapper
return property(getter(attr), setter(attr))
for attr in id_attributes:
setattr(cls, attr, prop_factory(attr))
def __init__(self):
self.dynamic_descriptors(*self.dynamic_attrs)
t = Test()
print(Test.a)
#<property object at 0x7f4962138cc0>
t.a = 'aaa'
#setter a: None -> aaa
t.a
#getter a: aaa
t.b = 'b'*4
#setter b: None -> bbbb
t.b = 'b'*2
#setter b: bbbb -> bb
t.b
#getter b: bb
t.c = 'c'
#setter c: None -> c
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.)
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
I want to create an int type with boundaries like:
> a = BoundInt(2,4)
a will only hold values 2, 3 or 4 and raise an error if a different value is assigned to it.
But since Python doesn't let me override assignment and using setters is no Pythonic I'm kind of lost.
How could I work this out in my class and what specific syntax would this require when assigning new values to my vars?
Assuming you're trying to attach this as an attribute to a class
You can do this via a property:
class Foo(object):
def __init__(self, a):
self._a = a
#property
def a(self):
return self._a
#a.setter
def a(self, value):
if not (2 <= value <= 4):
raise ValueError
self._a = value
Note that I am using a setter. Python style generally discourages using a function when attribute access will do. However, if you need to take some action when you set a variable, then a traditional setter or property is fine (depending on the API you want to expose).
If you actually want to do this using a syntax similar to what you've already written, maybe using a descriptor:
class BoundInt(object):
def __init__(self, lower, upper, name):
self.lower = lower
self.upper = upper
self.name = '_' + name
def __get__(self, inst, cls):
return getattr(inst, self.name)
def __set__(self, inst, val):
if self.lower <= val <= self.upper:
setattr(inst, self.name, val)
else:
raise ValueError('must be in bounds!')
class Foo(object):
a = BoundInt(2, 4, 'a')
f = Foo()
f.a = 2
print(f.a)
f.a = 3
print(f.a)
f.a = 4
print(f.a)
f.a = 5
results in:
2
3
4
Traceback (most recent call last):
File "/usr/home/mgilson/sandbox/test.py", line 28, in <module>
f.a = 5
File "/usr/home/mgilson/sandbox/test.py", line 15, in __set__
raise ValueError('must be in bounds!')
ValueError: must be in bounds!
You can use a property:
class BoundInt(object):
def __init__(self, bottom, top):
self.bounds = range(bottom, top) # xrange for Python 2.x
self._value = bottom
#property
def value():
return self._value
#value.setter
def value(x):
if x in self.bounds:
self._value = x
else:
raise ValueError("%s is not in %r" % (x, self.bounds))
Problem here is that you have to hold your value as an attribute of your object, and then implement all the methods of int that allow you to use it as a number. In Python, this is basically unavoidable.
You can subclass int:
class BoundInt(int):
def __init__(self, x, allowed=(2, 3, 4), *args, **kwargs):
value = int(x, *args, **kwargs)
if value not in allowed:
raise ValueError("Not in allowed values: " + repr(allowed))
else:
self = value
a = BoundInt(2)
print a + 3
b = BoundInt('11', base=2)
print b - a
c = BoundInt(7)
Please note, that you cannot prevent in Python assigment or value to name. Operator = only binds existing object (value of right hand expression) to a variable name. In Python it is value, that has type, not variable name.