Como fazer uma propriedade de classe? [duplicado]
em python Posso adicionar um método a uma classe com o decorador @classmethod
. Existe um decorador semelhante para adicionar uma propriedade a uma classe? É melhor Mostrar do que estou a falar.
class Example(object):
the_I = 10
def __init__( self ):
self.an_i = 20
@property
def i( self ):
return self.an_i
def inc_i( self ):
self.an_i += 1
# is this even possible?
@classproperty
def I( cls ):
return cls.the_I
@classmethod
def inc_I( cls ):
cls.the_I += 1
e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21
assert Example.I == 10
Example.inc_I()
assert Example.I == 11
a sintaxe que usei acima é possível ou exigiria algo mais?
a razão pela qual quero propriedades da classe é para poder carregar atributos preguiçosos da classe, o que parece razoável.
9 answers
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Bar(object):
_bar = 1
@classproperty
def bar(cls):
return cls._bar
@bar.setter
def bar(cls, value):
cls._bar = value
# test instance instantiation
foo = Bar()
assert foo.bar == 1
baz = Bar()
assert baz.bar == 1
# test static variable
baz.bar = 5
assert foo.bar == 5
# test setting variable on the class
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
O setter não funcionou na altura em que ligámos.
TypeOfBar.bar.__set__
, que não é Bar.bar.__set__
.
Adicionar uma definição metaclass resolve isto:
class ClassPropertyMetaClass(type):
def __setattr__(self, key, value):
if key in self.__dict__:
obj = self.__dict__.get(key)
if obj and type(obj) is ClassPropertyDescriptor:
return obj.__set__(self, value)
return super(ClassPropertyMetaClass, self).__setattr__(key, value)
# and update class define:
# class Bar(object):
# __metaclass__ = ClassPropertyMetaClass
# _bar = 1
# and update ClassPropertyDescriptor.__set__
# def __set__(self, obj, value):
# if not self.fset:
# raise AttributeError("can't set attribute")
# if inspect.isclass(obj):
# type_ = obj
# obj = None
# else:
# type_ = type(obj)
# return self.fset.__get__(obj, type_)(value)
Agora vai correr tudo bem.
Se definir classproperty
como se segue, então o seu exemplo funciona exactamente como pediu.
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
A ressalva é que você não pode usar isso para propriedades de escrita. Enquanto e.I = 20
AttributeError
, Example.I = 20
irá sobrepor o objecto da propriedade em si.
[resposta escrita com base no python 3.4; A sintaxe do metaclass difere em 2, mas acho que a técnica ainda vai funcionar]
Podes fazer isto com um metaclass...principalmente. Dappawit está quase a funcionar, mas acho que tem uma falha:class MetaFoo(type):
@property
def thingy(cls):
return cls._thingy
class Foo(object, metaclass=MetaFoo):
_thingy = 23
Isto dá-te um classsproperty no Foo, mas há um problema...
print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
print("Foo().thingy is {}".format(foo.thingy))
else:
print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?
Que raio se passa aqui? Porque não consigo chegar à propriedade de uma instância?
Estava a bater com a cabeça nisto há algum tempo. encontrar aquilo em que acredito é a resposta. Python @properties são um subconjunto de descritores, e, a partir da documentação descritor (emphasis mine):
A resolução do método a ordem não inclui as propriedades da nossa classe (ou qualquer outra coisa definida no metaclass). Ele é possível fazer uma subclasse da propriedade interna decorador que se comporta de forma diferente, mas (carece de fontes) que eu comecei a impressão de googling que os desenvolvedores tinham um bom motivo (que eu não entendo) para fazê-lo dessa forma. Isso não significa que estamos sem sorte, podemos aceder às propriedades da própria classe muito bem...e podemos conseguir a aula de dentro. a instância, que podemos usar para fazer @ property dispatchers:O comportamento por omissão para o acesso ao atributo é obter, definir ou apagar o atributo do dicionário de um objeto. Por exemplo,
a.x
tem uma cadeia de procura começando coma.__dict__['x']
, depoistype(a).__dict__['x']
, e continuando através das classes de base detype(a)
excluindo metaclasses.
class Foo(object, metaclass=MetaFoo):
_thingy = 23
@property
def thingy(self):
return type(self).thingy
Agora Foo().thingy
funciona como previsto para a classe e as instâncias! Ele também continuará a fazer a coisa certa se uma classe derivada substitui seu subjacente _thingy
(que é o caso de uso que me levou a esta caçada originalmente).
property
no metaclass funciona da mesma forma. (Eu não tentei isso antes, mas agora estou curioso...)
[actualização:]
Realmente funciona.class MetaClass(type):
def getfoo(self):
return self._foo
foo = property(getfoo)
@property
def bar(self):
return self._bar
class MyClass(object):
__metaclass__ = MetaClass
_foo = 'abc'
_bar = 'def'
print MyClass.foo
print MyClass.bar
Nota: Isto é no Python 2.7. O Python 3+ usa um diferente técnica para declarar um metaclass. Usar: class MyClass(metaclass=MetaClass):
, Remover __metaclass__
, e o resto é o mesmo.
from django.utils.decorators import classproperty
class TitleMeta(type):
@property
def title(self):
return getattr(self, '_title', 'Default Title')
@title.setter
def title(self, title):
self._title = title
# Do whatever else you want when the title is set...
Agora faça a classe real que deseja como normal, excepto que use o metaclass que criou acima.
# Python 2 style:
class ClassWithTitle(object):
__metaclass__ = TitleMeta
# The rest of your class definition...
# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
# Your class definition...
É um pouco estranho definir isto. metaclass como fizemos acima, se o usarmos apenas numa única classe. Nesse caso, se você estiver usando o estilo Python 2, você pode realmente definir o metaclass dentro do corpo da classe. Dessa forma, não é definido no escopo do módulo.
Se você só precisa de carga preguiçosa, então você poderia ter apenas um método de inicialização de classe.
EXAMPLE_SET = False
class Example(object):
@classmethod
def initclass(cls):
global EXAMPLE_SET
if EXAMPLE_SET: return
cls.the_I = 'ok'
EXAMPLE_SET = True
def __init__( self ):
Example.initclass()
self.an_i = 20
try:
print Example.the_I
except AttributeError:
print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I
Mas a abordagem metaclass parece mais limpa e com um comportamento mais previsível.
Talvez o que procura seja o padrão de design de Singleton. Há uma boa pergunta sobre a implementação do estado compartilhado em Python.
class MetaFoo(type):
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.thingy})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
@property
def thingy(cls):
if not inspect.isclass(cls):
cls = type(cls)
return cls._thingy
@thingy.setter
def thingy(cls, value):
if not inspect.isclass(cls):
cls = type(cls)
cls._thingy = value
class Foo(metaclass=MetaFoo):
_thingy = 23
class Bar(Foo)
_thingy = 12
Esta tem a melhor de todas as respostas:
O" metaproperty " é adicionado à classe, de modo que ainda será uma propriedade da instância {[[5]}
-
Não é preciso redefinir essa coisa em nenhuma das classes.
- a propriedade funciona como uma "propriedade de classe" em ambos os casos e classe
- tens a flexibilidade para personalizar como a coisa é herdada
Na minha case, Eu realmente personalizei _thingy
para ser diferente para cada criança, sem defini-lo em cada classe (e sem um valor padrão) por:
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
def _create_type(meta, name, attrs):
type_name = f'{name}Type'
type_attrs = {}
for k, v in attrs.items():
if type(v) is _ClassPropertyDescriptor:
type_attrs[k] = v
return type(type_name, (meta,), type_attrs)
class ClassPropertyType(type):
def __new__(meta, name, bases, attrs):
Type = _create_type(meta, name, attrs)
cls = super().__new__(meta, name, bases, attrs)
cls.__class__ = Type
return cls
class _ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, owner):
if self in obj.__dict__.values():
return self.fget(obj)
return self.fget(owner)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
return self.fset(obj, value)
def setter(self, func):
self.fset = func
return self
def classproperty(func):
return _ClassPropertyDescriptor(func)
class Bar(metaclass=ClassPropertyType):
__bar = 1
@classproperty
def bar(cls):
return cls.__bar
@bar.setter
def bar(cls, value):
cls.__bar = value
bar = Bar()
assert Bar.bar==1
Bar.bar=2
assert bar.bar==2
nbar = Bar()
assert nbar.bar==2