2017-09-28 4 views
7

In Python habe ich einen Dekorator, der jede echte Arbeit überspringen muss, wenn eine Funktion lokal in der definiert ist, die sie aufruft. Ich habe ein einfaches Testskript:Wie erkennt man, ob eine Funktion lokal definiert wurde?

def fn1(): 
    # @my_decorator will be here 
    def fn2(): 
     pass 

    print(fn2) 
    return fn2 

x = fn1() 
print(x) 
print(x.__module__) 

Er druckt diesen:

<function fn1.<locals>.fn2 at 0x7fd61bdf3ae8> 
<function fn1.<locals>.fn2 at 0x7fd61bdf3ae8> 
__main__ 

Wie ich sehe, Python sieht, dass die Funktion in einem lokalen Raum definiert (<locals> im gedruckten Text), aber ich kann nicht sehen, wie ich diese Daten finden kann. Ich ging durch das Modul inspect und sehe nichts Ähnliches.

Ich kann nicht darauf verlassen, ob die Funktion in Globals oder nicht ist.

Was verwende ich?

Antwort

3

First off, der direkte Ansatz, wenn the CO_NESTED flag is set on the function's code object zu überprüfen ist:

import inspect 

... 

def is_nested(func): 
    return func.__code__.co_flags & inspect.CO_NESTED 

def deco(func): 
    if is_nested(func): 
     # This is a nested function, return it unchanged 
     return func 
    ... otherwise, do your decoration here ... 

That sagte, es gibt einen anderen Ansatz, wenn was dich interessiert, ist, ob du wirklich über alles geschlossen hast. Eine Funktion, die nichts aus dem umschließenden Bereich verwendet, ist zwar verschachtelt, aber kein Abschluss, und diese Unterscheidung ist oft wichtig. So zum Beispiel:

def foo(x): 
    def bar(y): 
     pass 
    return bar 

ist nicht Herstellung einer Schließung, weil bar Verwendung von nicht-Variablen aus dem Anwendungsbereich des Anrufs foo macht. Im Gegensatz dazu, obwohl es ein Müll Referenz, diese ist einen Verschluss macht einfach durch den Wert von x aus dem umschließenden Umfang zu lesen:

def foo(x): 
    def baz(y): 
     pass 
    return bar 

Sie den Unterschied zwischen bar und baz sagen kann durch Testen des __closure__ Attribut (das ist None, wenn keine verschachtelten Variablen geschlossen wurden) oder durch Überprüfung des co_freevars Attributs des Objekts (das ein Tupel von Namen ist, das geschlossen ist, also wenn es leer ist, dann ist es entweder kein Abschluss, obwohl es immer noch sein kann eine verschachtelte Funktion):

def is_closure(func): 
    return func.__closure__ is not None 
    # Or using documented names, since __closure__ isn't for some reason, 
    # co_freevars is a tuple of names captured from nested scope 
    return bool(func.__code__.co_freevars) 

    # Or on 3.3+, you even get a function to aid you: 
    return bool(inspect.getclosurevars(func).nonlocals) 
3

Nun, hier ist ein Hacky Ansatz:

'<locals>' in f.__qualname__ 

Es scheint mir, spröde, though.

Ein weiterer Ansatz ist mit dem Frame zu spielen, aber Ich mag, dass weniger auch, denke ich:

In [1]: import inspect 

In [2]: def deco(f): 
    ...:  try: 
    ...:   frame = inspect.currentframe() 
    ...:   print(frame.f_back.f_locals is globals()) 
    ...:  finally: 
    ...:   del frame 
    ...:  return f 
    ...: 

In [3]: @deco 
    ...: def g(): pass 
    ...: 
True 

In [4]: def f(): 
    ...:  @deco 
    ...:  def g(): pass 
    ...: 

In [5]: f() 
False 
+0

Ich denke, das ist eine recht gute Lösung. '__qualname__' ist auch das, was (zumindest in CPython) verwendet wird, um den' repr' der Funktion zu erstellen, es gibt keine anderen versteckten Attribute, die geprüft werden. –

+0

@JimFasarakisHilliard Ja, ich spielte mit 'inspect.currentframe() herum. F_back.f_locals ist globals()', was auch zu funktionieren scheint, aber ich mag das sogar * weniger *. –

Verwandte Themen