tm1 - python attrs default value




Comment puis-je éviter le "self.x=x; self.y=y; self.z=z ”motif dans__init__? (8)

Je vois des modèles comme

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

assez souvent, souvent avec beaucoup plus de paramètres. Existe-t-il un bon moyen d'éviter ce type de répétition fastidieuse? La classe devrait-elle hériter de namedtuple place?


Python 3.7 et plus

Dans Python 3.7, vous pouvez (ab) utiliser le décorateur de dataclasses , disponible à partir du module dataclasses . De la documentation:

Ce module fournit un décorateur et des fonctions permettant d’ajouter automatiquement des méthodes spéciales générées telles que __init__() et __repr__() à des classes définies par l’utilisateur. Il a été décrit à l'origine dans PEP 557.

Les variables membres à utiliser dans ces méthodes générées sont définies à l'aide d'annotations de type PEP 526. Par exemple ce code:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

__init__() , entre autres, un __init__() qui ressemble à:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

Notez que cette méthode est automatiquement ajoutée à la classe: elle n'est pas spécifiée directement dans la définition InventoryItem indiquée ci-dessus.

Si votre classe est grande et complexe, l'utilisation d'une dataclass peut être inappropriée. J'écris ceci le jour de la publication de Python 3.7.0, donc les modèles d'utilisation ne sont pas encore bien établis.


C'est une façon naturelle de faire les choses en Python. N'essayez pas d'inventer quelque chose de plus intelligent, cela conduirait à un code trop intelligent que personne ne comprendra. Si vous voulez être un joueur d'équipe et continuez à l'écrire de cette façon.


Comme d'autres l'ont déjà mentionné, la répétition n'est pas mauvaise, mais dans certains cas, un nom nommé peut être un choix judicieux pour ce type de problème. Cela évite d'utiliser locals () ou kwargs, qui sont généralement une mauvaise idée.

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

J'ai trouvé un usage limité pour cela, mais vous pouvez hériter d'un nom nommé comme avec n'importe quel autre objet (exemple):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

Edit: Si vous avez Python 3.7+, utilisez simplement des dataclasses

Une solution de décorateur qui conserve la signature:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

Pour développer la réponse de gruszczy , j’ai utilisé un modèle tel que:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

J'aime cette méthode car elle:

  • évite les répétitions
  • est résistant aux fautes de frappe lors de la construction d'un objet
  • fonctionne bien avec les sous-classes (peut simplement super().__init(...) )
  • permet de documenter les attributs au niveau de la classe (là où ils appartiennent) plutôt que dans X.__init__

Avant Python 3.6, cela ne permettait pas de contrôler l'ordre dans lequel les attributs étaient définis, ce qui pourrait poser problème si certains attributs sont des propriétés avec des paramètres qui accèdent à d'autres attributs.

Cela pourrait probablement être amélioré un peu, mais je suis le seul utilisateur de mon propre code, je ne m'inquiète donc d'aucune forme d'assainissement des entrées. Peut-être qu'un AttributeError serait plus approprié.


Une bibliothèque intéressante qui gère cela (et évite beaucoup d'autres attrs passe- attrs ) est attrs . Votre exemple, par exemple, pourrait être réduit à ceci (supposons que la classe s'appelle MyClass ):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

Vous n'avez même plus besoin d'une méthode __init__ , à moins qu'elle ne fasse également d'autres __init__ . Voici une belle introduction de Glyph Lefkowitz .


explicite vaut mieux qu'implicite ... vous pouvez donc le rendre plus concis:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

La meilleure question est devrait vous?

... cela dit que si vous voulez un tuple nommé, je vous recommanderais d'utiliser un nommé (vous vous rappelez que les n-uplets sont assortis de certaines conditions) ... peut-être voulez-vous un ordre ou même un dict ...


Clause de non-responsabilité: Il semble que plusieurs personnes s’inquiètent de la présentation de cette solution. Je vais donc fournir une clause de non-responsabilité très claire. Vous ne devriez pas utiliser cette solution. Je ne le fournis qu'à titre d'information, pour que vous sachiez que le langage en est capable. Le reste de la réponse montre simplement les capacités linguistiques, et ne recommande pas de les utiliser de cette manière.

Il n'y a rien de mal à copier explicitement des paramètres dans des attributs. Si vous avez trop de paramètres dans le ctor, cela est parfois considéré comme une odeur de code et vous devriez peut-être regrouper ces paramètres dans un nombre moins élevé d'objets. D'autres fois, c'est nécessaire et il n'y a rien de mal à cela. Quoi qu'il en soit, le faire explicitement est la voie à suivre.

Cependant, puisque vous demandez COMMENT cela peut être fait (et non si cela devrait être fait), alors une solution est la suivante:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2




python-attrs