Frage

Ich möchte einen Dekorateur wie unten erstellen, aber ich kann mir nicht an eine Implementierung vorstellen, die funktioniert. Ich fange an zu denken, dass es nicht möglich ist, aber ich dachte, ich würde euch zuerst fragen.

Mir ist klar, dass es verschiedene andere Möglichkeiten gibt, in Python statische Variablen zu erstellen, aber ich finde diese Wege hässlich. Ich würde die folgende Syntax, wenn möglich, wirklich gerne verwenden.

@static(x=0)
def f():
    x += 1
    print x

f() #prints 1
f() #prints 2

Es ist mir egal, ob die Implementierung von static ist lang oder hacktheit, solange es wie oben funktioniert.

Ich habe diese Version erstellt, aber sie erlaubt nur a <function>.<varname> Syntax, die mit längeren Funktionen und variablen Namen ziemlich schnell umständlich wird.

def static(**assignments):
    def decorate(func):
        for var, val in assignments.items():
            setattr(func, var, val)
        return func
    return decorate

Verschiedene Dinge, an die ich dachte, aber nicht zur Arbeit gehen konnte, waren:

  1. Wechseln Sie F (die dekorierte Funktion) in eine aufrufbare Klasse und speichern Sie die statischen Vars irgendwie in self transparent.
  2. Modifizieren Sie die Globals von F () im Dekorateur und einfügen irgendwie 'globale X' in den Code für F.
  3. Ändern Sie F in einen Generator, in dem wir die Variablen von Hand binden und dann den Code von F direkt ausführen.
War es hilfreich?

Lösung

Hier ist ein Dekorateur, der zu funktionieren scheint. Beachten Sie, dass dies am Ende der Funktion zurückgegeben werden muss, da die Einheimischen nicht in der Lage sind, Außenbereiche zu setzen (ich habe nicht viel Erfahrung mit der Programmierung. Wenn es also einen Weg gibt, weiß ich es nicht).

class Static(object):
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, f):
        def wrapped_f():
            try:
                new_kwargs = {}
                for key in self.kwargs:
                    i = getattr(f, key)
                    new_kwargs[key] = i
                self.kwargs = new_kwargs
            except:
                pass
            for key, value in f(**self.kwargs).items():
                setattr(f, key, value)
        return wrapped_f

@Static(x=0, y=5, z='...')
def f(x, y, z):
    x += 1
    y += 5
    print x, y, z
    return locals()

Die Ausgabe wäre:

>>> f()
1 10 ...
>>> f()
2 15 ...
>>> f()
3 20 ...

BEARBEITEN:

Ich fand etwas bei http://code.activestate.com/recipes/410698/ und beschloss, es hinzuzufügen. Es funktioniert jetzt ohne die Rückkehr.

Wieder bearbeiten: geändert in ein paar Sekunden schneller. Bearbeiten 3; geändert, um anstelle der Klasse zu funktionieren

def static(**kwargs):
    def wrap_f(function):
        def probeFunc(frame, event, arg):
            if event == 'call':
                frame.f_locals.update(kwargs)
                frame.f_globals.update(kwargs)
            elif event == 'return':
                for key in kwargs:
                    kwargs[key] = frame.f_locals[key]
                sys.settrace(None)
            return probeFunc
        def traced():
            sys.settrace(probeFunc)
            function()
        return traced
    return wrap_f

geprüft:

@static(x=1)
def f():
    x += 1

global_x = 1
def test_non_static():
    global global_x
    global_x += 1


print 'Timeit static function: %s' % timeit.timeit(f)
print 'Timeit global variable: %s' % timeit.timeit(test_non_static)

Ausgang:

Timeit static function: 5.10412869535
Timeit global variable: 0.242917510783

Die Verwendung von Settrace verlangsamt es ziemlich drastisch.

Andere Tipps

Bis Ihr Dekorateur das Funktionsobjekt bekommt f, Es wurde bereits zusammengestellt - insbesondere wurde es mit dem Wissen zusammengestellt, dass x ist lokal (weil es mit dem zugewiesen ist += Zuordnung), die normale Optimierung (in 2.* Sie können die Optimierung zu einem erstaunlichen Preis in der Leistung besiegen, indem Sie anfangen f mit exec ''; in 2.*, Sie können die Optimierung nicht besiegen). Um die Syntax zu verwenden, nach der Sie sich sehnen, müssen Sie im Wesentlichen neu kompilieren f (Durch die Wiederherstellung seiner Quellen, wenn Sie wissen, dass sie zur Laufzeit verfügbar sind oder durch Bytecode-Hacks viel schwieriger sind) mit irgendwie modifizierten Quellen-sobald Sie sich entschieden haben, diesen Weg zu gehen, ist der einfachste Ansatz wahrscheinlich, sich zu ändern x hinein f.x im gesamten Körper von f.

Persönlich, wenn ich so hart gegen die Sprache (oder andere Technologie) kämpfe, dass ich versuche, mich nach meinem Willen zu beugen, meine Wünsche aufzuzwingen, erkenne ich an, dass ich entweder die falsche Sprache (oder andere Technologie) verwende. , wenn diese Wünsche absolut entscheidend sind und dann die Lösung darin bestehen, die Technologie zu ändern. Oder, wenn diese Wünsche nicht so entscheidend sind, geben Sie sie auf.

In jedem Fall gebe ich auf und versuche, die Sprache zu weit von ihren offensichtlichen Designabsichten zu verzerren: Selbst wenn ich mir einen hackigen, zerbrechlichen Kludge ausgedacht hätte, wäre es zweifellos unbetentbar. In diesem Fall sind Pythons Wunschabsichten sehr klar: BARENAMENS, die innerhalb einer Funktionen wiederhergestellt werden, sind Einheimische dieser Funktion, sofern nicht ausdrücklich als Globale bezeichnet wird-Zeitraum. Ihr Versuch, BARENAMEMALE zu machen (die innerhalb einer Funktion wiederhergestellt werden), bedeuten etwas völlig anderes als "Einheimische", genau diese Art von Kampf.

Bearbeiten: Wenn Sie bereit sind, das Bestehen auf der Verwendung aufzugeben BARENAMENS Für Ihre "Statik" kämpfen Sie plötzlich nicht mehr gegen Python, sondern "mit dem Korn der Sprache global und nonlocal], aber das ist ein separates Scherz ;-). Also zum Beispiel:

class _StaticStuff(object):
  _static_stack = []
  def push(self, d):
    self._static_stack.append(d)
  def pop(self):
    self._static_stack.pop()
  def __getattr__(self, n):
    return self._static_stack[-1][n]
  def __setattr__(self, n, v):
    self._static_stack[-1][n] = v
import __builtin__
__builtin__.static = _StaticStuff()

def with_static(**variables):
  def dowrap(f):
    def wrapper(*a, **k):
      static.push(variables)
      try: return f(*a, **k)
      finally: static.pop()
    return wrapper
  return dowrap

@with_static(x=0)
def f():
    static.x += 1
    print static.x

f()
f()

Dies funktioniert genauso wie Sie möchten, drucken 1 und dann 2 (ich benutze __builtin__ Um es am einfachsten zu verwenden with_static Natürlich in einem Modul zu dekorieren, die in einem Modul leben). Sie könnten mehrere verschiedene Implementierungen haben, aber der entscheidende Punkt von allen gut Implementierung ist, dass "statische Variablen" sein werden qualifiziert Namen, nicht BARENAMENS - Machen Sie deutlich, dass sie keine lokalen Variablen sind, mit dem Körnchen der Sprache spielen und so weiter. (Ähnliche integrierte Container und auf ihnen basierende qualifizierte Namen. sollte wurden in Pythons Design anstelle der verwendet global und nonlocal Entwurfsfehler, um andere Arten von Variablen anzuzeigen, die nicht lokal sind und daher keine BARENAMENS - globvar Spezieller Container in denselben Zeilen der oben genannten static solche, ohne Dekoration zu benötigen, obwohl ich nicht so sicher bin, dass das für die völlig machbar ist nonlocal Fall [vielleicht mit Einige Dekorationen und die kleinste Menge schwarzer Magie ...; =)]).

Bearbeiten: Ein Kommentar zeigt, dass der angegebene Code nicht funktioniert, wenn Sie nur eine Funktion dekorieren, die eine Schließung zurückgibt (anstatt die Schließung selbst zu dekorieren). Das ist richtig: Natürlich müssen Sie die spezifische Funktion dekorieren, die die verwendet static (Und es kann nur eine, per Definition von Funktionen geben.static Variablen!), keine zufällige Funktion, die die tatsächlich nicht verwendet static sondern befindet sich einfach in einer lexikalischen Verbindung mit dem, das tut. Zum Beispiel:

def f():
  @with_static(x=0)
  def g():
    static.x += 1
    print static.x
  return g

x = f()
x()
x()

Dies funktioniert, während der Dekorateur auf den Dekorateur bewegt wird f Anstatt von g nicht (und könnte es möglicherweise nicht).

Wenn es in der tatsächlichen Desiderata nicht um statische Variablen geht (nur in einer einzigen Funktion sichtbar und verwendbar Was die tatsächlichen Spezifikationen sind) - und im Idealfall muss das in einem neuen und getrennten SO -Fragen passieren, weil es in diesem (was speziell um statisch Stattdessen) und diese Antwort auf diese spezielle Frage sind bereits groß genug.

Hier ist eine wirklich einfache Lösung, die genau wie normale statische Python -Variablen funktioniert.

def static(**kwargs):
  def wrap(f):
    for key, value in kwargs.items():
      setattr(f, key, value)
    return f
  return wrap

Beispiel Verwendung:

@static(a=0)
def foo(x):
  foo.a += 1
  return x+foo.a

foo(1)  # => 2
foo(2)  # => 4
foo(14) # => 17

Dies entspricht genauer der normalen Art, statische Python -Variablen durchzuführen

def foo(x):
  foo.a += 1
  return x+foo.a
foo.a = 10

Sie könnten so etwas tun (aber ich habe dies nicht ausgiebig getestet; verwendet CPython 2.6):

import types

def static(**dict):
    def doWrap(func):
        scope = func.func_globals
        scope.update(dict)
        return types.FunctionType(func.func_code, scope)
    return doWrap

# if foo() prints 43, then it's wrong
x = 42

@static(x = 0)
def foo():
   global x
   x += 1
   print(x)

foo() # => 1
foo() # => 2

Es erfordert die Deklaration dieser Variablen als globale und schatten sich globale Variablen auf höchstem Niveau, sollte aber ansonsten funktionieren. Ich bin mir jedoch nicht sicher über die Leistung.

Wie wäre es damit, ohne Dekorateure?

class State(dict):
    """Object interface to dict."""
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError, name

def f(d=State(x=0)):
    d.x += 1
    return d.x

Und hier ist es in Aktion:

>>> f()
1
>>> f()
2
>>> f()
3

Hier ist etwas, das viel klarer sein könnte. Es beinhaltet keine Dekorateure oder Hacking.

class F( object ):
    def __init__( self ):
        self.x= 0
    def __call__( self ):
        self.x += 1
        print self.x

f= F()

Jetzt haben Sie Ihre Funktion f mit einer statischen Variablen.

f() #prints 1
f() #prints 2

Wenn Sie den Zustand zwischen den Aufrufe einer Funktion sparen müssen, sind Sie fast immer besser dran, wenn Sie einen Generator/Coroutine oder ein Objekt verwenden. Da Sie "bare" Variablennamen verwenden möchten, möchten Sie die Coroutine -Version.

# the coroutine holds the state and yields rather than returns values
def totalgen(x=0, y=0, z=0):
    while True:
       a, b, c = (yield x, y, z)
       x += a
       y += b
       z += c

# the function provides the expected interface to callers
def runningtotal(a, b, c, totalgen=totalgen()):
    try:
        return totalgen.send((a, b, c))    # conveniently gives TypeError 1st time
    except TypeError:
        totalgen.next()                    # initialize, discard results
        return totalgen.send((a, b, c))

Das Ergebnis ist eine Funktion, die die Summen der drei an sie übergebenen Werte ansammelt, genau so, als ob es statische Variablen hätte, aber die Akkumulatoren sind einfache alte lokale Variablen in einem im Wesentlichen ein unendlichen Generator.

Eine leichte Änderung von Ein weiterer Anwser:

def static(**kwargs):
    def decorator(func):
        return type(func)(func.func_code, dict(func.func_globals, **kwargs))
    return decorator

message = "goodbye, world!"
@static(message="hello, world!")
def hello(): print message

hello()

Ich fand es eklig, einen gebauten Namen mit einem Funktionsargumentnamen zu überschreiben, also habe ich mich geändert **dict in die kanonischere **kwargs. Ich habe auch ein paar Zeilen gespeichert und IMO hat den Code sauberer gemacht, indem ich einen neuen Diktier mit konstruierten dict(the_original, **the_updates). Zuletzt habe ich ein paar Zeilen gespeichert, indem ich über den Funktionskonstruktor über den Zugriff auf type(func) Anstelle eines Imports-Typ- und Klassenobjekte sind Werksmethoden, verwenden Sie sie also!

Ich habe auch a entfernt global Erklärung. Dies funktioniert, solange Sie die Variable nicht wiederherstellen, dh Entfernen global Tatsächlich macht der Zeiger (aber nicht das Objekt) nur schreibgeschützt. Wenn Sie es vielleicht so verwenden, vielleicht let ist ein besserer Name als static für die so eingeführte Bindung.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top