Instanzvariablen automatisch initialisieren?

86

Ich habe eine Python-Klasse, die so aussieht:

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 wie die Initialisierungsliste von C ++ automatisch zu initialisieren? Es würde viel redundanten Code sparen.

Adam Matan
quelle
1
Siehe auch Diskussion des autoassignActivestate-Rezepts und eine alternative autoargsImplementierung unter: Was ist der beste Weg, um eine automatische Attributzuweisung in Python
Stapelüberlauf

Antworten:

103

Sie können 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

Verwenden Sie es, um die __init__Methode zu dekorieren :

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

Ausgabe:

>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'
Nadia Alramli
quelle
4
Das funktioniert und beantworte die Frage, also habe ich abgestimmt. Aber ich habe Ferdidand Beyers Antwort beibehalten: "Explizit ist besser als implizit"
Lucas Gabriel Sánchez
13
+1 Für eine großartige Antwort, die mein Problem gelöst hat. Aber sollte es nicht eine Kernfunktionalität der Sprache sein? Glaubst du, es lohnt sich, einen PEP zu schreiben?
Adam Matan
3
Dies ist eine wirklich gute Antwort - dies ist direkt in meine Toolbox eingegangen.
Michael van der Westhuizen
3
@ NadiaAlramli Ich denke, es gibt einen kleinen Fehler im Code. Schauen Sie hier gist.github.com/pmav99/137dbf4681be9a58de74 (original.py)
pmav99
2
Das aktuelle Beispiel weist einen Fehler auf und funktioniert nicht, wenn die Signatur keine Standardargumente enthält. Sie müssen eine Prüfung einschließen, um sicherzustellen, dass Namen und Standardeinstellungen nicht Keine sind. Beispiel: Wenn Namen und
36

Wenn Sie Python 2.6 oder höher verwenden, können Sie collection.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 dann angebracht, wenn Ihre Klasse wirklich nur eine große Tüte mit Werten ist.

Kiv
quelle
2
"Dies ist besonders dann angebracht, wenn Ihre Klasse wirklich nur eine große Tüte mit Werten ist." In einem solchen Szenario können Sie auch
Folgendes
33

Für Python 3.7+ können Sie verwenden Datenklasse , die ein sehr pythonic und wartbar Weg ist , zu tun , was Sie wollen.

Hier können Sie Felder für Ihre Klasse definieren, die Ihre automatisch initialisierten Instanzvariablen sind.

Es würde ungefähr so ​​aussehen:

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

Die __init__Methode wird bereits in Ihrer Klasse sein.

Beachten Sie, dass hier Typhinweise erforderlich sind, deshalb habe ich intund strim Beispiel verwendet. Wenn Sie den Typ Ihres Feldes nicht kennen, können Sie Beliebig aus dem typingModul verwenden .

Die Datenklasse hat gegenüber den vorgeschlagenen Lösungen viele Vorteile:

  • Es ist explizit : Alle Felder sind sichtbar, was das Zen von Python respektiert und es lesbar und wartbar macht. Vergleichen Sie es mit der Verwendung von **kwargs.
  • Es kann Methoden haben . Genau wie jede andere Klasse.
  • __init__Mit dieser __post_init__Methode können Sie über die automatische Methode hinausgehen .

BEARBEITEN: Gründe, die Verwendung von NamedTuples zu vermeiden

Einige schlagen die Verwendung von namedtuplefür diesen Fall vor, aber Namedtuples weisen einige Verhaltensweisen auf, die sich von Python-Klassen unterscheiden, die zunächst nicht wirklich offensichtlich sind und bekannt sein sollten:

1. NamedTuples sind unveränderlich

Unveränderlichkeit kann nützlich sein, aber vielleicht ist es nicht das, was Sie für Ihre Instanzen wollen. DataClasses können auch irgendwie unveränderlich sein, wenn Sie das Argument frozen=Truefür den @dataclassDekorator verwenden.

2. NamedTuples __eq__verhält sich wie Tuples

In Python SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)ist True! Die __eq__in Vergleichen verwendete Funktion NamedTuple berücksichtigt nur die Werte und Positionen dieser Werte in den verglichenen Instanzen, nicht die Namen ihrer Klassen oder Felder.

Jundiaius
quelle
Dies sollte nur verwendet werden, wenn der Zweck der Klasse darin besteht, Daten zu speichern.
JC Rocamonde
Oder um Daten zu speichern.
JC Rocamonde
3
Würden Sie erklären, warum Datenklassen nur für Klassen verwendet werden sollten, in denen Daten gespeichert werden, anstatt auch ein anderes Verhalten zu haben? Ich könnte einen neuen SO-Beitrag dafür erstellen, um die entsprechenden Anwendungsfälle besser zu verstehen. Vielen Dank.
Vahid Pazirandeh
Data Classes can be thought of as "mutable namedtuples with defaults". - PEP557
aafulei
27

Zitiert das Zen von Python ,

Explizit ist besser als implizit.

Ferdinand Beyer
quelle
9
Wäre eine Initialisierungslistendeklaration nicht explizit genug?
Adam Matan
Ich vermute. Aber ich sehe keinen Grund, der Sprache so etwas hinzuzufügen. Ich bevorzuge eindeutig mehrere Zuweisungsaussagen gegenüber einer Art Dekorationsmagie hinter den Kulissen.
Ferdinand Beyer
29
@Ferdinand, ich stimme zu, es wäre dumm, in der Sprache etwas zu haben, das durchaus in der stdlib enthalten sein kann, aber es sollte in der stdlib sein, weil "schön ist besser als hässlich" Vorrang hat und viele sich wiederholende Aufgaben hässlich sind (wie jede Form der Wiederholung).
Alex Martelli
Nun, um zu kontern: DWIM> DWIS
Terrence Brannon
Ich würde zustimmen, dass Dekorateur schöner ist als eine Liste von Aufgaben, aber PyCharm macht es hässlicher, indem es es nicht versteht :-(
user110954
23

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, ist "Makro in Ihrem Editor erstellen" ;-p

Jochen Ritzel
quelle
1
Guter Fang beim Löschen von 'Selbst'.
Michael
15

Sie können dies problemlos mit den Schlüsselwortargumenten tun, z. B.:

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

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

Eine ä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'

was mir Ihr Problem nicht zu lösen scheint.

SilentGhost
quelle
3
Eine andere Variation, die ich mag, istself.__dict__.update( **kwargs )
S.Lott
Könnte auch einheimische () verwenden und normale Argumente setzen.
mk12
7

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

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"
Andrew Magee
quelle
5

Ich brauchte etwas für den gleichen Zweck, aber keine der vorhandenen Antworten deckte alle von mir getesteten Fälle ab. Nadias Antwort kam meiner Suche am nächsten, also begann ich mit ihrem Code als Basis.

Der folgende Dekorateur arbeitet mit 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        )

Außerdem wird die Standardkonvention _-prefix implementiert , um __init__-private Variablen zuzulassen, die Klasseninstanzen nicht zugewiesen 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:

Ich habe Tests eingeschlossen, sie aber der Kürze halber in der letzten Zeile ( 58 ) zusammengefasst. Sie können die Tests, in denen alle potenziellen Anwendungsfälle aufgeführt sind, erweitern, indem Sie find/replacealle $Zeichen mit einer neuen Zeile versehen.

Enteleform
quelle
3

Möglicherweise müssen keine Variablen initialisiert werden, da local () die Werte bereits 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']

3

Natürlich kann man innerhalb einer Klasse self.params verwenden

user3215769
quelle
Es ist ein schöner und origineller Ansatz, sondern d['b']ist in Python geschrieben lingua franca , während d.params['b']wird Verwirrung für die Code - Leser verursachen.
Adam Matan
3

Sobald getargspeces seit Python 3.5 veraltet ist, ist hier die 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 es funktioniert:

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")
Mikhail Gerasimov
quelle
2

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-Dekorator-Ansatz für 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'
Ashwini Chaudhary
quelle
1

nu11ptr hat mit PyInstanceVars ein kleines Modul erstellt , das diese Funktionalität als Funktionsdekorateur enthält. In der README des Moduls heißt es, dass die [...] Leistung jetzt nur noch 30-40% schlechter ist als die explizite Initialisierung unter CPython .

Anwendungsbeispiel, direkt aus der Dokumentation des Moduls entnommen :

>>> 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'
benregn
quelle
0

Vielleicht ist dies eine geschlossene Frage, aber ich möchte meine Lösung vorschlagen, um zu wissen, was Sie darüber denken. Ich habe eine Metaklasse verwendet, die einen Dekorator auf die Init- Methode anwendet

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
delca85
quelle
0

Die attrs- Bibliothek macht so etwas.

offby1
quelle
0

am Ende der Init- Funktion:

for var in list(locals().keys()):
    setattr(self,var,locals()[var])

Weitere setattr()Informationen finden Sie hier

LuWil
quelle