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)
Related
I have a class which has fields that would all be properties with pass through getters and setters that are validated in a certain way, such that it would satisfy the following pattern:
import numpy as np
import typing
def validate_field(value, dtype: typing.Type):
limits = np.iinfo(dtype)
assert limits.min < value < limits.max, \
"value shoule be in range: {} < {} < {}".format(limits.min, value,
limits.max)
return value
class Foo:
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
#property
def a(self):
return self._a
#property
def b(self):
return self._b
#property
def c(self):
return self._c
#a.setter
def a(self, value):
self._a = validate_field(value, self._a.dtype)
#b.setter
def b(self, value):
self._b = validate_field(value, self._b.dtype)
#c.setter
def c(self, value):
self._c = validate_field(value, self._c.dtype)
I want to eliminate having to type a separate property and setter decorator for each method.
I thought about using properties manually via
self._a = a
def set_a(self, value):
self._a = validate_field(value, self._a.dtype)
self.a = property(lambda self: self._a, set_a)
...
However, it seemed I would still have to manually define a function that accessed the required member for both setter and getter, so I wasn't really saving much work.
If there was a way to automatically generate such functions via naming the parameter e.g.:
def generate_function(self, parameter)
def temp(self, value):
self.parameter = validate_field(value, self.parameter.dtype)
return temp
then I wouldn't have any issues, but right now I don't see how to accomplish this.
Is there a way for me to generate these functions with a single decorator per field or automated function based property generation in __init__?
You can use getattr() and setattr(), or direct dictionary access via self.__dict__, to parametrize the attribute name:
def validated_property(name):
def getter(self):
return getattr(self, name)
def setter(self, value):
dtype = getter(self).dtype
setattr(self, name, validate_field(value, dtype))
return property(getter, setter)
then use this as
class Foo:
# ...
a = validated_property('_a')
b = validated_property('_b')
c = validated_property('_c')
etc.
If you are using Python 3.6 or newer, you can avoid having to repeat the attribute name and generate one from the name for the property (by prefixing it with _, for example), by implementing your own descriptor object, which is passed the name under which it is being assigned to a class via the descriptor.__set_name__() method:
class ValidatedProperty:
_name = None
def __set_name__(self, owner, name):
self._name = '_' + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self._name)
def __set__(self, instance, value):
dtype = self.__get__(instance, type(instance)).dtype
setattr(instance, self._name, validate_field(value, dtype))
then use this like this:
class Foo:
# ...
a = ValidatedProperty()
b = ValidatedProperty()
c = ValidatedProperty()
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 a class as follows:
class A:
def __init__(self):
pass
def add_attr(self, name):
setattr(self, name, 'something')
How do I define custom setter, getter for self.name? I cannot use __setattr__, __getattribute__ because that will change the behaviour of add_attr too.
EDIT: the users of this class will add arbitrary number of attributes with arbitrary names:
a = A()
a.add_attr('attr1')
a.add_attr('attr2')
I want custom behavior for only these user added attributes.
Building off #Devesh Kumar Singh’s answer, I would implement it in some way like this:
class A:
def __init__(self):
self.attrs = {}
def __setattr__(self, key, value):
if key in self.attrs:
self.set_attr(key, value)
else:
object.__setattr__(self, key, value)
def __getattribute__(self, key):
if key in self.__dict__.get(attrs, {}):
return self.__dict__['get_attr'](self, key)
return object.__getattribute__(self, key)
def get_attr(self, key):
r = self.attrs[key]
# logic
return r
def set_attr(self, key, value):
# logic
self.attrs[key] = value
def add_attr(self, key, value=None):
self.attrs[key] = value
add_attr is only used to initialise the variable the first time. You could also edit __setattr__ to set all new attributes in the self.attrs rather than self.__dict__
Custom getter and setter logic? That's what a property is made for. Usually these are used to magically mask function calls and make them look like attribute access
class MyDoubler(object):
def __init__(self, x):
self._x = x
#property
def x(self):
return x * 2
#x.setter
def x(self, value):
self._x = value
>>> md = MyDoubler(10)
>>> md.x
20
>>> md.x = 20
>>> md.x
40
>>> md._x
20
But there's no rule saying you can't abuse that power to add custom behavior to your getters and setters.
class A(object):
def __init__(self):
pass
#staticmethod
def default_getter_factory(name):
def default_getter(self):
return self.name
return default_getter
#staticmethod
def default_setter_factory(name):
def default_setter(self, value):
setattr(self, name, value)
return default_setter
def add_attr(self, name, getterfactory=None, setterfactory=None):
private_name = f"_{name}"
if getterfactory is None:
getterfactory = self.__class__.default_getter_factory
if setterfactory is None:
setterfactory = self.__class__.default_setter_factory
getter, setter = getterfactory(private_name), setterfactory(private_name)
getter = property(getter)
setattr(self.__class__, name, getter)
setattr(self.__class__, name, getter.setter(setter))
That said this is all a bit silly, and chances are that whatever it is you're trying to do is a thing that shouldn't be done. Dynamic programming is all well and good, but if I were to review code that did this, I would think very long and hard about alternative solutions before approving it. This reeks of technical debt to me.
One possibility I could think of is to have a dictionary of dynamic attributes, and set and get the dynamic attributes using the dictionary
class A:
def __init__(self):
#Dictionary of attributes
self.attrs = {}
#Set attribute
def set_attr(self, name):
self.attrs[name] = 'something'
#Get attribute
def get_attr(self, name):
return self.attrs.get(name)
a = A()
a.set_attr('var')
print(a.get_attr('var'))
The output will be something
Or an alternate is to use property decorator to add arguments explicitly outside the class, as described here
class A:
def __init__(self):
pass
a = A()
#Add attributes via property decorator
a.attr_1 = property(lambda self: self.attr_1)
a.attr_2 = property(lambda self: self.attr_2)
#Assign them values and print them
a.attr_1 = 4
a.attr_2 = 6
print(a.attr_1, a.attr_2)
The output will be 4 6
I am gonna answer my own question just for reference. This is based on others' answers here. The idea is to use default __setattr__ and __getattribute__ on attributes not added through add_attr.
class A:
def __init__(self):
self.attrs = {}
def add_attr(self, name):
self.attrs[name] = 'something'
def __getattribute__(self, name):
try:
object.__getattribute__(self, 'attrs')[name] # valid only if added by user
# custom logic and return
except (KeyError, AttributeError):
return object.__getattribute__(self, name)
def __setattr__(self, name, val):
# similar to __getattribute__
I want to create a class myCalc that has a constructor, with get and set methods and uses a property. I understand the get and set methods but am having trouble with putting all of the pieces together so it performs what I want it to do. What I have so far is this:
class myCalc(object):
def __init__(self):
self._ =name
def (self):
"""Get the answer doubled."""
return self._plus
def (self):
"""Get the answer squared."""
return self._times
I'd like to get as far as having the program respond with properties that double and square the number in the object. Any suggestions would be helpful.
A property object has getter, setter, and deleter methods
You should use decorators as follows:
class C(object):
def __init__(self):
self._x = None
#property
def x(self):
"""I'm the 'x' property."""
return self._x
#x.setter
def x(self, value):
self._x = value
#x.deleter
def x(self):
del self._x
If you want it readonly, just use #property and remove the two others
A simple object that has doubled and squared properties
class myCalc(object):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
def dbler(self):
return self.value*2
doubled = property(dbler)
squared = property(lambda self: self.value**2)
# equivalent with the decorator
#property
def cubed(self):
return self.value**3
if __name__ == '__main__':
x = myCalc(10)
print x # 10
print x.doubled # 20
print x.squared # 100
print x.cubed # 1000
Adapted from http://docs.python.org/2/howto/descriptor.html#properties
class myCalc(object):
def_init_(self, value)
self.value=value
def_init_(self, name)
print "Welcome to myCalc!"
self._name=name
def get_name(self):
return self._name
def set_name(self, new_name):
if new_name=="":
print "You mut enter a name."
else:
self._name=new_name
print "The name has been changed."
def_str_(self)
return str(self.value)
def dbler(self):
return self.value*2
doubled=property(dbler)
squared=property(lambda self: self.value**2)
name=property(get_name, set_name)
def talk(self):
print "\nWelcome, this is," self.name
if name=='main':
x= myCalc(5)
print x
print x.doubled
print x.squared
mainline
calc=myMath("Calculator")
calc.talk()
print "\nmyCalc's name is:",
print calc.name
print "\nChanging myCalc's name."
calc.name=""
calc.talk()
raw_input("\n\nPress the enter key to exit."
Here is what I am looking to do, however I keep coming across errors. I want to be able to change the object name AND number.