I have a class with many parameters that are stored in a dictionary as below (in practice, it's a bit more complex, but this gives a good idea). I am able to 'autogenerate' get and set methods using partialmethod:
for a_name in ['x', 'y', 'z']:
locals()["get_" + a_name] = partialmethod(_get_arg,
arg_name=a_name)
locals()["set_" + a_name] = partialmethod(_set_arg,
arg_name=a_name)
Ideally, I wanted to use #property, but only if I can 'autogenerate' #property, #setter and #deleter. Would it be possible? The second snippet show the 'manually' added properties; I am looking into using an equivalent of partialmethod to avoid repetition and unmaintainable code.
class C:
def __init__(self):
self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}
def __get_arg(self, arg_name):
assert arg_name in self.kwargs
return self.kwargs[arg_name]
def __set_arg(self, arg_name, value):
assert arg_name in self.kwargs
self.kwargs[arg_name] = value
def __del_arg(self, arg_name):
assert arg_name in self.kwargs
del self.kwargs[arg_name]
#property
def x(self):
return self.__get_arg('x')
#x.setter
def x(self, value):
self.__set_arg('x', value)
#x.deleter
def x(self):
self.__del_arg('x')
#property
def y(self):
return self.__get_arg('y')
#y.setter
def y(self, value):
self.__set_arg('y', value)
#y.deleter
def y(self):
self.__del_arg('y')
#property
def z(self):
return self.__get_arg('z')
#x.setter
def z(self, value):
self.__set_arg('z', value)
#x.deleter
def z(self):
self.__del_arg('z')
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
After struggling for awhile, I came up with the following partialproperty descriptor.
The __get__ method is called when an attribute is accessed. In it I call the
setter() method with the first argument of obj for the self paramater
followed by the unpacked self.args and self.kwargs.
class partialproperty:
"""Combine the functionality of property() and partialmethod()"""
def __init__(self, getter, setter=None, deleter=None, *args, **kwargs):
self.getter = getter
self.setter = setter
self.deleter = deleter
self.args = args
self.kwargs = kwargs
def __set_name__(self, owner, name):
self._name = name
self._owner = owner
def __get__(self, obj, objtype=None):
return self.getter(obj, *self.args, **self.kwargs)
def __set__(self, obj, value):
if self.setter is None:
raise AttributeError(f"{self._owner.__class__.__name__} object can't set attribute: {self._name}")
self.setter(obj, *self.args, value, **self.kwargs)
def __delete__(self, obj):
if self.deleter is None:
raise AttributeError(f"{self._owner.__class__.__name__} object can't delete attribute: {self._name}")
self.deleter(obj, *self.args, **self.kwargs)
You can use it in a class to create property attributes like so, much like you
would with partialmethod().
If you don't need a setter and deleter, you need to either use keyword
arguments for your args, or explicitly pass None.
class RGBA:
def __init__(self, rgba):
self.rgba = rgba
def _component_(self, idx):
return self.rgba[idx]
# using keyword args
red = partialproperty(_component_, idx=0)
green = partialproperty(_component_, idx=1)
blue = partialproperty(_component_, idx=2)
# this has the same effect but with positional args
alpha = partialproperty(_component_, None, None, 3)
Then in your instances the respective attributes will behave like properties.
>>> color = RGBA([0, 75, 255, 1])
>>> color.red
0
>>> color.green
75
>>> color.blue
255
>>> color.alpha
1
For your example it would look like:
class C:
def __init__(self, **kwargs):
self.kwargs = kwargs
def __get_arg(self, arg_name):
try:
return self.kwargs[arg_name]
except KeyError:
raise AttributeError(arg_name)
def __set_arg(self, arg_name, value):
self.kwargs[arg_name] = value
def __del_arg(self, arg_name):
try:
del self.kwargs[arg_name]
except KeyError:
raise AttributeError(arg_name)
x = partialproperty(__get_arg, __set_arg, __del_arg, "x")
y = partialproperty(__get_arg, __set_arg, __del_arg, "y")
z = partialproperty(__get_arg, __set_arg, __del_arg, "z")
>>> obj = C(x=24)
>>> obj.x
24
>>> obj.y = 25
>>> obj.y
25
>>> del obj.y
>>> obj.y
AttributeError: y
>>> obj.kwargs["z"] = 26
>>> obj.z
26
You can write your own descriptor type, and use the __set_name__ method that will get called on it (by the class creation machinery) to figure out what name it's been saved to in the class:
class MyProp:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
if instance is None:
return self
return instance._get_arg(self.name)
def __set__(self, instance, value):
instance._set_arg(self.name, value)
def __delete__(self, instance):
instance._del_arg(self.name)
You'd use it this way:
class C:
# define __init__, _get_arg, etc.
x = MyProp()
y = MyProp()
z = MyProp()
Note that because it's code from another class calling the _X_arg methods, you probably don't want to do name mangling, so I've changed the __ prefixes to just a single _.
For your specific example, an easier way to do this would probably to subclass
dict then define the magic methods __getattr__, __setattr__, and
__delattr__ which are each called when an attribute doesn't exist. Then you
can call super() to inherit the behavior from the dict class.
class AttrDict(dict):
attrs = ("x", "y", "z")
def __getattr__(self, name):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__getitem__(name)
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__setitem__(name, value)
except KeyError:
raise AttributeError(name)
def __delattr__(self, name):
if not name in self.attrs:
raise AttributeError(name)
try:
return super().__delitem__(name)
except KeyError:
raise AttributeError(name)
>>> obj = AttrDict(x=24)
>>> obj.x
24
>>> obj.a = 1
AttributeError: a
>>> obj.y = 25
>>> obj.y
25
>>> del obj.y
>>> obj.y
AttributeError: y
>>> obj.kwargs["z"] = 26
>>> obj.z
26
If you don't care about restricting to specific attributes it's even easier.
You can simply assign each __*attr__ method to its cooresponding
dict.__*item__ method, which is what gets called for subscription operations.
(ie obj[key])
class AttrDict(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
>>> obj = AttrDict({"a": 1})
>>> obj.a
1
>>> obj["b"] = 2
>>> obj.b
2
>>> del obj.b
>>> obj.b
KeyError
Related
I need to intercept setattr and getattr after init completion, i.e. if main class doesn't have required attribute, it would look for it in subclass Extra, or when setting attribute, if it's not in main class then setting went to subclass Extra, how to understand that init was executed and intercept it only after completion? Here's the code I tried to do it with but it didn't work
class Test:
def __init__(self):
self.default_name = "Michael"
def __setattr__(self, key, value):
if not hasattr(self, key):
self.Extra.__dict__[key] = value;
self.__dict__[key] = v
def __getattr__(self, item):
if not hasattr(self, item):
return self.Extra.__dict__[item]
class Extra:
pass
user = Test()
user.default_name = "Tomas"
user.some_data = "test"
print(user.default_name)
print(user.some_data)
Direct operation attribute dictionary:
class Test:
def __init__(self):
vars(self)['default_name'] = "Michael"
vars(self)['extra'] = Test.Extra()
def __setattr__(self, key, value):
if key not in vars(self):
setattr(self.extra, key, value)
else:
vars(self)[key] = value
def __getattr__(self, item):
return getattr(self.extra, item)
class Extra:
pass
Test:
>>> user = Test()
>>> user.default_name
'Michael'
>>> user.default_name = 'Tomas'
>>> user.default_name
'Tomas'
>>> user.some_data = 'test'
>>> user.some_data
'test'
>>> vars(user)
{'default_name': 'Tomas', 'extra': <__main__.Test.Extra object at 0x000001D5151D6380>}
Are there any functions like the built-in functions getattr and hasattr in the standard library but which bypass instance attributes during attribute lookup, like the implicit lookup of special methods?
Let’s call these hypothetical functions getclassattr and hasclassattr. Here are the implementations that I would expect:
null = object()
def getclassattr(obj, name, default=null, /):
if not isinstance(name, str):
raise TypeError('getclassattr(): attribute name must be string')
try:
classmro = vars(type)['__mro__'].__get__(type(obj))
for cls in classmro:
classdict = vars(type)['__dict__'].__get__(cls)
if name in classdict:
attr = classdict[name]
attrclassmro = vars(type)['__mro__'].__get__(type(attr))
for attrclass in attrclassmro:
attrclassdict = vars(type)['__dict__'].__get__(attrclass)
if '__get__' in attrclassdict:
return attrclassdict['__get__'](attr, obj, type(obj))
return attr
classname = vars(type)['__name__'].__get__(type(obj))
raise AttributeError(f'{classname!r} object has no attribute {name!r}')
except AttributeError as exc:
try:
classmro = vars(type)['__mro__'].__get__(type(obj))
for cls in classmro:
classdict = vars(type)['__dict__'].__get__(cls)
if '__getattr__' in classdict:
return classdict['__getattr__'](obj, name)
except AttributeError as exc_2:
exc = exc_2
except BaseException as exc_2:
raise exc_2 from None
if default is not null:
return default
raise exc from None
def hasclassattr(obj, name, /):
try:
getclassattr(obj, name)
except AttributeError:
return False
return True
A use case is a pure Python implementation of the built-in class classmethod:*
import types
class ClassMethod:
def __init__(self, function):
self.__func__ = function
def __get__(self, instance, owner=None):
if instance is None and owner is None:
raise TypeError('__get__(None, None) is invalid')
if owner is None:
owner = type(instance)
# Note that we use hasclassattr instead of hasattr here.
if hasclassattr(self.__func__, '__get__'):
# Note that we use getclassattr instead of getattr here.
return getclassattr(self.__func__, '__get__')(owner, type(owner))
return types.MethodType(self.__func__, owner)
#property
def __isabstractmethod__(self):
return hasattr(self.__func__, '__isabstractmethod__')
* Note that this implementation would not work with the built-in functions getattr and hasattr since they look up in instance attributes first, as this comparison with the built-in class classmethod shows:
>>> import types
>>> class ClassMethod:
... def __init__(self, function):
... self.__func__ = function
... def __get__(self, instance, owner=None):
... if instance is None and owner is None:
... raise TypeError('__get__(None, None) is invalid')
... if owner is None:
... owner = type(instance)
... if hasattr(self.__func__, '__get__'):
... return getattr(self.__func__, '__get__')(owner, type(owner))
... return types.MethodType(self.__func__, owner)
... #property
... def __isabstractmethod__(self):
... return hasattr(self.__func__, '__isabstractmethod__')
...
>>> class M(type):
... def __get__(self, instance, owner=None):
... return 'metaclass'
...
>>> class A(metaclass=M):
... def __get__(self, instance, owner=None):
... return 'class'
...
>>> ClassMethod(A).__get__('foo')
'class'
>>> classmethod(A).__get__('foo')
'metaclass'
Instead of introducing the new functions getclassattr and hasclassattr to bypass instance attributes during attribute lookup, like the implicit lookup of special methods, an alternative approach is to introduce a proxy class (let’s call it bypass) that overrides the method __getattribute__. I think this may be a better approach since the method __getattribute__ is a hook designed for customising attribute lookup, and it works with the built-in functions getattr and hasattr but also with the attribute retrieval operator .:
class bypass:
def __init__(self, subject):
self.subject = subject
def __getattribute__(self, name):
obj = super().__getattribute__('subject')
classmro = vars(type)['__mro__'].__get__(type(obj))
for cls in classmro:
classdict = vars(type)['__dict__'].__get__(cls)
if name in classdict:
attr = classdict[name]
attrclassmro = vars(type)['__mro__'].__get__(type(attr))
for attrclass in attrclassmro:
attrclassdict = vars(type)['__dict__'].__get__(attrclass)
if '__get__' in attrclassdict:
return attrclassdict['__get__'](attr, obj, type(obj))
return attr
classname = vars(type)['__name__'].__get__(type(obj))
raise AttributeError(f'{classname!r} object has no attribute {name!r}')
class M(type):
x = 'metaclass'
class A(metaclass=M):
x = 'class'
a = A()
a.x = 'object'
assert getattr(a, 'x') == 'object' and getattr(bypass(a), 'x') == 'class'
assert getattr(A, 'x') == 'class' and getattr(bypass(A), 'x') == 'metaclass'
Here is a slightly modified version of some code found in a python book:
class TypedProperty(object):
def __init__(self,name,type,default=None):
self.name = "_" + name
self.type = type
self.default = default if default else type()
def __get__(self,instance,cls):
return getattr(instance,self.name,self.default)
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError("Must be a %s" % self.type)
setattr(instance,self.name,value)
class Foo(object):
name = TypedProperty("name",str)
num = TypedProperty("num",int,42)
f = Foo()
f.name = 'blah'
My question: why are we creating attributes in f? In the code above, TypedProperty is written such that f.name = 'blah' creates the attribute "_name" in the instance f.
Why not save the values as attributes of the class TypedProperty? Here is what I had in mind:
class TypedProperty2(object):
def __init__(self, val, typ):
if not isinstance(val, typ):
raise TypeError()
self.value = val
self.typ = typ
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, val):
if not isinstance(val, self.typ):
raise TypeError()
self.value = val
Is this an arbitrary design decision?
All instances of the class will share the same instance of the descriptor (e.g. TypedProperty). So, if you store the value on the TypedProperty, then all instances of Foo will have the same value for the name and num values. This is usually not desirable (or expected) for descriptors.
e.g. if you run the following script:
class TypedProperty2(object):
def __init__(self, val, typ):
if not isinstance(val, typ):
raise TypeError()
self.value = val
self.typ = typ
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, val):
if not isinstance(val, self.typ):
raise TypeError()
self.value = val
class Foo(object):
name = TypedProperty2("name", str)
f1 = Foo()
f1.name = 'blah'
f2 = Foo()
print(f2.name)
f2.name = 'bar'
print(f1.name)
You'll see the following output:
blah
bar
so we can see that initially f2 had f1's name and then, after changing the name of f2, f1 picked up f2's name.
I've been looking for a way to define class properties in Python.
The expected behavior would be something intuitive like:
class A:
_access_count = 0
#classproperty
def value1(cls):
cls._access_count += 1
return 1
A.value1 # return 1
A().value1 # return 1
A._access_count # return 2
A.value1 = 2 # raise an AttributeError
I found related questions on SO, but none of them propose this exact feature.
This thread has a nice example of metaclass even though it doesn't really apply in that case. The accepted answer of this one propose a close solution but doesn't handle the setter mecanism.
Since it is ok to answer his own question I'll write what I've come up with so far.
class classproperty(property):
"""Class property works exactly like property."""
pass
def create_instance_property(cls_prop):
"""Create instance property from class property."""
fget, fset, fdel = None, None, None
if cls_prop.fget is not None :
fget = lambda self: cls_prop.fget(type(self))
if cls_prop.fset is not None :
fset = lambda self, value: cls_prop.fset(type(self), value)
if cls_prop.fdel is not None :
fdel = lambda self: cls_prop.fdel(type(self))
return property(fget, fset, fdel, cls_prop.__doc__)
def init_for_metaclass(cls, name, bases, dct):
"""__init__ method for a metaclass to handle class properties."""
super(type(cls), cls).__init__(name, bases, dct)
for key, prop in dct.items():
if isinstance(prop, classproperty):
setattr(cls, key, create_instance_property(prop))
setattr(type(cls), key, prop)
def handle_class_property(cls):
"""Class decorator to handle class properties."""
name = type(cls).__name__ + "_for_" + cls.__name__
metacls = type(name, (type(cls),), {"__init__": init_for_metaclass})
return metacls(cls.__name__, cls.__bases__, dict(cls.__dict__))
So far it works exactly as expected, even for inheritance cases:
#handle_class_property
class A(object):
_access_count = 0
#classproperty
def value1(cls):
print cls
cls._access_count += 1
return 1
class B(A):
_access_count = 0
#classproperty
def value2(cls):
cls._access_count += 1
return 2
#value2.setter
def value2(cls, value):
print(value)
a = A()
b = B()
assert (a.value1, A.value1) == (1,1)
assert (b.value1, B.value1) == (1,1)
assert (b.value2, B.value2) == (2,2)
assert B._access_count == 4
assert A._access_count == 2
B.value2 = 42 # This should print '42'
try: B.value1 = 42 # This should raise an exception
except AttributeError as e: print(repr(e))
When one defines a descriptor value retrieval etc. is overriden, making the instance of the descriptor effectively unaccessible.
I.e. one can't write instance_with_descriptor_attr.descriptor_attr.some_method_on_descriptor()... won't work. My question is basically how one can still access the descriptor's instance anway...
As noted by eryksun, Martijn's solution works for properties but not all descriptors:
class Desc(object):
def __init__(self, val=None):
self.val = val
def __get__(self, obj, cls):
return self.val
def __set__(self, obj, val):
self.val = val
class Test(object):
x = Desc(5)
>>> o = Test()
>>> print o.x
5
>>> print Test.x
5
The reason it works for property descriptors can be seen in the example property descriptor implementation in the docs:
http://docs.python.org/2/howto/descriptor.html#properties
the key is the __get__ function:
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
If obj is None it returns self, which is the instance of the descriptor itself. obj is the instance of the class accessing the descriptor. When you access the attribute from a class instance, obj is that instance, when you access it from a class object, then obj is None.
Changing the previous descriptor to:
class Desc(object):
def __init__(self, val=None):
self.val = val
def __get__(self, obj, cls):
if obj is None:
return self
return self.val
def __set__(self, obj, val):
self.val = val
class Test(object):
x = Desc(5)
yields (class must be re-defined if you're using a python shell)
o = Test()
>>> print o.x
5
>>> print Test.x
<__main__.Desc object at 0x23205d0>
You need to go up to the class itself:
type(instance_with_descriptor_attr).descriptor_attr
Demonstration:
>>> class Foo():
... #property
... def bar(self): return 'bar'
...
>>> foo = Foo()
>>> foo.bar
'bar'
>>> type(foo).bar
<property object at 0x109f24310>
if the __get__ method of the descriptor dont have a "return self" statement then the descriptor can only be accessed by the __dict__ attribute of the class:
class descriptor:
def __get__(self, instance, owner=None):
return 1
class A:
d = descriptor()
a = A()
a.d # --> 1
A.d # --> 1
A.__dict__['d'] # --> <__main__.descriptor object at ...>