2015-09-16 10 views
5

Ich schreibe eine Klasse, die eine Reihe von Methoden arbeitet auf ähnliche Arten von Argumenten hat:Pythonic Art und Weise der Parameter auf den gleichen Standard in allen Methoden einer Klasse Umwandlung

class TheClass(): 
    def func1(self, data, params, interval): 
     .... 
    def func2(self, data, params): 
     .... 
    def func3(self, data, interval): 
     .... 
    def func4(self, params): 
     .... 
    ... 

Es gibt eine bestimmte Konvention über diese Argumente (zB data/params sollte numpy.farrays, interval - ein list von 2 floats sein), aber ich möchte dem Benutzer erlauben, mehr Freiheit zu haben: zB Funktionen sollten int als data oder params oder nur eine int als interval annehmen und dann davon ausgehen, dass dies der Endpunkt mit dem Startpunkt ist 0 usw.

So all diese Transformationen innerhalb von Methoden zu vermeiden, sollte das nur tun die Logik, ich bin mit Dekorateure wie folgt aus:

def convertparameters(*types): 
    def wrapper(func): 

     def new_func(self, *args, **kwargs): 
      # Check if we got enough parameters 
      if len(types) > len(args): 
       raise Exception('Not enough parameters') 
      # Convert parameters 
      new_args = list(args) 
      for ind, tip in enumerate(types): 
       if tip == "data": 
        new_args[ind] = _convert_data(new_args[ind]) 
       elif tip == "params": 
        new_args[ind] = _convert_params(new_args[ind]) 
       elif tip == "interval": 
        new_args[ind] = _convert_interval(new_args[ind]) 
       else: 
        raise Exception('Unknown type for parameter') 
      return func(self, *new_args, **kwargs) 

     return new_func 
    return wrapper 

Wo _convert_data, _convert_params und _convert_interval die schmutzige Arbeit zu tun. Dann definiere ich die Klasse wie folgt:

class TheClass(): 
    @convertparameters("data", "params", "interval") 
    def func1(self, data, params, interval): 
     .... 

    @convertparameters("data", "params") 
    def func2(self, data, params): 
     .... 

    @convertparameters("data", "interval") 
    def func3(self, data, interval): 
     .... 

    @convertparameters("params") 
    def func4(self, params): 
     .... 
    ... 

Es funktioniert der Trick, aber es sind einige sehr störend Dinge:

  1. Es clutters der Code (obwohl es ein kleines Problem ist, und ich finde, Diese Lösung ist viel kompakter als der explizite Aufruf der Transformationsfunktion zu Beginn jeder Methode.
  2. Wenn ich diesen Dekorator mit einem anderen Decorator kombinieren muss (@staticmethod oder etwas, das die Ausgabe der Methode nachbearbeitet), dann muss die Reihenfolge geändert werden diese Dekorateure sind wichtig
  3. Die beunruhigendste Sache ist, dass der Dekorateur dieses Formular vollständig aus der Parameterstruktur des Verfahrens versteckt und IPython zeigt es als func1(*args, **kwargs)

Gibt es eine bessere (oder mehr „Pythonic“) Wege Machen Sie solche massive Parametertransformation?

UPDATE 1: SOLUTION basierend auf n9code ‚s Vorschlag

Um Verwirrung zu vermeiden, eine Modifikation des convertparameters Wrapper gibt es die dritte Ausgabe löst (Maskierung der Unterzeichnung und docstring von Methoden) - durch vorgeschlagen n9code für Python> 2.5.

Mit decorator Modul (separat installiert: pip install decorator) wir alle „Metadaten“ die Funktion übertragen können (docstring, Name und Unterschrift) zur gleichen Zeit von verschachtelten Struktur im Inneren der Hülle loszuwerden

from decorator import decorator 

def convertparameters(*types): 

    @decorator 
    def wrapper(func, self, *args, **kwargs): 
     # Check if we got enough parameters 
     if len(types) > len(args): 
      raise Exception('Not enough parameters') 
     # Convert parameters 
     new_args = list(args) 
     for ind, tip in enumerate(types): 
      if tip == "data": 
       new_args[ind] = _convert_data(new_args[ind]) 
      elif tip == "params": 
       new_args[ind] = _convert_params(new_args[ind]) 
      elif tip == "interval": 
       new_args[ind] = _convert_interval(new_args[ind]) 
      else: 
       raise Exception('Unknown type for parameter') 
     return func(self, *new_args, **kwargs) 

    return wrapper 

UPDATE 2: MODIFIED SOLUTION basierend auf zmbq ‚s Vorschlag

Mit inspect Modul wir auch Argumente von Dekorateur loswerden könnte, überprüft die Namen von argu der ursprünglichen Funktion.Dies wird eine andere Schicht des Dekorators

beseitigen und die Verwendung ist dann viel einfacher. Die einzige wichtige Sache ist, die gleichen Namen für Argumente zwischen verschiedenen Funktionen zu verwenden.

class TheClass(): 
    @convertparameters 
    def func1(self, data, params, interval): 
     .... 

    @convertparameters 
    def func2(self, data, params): 
     .... 

Antwort

3
  1. würde ich Ihnen zustimmen, dass dies eine bessere Lösung ist, so ist hier nichts zu tun.
  2. Ich denke, Sie werden eine gute Bestellung machen und sie weiterführen, was in Zukunft kein Problem mehr werden wird.
  3. Dies ist ein Problem, Sie haben Recht, aber es wird einfach mit functools.wraps gelöst. Dekorieren Sie einfach Ihre newfunc damit, und Sie werden die Signatur der ursprünglichen Funktion speichern.

    from functools import wraps 
    
    def convertparameters(*types): 
        def wrapper(func): 
         @wraps(func) 
         def new_func(self, *args, **kwargs): 
          pass # Your stuff 
    

    Tough funktioniert dies nur für Python 3. In Python 2 würde die Signatur nicht beibehalten werden, nur __name__ und __doc__ wäre. So im Fall von Python, könnten Sie die decorator Modul verwenden:

    from decorator import decorator 
    
    def convertparameters(*types): 
        @decorator 
        def wrapper(func, self, *args, **kwargs): 
         pass # return a result 
        return wrapper 
    

EDIT basierend auf user3160867's Update.

+0

Danke, ich wusste nicht über 'Wraps'. Ich nehme an, es sollte auch 'return new_func' und' return wrapper' geben? – Vladimir

+0

Ich habe noch eine Frage: 'Wraps' überträgt den Docstring korrekt. Die Signatur bleibt jedoch 'func1 (* args, ** kwargs)'. Gibt es eine Möglichkeit, auch die Signatur zu übertragen? – Vladimir

+0

Ich vermute, Sie verwenden Python 2. Im Falle von Python 3 würde die Signatur auch erhalten bleiben. Schau dir mein Update an. – bagrat

1

Um den Code ein wenig prägnanter zu machen, sollten Sie dem Dekorateur keine Argumente liefern - leiten Sie sie von der dekorierten Funktion ab.

+0

Das klingt interessant! Willst du 'def wrapper (func, self, ** kwargs)' ohne 'args' deklarieren und von den' kwargs' ableiten? – Vladimir

+0

Nein, Sie können dies verwenden: http://stackoverflow.com/questions/218616/getting-method-parameter-names-in-python – zmbq

Verwandte Themen