Ich frage mich, wie ich auf eine Funktion innerhalb einer anderen Funktion zugreifen kann. Ich habe Code wie diesen gesehen:

>>> def make_adder(x):
      def adder(y):
        return x+y
      return adder
>>> a = make_adder(5)
>>> a(10)
15

Gibt es eine andere Möglichkeit, die Funktion adder aufzurufen? Und meine zweite Frage ist, warum ich in der letzten Zeile adder nicht adder(...) anrufe.

Gute Erklärungen werden sehr geschätzt.

12
ovrwngtvity 1 Juli 2013 im 02:12

4 Antworten

Beste Antwort

Nein, Sie können es nicht direkt aufrufen, da es sich um eine lokale Variable für make_adder handelt.

Sie müssen adder() verwenden, da return adder das Funktionsobjekt adder zurückgegeben hat, als Sie make_adder(5) aufgerufen haben. Um dieses Funktionsobjekt auszuführen, benötigen Sie ()

def make_adder(x):
       def adder(y):
           return x+y
       return adder
... 
>>> make_adder(5)             #returns the function object adder
<function adder at 0x9fefa74>

Hier können Sie es direkt aufrufen, da Sie Zugriff darauf haben, da es von der Funktion make_adder zurückgegeben wurde. Das zurückgegebene Objekt wird tatsächlich als Closure bezeichnet, da gerade Obwohl die Funktion make_addr bereits zurückgegeben wurde, kann das von ihr zurückgegebene Funktionsobjekt adder weiterhin auf die Variable x zugreifen. In py3.x können Sie den Wert von x auch mit der Anweisung nonlocal ändern.

>>> make_adder(5)(10)          
15

Py3.x Beispiel:

>>> def make_addr(x):
        def adder(y):
                nonlocal x
                x += 1
                return x+y
        return adder
... 
>>> f = make_addr(5)
>>> f(5)               #with each call x gets incremented
11
>>> f(5)
12

#g gets it's own closure, it is not related to f anyhow. i.e each call to 
# make_addr returns a new closure.
>>> g = make_addr(5)  
>>> g(5)
11 
>>> g(6)
13
7
IgnisErus 30 Juni 2013 im 23:31

Sie geben die Funktion adder an den Aufrufer zurück, nicht das Ergebnis des Aufrufs, daher fehlen Klammern.

Da make_adder adder zurückgibt, haben Sie bereits direkten Zugriff auf adder. Tatsächlich ist a(10) ein Aufruf von adder(10).

4
Marcelo Cantos 30 Juni 2013 im 22:15

Als Ergänzung zur Antwort von @ AshwiniChaudhary können Sie die nichtlokale Version von Python 3.x mit veränderbaren Objekten emulieren. Zum Beispiel:

def counter(name):
    x = [0]
    def inc(n):
        x[0] += n
        print "%s: %d" % (name, x[0])
    return inc

spam = counter('spams')
ham = counter('hams')

spam(3)
ham(1)
spam(1)
ham(2)

In Python2.7 erzeugt dies:

$ python closure.py
spams: 3
hams: 1
spams: 4
hams: 3

Der Grund für die Verwendung von x[0] besteht darin, dass beim erneuten Zuweisen von x ein neues Local-to - inc x erstellt wird:

def counter(name):
    x = 0
    def inc(n):
        x += n # this doesn't work!
        print "%s: %d" % (name, x[0])
    return inc

Der Versuch, dies zu verwenden, erzeugt:

Traceback (most recent call last):
  File "closure.py", line 11, in <module>
    spam(3)
  File "closure.py", line 4, in inc
    x += n
UnboundLocalError: local variable 'x' referenced before assignment

Die verbleibende offensichtliche Sache, ein Versuch, global zu verwenden, schlägt ebenfalls fehl, da versucht wird, auf eine Modulebene x anstelle der in counter zuzugreifen. (Deshalb wurde nonlocal an erster Stelle hinzugefügt!)

Ein weiterer Punkt bei Schließungen: Sie können mechanisch in / aus Klassen mit Instanzvariablen transformiert werden. Anstatt counter wie oben zu definieren, könnte ich eine Klasse erstellen:

class Counter(object):
    def __init__(self, name):
        self.name = name
        self.x = 0
    def inc(self, n):
        self.x += n
        print "%s: %d" % (self.name, self.x)

Und dann verwenden Sie es als:

spam = Counter('spams')
spam.inc(3)

Zum Beispiel. Wenn Sie die Aufrufsyntax beibehalten möchten, lässt Python Folgendes zu: Anstatt inc(self, n) zu definieren, definieren Sie __call__(self, n) - oder definieren Sie __call__ als Aufruf von inc, wodurch Folgendes entsteht:

class Counter(object):
    def __init__(self, name):
        self.name = name
        self.x = 0
    def inc(self, n):
        self.x += n
        print "%s: %d" % (self.name, self.x)
    __call__ = inc

spam = Counter('spams')
ham = Counter('hams')

spam.inc(3)
ham.inc(1)
spam(1)
ham(2)

Dies zeigt die etwas schizophrene "Zwei Möglichkeiten, es zu nennen" -Schnittstelle in der Klasse. :-)

1
torek 1 Juli 2013 im 01:36

Du willst wirklich nicht in dieses Kaninchenloch gehen, aber wenn du darauf bestehst, ist es möglich. Mit etwas Arbeit.

Die verschachtelte Funktion wird für jeden Aufruf von make_adder() neu erstellt:

>>> import dis
>>> dis.dis(make_adder)
  2           0 LOAD_CLOSURE             0 (x)
              3 BUILD_TUPLE              1
              6 LOAD_CONST               1 (<code object adder at 0x10fc988b0, file "<stdin>", line 2>)
              9 MAKE_CLOSURE             0
             12 STORE_FAST               1 (adder)

  4          15 LOAD_FAST                1 (adder)
             18 RETURN_VALUE        

Der dortige MAKE_CLOSURE Opcode erstellt eine Funktion mit einem Abschluss, eine verschachtelte Funktion, die sich auf x aus der übergeordneten Funktion bezieht (der LOAD_CLOSURE Opcode erstellt die Abschlusszelle für die Funktion).

Ohne die Funktion make_adder aufzurufen, können Sie nur auf das Codeobjekt zugreifen. Es wird als Konstante mit dem Funktionscode make_adder() gespeichert. Der Bytecode für adder setzt jedoch voraus, dass Sie auf die Variable x als Zelle mit Gültigkeitsbereich zugreifen können, wodurch das Codeobjekt für Sie fast unbrauchbar wird:

>>> make_adder.__code__.co_consts
(None, <code object adder at 0x10fc988b0, file "<stdin>", line 2>)
>>> dis.dis(make_adder.__code__.co_consts[1])
  3           0 LOAD_DEREF               0 (x)
              3 LOAD_FAST                0 (y)
              6 BINARY_ADD          
              7 RETURN_VALUE        

LOAD_DEREF lädt einen Wert aus einer Schließzelle. Um das Codeobjekt wieder in ein Funktionsobjekt zu verwandeln, müssen Sie dies an den Funktionskonstruktor übergeben:

>>> from types import FunctionType
>>> FunctionType(make_adder.__code__.co_consts[1], globals(),
...              None, None, (5,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: arg 5 (closure) expected cell, found int

Wie Sie jedoch sehen können, erwartet der Konstruktor einen Abschluss und keinen ganzzahligen Wert. Um einen Abschluss zu erstellen, benötigen wir eine Funktion mit freien Variablen. diejenigen, die vom Compiler als zum Schließen verfügbar markiert wurden. Und es muss diese geschlossenen Werte an uns zurückgeben, es ist sonst nicht möglich, einen Abschluss zu erstellen. Daher erstellen wir eine verschachtelte Funktion nur zum Erstellen eines Abschlusses:

def make_closure_cell(val):
    def nested():
        return val
    return nested.__closure__[0]

cell = make_closure_cell(5)

Jetzt können wir adder() neu erstellen, ohne make_adder aufzurufen:

>>> adder = FunctionType(make_adder.__code__.co_consts[1], globals(),
...                      None, None, (cell,))
>>> adder(10)
15

Vielleicht wäre es einfacher gewesen, nur make_adder() aufzurufen.

Wie Sie sehen, sind Funktionen in Python übrigens erstklassige Objekte. make_adder ist ein Objekt, und durch Hinzufügen von (somearguments) rufen Sie auf oder rufen die Funktion auf. In diesem Fall gibt diese Funktion ein anderes Funktionsobjekt zurück, das Sie ebenfalls aufrufen können. In dem obigen gewundenen Beispiel, wie man adder() erstellt, ohne make_adder() aufzurufen, habe ich auf das Funktionsobjekt make_adder verwiesen, ohne es aufzurufen. um beispielsweise den daran angehängten Python-Bytecode zu zerlegen oder Konstanten oder Abschlüsse daraus abzurufen. Auf die gleiche Weise gibt die Funktion make_adder() das Funktionsobjekt adder zurück. Der Punkt von make_adder() besteht darin, diese Funktion für etwas anderes zu erstellen, um sie später aufzurufen.

Die obige Sitzung wurde unter Berücksichtigung der Kompatibilität zwischen Python 2 und 3 durchgeführt. Ältere Python 2-Versionen funktionieren auf die gleiche Weise, obwohl sich einige Details geringfügig unterscheiden. Einige Attribute haben unterschiedliche Namen, z. B. func_code anstelle von __code__. Lesen Sie die Dokumentation dazu im inspect -Modul und im < a href = "http://docs.python.org/2/reference/datamodel.html" rel = "noreferrer"> Python-Datenmodell , wenn Sie die wichtigsten Details kennen möchten.

19
Martijn Pieters 30 Juni 2013 im 23:05