2009-09-07 13 views
53

Ich habe eine Python-Klasse, die wie folgt aussieht:Instanzvariablen automatisch initialisieren?

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

gefolgt von:

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

Gibt es eine Möglichkeit, diese Instanzvariablen autoinitialize, wie C++ 's Initialisierung Liste? Es würde viel redundanten Code ersparen.

+0

Siehe auch das 'activestate'-Rezept 'autoassign' und eine alternative' autoargs'-Implementierung unter: [Was ist der beste Weg, automatische Attributzuweisung in Python zu machen, und ist das eine gute Idee? - Stapelüberlauf] (http://stackoverflow.com/questions/3652851/what-is-the-best-way-to-do-automatic-attribute-assignment-in-python-and-is-it-a) – nealmcb

Antwort

66

Sie einen Dekorateur verwenden:

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 

es Verwenden Sie die __init__ Methode zu dekorieren:

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

Ausgang:

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

Minor Nitpick - Sie vergessen zu importieren inspizieren –

+2

Das funktioniert und beantworten Sie die Frage, so habe ich abgestimmt. Aber ich behielt Ferdidand Beyers Antwort: "Explizit ist besser als implizit" –

+5

+1 Für eine gute Antwort, die mein Problem gelöst hat. Aber sollte es nicht eine Kernfunktionalität der Sprache sein? Denken Sie, dass es sich lohnt, eine PEP zu schreiben? –

13

Sie könnten es leicht mit den Schlüsselwort Argumenten, z. dies wie:

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

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

ähnliche Implementierung für die Positionsargumente wäre:

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


>>> C('abc', 'def').t 
'abc' 
>>> C('abc', 'def').d 
'def' 

mir das nicht Ihr Problem zu lösen scheint.

+1

Eine andere Variante, die ich mag, ist 'selbst .__ dict __. Update (** kwargs)' –

+0

Könnte auch Locals() verwenden und normale Argumente setzen. – mk12

22

der Zen of Python Zitiert,

Explizit ist besser als implizit.

+1

Wäre eine Initialisierungslisten-Deklaration nicht explizit genug? –

+0

Ich denke. Aber ich sehe keinen Grund dafür, der Sprache so etwas hinzuzufügen. Ich bevorzuge eindeutig mehrere Zuweisungsaussagen gegenüber einer Art Dekorator-Magie hinter den Kulissen. –

+20

@Ferdinand, ich stimme zu, es wäre albern, in der Sprache etwas zu haben, das durchaus in der stdlib sein kann, aber es sollte in der stdlib sein, denn "schön ist besser als hässlich" hat Vorrang, und viele sich wiederholende Aufgaben sind hässlich (wie jede Form von Wiederholung). –

27

Wenn Sie mit Python 2.6 oder höher, können Sie collections.namedtuple verwenden:

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

Dies ist besonders geeignet, wenn die Klasse wirklich nur eine große Tasche von Werten ist.

+0

+1 für wirklich ordentlich Werkzeug. –

+2

"Dies ist besonders angebracht, wenn Sie Klasse ist wirklich nur eine große Tasche von Werten. "In einem solchen Szenario könnten Sie das auch tun: [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

Eine andere Sache, die Sie tun können:

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` 

Aber die einzige Lösung, die ich empfehlen würde, außer Rechtschreibung es einfach aus, ist „ein Makro in Ihrem Editor machen“ ;-p

6

Nadias Lösung besser und leistungsfähiger, aber ich denke, das ist auch interessant ist:

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 ein kleines Modul gemacht hat, PyInstanceVars, die diese Funktionalität beinhaltet als Dekorateur. In der README des Moduls heißt es, dass die "[...] Leistung jetzt nur 30-40% schlechter ist als die explizite Initialisierung unter CPython".

Usage Beispiel angehoben direkt aus dem documentation des Moduls:

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

Es ist kein Bedarf Variablen zu initialisieren sein können, wie die Einheimischen() bereits die Werte enthält!

Klasse Dummy (Objekt):

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 ', default ': 'Fred'}

d.params [' b ']

Natürlich könnte man innerhalb einer Klasse self.params verwenden

+0

Es ist eine nette und originelle Herangehensweise, aber 'd ['b']' wird in Pythons * lingua franca * geschrieben, während 'd.params ['b']' Verwirrung bei den Codelesern verursachen wird . –

0

Vielleicht ist dies eine geschlossene Frage, aber ich würde gerne meine Lösung vorschlagen, um zu wissen, was Sie darüber denken. Ich habe eine Metaklasse verwendet, die einen Dekorateur init Methode

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

Sobald getargspec seit Python 3.5 als veraltet gilt, ist hier Lösung mit 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 

Überprüfen Sie, ob Werke:

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

Ich brauchte etwas für den gleichen Zweck, aber keine der vorhandenen Antworten deckt alle Fälle I ab geprüft.   Nadias Antwort entsprach am ehesten meiner Suche, also begann ich mit ihrem Code als Basis.

Der Dekorateur arbeitet unter allen gültigen Kombinationen von Argumenten:

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  ) 

Es implementiert auch die Standard-_ -prefix Konvention für __init__ -privater Variablen zu ermöglichen, die auf Klasseninstanzen nicht zugeordnet werden.


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

Hinweis:

I enthalten Tests, aber sie in der letzten Zeile brach zusammen() der Kürze halber.   Sie können die Tests, die alle potenziellen Anwendungsfälle detailliert darstellen, um find/replace erweitern - indem Sie alle $ Zeichen mit einem Zeilenumbruch versehen.

0

Für 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 

Demo:

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

Ein nicht-decorator Ansatz für beide Python 2 und 3 unter Verwendung von Frames:

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) 

Demo:

>>> p = Point('foo', 'bar') 
>>> p.x 
'foo' 
>>> p.y 
'bar' 
Verwandte Themen