2015-02-10 15 views
6

Ich habe es satt, immer wieder die gleichen, sich wiederholenden Befehle immer wieder in meiner __init__ Funktion eingeben. Ich habe mich gefragt, ob ich einen Dekorateur schreiben könnte, der die Arbeit für mich erledigt. Hier ist ein Beispiel für meine Frage:Python Decorator automatisch zu definieren __init__ Variablen

class Point: 
    def __init__(self, x, y): 
     self.x = x 
     self.y = y 

Gibt es irgendeine Art und Weise, kann ich automatisch alle Argumente in die Funktion wurde Instanzvariablen mit den gleichen Namen bestanden hat? Zum Beispiel:

class Point: 
    @instance_variables 
    def __init__(self, x, y): 
     pass 

Wo @instance_variables automatisch self.x = x und self.y = y gesetzt würde. Wie könnte ich das tun?
Danke!

EDIT: Ich sollte erwähnen, dass ich CPython 2.7 verwende.

+0

Dies scheint, wie es solch ein nützliches Werkzeug sein, könnte ich mir vorstellen, dass es einige 3rd-Party-Bibliothek auf PyPI, die diese Funktionalität zur Verfügung stellt. – SethMMorton

+0

Python-Version? –

+1

@SethMMorton Ich stimme nicht zu, dass dies nützlich ist. Ich finde, dass das Zwingen von Entwicklern (einschließlich mir), das schmerzhafte Boilerplate der Membervariableninitiation zu tippen, eine gute Möglichkeit ist, Leute davon abzuhalten, "__init__" -Methoden zu haben, die eine lächerliche Anzahl von Argumenten akzeptieren, die in eine lächerliche Anzahl von Mitgliedsvariablen umgewandelt werden . Es ist wie eine Steuer auf Klassenblähung. Wenn Sie so viele Argumente in "__init__" akzeptieren, dass Sie diese Funktion benötigen, ist es normalerweise ein guter Hinweis darauf, dass Sie Ihr Design mit kleineren, unterteilten Klassen, vielleicht einem MixIn-Design, umgestalten sollten. – ely

Antwort

3

Hier ist mein erster Versuch an der Dekorateur:

[EDIT zweiten Versuch: Ich habe Voreinstellungen für Variablen und Überprüfung auf gültige Schlüsselwörter Handhabung. Dank ivan_pozdeev]

[EDIT 3: Hinzugefügt Check für Ausfälle ist nicht Keine]

def instanceVariables(func): 
    def returnFunc(*args, **kwargs): 
     selfVar = args[0] 

     argSpec = inspect.getargspec(func) 
     argumentNames = argSpec[0][1:] 
     defaults = argSpec[3] 
     if defaults is not None: 
      defaultArgDict = dict(zip(reversed(argumentNames), reversed(defaults))) 
      selfVar.__dict__.update(defaultArgDict) 

     argDict = dict(zip(argumentNames, args[1:])) 
     selfVar.__dict__.update(argDict) 


     validKeywords = set(kwargs) & set(argumentNames) 
     kwargDict = {k: kwargs[k] for k in validKeywords} 
     selfVar.__dict__.update(kwargDict) 

     func(*args, **kwargs) 

    return returnFunc 

Hier ist ein Beispiel:

class Test(): 

    @instanceVariables 
    def __init__(self, x, y=100, z=200): 
     pass 

    def printStr(self): 
     print(self.x, self.y, self.z) 

a = Test(1, z=2) 

a.printStr() 

>>> 1 100 2 
+0

Ich sehe einen Nebeneffekt: eine Kollision zwischen Meta-Variablen und 'Kwargs' wird nicht überprüft bis' func (* args, ** kwargs) ', d. H. Wenn die Daten bereits im Objekt sind. –

+0

@ivan_pozdeev, Sie sind richtig, persönlich würde ich nicht gerne '** kwargs' unterstützt, weil dann der Code Variablennamen basierend auf der Initialisierung des Objekts erstellt, die wiederum Code schwierig zu lesen macht. Lassen Sie mich sehen, ob ich daran arbeiten und auch Standardwerte zu Variablen hinzufügen kann. – ashwinjv

+0

Ich würde sogar sagen, den Import von 'Kwarts' direkt ohne Desinfektion ist ein Sicherheitsrisiko. –

0

Sie können entweder Reflexion Code-Duplizierung

self.__dict__.update(v,locals()[v] for v in 'x','y') 

reduzieren (oder fast gleichwertig (v darf kein Meta-Variablenname sein))

for v in 'x','y': setattr(self,v,locals()[v]) 

Oder CPython die Details der Implementierung verwenden abrufen Argumentnamen aus der Laufzeit gemäß Getting method parameter names in python

cur_fr = sys._getframe().f_code 
self.__dict__.update(v,locals()[v] for v in cur_fr.co_varnames[1:cur_fr.co_argcount]) # cur_fr.f_locals is the same as locals() 

Der zweite Ansatz erscheint eher "automatisiert", aber as I've said erweist sich als ziemlich unflexibel. Wenn Ihre Argumentliste länger als 3-4 ist, müssen Sie wahrscheinlich nur einige der Argumente auf diese Weise behandeln. In diesem Fall haben Sie keine anderen Optionen, als ihre Liste manuell zu konstruieren.

+0

FYI: Dass die zweite Antwort in dem Link eine nicht-CPython-spezifische Antwort bietet, die das 'inspect' Modul aus der Standardbibliothek verwendet. – SethMMorton

+3

Ich hoffe wirklich, dass ich keine Lösung in irgendeinem Produktionscode sehen muss ... – ThiefMaster

+0

'inspect' ist nicht voll funktionsfähig ohne CPython-spezifisches' sys._getframe() ' –

1

Sie können dies tun:

def __init__(self, x, y): 
    self.__dict__.update(locals()) 
    del self.self # redundant (and a circular reference) 

Aber das ist wahrscheinlich keine wirkliche Verbesserung, Lesbarkeit weisen.

+0

was ist mit' self'? –

+0

Sie könnten es nachher, wenn Sie interessiert sind. Hinzugefügt das. – kindall

+0

Dies macht immer noch eine Annahme über den Namen der ersten Meta-Variablen und ist daher für einen Allzweck-Dekorator ungeeignet. –

1

Ich bin nicht einverstanden, dass dies nützlich. Ich finde, dass zwingt Entwickler (einschließlich mir) zwingen, die schmerzhafte BoilerPlate der Member Variable Initiation ist eine gute Möglichkeit, Menschen davon abzuhalten, __init__ Methoden, die eine lächerliche Anzahl von Argumenten akzeptieren, die in eine lächerliche Anzahl von Member-Variablen umgewandelt werden.

Dies passiert häufig, wenn jemand die in einer Klasse verfügbaren Features durch zusätzliche Argumente, Feature-Flags und boolesche Switch-Variablen erweitern möchte, die die benutzerdefinierte Instanziierung steuern. Ich betrachte alle diese als unzulängliche Wege, um mit der Notwendigkeit fertig zu werden, neue oder optionale erweiterte Komplexität zu berücksichtigen.

Wenn man diese besondere Art von Boilerplate eingeben muss, ist das eine Art Steuer auf Klassenblähungen. Wenn Sie so viele Argumente in __init__ akzeptieren, dass Sie diese Funktion benötigen, ist es normalerweise ein guter Indikator, dass Sie Ihr Design mit kleineren, unterteilten Klassen, vielleicht einem MixIn Design, umgestalten sollten.

Dennoch ist hier eine Möglichkeit, es ohne die Fehlleitung des Dekorators zu tun. Ich habe keinen Versuch unternommen, mit *args umzugehen, aber dann müssten Sie in diesem speziellen Fall eine spezielle Logik dafür definieren, welche unbenannten Positionsargumente ohnehin gemeint sind.

def init_from_map(obj, map): 
    for k,v in map.iteritems(): 
     if k not in ["self", "kwargs"]: 
      setattr(obj, k, v) 
     elif k == "kwargs": 
      for kk, vv in v.iteritems(): 
       setattr(obj, kk, vv) 

class Foo(object): 
    def __init__(self, x, y, **kwargs): 
     init_from_map(self, locals()) 

f = Foo(1, 2, z=3) 
print f.x, f.y, f.z 
print f.__dict__ 

Drucke:

1 2 3 
{'x': 1, 'y': 2, 'z': 3} 
+0

Obwohl ich nicht zustimmen kann, ist das streng abträglich, es kommt mir in den Sinn, dass der "komplette Arglist" -basierte Ansatz ziemlich unflexibel ist. Wenn Sie auf diese Weise nur einen Teil der Argliste bearbeiten müssen, stecken Sie auf jeden Fall mit einer handschriftlichen Liste fest. –

+0

Ein Ort, den ich sehen kann, ist nützlich in maschinengeneriertem Code. Zum Beispiel, vielleicht haben Sie eine DSL in JSON oder XML geschrieben, und daraus wird automatisch eine Python-Klasse mit so vielen Parametern wie von etwas in der DSL benötigt. Die Absicht muss sein, dass der Code nicht dazu gedacht ist, von Menschen gelesen oder bearbeitet zu werden, oder dass er den Nutzen sofort verliert. – ely

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