207 votes

Comment créer une propriété de classe ?

En python, je peux ajouter une méthode à une classe à l'aide de la commande @classmethod décorateur. Existe-t-il un décorateur similaire pour ajouter une propriété à une classe ? Je peux mieux montrer ce dont je parle.

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

La syntaxe que j'ai utilisée ci-dessus est-elle possible ou nécessite-t-elle quelque chose de plus ?

La raison pour laquelle je veux des propriétés de classe est que je peux charger paresseusement les attributs de la classe, ce qui semble assez raisonnable.

2 votes

Je ne connais pas python... est-ce que c'est le genre de choses que vous recherchez ? python.org/download/releases/2.2/descrintro/#property

0 votes

Si je lis bien, on peut créer des méthodes pour agir comme setter et getter (comme dans d'autres langages) afin de charger paresseusement la valeur de la propriété lors du premier get... ce qui, je pense, est ce que vous vouliez ?

3 votes

@White Dragon. La fonctionnalité de propriété que vous recherchez ajoute des propriétés aux instances de classe, pas aux classes elles-mêmes. Ma question porte sur Example.I no e.i .

6voto

ArtOfWarfare Points 2345

Pour autant que je sache, il n'existe aucun moyen d'écrire un setter pour une propriété de classe sans créer une nouvelle métaclasse.

J'ai constaté que la méthode suivante fonctionne. Définissez une métaclasse avec toutes les propriétés de la classe et les paramètres que vous souhaitez. Par exemple, je voulais une classe avec une propriété title avec un filtre (setter). Voici ce que j'ai écrit :

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...

Maintenant, créez la classe que vous voulez comme d'habitude, mais faites-lui utiliser la métaclasse que vous avez créée ci-dessus.

# 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...

Il est un peu bizarre de définir cette métaclasse comme nous l'avons fait ci-dessus si nous ne l'utiliserons jamais que pour une seule classe. Dans ce cas, si vous utilisez le style de Python 2, vous pouvez définir la métaclasse à l'intérieur du corps de la classe. De cette façon, elle n'est pas définie dans la portée du module.

4voto

metaphy Points 605
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

1voto

Apalala Points 2999

Si vous n'avez besoin que d'un chargement paresseux, vous pouvez vous contenter d'une méthode d'initialisation de la 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

Mais l'approche des métaclasses semble plus propre, et avec un comportement plus prévisible.

Ce que vous recherchez peut-être, c'est le Singleton modèle de conception. Il y a un bon AQ de l'OS sur la mise en œuvre de l'état partagé en Python.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X