2017-09-28 3 views
7

Pythonでは、私がデコレータを持っているのは、それを呼び出す関数がローカルに定義されていれば、実際の作業をスキップする必要があります。機能がローカルに定義されているかどうかを検出するには?

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

    print(fn2) 
    return fn2 

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

それは、この出力します:私は、簡単なテストスクリプト行わ

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

私は見ての通り、Pythonは関数が(印刷されたテキストに<locals>)ローカル空間で定義されていることを見ているのが、私私はそのビットをどのように見つけることができません。私はinspectモジュールを通って歩き、同様のものは見ません。

関数がグローバルにあるかどうかに依存することはできません。

私は何を使用しますか?

答えて

3

まずオフ、直接的なアプローチがthe CO_NESTED flag is set on the function's code objectかどうかを確認することです。

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

ことあなたが気にしていることは、あなたが実際に何かを閉じてしまったかどうかである場合、別のアプローチがあります。囲みスコープから何も使用しない関数は入れ子になっていますが、クロージャではありません。その区別はしばしば重要です。ですから、例えば:barfooコールの範囲からの無変数を使用していますので、

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

クロージャを作るではありません。これとは対照的に、それはゴミの参照だにもかかわらず、このが囲む範囲からxの値を読み出すことにより、単にクロージャを作るです:

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

あなたは__closure__属性をテストすることによってbarbazの違いを伝えることができます(ネストされた変数が閉じられていない場合はNone)、または__code__オブジェクトのco_freevars属性(これは閉じられた名前のタプルです。したがって、空の場合はまだ閉じていますが、まだ閉じている可能性があります)入れ子関数):

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

さて、ここでハックのアプローチです:

'<locals>' in f.__qualname__ 

それはしかし、私には脆いと思われます。

別のアプローチは、Frameと遊ぶのですが、私も少なく、私が考えることを好む:

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

これは合法的に良い解決策だと思います。 '__qualname__'は、関数の' repr'を作成するために(少なくともCPythonでは)使われています。それがチェックする他の隠し属性はありません。 –

+0

@JimFasarakisHilliardええと、私は 'inspect.currentframe()で遊んでいました.f_back.f_localsはglobals()ですが、うまくいくようですが、私は* less *でもそれが好きです。 –

関連する問題