J'ai une classe python qui ressemble à ceci:
class Process:
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
suivi par:
self.PID=PID
self.PPID=PPID
self.cmd=cmd
...
Est-il possible d'autoinitialiser ces variables d'instance, comme la liste d'initialisation de C++? Cela épargnerait beaucoup de code redondant.
Vous pouvez utiliser un décorateur:
from functools import wraps
import inspect
def initializer(func):
"""
Automatically assigns the parameters.
>>> class process:
... @initializer
... def __init__(self, cmd, reachable=False, user='root'):
... pass
>>> p = process('halt', True)
>>> p.cmd, p.reachable, p.user
('halt', True, 'root')
"""
names, varargs, keywords, defaults = inspect.getargspec(func)
@wraps(func)
def wrapper(self, *args, **kargs):
for name, arg in list(Zip(names[1:], args)) + list(kargs.items()):
setattr(self, name, arg)
for name, default in Zip(reversed(names), reversed(defaults)):
if not hasattr(self, name):
setattr(self, name, default)
func(self, *args, **kargs)
return wrapper
Utilisez-le pour décorer la méthode __init__
:
class process:
@initializer
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
pass
Sortie:
>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'
Si vous utilisez Python 2.6 ou supérieur, vous pouvez utiliser collections.namedtuple :
>>> from collections import namedtuple
>>> Process = namedtuple('Process', 'PID PPID cmd')
>>> proc = Process(1, 2, 3)
>>> proc.PID
1
>>> proc.PPID
2
Cela convient particulièrement lorsque votre classe est vraiment un sac de valeurs.
Citant le Zen of Python ,
Explicite est meilleur qu'implicite.
Une autre chose que vous pouvez faire:
class X(object):
def __init__(self, a,b,c,d):
vars = locals() # dict of local names
self.__dict__.update(vars) # __dict__ holds and object's attributes
del self.__dict__["self"] # don't need `self`
Mais la seule solution que je recommanderais, en plus de la préciser, est de "créer une macro dans votre éditeur" ;-p
Pour Python 3.7+, vous pouvez utiliser la classe Data Class, qui est une manière très pythonique et maintenable de faire ce que vous voulez.
Il vous permet de définir champs pour votre classe, qui sont vos variables d'instance initialisées automatiquement.
Cela ressemblerait à quelque chose comme ça:
@dataclass
class Process:
PID: int
PPID: int
cmd: str
...
La méthode __init__
sera déjà dans votre classe.
Notez que ici le type de repère est requis, c’est pourquoi j’ai utilisé int
et str
dans cet exemple. Si vous ne connaissez pas le type de votre champ, vous pouvez utiliser N'importe quel élément du module typing
.
La classe de données présente de nombreux avantages par rapport aux solutions proposées:
**kwargs
. namedtuple
. __init__
automatique en utilisant la méthode __post_init__
. Vous pouvez le faire facilement avec les arguments de mots clés, par exemple. comme ça:
>>> class D:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
>>> D(test='d').test
'd'
une implémentation similaire pour les arguments de position serait:
>> class C:
def __init__(self, *args):
self.t, self.d = args
>>> C('abc', 'def').t
'abc'
>>> C('abc', 'def').d
'def'
ce qui pour moi ne semble pas résoudre votre problème.
La solution de Nadia est meilleure et plus puissante, mais je pense que c'est aussi intéressant:
def constructor(*arg_names):
def __init__(self, *args):
for name, val in Zip(arg_names, args):
self.__setattr__(name, val)
return __init__
class MyClass(object):
__init__ = constructor("var1", "var2", "var3")
>>> c = MyClass("fish", "cheese", "beans")
>>> c.var2
"cheese"
J'avais besoin de quelque chose dans le même but, mais aucune des réponses existantes ne couvrait tous les cas que j'avais testés. La réponse de Nadia était la plus proche de ce que je cherchais, alors j'ai commencé avec son code comme base.
Le décorateur ci-dessous fonctionne avec toutes les combinaisons d'arguments valides:
Positional __init__(self, a, b )
Keyword __init__(self, a=None, b=None )
Positional + Keyword __init__(self, a, b, c=None, d=None)
Variable Positional __init__(self, *a )
Variable Positional + Keyword __init__(self, *a, b=None )
Variable Positional + Variable Keyword __init__(self, *a, **kwargs )
Positional + Variable Positional + Keyword __init__(self, a, *b, c=None )
Positional + Variable Positional + Variable Keyword __init__(self, a, *b, **kwargs )
Keyword Only __init__(self, *, a=None )
Positional + Keyword Only __init__(self, a, *, b=None )
Il implémente également la convention standard du préfixe _
- pour autoriser les variables privées __init__
- qui ne seront pas affectées à des instances de classe.
### StdLib ###
from functools import wraps
import inspect
###########################################################################################################################
#//////| Decorator |//////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################
def auto_assign_arguments(function):
@wraps(function)
def wrapped(self, *args, **kwargs):
_assign_args(self, list(args), kwargs, function)
function(self, *args, **kwargs)
return wrapped
###########################################################################################################################
#//////| Utils |//////////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################
def _assign_args(instance, args, kwargs, function):
def set_attribute(instance, parameter, default_arg):
if not(parameter.startswith("_")):
setattr(instance, parameter, default_arg)
def assign_keyword_defaults(parameters, defaults):
for parameter, default_arg in Zip(reversed(parameters), reversed(defaults)):
set_attribute(instance, parameter, default_arg)
def assign_positional_args(parameters, args):
for parameter, arg in Zip(parameters, args.copy()):
set_attribute(instance, parameter, arg)
args.remove(arg)
def assign_keyword_args(kwargs):
for parameter, arg in kwargs.items():
set_attribute(instance, parameter, arg)
def assign_keyword_only_defaults(defaults):
return assign_keyword_args(defaults)
def assign_variable_args(parameter, args):
set_attribute(instance, parameter, args)
POSITIONAL_PARAMS, VARIABLE_PARAM, _, KEYWORD_DEFAULTS, _, KEYWORD_ONLY_DEFAULTS, _ = inspect.getfullargspec(function)
POSITIONAL_PARAMS = POSITIONAL_PARAMS[1:] # remove 'self'
if(KEYWORD_DEFAULTS ): assign_keyword_defaults (parameters=POSITIONAL_PARAMS, defaults=KEYWORD_DEFAULTS)
if(KEYWORD_ONLY_DEFAULTS): assign_keyword_only_defaults(defaults=KEYWORD_ONLY_DEFAULTS )
if(args ): assign_positional_args (parameters=POSITIONAL_PARAMS, args=args )
if(kwargs ): assign_keyword_args (kwargs=kwargs )
if(VARIABLE_PARAM ): assign_variable_args (parameter=VARIABLE_PARAM, args=args )
###########################################################################################################################$#//////| Tests |//////////////////////////////////////////////////////////////////////////////////////////////////////#$###########################################################################################################################$$if __== "__main__":$$#######| Positional |##################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b):$ pass$$ t = T(1, 2)$ assert (t.a == 1) and (t.b == 2)$$#######| Keyword |#####################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a="KW_DEFAULT_1", b="KW_DEFAULT_2"):$ pass$$ t = T(a="kw_arg_1", b="kw_arg_2")$ assert (t.a == "kw_arg_1") and (t.b == "kw_arg_2")$$#######| Positional + Keyword |########################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b, c="KW_DEFAULT_1", d="KW_DEFAULT_2"):$ pass$$ t = T(1, 2)$ assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "KW_DEFAULT_2")$$ t = T(1, 2, c="kw_arg_1")$ assert (t.a == 1) and (t.b == 2) and (t.c == "kw_arg_1") and (t.d == "KW_DEFAULT_2")$$ t = T(1, 2, d="kw_arg_2")$ assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "kw_arg_2")$$#######| Variable Positional |#########################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a):$ pass$$ t = T(1, 2, 3)$ assert (t.a == [1, 2, 3])$$#######| Variable Positional + Keyword |###############################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a, b="KW_DEFAULT_1"):$ pass$$ t = T(1, 2, 3)$ assert (t.a == [1, 2, 3]) and (t.b == "KW_DEFAULT_1")$$ t = T(1, 2, 3, b="kw_arg_1")$ assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1")$$#######| Variable Positional + Variable Keyword |######################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a, **kwargs):$ pass$$ t = T(1, 2, 3, b="kw_arg_1", c="kw_arg_2")$ assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1") and (t.c == "kw_arg_2")$$#######| Positional + Variable Positional + Keyword |##################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *b, c="KW_DEFAULT_1"):$ pass$$ t = T(1, 2, 3, c="kw_arg_1")$ assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1")$$#######| Positional + Variable Positional + Variable Keyword |#########################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *b, **kwargs):$ pass$$ t = T(1, 2, 3, c="kw_arg_1", d="kw_arg_2")$ assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1") and (t.d == "kw_arg_2")$$#######| Keyword Only |################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *, a="KW_DEFAULT_1"):$ pass$$ t = T(a="kw_arg_1")$ assert (t.a == "kw_arg_1")$$#######| Positional + Keyword Only |###################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *, b="KW_DEFAULT_1"):$ pass$$ t = T(1)$ assert (t.a == 1) and (t.b == "KW_DEFAULT_1")$$ t = T(1, b="kw_arg_1")$ assert (t.a == 1) and (t.b == "kw_arg_1")$$#######| Private __init__ Variables (underscored) |####################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b, _c):$ pass$$ t = T(1, 2, 3)$ assert hasattr(t, "a") and hasattr(t, "b") and not(hasattr(t, "_c"))
J'ai inclus des tests, mais je les ai rassemblés dans la dernière ligne ( 58 ) par souci de concision. Vous pouvez développer les tests, qui détaillent tous les cas d'utilisation potentiels, en find/replace
- tous les caractères $
avec une nouvelle ligne.
Il n’est peut-être pas nécessaire d’initialiser les variables, car locals () contient déjà les valeurs!
classe Dummy (objet):
def __init__(self, a, b, default='Fred'):
self.params = {k:v for k,v in locals().items() if k != 'self'}
d = Dummy (2, 3)
d.param
{'a': 2, 'b': 3, 'default': 'Fred'}
d.params ['b']
3
Bien sûr, dans une classe, on pourrait utiliser self.params
Dès que getargspec
est obsolète depuis Python 3.5, voici une solution utilisant inspect.signature
:
from inspect import signature, Parameter
import functools
def auto_assign(func):
# Signature:
sig = signature(func)
for name, param in sig.parameters.items():
if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
raise RuntimeError('Unable to auto assign if *args or **kwargs in signature.')
# Wrapper:
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
for i, (name, param) in enumerate(sig.parameters.items()):
# Skip 'self' param:
if i == 0: continue
# Search value in args, kwargs or defaults:
if i - 1 < len(args):
val = args[i - 1]
Elif name in kwargs:
val = kwargs[name]
else:
val = param.default
setattr(self, name, val)
func(self, *args, **kwargs)
return wrapper
Vérifiez si fonctionne:
class Foo(object):
@auto_assign
def __init__(self, a, b, c=None, d=None, e=3):
pass
f = Foo(1, 2, d="a")
assert f.a == 1
assert f.b == 2
assert f.c is None
assert f.d == "a"
assert f.e == 3
print("Ok")
nu11ptr a créé un petit module, PyInstanceVars , qui inclut cette fonctionnalité en tant que décorateur de fonctions. Dans le module README il est indiqué que les "performances de [...] sont maintenant 30% à 40% plus mauvaises que l'initialisation explicite sous CPython".
Exemple d'utilisation, extrait directement du module documentation } _:
>>> from instancevars import *
>>> class TestMe(object):
... @instancevars(omit=['arg2_'])
... def __init__(self, _arg1, arg2_, arg3='test'):
... self.arg2 = arg2_ + 1
...
>>> testme = TestMe(1, 2)
>>> testme._arg1
1
>>> testme.arg2_
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'TestMe' object has no attribute 'arg2_'
>>> testme.arg2
3
>>> testme.arg3
'test'
Pour Python 3.3+:
from functools import wraps
from inspect import Parameter, signature
def instance_variables(f):
sig = signature(f)
@wraps(f)
def wrapper(self, *args, **kwargs):
values = sig.bind(self, *args, **kwargs)
for k, p in sig.parameters.items():
if k != 'self':
if k in values.arguments:
val = values.arguments[k]
if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
setattr(self, k, val)
Elif p.kind == Parameter.VAR_KEYWORD:
for k, v in values.arguments[k].items():
setattr(self, k, v)
else:
setattr(self, k, p.default)
return wrapper
class Point(object):
@instance_variables
def __init__(self, x, y, z=1, *, m='meh', **kwargs):
pass
Démo:
>>> p = Point('foo', 'bar', r=100, u=200)
>>> p.x, p.y, p.z, p.m, p.r, p.u
('foo', 'bar', 1, 'meh', 100, 200)
Une approche non décoratrice pour Python 2 et 3 utilisant des cadres:
import inspect
def populate_self(self):
frame = inspect.getouterframes(inspect.currentframe())[1][0]
for k, v in frame.f_locals.items():
if k != 'self':
setattr(self, k, v)
class Point(object):
def __init__(self, x, y):
populate_self(self)
Démo:
>>> p = Point('foo', 'bar')
>>> p.x
'foo'
>>> p.y
'bar'
La bibliothèque attrs fait quelque chose comme ceci.
C'est peut-être une question fermée, mais j'aimerais proposer ma solution afin de savoir ce que vous en pensez. J'ai utilisé une métaclasse qui applique un décorateur à init method
import inspect
class AutoInit(type):
def __new__(meta, classname, supers, classdict):
classdict['__init__'] = wrapper(classdict['__init__'])
return type.__new__(meta, classname, supers, classdict)
def wrapper(old_init):
def autoinit(*args):
formals = inspect.getfullargspec(old_init).args
for name, value in Zip(formals[1:], args[1:]):
setattr(args[0], name, value)
return autoinit