2017-09-28 16 views
7

Trong Python, tôi có một trình trang trí phải bỏ qua bất kỳ công việc thực nào nếu một hàm được định nghĩa cục bộ trong một hàm gọi nó. Tôi đã thực hiện một kịch bản thử nghiệm đơn giản:Cách phát hiện xem một hàm đã được xác định cục bộ chưa?

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

    print(fn2) 
    return fn2 

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

It in này:

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

Như tôi thấy, Python thấy rằng hàm được định nghĩa trong một không gian địa phương (<locals> trong văn bản in), nhưng tôi không thể thấy cách tôi có thể tìm thấy dữ liệu đó. Tôi đã đi qua mô-đun inspect và không thấy bất kỳ điều gì tương tự.

Tôi không thể dựa vào việc hàm có trong hình cầu hay không.

Tôi nên sử dụng cái gì?

Trả lời

3

Trước hết, cách tiếp cận trực tiếp là để kiểm tra xem 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 ... 

Đó cho biết, có một cách tiếp cận khác nếu những gì bạn quan tâm là liệu bạn đã thực sự đóng cửa trên bất cứ điều gì. Một hàm không sử dụng bất cứ thứ gì từ phạm vi kèm theo được lồng vào nhau, nhưng không phải là một đóng, và sự khác biệt đó thường là quan trọng. Vì vậy, ví dụ:

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

không thực hiện một đóng cửa vì bar tận dụng không có biến từ phạm vi của foo gọi. Ngược lại, mặc dù đó là một tài liệu tham khảo rác, điều này thực hiện một đóng cửa chỉ đơn giản bằng cách đọc giá trị của x từ phạm vi kèm theo:

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

Bạn có thể biết sự khác biệt giữa barbaz bằng cách kiểm tra các thuộc tính __closure__ (là None nếu không có biến lồng nhau nào được đóng) hoặc bằng cách kiểm tra thuộc tính co_freevars của đối tượng __code__ (là một bộ tên được đóng, vì vậy nếu nó trống, thì nó không phải là đóng, mặc dù nó vẫn có thể là hàm lồng nhau):

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

Vâng, đây là một cách tiếp cận hacky:

'<locals>' in f.__qualname__ 

Dường giòn với tôi, mặc dù.

Một cách khác là để chơi với Frame, nhưng tôi thích điều đó thậm chí ít hơn, tôi nghĩ rằng:

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

Tôi nghĩ đây là giải pháp hợp pháp tốt. '__qualname__' cũng là những gì được sử dụng (trong CPython ít nhất) để tạo' repr' của hàm, không có thuộc tính ẩn nào khác mà nó kiểm tra. –

+0

@JimFasarakisHilliard vâng, tôi đang chơi với 'inspect.currentframe(). F_back.f_locals là globals()', có vẻ như cũng hoạt động, nhưng tôi thích điều đó ngay cả * ít hơn *. –

Các vấn đề liên quan