2013-06-30 41 views
9

Tôi tự hỏi làm thế nào tôi có thể truy cập một hàm bên trong một hàm khác. tôi thấy mã như thế này:Làm cách nào để truy cập chức năng bên trong một chức năng?

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

Vì vậy, có một cách khác để gọi adder chức năng? Và câu hỏi thứ hai của tôi là tại sao ở dòng cuối cùng tôi gọi adder không phải là adder(...)?

Giải thích tốt được đánh giá cao.

+0

tương đương: 'make_adder = lambda x: lambda y: x + y' bây giờ, bạn có thể gọi bên trong' lambda' không? – Elazar

+0

vâng! 'make_adder = make_adder (...)', sau đó 'make_adder (...)' – ovrwngtvity

+0

Đó là, không, bạn không thể. – Elazar

Trả lời

5

Không, bạn không thể gọi trực tiếp vì đây là biến cục bộ thành make_adder.

Bạn cần sử dụng adder()return adder trả về đối tượng hàm adder khi bạn gọi make_adder(5). Để thực hiện chức năng đối tượng này, bạn cần ()

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

đây bạn có thể gọi nó là trực tiếp vì bạn đã truy cập vào nó, vì nó đã được trả về bởi hàm make_adder. Đối tượng trả về thực sự được gọi là closure vì mặc dù hàm make_addr đã trả về, đối tượng hàm adder được trả về bởi nó vẫn có thể truy cập biến x. Trong py3.x bạn cũng có thể sửa đổi giá trị x bằng cách sử dụng câu lệnh nonlocal.

>>> make_adder(5)(10)   
15 

Py3.x dụ:

>>> 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 
+0

Đáng chú ý: vì Python 2.x thiếu "nonlocal", bạn không thể thay đổi các biến không phải cục bộ của bạn đơn giản. Tuy nhiên, bạn vẫn có thể thực hiện các giá trị có thể sửa đổi bằng cách sử dụng danh sách (tôi sẽ thêm một câu trả lời dưới đây cho thấy điều này). – torek

18

Bạn thực sự không muốn đi xuống hố thỏ này, nhưng nếu bạn nhấn mạnh, điều đó là có thể. Với một số công việc.

Chức năng lồng nhau được tạo ra lần nữa cho mỗi cuộc gọi đến make_adder():

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

Các MAKE_CLOSURE opcode có tạo một hàm với một kết thúc, một hàm lồng nhau đề cập đến x từ hàm mẹ (LOAD_CLOSURE opcode xây dựng các tế bào đóng cửa cho chức năng).

Nếu không gọi hàm make_adder, bạn chỉ có thể truy cập đối tượng mã; nó được lưu trữ như một hằng số với mã chức năng make_adder(). Mã byte cho adder đếm vào khả năng truy cập vào biến x như một tế bào scoped, tuy nhiên, mà làm cho các đối tượng đang gần như vô ích cho bạn:

>>> 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 tải một giá trị từ một tế bào đóng cửa. Để thực hiện các đối tượng mã vào một đối tượng chức năng một lần nữa, bạn sẽ phải vượt qua đó để các nhà xây dựng chức năng:

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

nhưng khi bạn có thể thấy, các nhà xây dựng hy vọng sẽ tìm thấy một đóng cửa, không phải là một giá trị số nguyên. Để tạo ra một đóng cửa, chúng ta cần, một hàm có các biến tự do; những người được trình biên dịch đánh dấu là có sẵn để đóng lại. Và nó cần phải trả lại những giá trị đóng trên giá trị cho chúng ta, không thể tạo ra một đóng cửa bằng cách khác. Do đó, chúng ta tạo một hàm lồng nhau chỉ để tạo ra một đóng cửa:

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

cell = make_closure_cell(5) 

Bây giờ chúng ta có thể tái tạo adder() mà không gọi make_adder:

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

Có lẽ chỉ gọi make_adder() sẽ được đơn giản hơn.

Ngẫu nhiên, như bạn có thể thấy, hàm là đối tượng hạng nhất trong Python. make_adder là một đối tượng và bằng cách thêm (somearguments) bạn gọi hoặc gọi chức năng. Trong trường hợp này, hàm đó trả về đối tượng hàm khác, một đối tượng mà bạn cũng có thể gọi. Trong ví dụ quanh co ở trên về cách tạo adder() mà không cần gọi make_adder(), tôi đã gọi đối tượng hàm make_adder mà không gọi nó; để tháo rời mã byte Python gắn với nó, hoặc để lấy các hằng số hoặc các bao đóng từ nó, ví dụ.Trong cùng một cách, hàm make_adder() trả về đối tượng hàm adder; các điểm của make_adder() là để tạo ra chức năng đó cho một cái gì đó khác để sau này gọi nó.

Phiên trên được tiến hành với khả năng tương thích giữa Python 2 và 3 trong đầu. Các phiên bản Python 2 cũ hơn hoạt động theo cùng một cách, mặc dù một số chi tiết khác nhau một chút; một số thuộc tính có tên khác, chẳng hạn như func_code thay vì __code__ chẳng hạn. Tra cứu tài liệu trên các tài liệu này trong các số inspect modulePython datamodel nếu bạn muốn biết chi tiết về các chi tiết có liên quan đến nitty.

+0

Chỉ cần tự hỏi: là '__code__' di động trên các triển khai, hoặc CPython cụ thể? –

+0

@rightfold: Từ [thuật ngữ trên bytecode] (http://docs.python.org/2/glossary.html#term-bytecode): * Mã nguồn Python được biên dịch thành bytecode, biểu diễn bên trong của chương trình Python trong trình thông dịch CPython. *. Jython và IronPython có các thuộc tính 'func_code' nhưng tôi không biết nó chứa cái gì. –

+0

"Bạn thực sự không muốn đi xuống hố thỏ này, nhưng nếu bạn nhấn mạnh, có thể" Điều này làm tôi cười. Đây cũng là một lời giải thích rất chi tiết. – coder543

4

Bạn đang trả lại hàm adder cho người gọi, không phải là kết quả của việc gọi, do đó không có dấu ngoặc đơn.

make_adder trả về adder, bạn đã có quyền truy cập trực tiếp vào adder. Thực tế, a(10) thực sự là một cuộc gọi đến adder(10).

1

Là một phụ lục để trả lời @ AshwiniChaudhary, bạn có thể bắt chước 3.x của Python không cục bộ với các đối tượng sửa đổi. Ví dụ:

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) 

Trong python2.7 này sản xuất:

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

Lý do cho việc sử dụng x[0] là nỗ lực để chỉ định lại x tạo mới địa phương-to- incx:

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

Cố gắng sử dụng tính năng này:

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 

Điều hiển nhiên còn lại, cố gắng sử dụng global, cũng không thành công vì nó cố gắng truy cập vào cấp mô-đun x thay vì một bên trong counter. (Đây là lý do tại sao nonlocal được thêm vào ngay từ đầu!)

Một điểm khác về đóng cửa: chúng có thể biến đổi cơ học thành/từ các lớp có biến mẫu. Thay vì xác định counter như trên, tôi có thể làm cho một lớp:

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) 

và sau đó sử dụng nó như:

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

ví dụ. Nếu bạn muốn giữ gìn cú pháp gọi, Python phép này: thay vì xác định inc(self, n), xác định __call__(self, n) -Hoặc xác định __call__ như cách gọi inc, làm phát sinh:

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) 

trong đó cho thấy phần nào tâm thần phân liệt "hai cách để gọi nó là "giao diện trong lớp. :-)

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