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'
Related
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
Let's say I have a class like this:
class C:
def __init__(self, stuff: int):
self._stuff = stuff
#property
def stuff(self) -> int:
return self._stuff
then stuff is read-only:
c = C(stuff=10)
print(c.stuff) # prints 10
and
c.stuff = 2
fails as expected
AttributeError: can't set attribute
How can I get the identical behavior using a dataclass? If I wanted to also have a setter, I could do:
#dataclass
class DC:
stuff: int
_stuff: int = field(init=False, repr=False)
#property
def stuff(self) -> int:
return self._stuff
#stuff.setter
def stuff(self, stuff: int):
self._stuff = stuff
But how could I do it without the #stuff.setter part?
This answer extends directly from my other post on using descriptor classes, which is a convenient and handy way to define properties, more or less.
Since dataclasses does not offer a field(frozen=True) approach, I think this one can instead work for you.
Here is a straightforward example of usage below:
from dataclasses import dataclass, MISSING
from typing import Generic, TypeVar
_T = TypeVar('_T')
class Frozen(Generic[_T]):
__slots__ = (
'_default',
'_private_name',
)
def __init__(self, default: _T = MISSING):
self._default = default
def __set_name__(self, owner, name):
self._private_name = '_' + name
def __get__(self, obj, objtype=None):
value = getattr(obj, self._private_name, self._default)
return value
def __set__(self, obj, value):
if hasattr(obj, self._private_name):
msg = f'Attribute `{self._private_name[1:]}` is immutable!'
raise TypeError(msg) from None
setattr(obj, self._private_name, value)
#dataclass
class DC:
stuff: int = Frozen()
other_stuff: str = Frozen(default='test')
dc = DC(stuff=10)
# raises a TypeError: Attribute `stuff` is immutable!
# dc.stuff = 2
# raises a TypeError: Attribute `other_stuff` is immutable!
# dc.other_stuff = 'hello'
print(dc)
# raises a TypeError: __init__() missing 1 required positional argument: 'stuff'
# dc = DC()
Another option, is to use a metaclass which automatically applies the #dataclass decorator. This has a few advantages, such as being able to use dataclasses.field(...) for example to set a default value if desired, or to set repr=False for instance.
Note that once #dataclass_transform comes out in PY 3.11, this could potentially be a good use case to apply it here, so that it plays more nicely with IDEs in general.
In any case, here's a working example of this that I was able to put together:
from dataclasses import dataclass, field, fields
class Frozen:
__slots__ = ('private_name', )
def __init__(self, name):
self.private_name = '_' + name
def __get__(self, obj, objtype=None):
value = getattr(obj, self.private_name)
return value
def __set__(self, obj, value):
if hasattr(obj, self.private_name):
msg = f'Attribute `{self.private_name[1:]}` is immutable!'
raise TypeError(msg) from None
setattr(obj, self.private_name, value)
def frozen_field(**kwargs):
return field(**kwargs, metadata={'frozen': True})
def my_meta(name, bases, cls_dict):
cls = dataclass(type(name, bases, cls_dict))
for f in fields(cls):
# if a dataclass field is supposed to be frozen, then set
# the value to a descriptor object accordingly.
if 'frozen' in f.metadata:
setattr(cls, f.name, Frozen(f.name))
return cls
class DC(metaclass=my_meta):
other_stuff: str
stuff: int = frozen_field(default=2)
# DC.stuff = property(lambda self: self._stuff)
dc = DC(other_stuff='test')
print(dc)
# raises TypeError: Attribute `stuff` is immutable!
# dc.stuff = 41
dc.other_stuff = 'hello'
print(dc)
To get the boilerplate reduction that dataclass provides I found the only way to do this is with a descriptor.
In [236]: from dataclasses import dataclass, field
In [237]: class SetOnce:
...: def __init__(self):
...: self.block_set = False
...: def __set_name__(self, owner, attr):
...: self.owner = owner.__name__
...: self.attr = attr
...: def __get__(self, instance, owner):
...: return getattr(instance, f"_{self.attr}")
...: def __set__(self, instance, value):
...: if not self.block_set:
...: self.block_set = True
...: setattr(instance, f"_{self.attr}", value)
...: else:
...: raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")
In [239]: #dataclass
...: class Foo:
...: bar:str = field(default=SetOnce())
In [240]: test = Foo("bar")
In [241]: test.bar
Out[241]: 'bar'
In [242]: test.bar = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-242-9cc7975cd08b> in <module>
----> 1 test.bar = 1
<ipython-input-237-bddce9441c9a> in __set__(self, instance, value)
12 self.value = value
13 else:
---> 14 raise AttributeError(f"{self.owner}.{self.attr} cannot be set.")
15
AttributeError: Foo.bar cannot be set.
In [243]: test
Out[247]: Foo(bar='bar')
from dataclasses import dataclass
#dataclass(frozen=True)
class YourClass:
"""class definition"""
https://docs.python.org/3/library/dataclasses.html#frozen-instances
After instantiation of the class, when trying to change any of its properties, the exception is raised.
Because using the decorator in the class definition essentially triggers the #dataclass decorator to use the property object as a default field, it doesn't play nice. You can set the property outside like:
>>> from dataclasses import dataclass, field
>>> #dataclass
... class DC:
... _stuff: int = field(repr=False)
... stuff: int = field(init=False)
...
>>> DC.stuff = property(lambda self: self._stuff) # dataclass decorator cant see this
>>> dc = DC(42)
>>> dc
DC(stuff=42)
>>> dc.stuff
42
>>> dc.stuff = 99
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
import operator
#dataclass
class Enum:
name: str = property(operator.attrgetter("_name"))
def __init__(self, name):
self._name = name
You can do it by combining three things:
Set frozen to False (the default);
Use __post_init__, which is called after the auto-generated __init__ finishes, to mark when the initial setting of values is set and the read-only behavior has to start;
Create your own version of __setattr__ to enforce the read-only behavior after the initial assignment.
Example Person class with a read-only ID field and a read-write name field:
from dataclasses import dataclass
#dataclass
class Person(object):
id : str
name : str
def __post_init__(self):
self._initialized = True
def __setattr__(self, key, value):
if "_initialized" not in self.__dict__:
# we are still inside __init__, assign all values
super().__setattr__(key, value)
elif key == 'id':
# __init__ has finished, enforce read-only attributes
raise AttributeError(f'Attribute id is read-only')
else:
# set read-write attributes normally
super().__setattr__(key, value)
p = Person(id="1234", name="John Doe")
p.name = "John Wick" # succeeds
p.id = "3456" # fails
I haven't implemented __delattr__ in this example, but it could follow the same logic we used on __setattr__.
Using a decorator so you don't need to write this much code for each class:
from typing import Optional, Iterable, Callable, Union
from dataclasses import dataclass
def readonlyattr(attrs : Optional[Union[str, Iterable[str]]] = None):
# ensure attrs is a set of strings
if isinstance(attrs, str):
attrs = set([attrs])
elif not isinstance(attrs, set):
attrs = set(attrs)
# return decorator
def wrap_readonly_attributes(cls: type):
# update post_init method
def make_post_init(cls: type, method: Callable):
def post_init(self, *args, **kwargs):
self._initialized = True
if method:
method(self, *args, **kwargs)
else:
for base in cls.__bases__:
try:
getattr(base, "__post_init__")(self, *args, **kwargs)
except AttributeError:
pass
return post_init
setattr(cls, "__post_init__", make_post_init(cls, getattr(cls, "__post_init__", None)))
# update setattr method
def make_setattr(cls: type, method: Callable):
def new_setattr(self, key, value):
if "_initialized" not in self.__dict__:
if method:
method(self, key, value)
else:
super().__setattr__(key, value)
elif key in attrs:
raise AttributeError(f'Attribute {key} is read-only')
else:
if method:
method(self, key, value)
else:
super().__setattr__(key, value)
return new_setattr
setattr(cls, "__setattr__", make_setattr(cls, getattr(cls, "__setattr__", None)))
return cls
return wrap_readonly_attributes
#dataclass
#readonlyattr(["id", "passport_no"])
class Person(object):
id : str
passport_no : str
name : str
p = Person(id="1234", passport_no="AB12345", name="John Doe")
print(p)
p.name = "John Wick" # succeeds
p.id = "3456" # fails
The first demo:
class B:
def __init__(self):
self.name = '234'
# def __getattribute__(self, name):
# print('getattr')
def __getattr__(self, name):
print('get')
def __setattr__(self, name, value):
print('set')
def __delattr__(self, name):
print('del')
b = B()
print(b.__dict__)
b.name
b.__dict__ is {}, but the second demo:
class B:
def __init__(self):
self.name = '234'
def __getattribute__(self, name):
print('getattr')
def __getattr__(self, name):
print('get')
def __setattr__(self, name, value):
print('set')
def __delattr__(self, name):
print('del')
b = B()
print(b.__dict__)
b.name
b.__dict__ is None, why? And b.__dict__ invokes __getattribute__, but don't invoke __getattr__, does it mean __getattribute__ will prevent from invoking __getattr__?
The __getattribute__, __setattr__ and __delattr__ methods are called for all attribute access (getting, setting and deleting). __getattr__ on the other hand is only called for missing attributes; it is not normally already implemented, but if it is then __getattribute__ calls it if it could not otherwise locate the attribute, or if an AttributeError was raised by __getattribute__.
You replaced the standard implementations of the 3 main methods with methods that do nothing but print and return None (the default in the absence of an explicit return statement). __dict__ is just another attribute access, and your __getattribute__ method returns None, and never itself calls __getattr__ or raises an AttributeError.
From the Customizing attribute access documentation:
object.__getattr__(self, name)
Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self).
and
object.__getattribute__(self, name)
Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError.
(Bold emphasis mine).
Either call the base implementation (via super().__getattribute__) or raise an AttributeError:
>>> class B:
... def __init__(self):
... self.name = '234'
... def __getattribute__(self, name):
... print('getattr')
... return super().__getattribute__(name)
... def __getattr__(self, name):
... print('get')
... def __setattr__(self, name, value):
... print('set')
... def __delattr__(self, name):
... print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
{}
>>> b.name
getattr
get
>>> class B:
... def __init__(self):
... self.name = '234'
... def __getattribute__(self, name):
... print('getattr')
... raise AttributeError(name)
... def __getattr__(self, name):
... print('get')
... def __setattr__(self, name, value):
... print('set')
... def __delattr__(self, name):
... print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
get
>>> b.name
getattr
get
Note that by calling super().__getattribute__ the actual __dict__ attribute is found. By raising an AttributeError instead, __getattr__ was called, which also returned None.
I have the following snippet:
class Meta(type):
def __getattr__(self, name):
pass
class Klass(object):
__metaclass__ = Meta
def get(self, arg):
pass
Now, if I do:
kls = Klass()
kls.get('arg')
everything works as expected (the instance method get is called).
But if I do:
Klass.get('arg')
again the instance method is found and an exception is given, since it is treated as an unbound method.
How can I make a call to Klass.get('arg') go through the __getattr__ defined in the metaclass? I need this because I want to proxy all methods called on a class to another object (this would be done in __getattr__).
You'll have to look up the method on the type and pass in the first (self) argument manually:
type(Klass).get(Klass, 'arg')
This problem is the very reason that special method names are looked up using this path; custom classes would not be hashable or representable themselves if Python didn't do this.
You could make use of that fact; rather than use a get() method, use __getitem__, overloading [..] indexing syntax, and have Python do the type(ob).methodname(ob, *args) dance for you:
class Meta(type):
def __getitem__(self, arg):
pass
class Klass(object):
__metaclass__ = Meta
def __getitem__(self, arg):
pass
and then Klass()['arg'] and Klass['arg'] work as expected.
However, if you have to have Klass.get() behave differently (and the lookup for this to be intercepted by Meta.__getattribute__) you have to explicitly handle this in your Klass.get method; it'll be called with one argument less if called on the class, you could make use of that and return a call on the class:
_sentinel = object()
class Klass(object):
__metaclass__ = Meta
def get(self, arg=_sentinel):
if arg=_sentinel:
if isinstance(self, Klass):
raise TypeError("get() missing 1 required positional argument: 'arg'")
return type(Klass).get(Klass, self)
# handle the instance case ...
You could also handle this in a descriptor that mimics method objects:
class class_and_instance_method(object):
def __init__(self, func):
self.func = func
def __get__(self, instance, cls=None):
if instance is None:
# return the metaclass method, bound to the class
type_ = type(cls)
return getattr(type_, self.func.__name__).__get__(cls, type_)
return self.func.__get__(instance, cls)
and use this as a decorator:
class Klass(object):
__metaclass__ = Meta
#class_and_instance_method
def get(self, arg):
pass
and it'll redirect look-ups to the metaclass if there is no instance to bind to:
>>> class Meta(type):
... def __getattr__(self, name):
... print 'Meta.{} look-up'.format(name)
... return lambda arg: arg
...
>>> class Klass(object):
... __metaclass__ = Meta
... #class_and_instance_method
... def get(self, arg):
... print 'Klass().get() called'
... return 'You requested {}'.format(arg)
...
>>> Klass().get('foo')
Klass().get() called
'You requested foo'
>>> Klass.get('foo')
Meta.get look-up
'foo'
Applying the decorator can be done in the metaclass:
class Meta(type):
def __new__(mcls, name, bases, body):
for name, value in body.iteritems():
if name in proxied_methods and callable(value):
body[name] = class_and_instance_method(value)
return super(Meta, mcls).__new__(mcls, name, bases, body)
and you can then add methods to classes using this metaclass without having to worry about delegation:
>>> proxied_methods = ('get',)
>>> class Meta(type):
... def __new__(mcls, name, bases, body):
... for name, value in body.iteritems():
... if name in proxied_methods and callable(value):
... body[name] = class_and_instance_method(value)
... return super(Meta, mcls).__new__(mcls, name, bases, body)
... def __getattr__(self, name):
... print 'Meta.{} look-up'.format(name)
... return lambda arg: arg
...
>>> class Klass(object):
... __metaclass__ = Meta
... def get(self, arg):
... print 'Klass().get() called'
... return 'You requested {}'.format(arg)
...
>>> Klass.get('foo')
Meta.get look-up
'foo'
>>> Klass().get('foo')
Klass().get() called
'You requested foo'
I want to be able use python descriptors in a class which has the slots optimization:
class C(object):
__slots__ = ['a']
a = MyDescriptor('a')
def __init__(self, val):
self.a = val
The problem I have is how to implement the descriptor class in order to be able to store values in the class instance which invokes the descriptor object. The usual solution would look like the one below but will not work since "dict" is no longer defined when "slots" is invoked in the C class:
class MyDescriptor(object):
__slots__ = ['name']
def __init__(self, name_):
self.name = name_
def __get__(self, instance, owner):
if self.name not in instance.__dict__:
raise AttributeError, self.name
return instance.__dict__[self.name]
def __set__(self, instance, value):
instance.__dict__[self.name] = value
Don't declare the same name as a slot and as an instance method. Use different names, and access the slot as an attribute, not via __dict__.
class MyDescriptor(object):
__slots__ = ['name']
def __init__(self, name_):
self.name = name_
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
setattr(instance, self.name, value)
class C(object):
__slots__ = ['_a']
a = MyDescriptor('_a')
def __init__(self, val):
self.a = val
foo = C(1)
print foo.a
foo.a = 2
print foo.a
Though of dubious value, the following trick will work, if it is ok to put the first __slots__ in a subclass.
class A( object ):
__slots__ = ( 'a', )
class B( A ):
__slots__ = ()
#property
def a( self ):
try:
return A.a.__get__( self )
except AttributeError:
return 'no a set'
#a.setter
def a( self, val ):
A.a.__set__( self, val )
(You can use your own descriptor rather than property.) With these definitions:
>>> b = B()
>>> b.a
'no a set'
>>> b.a = 'foo'
>>> b.a
'foo'
As far as I understand, __slots__ is implemented with its own descriptor, so another descriptor after __slots__ in the same class would just overwrite. If you want to elaborate this technique, you could search for a candidate descriptor in self.__class__.__mro__ (or starting with instance in your own __get__).
Postscript
Ok ... well if you really want to use one class, you can use the following adaptation:
class C( object ):
__slots__ = ( 'c', )
class MyDescriptor( object ):
def __init__( self, slots_descriptor ):
self.slots_descriptor = slots_descriptor
def __get__( self, inst, owner = None ):
try:
return self.slots_descriptor.__get__( inst, owner )
except AttributeError:
return 'no c'
def __set__( self, inst, val ):
self.slots_descriptor.__set__( inst, val )
C.c = MyDescriptor( C.c )
If you insist on inscrutability, you can make the assignment in a metaclass or a class decorator.
The #Glenn Maynard's answer is the good one.
But I would like to point at a problem in the OP's question (I can't add a comment to his question since I havn't enough reputation yet):
The following test is raising an error when the instance hasn't a __dict__ variable:
if self.name not in instance.__dict__:
So, here is an a generic solution that tries to acces to the __dict__ variable first (which is the default anyway) and, if it fails, use getattr and setattr:
class WorksWithDictAndSlotsDescriptor:
def __init__(self, attr_name):
self.attr_name = attr_name
def __get__(self, instance, owner):
try:
return instance.__dict__[self.attr_name]
except AttributeError:
return getattr(instance, self.attr_name)
def __set__(self, instance, value):
try:
instance.__dict__[self.attr_name] = value
except AttributeError:
setattr(instance, self.attr_name, value)
(Works only if the attr_name is not the same as the real instance variable's name, or you will have a RecursionError as pointed to in the accepted answer)
(Won't work as expected if there is both __slots__ AND __dict__)
Hope this helps.