Hàm lồng nhau trong Python


83

Chúng ta có thể nhận được lợi ích hoặc ý nghĩa gì với mã Python như thế này:

class some_class(parent_class):
    def doOp(self, x, y):
        def add(x, y):
            return x + y
        return add(x, y)

Tôi đã tìm thấy điều này trong một dự án mã nguồn mở, thực hiện điều gì đó hữu ích bên trong hàm lồng nhau, nhưng hoàn toàn không làm gì bên ngoài nó ngoại trừ việc gọi nó. (Có thể tìm thấy mã thực tế ở đây .) Tại sao ai đó có thể viết mã nó như thế này? Có một số lợi ích hoặc tác dụng phụ khi viết mã bên trong hàm lồng nhau hơn là ở bên ngoài, hàm bình thường không?


4
Đây có phải là mã thực tế bạn đã tìm thấy hay chỉ là một ví dụ đơn giản mà bạn đã tạo?
MAK

Đó là một ví dụ đơn giản. Bạn có thể tìm thấy mã thực tế tại đây: bazaar.launchpad.net/%7Eopenerp/openobject-server/trunk/…
Hosam Aly

2
Liên kết của bạn (trỏ đến HEAD) bây giờ không chính xác. Hãy thử: bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/…
Craig McQueen

Bạn nói đúng Craig. Cảm ơn bạn.
Hosam Aly

Câu trả lời:


111

Thông thường bạn làm điều đó để đóng cửa :

def make_adder(x):
    def add(y):
        return x + y
    return add

plus5 = make_adder(5)
print(plus5(12))  # prints 17

Các hàm bên trong có thể truy cập các biến từ phạm vi bao quanh (trong trường hợp này là biến cục bộ x). Nếu bạn không truy cập bất kỳ biến nào từ phạm vi kèm theo, chúng thực sự chỉ là các hàm thông thường với phạm vi khác.


5
Cho rằng tôi muốn partials: plus5 = functools.partial(operator.add, 5). Decorator sẽ là một ví dụ tốt hơn cho việc đóng cửa.
Jochen Ritzel

4
Cảm ơn, nhưng như bạn có thể thấy trong đoạn mã tôi đã đăng, đó không phải là trường hợp ở đây: hàm lồng nhau chỉ đơn giản là được gọi trong hàm bên ngoài.
Hosam Aly

57

Ngoài các trình tạo hàm, trong đó tạo hàm bên trong gần như là định nghĩa của trình tạo hàm, lý do tôi tạo các hàm lồng nhau là để cải thiện khả năng đọc. Nếu tôi có một hàm nhỏ sẽ chỉ được gọi bởi hàm bên ngoài, thì tôi nội tuyến định nghĩa để bạn không cần phải bỏ qua để xác định hàm đó đang làm gì. Tôi luôn có thể di chuyển phương thức bên trong ra bên ngoài phương thức đóng gói nếu tôi thấy cần sử dụng lại hàm vào một ngày sau đó.

Ví dụ đồ chơi:

import sys

def Foo():
    def e(s):
        sys.stderr.write('ERROR: ')
        sys.stderr.write(s)
        sys.stderr.write('\n')
    e('I regret to inform you')
    e('that a shameful thing has happened.')
    e('Thus, I must issue this desultory message')
    e('across numerous lines.')
Foo()

4
Vấn đề duy nhất là điều này đôi khi có thể làm cho việc kiểm tra đơn vị khó hơn, vì bạn không thể truy cập vào chức năng bên trong.
Challenger

1
Trông chúng thật đáng yêu!
côn

FWIY @ Challenger5 nếu chức năng bên trong tương tự với các chức năng lớp riêng thì chúng vẫn không được kiểm tra đơn vị. Tôi đã bắt đầu làm điều này để làm cho các phương thức dễ đọc hơn trong khi vẫn giữ định nghĩa gần với phương thức được đề cập.
Mitchell Currie

26

Một lợi ích tiềm năng của việc sử dụng các phương thức bên trong là nó cho phép bạn sử dụng các biến cục bộ của phương thức bên ngoài mà không cần chuyển chúng dưới dạng đối số.

def helper(feature, resultBuffer):
  resultBuffer.print(feature)
  resultBuffer.printLine()
  resultBuffer.flush()

def save(item, resultBuffer):

  helper(item.description, resultBuffer)
  helper(item.size, resultBuffer)
  helper(item.type, resultBuffer)

có thể được viết như sau, được cho là đọc tốt hơn

def save(item, resultBuffer):

  def helper(feature):
    resultBuffer.print(feature)
    resultBuffer.printLine()
    resultBuffer.flush()

  helper(item.description)
  helper(item.size)
  helper(item.type)

8

Tôi không thể hình dung bất kỳ lý do chính đáng nào cho mã như vậy.

Có thể có một lý do cho chức năng bên trong trong các phiên bản cũ hơn, giống như các Ops khác.

Ví dụ, điều này có ý nghĩa hơn một chút :

class some_class(parent_class):
    def doOp(self, op, x, y):
        def add(x, y):
            return x + y
        def sub(x,y):
            return x - y
        return locals()[op](x,y)

some_class().doOp('add', 1,2)

nhưng sau đó hàm bên trong phải là các phương thức lớp ("private") thay thế:

class some_class(object):
    def _add(self, x, y):
        return x + y
    def doOp(self, x, y):
        return self._add(x,y)

Ừ, có lẽ Doop mất một chuỗi để xác định các nhà điều hành để sử dụng trên các đối số ...
Skilldrick

1
@ArtOfWarfare nên sử dụng mô-đun đơn giản hơn. Các lớp học dành cho tiểu bang.
aehlke

6

Ý tưởng đằng sau các phương thức cục bộ tương tự như các biến cục bộ: không gây ô nhiễm không gian tên lớn hơn. Rõ ràng là các lợi ích bị hạn chế vì hầu hết các ngôn ngữ cũng không cung cấp trực tiếp chức năng như vậy.


1

Bạn có chắc mã chính xác như thế này? Lý do bình thường để làm điều gì đó như vậy là để tạo một phần - một hàm với các tham số được cài sẵn. Việc gọi hàm bên ngoài trả về một hàm có thể gọi mà không cần tham số, do đó có thể được lưu trữ và sử dụng ở một nơi nào đó không thể truyền tham số. Tuy nhiên, mã bạn đã đăng sẽ không thực hiện điều đó - nó gọi hàm ngay lập tức và trả về kết quả, thay vì có thể gọi. Có thể hữu ích khi đăng mã thực bạn đã thấy.


1
Tôi đã thêm một liên kết đến mã gốc trong một bình luận về câu hỏi của tôi. Như bạn có thể thấy, của tôi là một ví dụ đơn giản hóa, nhưng nó vẫn gần như giống nhau.
Hosam Aly
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.