¿Inicializar automáticamente las variables de instancia?

Tengo una clase de python que se ve así:

class Process: def __init__(self, PID, PPID, cmd, FDs, reachable, user): 

seguido por:

  self.PID=PID self.PPID=PPID self.cmd=cmd ... 

¿Hay alguna manera de autoinicializar estas variables de instancia, como la lista de inicialización de C ++? Le ahorraría mucho código redundante.

Puedes usar un decorador:

 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 

Úsalo para decorar el método __init__ :

 class process: @initializer def __init__(self, PID, PPID, cmd, FDs, reachable, user): pass 

Salida:

 >>> c = process(1, 2, 3, 4, 5, 6) >>> c.PID 1 >>> dir(c) ['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user' 

Si estás usando Python 2.6 o superior, puedes usar colecciones.namedu tuple :

 >>> from collections import namedtuple >>> Process = namedtuple('Process', 'PID PPID cmd') >>> proc = Process(1, 2, 3) >>> proc.PID 1 >>> proc.PPID 2 

Esto es apropiado, especialmente cuando su clase es realmente una gran bolsa de valores.

Citando el Zen de Python ,

Explícito es mejor que implícito.

Otra cosa que puedes hacer:

 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` 

Pero la única solución que recomendaría, además de deletrearla, es “crea una macro en tu editor” ;-p

Para Python 3.7+, puede usar una Clase de datos , que es una forma muy pirónica y fácil de hacer para hacer lo que quiera.

Le permite definir campos para su clase, que son sus variables de instancia que se inicializan automáticamente.

Se vería algo así:

 @dataclass class Process: PID: int PPID: int cmd: str ... 

El método __init__ ya estará en su clase.

Tenga en cuenta que aquí se requieren sugerencias de tipo , por eso he usado int y str en el ejemplo. Si no conoce el tipo de su campo, puede usar Cualquiera del módulo de typing .

La clase de datos tiene muchas ventajas en comparación con las soluciones propuestas:

  • Es explícito : todos los campos son visibles, lo que respeta el Zen de Python y lo hace legible y mantenible. **kwargs con el uso de **kwargs .
  • Puede tener métodos . Al igual que cualquier otra clase. La ausencia de métodos, si quiere usarlos, es una desventaja de namedtuple .
  • Le permite ir más allá del __init__ automático utilizando el método __post_init__ .

Podría hacerlo fácilmente con los argumentos de palabras clave, por ejemplo, así:

 >>> class D: def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) >>> D(test='d').test 'd' 

Implementación similar para los argumentos posicionales sería:

 >> class C: def __init__(self, *args): self.t, self.d = args >>> C('abc', 'def').t 'abc' >>> C('abc', 'def').d 'def' 

Lo que para mí no parece resolver tu problema.

La solución de Nadia es mejor y más poderosa, pero creo que esto también es interesante:

 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" 

Necesitaba algo para el mismo propósito, pero ninguna de las respuestas existentes cubría todos los casos que probé. La respuesta de Nadia fue la más cercana a lo que estaba buscando, así que comencé con su código como base.

El decorador a continuación funciona con todas las combinaciones válidas de argumentos:

 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 ) 

También implementa la convención estándar de prefijo _ para permitir las variables __init__ que no se asignarán a las instancias de clase.


 ### 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 __name__ == "__main__":$$#######| Positional |##################################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, b):$ pass$$ t = T(1, 2)$ assert (ta == 1) and (tb == 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 (ta == "kw_arg_1") and (tb == "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 (ta == 1) and (tb == 2) and (tc == "KW_DEFAULT_1") and (td == "KW_DEFAULT_2")$$ t = T(1, 2, c="kw_arg_1")$ assert (ta == 1) and (tb == 2) and (tc == "kw_arg_1") and (td == "KW_DEFAULT_2")$$ t = T(1, 2, d="kw_arg_2")$ assert (ta == 1) and (tb == 2) and (tc == "KW_DEFAULT_1") and (td == "kw_arg_2")$$#######| Variable Positional |#########################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, *a):$ pass$$ t = T(1, 2, 3)$ assert (ta == [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 (ta == [1, 2, 3]) and (tb == "KW_DEFAULT_1")$$ t = T(1, 2, 3, b="kw_arg_1")$ assert (ta == [1, 2, 3]) and (tb == "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 (ta == [1, 2, 3]) and (tb == "kw_arg_1") and (tc == "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 (ta == 1) and (tb == [2, 3]) and (tc == "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 (ta == 1) and (tb == [2, 3]) and (tc == "kw_arg_1") and (td == "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 (ta == "kw_arg_1")$$#######| Positional + Keyword Only |###################################################################################$$ class T:$ @auto_assign_arguments$ def __init__(self, a, *, b="KW_DEFAULT_1"):$ pass$$ t = T(1)$ assert (ta == 1) and (tb == "KW_DEFAULT_1")$$ t = T(1, b="kw_arg_1")$ assert (ta == 1) and (tb == "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")) 

Nota:

Incluí pruebas, pero las colapsé en la última línea ( 58 ) para mayor brevedad. Puede ampliar las pruebas, que detallan todos los posibles casos de uso, find/replace todos $ caracteres de $ con una nueva línea.

¡Puede que no haya necesidad de inicializar variables, ya que locals () ya contiene los valores!

clase Dummy (objeto):

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

{‘a’: 2, ‘b’: 3, ‘predeterminado’: ‘Fred’}

d.params [‘b’]

3

Por supuesto, dentro de una clase uno podría usar self.params.

Tan pronto como getargspec está en desuso desde Python 3.5, aquí hay una solución utilizando 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 

Compruebe si funciona:

 class Foo(object): @auto_assign def __init__(self, a, b, c=None, d=None, e=3): pass f = Foo(1, 2, d="a") assert fa == 1 assert fb == 2 assert fc is None assert fd == "a" assert fe == 3 print("Ok") 

nu11ptr ha creado un pequeño módulo, PyInstanceVars , que incluye esta funcionalidad como un decorador de funciones. En el README del módulo, se indica que “el […] rendimiento ahora es solo un 30-40% peor que la inicialización explícita bajo CPython “.

Ejemplo de uso, levantado directamente de la documentación del módulo:

 >>> 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 "", line 1, in  AttributeError: 'TestMe' object has no attribute 'arg2_' >>> testme.arg2 3 >>> testme.arg3 'test' 

Para 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 

Manifestación:

 >>> p = Point('foo', 'bar', r=100, u=200) >>> px, py, pz, pm, pr, pu ('foo', 'bar', 1, 'meh', 100, 200) 

Un enfoque no decorativo para Python 2 y 3 utilizando marcos:

 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) 

Manifestación:

 >>> p = Point('foo', 'bar') >>> px 'foo' >>> py 'bar' 

Tal vez esta sea una pregunta cerrada, pero me gustaría proponer mi solución para saber qué piensa al respecto. He usado una metaclase que aplica un decorador al método init

 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 

La biblioteca de attrs hace algo como esto.