Если вы не против небольшой магии, это можно сделать относительно разумно, используя протокол дескриптора.
class FooDescriptor:
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj._foo
def __set__(self, obj, value):
if not isinstance(obj, type):
# disable instance name shadowing for sanity's sake
raise AttributeError("this attribute should be set on the class object")
obj._foo = value + "!!"
class FooMeta(type):
foo = FooDescriptor()
def __new__(cls, clsname, bases, namespace):
# pluck the "foo" attr out of the class namespace,
# and swap in our descriptor in its place
namespace["_foo"] = namespace.pop("foo", "(default foo val)")
namespace["foo"] = FooMeta.foo
return type.__new__(cls, clsname, bases, namespace)
При создании класса Foo
это заменит класс foo
атрибут, определенный обычным декларативным способом с дескриптором данных (для предоставления пользовательских методов получения и установки).Вместо этого мы будем хранить необработанное «неуправляемое» значение в Foo._foo
.
Демо:
>>> class Foo(metaclass=FooMeta):
... foo = "foo0"
...
>>> obj = Foo()
>>> obj.foo # accessible from instance, like a class attr
'foo0'
>>> Foo.foo # accessible from class
'foo0'
>>> Foo.foo = "foo1" # setattr has magic, this will add exclams
>>> obj.foo
'foo1!!'
>>> Foo.foo
'foo1!!'
>>> vars(obj) # still no instance attributes
{}
>>> type(Foo).foo # who does the trick?
<__main__.FooDescriptor at 0xcafef00d>
>>> obj.foo = "boom" # prevent name shadowing (optional!)
AttributeError: this attribute should be set on the class object