2009-09-07 13 views
53

tengo una clase Python que tiene este aspecto:¿Inicializar automáticamente variables de instancia?

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

seguido de:

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

¿Hay alguna manera de autoinitialize estas variables de instancia, al igual que la lista de inicialización C++ 's? Ahorraría un montón de código redundante.

+0

Consulte también la discusión de la receta del estado activo 'autoasignación' y una implementación alternativa de 'autoargs' en: [¿Cuál es la mejor manera de realizar la asignación automática de atributos en Python, y es una buena idea? - Desbordamiento de pila] (http://stackoverflow.com/questions/3652851/what-is-the-best-way-to-do-automatic-attribute-assignment-in-python-and-is-it-a) – nealmcb

Respuesta

66

Puede utilizar 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 

utilizarlo para decorar el __init__ método:

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' 
+1

nitpick menor - se olvidó de importar inspeccionar –

+2

Esto funciona y responder a la pregunta, así que voté. Pero mantuve la respuesta de Ferdidand Beyer: "Explícito es mejor que implícito" –

+5

+1 Por la excelente respuesta que resolvió mi problema. Pero, ¿no debería ser una funcionalidad central del lenguaje? ¿Crees que vale la pena escribir un PEP? –

13

Puede hacerlo fácilmente con los argumentos de la palabra clave, p. Ej. de esta manera:

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

>>> D(test='d').test 
'd' 

aplicació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' 

que para mí no parece resolver su problema.

+1

Otra variación que me gusta es 'self .__ dict __. Update (** kwargs)' –

+0

También podría usar locals() y poner argumentos normales. – mk12

22

Citando al Zen of Python,

Explícito es mejor que implícito.

+1

¿No sería una declaración de lista de inicialización suficientemente explícita? –

+0

supongo. Pero no veo una razón para agregar algo así al lenguaje. Claramente prefiero las declaraciones de asignación múltiple sobre algún tipo de magia decoradora detrás de la escena. –

+20

@Ferdinand, estoy de acuerdo en que sería una tontería tener en el lenguaje algo que perfectamente puede estar en el stdlib, pero, DEBERÍA estar en el stdlib, porque "lo bello es mejor que lo feo" tiene prioridad, y muchas tareas repetitivas son feo (como cualquier forma de repetición). –

27

Si está utilizando Python 2.6 o superior, puede utilizar collections.namedtuple:

>>> 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 en realidad una gran bolsa de valores.

+0

+1 para la herramienta realmente ordenada –

+2

"Esto es apropiado especialmente cuando la clase es en realidad una gran cantidad de valores ". En tal caso, también podría hacer esto: [https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends](https: //docs.python.org/3.3/tutorial/classes.html # odds-and-ends) –

16

Otra cosa que puede 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 sólo la ortografía a cabo, es "hacer una macro en el editor de" ;-P

6

solución de Nadia es mejor y más potente, 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" 
1

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

Ejemplo de uso, levantó directamente de documentation 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 "<stdin>", line 1, in <module> 
AttributeError: 'TestMe' object has no attribute 'arg2_' 
>>> testme.arg2 
3 
>>> testme.arg3 
'test' 
2

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

clase ficticia (objeto):

def __init__(self, a, b, default='Fred'): 
    self.params = {k:v for k,v in locals().items() if k != 'self'} 

d = simulada (2, 3)

d.params

{ 'a': 2, 'b': 3 ', default ': 'Fred'}

d.params [' b ']

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

+0

Es un enfoque agradable y original, pero 'd ['b']' está escrito en * lingua franca * de Python, mientras que 'd.params ['b']' causará confusión para los lectores de códigos . –

0

Quizás esta es una pregunta cerrada, pero me gustaría proponer mi solución para saber qué opina al respecto. He utilizado una metaclase que se aplica a un decorador de init método

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 
2

Tan pronto como getargspec está obsoleta desde Python 3.5, aquí está la solución usando 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 

Comprobar 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 f.a == 1 
assert f.b == 2 
assert f.c is None 
assert f.d == "a" 
assert f.e == 3 

print("Ok") 
1

Necesitaba algo para el mismo propósito, pero ninguna de las respuestas existentes cubría todos los casos que probado   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 de abajo 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 el estándar de convenciones _ -prefix para permitir __init__ las variables Privadas que no se pueden asignar a instancias de la 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 (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")) 

Nota:

que incluyeron pruebas, pero ellos se derrumbó en la última línea (58 ) por razones de brevedad.   Puede expandir las pruebas, que detallan todos los casos de uso potencial, por find/replace -todos los $ caracteres con una nueva línea.

0

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 

Demostración:

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

Un enfoque no decorador tanto para Python 2 y 3 usando 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) 

Demostración:

>>> p = Point('foo', 'bar') 
>>> p.x 
'foo' 
>>> p.y 
'bar' 
Cuestiones relacionadas