I would like to get this behavior where I have access to an inner property max specified into the class Room.
>>> r = Room()
>>> r.temperature
22.0
>>> r.temperature = 18.0
>>> r.temperature.max
30.0
>>> r.temperature
18.0
I would like to get this behavior with this minimal code:
class Room(object):
#inner_property('min', get_temperature_min)
#inner_property('max', get_temperature_max)
def temperature(self):
'''The temperature of the room'''
return get_temperature()
But currently, I only succeed to implement this behavior using this code:
class Room(object):
__init_done = False
def __init__(self):
self.temperature = "shadow"
self.__init_done = True
def __getattribute__(self, attr):
if attr is 'temperature':
return FloatMinMax(get_temperature(),
get_temperature_min(), get_temperature_max(),
'Room temperature')
return super(Room, self).__getattribute__(attr)
def __setattr__(self, attr, value):
if attr is not 'temperature' or self.__init_done is False:
super(Room, self).__setattr__(attr, value)
else:
set_temperature(value)
class FloatMinMax(float):
def __new__(cls, value, min_, max_, doc_):
return super(FloatMinMax, cls).__new__(cls, value)
def __init__(self, value, min_, max_, doc_):
self.__min = min_
self.__max = max_
self.__doc__ = doc_
super(FloatMinMax, self).__init__()
#property
def min(self): return self.__min
#property
def max(self): return self.__max
How can I simplify the writing using decorators?
Related
I'm new to metaclasses, so I'm sorry, If my question is somehow stupid. I need to make a metaclass, that takes particular methods of a class and turns them into property methods or setters. So, if I have
class TestClass():
def __init__(self, x: int):
self._x = x
def get_x(self):
print("this is property")
return self._x
def set_x(self, x: int):
print("this is setter")
self._x = x
I want it to work like this:
class TestClass():
def __init__(self, x: int):
self._x = x
#property
def x(self):
print("this is property")
return self._x
#x.setter
def x(self, x: int):
print("this is setter")
self._x = x
For now I've just realized how to make it for property:
class PropertyConvert(type):
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val)
if name.startswith('set_'):
# ???
else:
new_attr[name] = val
return type.__new__(cls, future_class_name, future_class_parents, new_attr)
But I can't figure out how to do it for setters.
I highly recommend docs about descriptors, they are really nice written with many similar examples explained.
But answering your question, to make a setter work you need to use the same property function but fill second arguments.
class property(fget=None, fset=None, fdel=None, doc=None)
So code could look like that:
class PropertyConvert(type):
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
fget, fset, property_name = None, None, None
for name, val in future_class_attr.items():
if not name.startswith("__"):
if name.startswith("get_"):
property_name = name[4:]
fget = val
if name.startswith("set_"):
property_name = name[4:]
fset = val
else:
new_attr[name] = val
if n:
new_attr[property_name] = property(fget, fset)
return type.__new__(cls, future_class_name, future_class_parents, new_attr)
I'd like to cache an object in __new__ method so that it can load the cache when a new object is constructed, but now the following code will got an exception:
RecursionError: maximum recursion depth exceeded while calling a Python object
I have no idea about how to break the recursion
import pickle
class Cache:
def __init__(self):
self.d = {}
def __setitem__(self, obj, val):
self.d[obj] = pickle.dumps(val)
def __getitem__(self, obj):
return pickle.loads(self.d[obj])
class Car:
cache = Cache()
def __reduce__(self):
return (self.__class__, (self.name,))
def __new__(cls, name):
try:
return cls.cache[name]
except KeyError:
return cls.new(name)
#classmethod
def new(cls, name):
car = object.__new__(cls)
car.init(name)
cls.cache[name] = car
return car
def init(self, name):
self.name = name
def __repr__(self):
return self.name
a = Car('audi')
b = Car('audi')
Have a try. This may fix this, but it may not be the proper solution. If anyone have any better idea, feel free to leave comments.
Just remove the __reduce__ method.
Then implement __getnewargs__ and __getnewargs_ex__
import pickle
class Cache:
def __init__(self):
self.d = {}
def __setitem__(self, obj, val):
self.d[obj] = pickle.dumps(val)
def __getitem__(self, obj):
return pickle.loads(self.d[obj])
def __contains__(self, x):
return x in self.d
class Car:
cache = Cache()
def __new__(cls, name, extra=None, _FORCE_CREATE=False):
if _FORCE_CREATE or name not in cls.cache:
car = object.__new__(cls)
car.init(name)
car.extra = extra
cls.cache[name] = car
return car
else:
return cls.cache[name]
def init(self, name):
self.name = name
def __repr__(self):
return self.name
def __getnewargs__(self):
return (self.name, None, True)
def __getnewargs_ex__(self):
# override __getnewargs_ex__ and __getnewargs__ to provide args for __new__
return (self.name, ), {"_FORCE_CREATE": True}
a = Car('audi', extra="extra_attr")
b = Car('audi')
print(id(a), a.extra) # 1921399938016 extra_attr
print(id(b), b.extra) # 1921399937728 extra_attr
I am using a class (MainClass) over which I have no control. I want to base my class on MainClass but to add extra functionality. I have added an attribute (index) to my class (SuperClass), but when I try convert index to a property, the #.setter seems to be ignored. What is wrong here?
class MainClass(object):
def __init__(self):
self.name = 'abc'
class SuperClass(object):
def __init__(self, main, *args, **kwargs):
super(SuperClass, self).__init__(*args, **kwargs)
self.__main = main
self._index = 0
def __getattr__(self, attr):
return getattr(self.__main, attr)
def __setattr__(self, attr, val):
if attr == '_SuperClass__main':
object.__setattr__(self, attr, val)
return setattr(self.__main, attr, val)
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value
main_object = MainClass()
super_object = SuperClass(main_object)
print('x', super_object.index, super_object.name)
super_object.index = 3
print('y', super_object.index)
super_object.index += 2
print('z', super_object.index)
__getattr__ is only used when the normal lookup mechanism fails.
__setattr__, however, is called for all attempts to set an attribute. This means your current definition creates an attribute named index on the
MainClass instance, rather than accessing the property's setter.
>>> super_object._SuperClass__main.index
2
Because __setattr__ always calls setattr(self.__main, attr, val), += is effectively treated as =.
__setattr__ has to handle three cases:
The attribute _SuperClass__main itself, for when you assign to self.__main in __init__.
Assignments to attributes that exist on self.__main
Assignments to attributes specific to SuperClass.
With that in mind, try
def __setattr__(self, attr, val):
if attr == '_SuperClass__main':
super().__setattr__(attr, val)
elif hasattr(self.__main, attr):
setattr(self.__main, attr, val)
else:
super().__setattr__(attr, val)
The __setattr__ method you have defined is taking precedence over the #index.setter
Simplify the code and it should work:
class MainClass(object):
def __init__(self):
self.name = 'abc'
class SuperClass(object):
def __init__(self, main, *args, **kwargs):
super(SuperClass, self).__init__(*args, **kwargs)
self.__main = main
self._index = 0
#property
def name(self):
return self.__main.name
#name.setter
def name(self):
return self.__main.name
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value
main_object = MainClass()
super_object = SuperClass(main_object)
print('x', super_object.index, super_object.name)
super_object.index = 3
print('y', super_object.index)
super_object.index += 2
print('z', super_object.index)
Output:
x 0 abc
y 3
z 5
I would also suggest the simpler option of just inheriting from MainClass instead of using composition and delegation:
class SuperClass(MainClass):
def __init__(self):
super().__init__()
self._index = 0
#property
def index(self):
return self._index
#index.setter
def index(self, value):
self._index = value
How can I create multiple property decorators with self defined function as getter and setter based on following class structure? I have try to use
setattr(self, 'a', property(_to_get('a'), _to_set('a'))) but it does not work.
class ABC:
def __init__(self):
pass
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
#property
def a(self):
res = self._to_get('a')
return res.split(' ')[0]
#a.setter
def a(self, value)
self._to_set('a', value)
#property
def b(self):
res = self._to_get('b')
return res.split(' ')[1]
#b.setter
def b(self, value)
self._to_set('b', value)
#property
def c(self):
res = self._to_get('c')
return res.split(' ')[2]
#c.setter
def c(self, value)
self._to_set('c', value)
No reason why something like this wouldn't work:
class A(object):
def __init__(self):
self._a = None
#property
def a(self):
return self._a
#a.setter
def a(self, x):
self._a = x
#a.deleter
def a(self):
del self._a
#property
def b(self):
return self._b
#b.setter
def b(self, x):
self._b = x
#b.deleter
def b(self):
del self._b
#property
def c(self):
return self._c
#c.setter
def c(self, x):
self._c = x
#c.deleter
def c(self):
del self._c
Consider your original class written without decorator syntax. (The translation may not be 100% accurate, but should be close enough to illustrate the point I want to make.)
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(lambda self: ABC._to_get(self, 'a').split(' ')[0],
lambda self, value: ABC._to_set(self, 'a', value))
b = property(lambda self: ABC._to_get(self, 'b').split(' ')[1],
lambda self, value: ABC._to_set(self, 'b', value))
c = property(lambda self: ABC._to_get(self, 'c').split(' ')[2],
lambda self, value: ABC._to_set(self, 'c', value))
a, b and c are all basically the same thing, but parameterized
by the name of the property and an integer.
def make_getter(attr, x):
def getter(self):
return self._to_get(attr).split(' ')[x]
return getter
def make_setter(attr):
def setter(self, value):
self._to_set(attr, value)
return setter
class ABC:
def _to_get(self, attr):
return something_function(attr)
def _to_set(self, attr, value):
dosomething_function(attr, value)
a = property(make_getter('a', 0), make_setter('a'))
b = property(make_getter('b', 1), make_setter('b'))
c = property(make_getter('c', 2), make_setter('c'))
Something like the following should also work (not heavily tested), moving the logic into a subclass of property.
class Foo(property):
def __init__(self, x):
super().__init__(self._to_get, self._to_set)
self.x = x
# name is the class attribute the instance of Foo
# will be assigned to
def __set_name__(self, owner, name):
self.attr = name
# In both of the following, obj is the instance that actually
# invokes the parameter. You would probably want to pass it
# to something_function and do_something as well.
def _to_get(self, obj):
return something_function(self.attr).split(' ')[self.x]
def _to_set(self, obj, value):
do_something(self.attr, value)
class ABC:
a = Foo(0) # Will call a.__set_name__(ABC, 'a')
b = Foo(1) # Will call b.__set_name__(ABC, 'b')
c = Foo(2) # Will call c.__set_name__(ABC, 'c')
I have two classes A and B, where B inherits from A and overrides a property. A is not under my control so I cannot change it.
The code looks as follows:
class A():
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
raise AttributeError("can't set attribute")
When I try to call B(1) I obviously get AttributeError: can't set attribute.
I would like to have a different behaviour when value is set from inside class methods
#value.setter
def value(self, value):
if set from inside class hierarchy:
pass
else:
raise AttributeError("can't set attribute")
The module inspect does not seem to give me enough information to do this, except checking against a list of known functions.
You can inspect the stack to determine who called, and whether that it's in the class hierarchy to decide whether or not to allow it:
import inspect
def who_called():
frame = inspect.stack()[2][0]
if 'self' not in frame.f_locals:
return None, None
cls = frame.f_locals['self'].__class__
method = frame.f_code.co_name
return cls, method
class A(object):
def __init__(self, value):
self._value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
# Assuming this existed it would also work
def change_value(self, value):
self.value = value
Class B now checking:
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
cls, method = who_called()
if cls in B.__mro__ and method in A.__dict__:
self._value = value
else:
raise AttributeError("can't set attribute")
Proof:
b = B('does not raise error')
b.change_value('does not raise error')
b.value = 'raises error'
You could use the code that made the call to determine whether the call came from inside the class. Only throw an exception if the call didn't start with self.value =.
import re
import traceback
class A(object):
def __init__(self, value):
self.value = value
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
class B(A):
def __init__(self, value):
super(B, self).__init__(value)
#property
def value(self):
return super(B, self).value
#value.setter
def value(self, value):
call = traceback.extract_stack(limit=2)[0][3]
if re.match(r'self.value\s*=', call):
pass
else:
raise AttributeError("can't set attribute")
b = B(1) # OK
b.value = 3 # Exception
Of course, this breaks as soon as you start calling your variables self:
self = B(1) # OK
self.value = 3 # Meh, doesn't fail