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
à la place?
Edit: Si vous avez python 3.7+, utilisez simplement 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__))
Disclaimer: Il semble que plusieurs personnes s’inquiètent de la présentation de cette solution, je vais donc fournir un disclaimer très clair. 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
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()
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 rappelez que les n-uplets sont assortis de certaines conditions) ... peut-être voulez-vous un ordre ou une dictée ...
Pour développer la réponse de gruszczy
s, 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:
super().__init(...)
)X.__init__
Avant Python 3.6, cela ne donne aucun contrôle sur l'ordre dans lequel les attributs sont 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 un peu amélioré, mais je suis le seul utilisateur de mon propre code et je ne m'inquiète donc d'aucune forme d'assainissement des entrées. Peut-être qu'un AttributeError
serait plus approprié.
Vous pouvez aussi faire:
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
Bien entendu, vous devrez importer le module inspect
.
C'est une solution sans aucune importation supplémentaire.
Une petite fonction d'aide le rend plus pratique et réutilisable:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
self = local_name_space.pop('self')
for name, value in local_name_space.items():
setattr(self, name, value)
Vous devez l'appeler avec locals()
:
class A:
def __init__(self, x, y, z):
auto_init(locals())
a = A(1, 2, 3)
print(a.__dict__)
Sortie:
{'y': 2, 'z': 3, 'x': 1}
locals()
Si vous n'aimez pas changer locals()
, utilisez cette version:
def auto_init(local_name_space):
"""Set instance attributes from arguments.
"""
for name, value in local_name_space.items():
if name != 'self':
setattr(local_name_space['self'], name, value)
Une bibliothèque intéressante qui gère cela (et évite beaucoup d'autres types de passe-partout) 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 pas besoin d'un __init__
_ _ _ _ _ _ _ _ _. Voici ne belle introduction de Glyph Lefkowitz .
Mon 0.02 $. C'est très proche de la réponse de Joran Beasley, mais plus élégante:
def __init__(self, a, b, c, d, e, f):
vars(self).update((k, v) for k, v in locals().items() if v is not self)
De plus, la réponse de Mike Müller (la meilleure à mon goût) peut être réduite avec cette technique:
def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)
Et l'appel juste auto_init(locals())
à partir de votre __init__
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.
Dans Python 3.7, vous pouvez (ab) utiliser le décorateur dataclass
, disponible dans le module dataclasses
. Dans 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
Ajoutera, entre autres choses, une
__init__()
ressemblant à ceci: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 présentée ci-dessus.
Si votre classe est grande et complexe, il est inapproprié d'utiliser un dataclass
. J'écris ceci le jour de la sortie de Python 3.7.0, donc les modèles d'utilisation ne sont pas encore bien établis.