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
Related
I've been using this style of inheritance to validate values set on instances of objects, but I'm wondering if there is a more fluent way to do this.
I'm following a spec where items of a certain classification (Foo) contain elements of a certain composition (Fe).
class Typed:
def __set__(self, obj, value):
assert isinstance(value, self._type), 'Incorrect type'
class Integer(Typed):
_type = int
class Float(Typed):
_type = float
class Positive(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value >= 0, 'Positive Values Only Accepted'
class PositiveInteger(Integer, Positive):
pass
class PositiveFloat(Float, Positive):
pass
class Sized(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value <= 2**self.size-1, f'{value} is too High'
class Fe(Sized, PositiveInteger):
name = 'Integer, 8 bit unsigned'
size = 8
class Foo(Fe):
name = 'Classificaion1'
def __set__(self, obj, id):
super().__set__(obj, id)
obj._id = id
def __get__(self, obj, objType=None):
return obj._id
def __del__(self):
pass
If you really need this level of abstraction, this is possibly the best way you can do it. My suggestion bellow can maybe save one line per class.
If you can afford to have attributes like "size" and "type" to be defined
on the final class, a richer base class and a declarative structure containing the checks as "lambda functions" can be used like this.
Note the usage of __init_subclass__ to check if all the parametes
needed for the guard expressions are defined:
from typing import Sequence
GUARDS = {
"typed": ((lambda self, value: "Incorrect type" if not instance(value, self._type) else None), ("_typed",)),
"positive": ((lambda self, value: "Only positive values" if value < 0 else None), ()),
"sized": ((lambda self, value: None if value <= 2 ** self.size - 1 else f"{value} must be smaller than 2**{self.size}"), ("size",)),
}
class DescriptorBase:
guards: Sequence[str]
def __init_subclass__(cls):
_sentinel = object()
for guard_name in cls.guards:
guard = GUARDS[guard_name]
required_attrs = guard[1]
missing = []
for attr in required_attrs:
if getattr(cls, attr, _sentinel) is _sentinel:
missing.append(attr)
if missing:
raise TypeError("Guarded descriptor {cls.__name__} did not declare required attrs: {missing}")
def __set_name__(self, owner, name):
self._name = f"_{name}""
def __set__(self, instance, value):
errors = []
for guard_name in self.guards:
if (error:= GUARDS[guard_name](self, value)) is not None:
errors.append(error)
if errors:
raise ValueError("\n".join(errors))
setattr (instance, self._name, value)
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.name)
def __del__(self, instance):
delattr(instance, self._name)
class Foo(DescriptorBase):
guards = ("typed", "positive", "sized")
size = 8
type_ = int
# No other code required here: __get__, __set__, __del__ handled in superclass
class UseAttr:
# Actual smart-attr usage:
my_foo = Foo()
Actually, if you want the class hierarchy, with less lines (no need to declare a __set__ method in each class), this approach can be used as well:
just change __init_superclass__ to collect "guards" in all superclasses,
and consolidate a single guards list on the class being defined, and then
define your composable guard-classes just as:
class Positive(BaseDescriptor):
guards = ("positive",)
class Sized(BaseDescriptor):
guards = ("sized",)
size = None
class Foo(Positive, Sized):
size = 8
class Fe(Foo):
name = "Fe name"
Actually, the change needed for this to work can be as simple as:
def __init_subclass__(cls):
_sentinel = object()
all_guards = []
for supercls in cls.__mro__:
all_guards.extend(getattr(supercls, "guards", ()))
# filter unique:
seem = {}
new_guards = []
for guard in all_guards:
if guard not in seem:
new_guards.append(guard)
seem.add(guard)
cls.guards = new_guards
for guard_name in cls.guards:
Also note that you could also collect the contents of the "GUARDS" registry from each defined class, instead of having to declare everything as lambdas before hand. I think you can get the idea from here on.
I'm trying to get the following behavior with pydantic.BaseModel:
class MyClass:
def __init__(self, value: T) -> None:
self._value = value
# Maybe:
#property
def value(self) -> T:
return self._value
# Maybe:
#value.setter
def value(self, value: T) -> None:
# ...
self._value = value
If T is also a pydantic model, then recursive initialization using dictionaries should work:
# Initialize `x._value` with `T(foo="bar", spam="ham")`:
x = MyClass(value={"foo": "bar", "spam": "ham"})
Note that _value is initialized using the kwargs value. Validation must also be available for private fields.
The pydantic docs (PrivateAttr, etc.) seem to imply that pydantic will never expose private attributes. I'm sure there is some hack for this. But is there an idiomatic way to achieve the behavior in pydantic? Or should I just use a custom class?
Not sure it this solution is advisable, based on: https://github.com/samuelcolvin/pydantic/issues/1577
https://github.com/samuelcolvin/pydantic/issues/655
import inspect
from typing import Dict
from pydantic import BaseModel, PrivateAttr
from pydantic.main import no_type_check
class PatchedModel(BaseModel):
#no_type_check
def __setattr__(self, name, value):
"""
To be able to use properties with setters
"""
try:
super().__setattr__(name, value)
except ValueError as e:
setters = inspect.getmembers(
self.__class__,
predicate=lambda x: isinstance(x, property) and x.fset is not None
)
for setter_name, func in setters:
if setter_name == name:
object.__setattr__(self, name, value)
break
else:
raise e
class T(BaseModel):
value1: str
value2: int
class MyClassPydantic(PatchedModel):
_value: T = PrivateAttr()
def __init__(self, value: Dict, **kwargs):
super().__init__(**kwargs)
object.__setattr__(self, "_value", T(**value))
#property
def value(self) -> T:
return self._value
#value.setter
def value(self, value: T) -> None:
self._value: T = value
# To avoid the PatchedModel(BaseModel) use instead
# def set_value(self, value: T) -> None:
# self._value: T = value
if __name__ == "__main__":
my_pydantic_class = MyClassPydantic({"value1": "test1", "value2": 1})
print(my_pydantic_class.value)
my_pydantic_class.value = T(value1="test2", value2=2)
# my_pydantic_class.set_value(T(value1="test2", value2=2))
print(my_pydantic_class.value)
I ended up with something like this, it acts like a private field, but i can change it by public methods:
import inspect
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, Field
class Entity(BaseModel):
"""Base entity class."""
def __setattr__(self, name, value):
if "self" not in inspect.currentframe().f_back.f_locals:
raise Exception("set attr is protected")
super().__setattr__(name, value)
class PostId(UUID):
"""Post unique id."""
class Post(Entity):
"""Post."""
post_id: PostId = Field(description='unique post id')
title: Optional[str] = Field(None, description='title')
def change_title(self, new_title: str) -> None:
"""Changes title."""
self.title = new_title
I just looking at inspect.currentframe().f_back.f_locals and looking for self key.
Ispired by accessify
Tested with this little test:
from uuid import uuid4
import pytest
import post_pydantic
def test_pydantic():
"""Test pydantic varriant."""
post_id = uuid4()
post = post_pydantic.Post(post_id=post_id)
with pytest.raises(Exception) as e:
post.post_id = uuid4()
assert post.post_id == post_id
assert e.value.args[0] == "set attr is protected"
new_title = "New title"
post.change_title(new_title)
assert post.title == new_title
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'
Thats what i came from at first place:
class DismRestartType(DismEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
and using it like:
class DismFeatureInfo(DismStructure):
_pack_ = 4
_fields_ = [
("FeatureName", c_wchar_p),
("RestartRequired", DismRestartType)
]
class DismEnum(Enum):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
return obj
Notice the additional parameter for the description. The plan is to display later the description instead of the value, so i don't have to prepare every structure itself.
The problem: I get an error because the structure expect a c type and got an enum.
I did some research and founds this i.e.:
Using an IntEnum as the type in a ctypes.Structure._fields_
So i tried:
class CEnumeration(c_int):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
print("Will never be executed")
return obj
def __repr__(self):
return self.description
Unfortunately i get *** AttributeError: 'DismRestartType' object has no attribute 'description', also the new method never gets executed. Can someone explain me why it's not executed and help me to reach the goal?
EDIT1:
I don't get it! Why does __new__ not executed in TestEnum but gets executed when i inherit from Enum? The metaclass new gets executed.
class PointlessMetaClass(type(c_int)):
def __new__(meta, classname, bases, classDict):
cls = type(c_int).__new__(meta, classname, bases, classDict)
pdb.set_trace()
return cls
class TestEnum(metaclass=PointlessMetaClass):
_type_ = "i"
def __new__(cls, value):
print("Why not executed")
pdb.set_trace()
return cls
class DismRestartType(TestEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
Solution:
I took long but now I got it:
from ctypes import c_int
from types import DynamicClassAttribute
class _EnumDict(dict):
"""Track enum member order and ensure member names are not reused.
EnumMeta will use the names found in self._member_names as the
enumeration member names.
"""
def __init__(self):
super().__init__()
self._member_names = []
def __setitem__(self, key, value):
if not isinstance(value, DynamicClassAttribute) and not key.startswith("_"):
self._member_names.append(key)
super().__setitem__(key, value)
class EnumerationType(type(c_int)):
"""Metaclass for Enum."""
#classmethod
def __prepare__(metacls, cls, bases):
edict = _EnumDict()
return edict
def __new__(metacls, classname, bases, classdict):
# save enum items into separate mapping so they don't get baked into
# the new class
enum_members = {k: classdict[k] for k in classdict._member_names}
for name in classdict._member_names:
del classdict[name]
# returns an instance of the new class, i.e. an instance of my enum
enum_class = super().__new__(metacls, classname, bases, classdict)
# Reverse value->name map for hashable values.
enum_class._value2member_map_ = {}
for member_name in classdict._member_names:
value = enum_members[member_name][0]
enum_member = c_int.__new__(enum_class)
enum_member.value = value # overwrites the value attr of c_int class
enum_member._name_ = member_name
enum_member._description_ = enum_members[member_name][1]
enum_member.__objclass__ = enum_class
# i.e DismRestartType.DismRestartNo will return an object instead of the value
setattr(enum_class, member_name, enum_member)
# i.e. {'0': <class DismRestartType:DismRestartNo: 0>}
enum_class._value2member_map_[value] = enum_member
return enum_class
def __repr__(self):
return "<Enumeration %s>" % self.__name__
class CEnumeration(c_int, metaclass=EnumerationType):
"""Generic enumeration.
Derive from this class to define new enumerations.
"""
def __new__(cls, value):
# all enum instances are actually created during class construction
# without calling this method; this method is called by the metaclass'
# __call__ (i.e. Color(3) ), and by pickle
if type(value) is cls:
# For lookups like Color(Color.RED)
return value
# by-value search for a matching enum member
# see if it's in the reverse mapping (for hashable values)
try:
if value in cls._value2member_map_:
return cls._value2member_map_[value]
except TypeError:
pass
return cls._missing_(value)
#classmethod
def _missing_(cls, value):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
# return only description
def __repr__(self):
return "<%s.%s: %r>" % (
self.__class__.__name__, self.name, self.value)
def __str__(self):
return "%s.%s" % (self.__class__.__name__, self.name)
# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`. This works because enumeration
# members are not set directly on the enum class -- __getattr__ is
# used to look them up.
#DynamicClassAttribute
def name(self):
"""The name of the Enum member."""
try:
# get name on instance
return self._name_
except AttributeError:
# get name on class
return self._value2member_map_[self.value]._name_
#DynamicClassAttribute
def description(self):
"""The description of the Enum member."""
try:
# get description on instance
return self._description_
except AttributeError:
# get description on class
return self._value2member_map_[self.value]._description_
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.