Nếu chức năng A chỉ được yêu cầu bởi chức năng B thì A có nên được xác định bên trong B không? [đóng cửa]


147

Ví dụ đơn giản. Hai phương thức, một phương thức được gọi từ phương thức khác:

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

Trong Python chúng ta có thể khai báo defbên trong cái khác def. Vì vậy, nếu method_bđược yêu cầu và chỉ được gọi từ method_a, tôi có nên khai báo method_bbên trong method_akhông? như thế này :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Hay tôi nên tránh làm điều này?


7
Bạn không cần phải xác định một chức năng bên trong một chức năng khác trừ khi bạn đang làm điều gì đó THỰC SỰ thú vị. Tuy nhiên, vui lòng giải thích chi tiết về những gì bạn đang cố gắng thực hiện, vì vậy chúng tôi có thể cung cấp câu trả lời hữu ích hơn
thanh

6
Bạn có nhận ra rằng ví dụ thứ hai là khác nhau, vì bạn không gọi method_b ? (@inspector: Bạn thực sự cần phải nói, nhưng nó cực kỳ hữu ích khi bạn tham gia vào một chút lập trình chức năng, đặc biệt là các lần đóng cửa).

3
@delnan: Tôi nghĩ bạn có nghĩa là "Bạn không cần, nói đúng, nhưng ..."
martineau

4
Các trường hợp sử dụng cho các chức năng bên trong được tóm tắt tuyệt vời trong liên kết: https://realpython.com/blog/python/inner-fifts-what-are-they-good-for/ . Nếu việc sử dụng của bạn không phù hợp với bất kỳ trường hợp nào, tốt hơn nên tránh nó.

1
Câu hỏi tuyệt vời nhưng không có câu trả lời thực sự vì có vẻ như ...
Mayou36

Câu trả lời:


136
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

Đây có phải là những gì bạn đang tìm kiếm? Nó được gọi là đóng cửa .


14
Đây là một lời giải thích tốt hơn nhiều. Tôi đã xóa câu trả lời của mình
pyfunc

tại sao không chỉ làm def sum (x, y): return x + y?
xoài

4
@mango: Đây chỉ là một ví dụ đồ chơi để truyền đạt khái niệm - trong thực tế sử dụng, những gì do_it()có lẽ sẽ phức tạp hơn một chút so với những gì có thể được xử lý bởi một số học trong một returncâu lệnh.
martineau

2
@mango Trả lời câu hỏi của bạn với một ví dụ hữu ích hơn một chút. stackoverflow.com/a/24090940/2125392
CivilFan

10
Nó không trả lời câu hỏi.
Hunsu

49

Bạn không thực sự đạt được nhiều bằng cách làm điều này, trên thực tế, nó chậm method_alại bởi vì nó sẽ xác định và biên dịch lại chức năng khác mỗi khi nó được gọi. Do đó, có lẽ sẽ tốt hơn nếu chỉ thêm tiền tố vào tên hàm với dấu gạch dưới để chỉ ra đó là một phương thức riêng tư - tức là _method_b.

Tôi cho rằng bạn có thể muốn làm điều này nếu định nghĩa của hàm lồng nhau thay đổi mỗi lần vì một số lý do, nhưng điều đó có thể chỉ ra một lỗ hổng trong thiết kế của bạn. Điều đó nói rằng, có một lý do chính đáng để làm điều này để cho phép các chức năng lồng nhau để sử dụng đối số được truyền cho hàm bên ngoài nhưng không được thông qua một cách rõ ràng về họ, mà đôi khi xảy ra khi viết trang trí chức năng, ví dụ. Đó là những gì đang được hiển thị trong câu trả lời được chấp nhận mặc dù trang trí không được xác định hoặc sử dụng.

Cập nhật:

Đây là bằng chứng cho thấy việc lồng chúng chậm hơn (sử dụng Python 3.6.1), mặc dù phải thừa nhận là không nhiều trong trường hợp tầm thường này:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

Lưu ý Tôi đã thêm một số selfđối số vào các hàm mẫu của bạn để làm cho chúng giống với các phương thức thực hơn (mặc dù method_b2về mặt kỹ thuật vẫn không phải là một phương thức của Testlớp). Ngoài ra chức năng lồng nhau thực sự được gọi trong phiên bản đó, không giống như của bạn.


21
Nó không thực sự biên dịch đầy đủ hàm bên trong mỗi khi hàm ngoài được gọi, mặc dù nó phải tạo một đối tượng hàm, việc này sẽ mất một ít thời gian. Mặt khác, tên hàm trở thành cục bộ chứ không phải toàn cầu, vì vậy việc gọi hàm này nhanh hơn. Trong các thử nghiệm của tôi, về mặt thời gian, về cơ bản, nó hầu như là một sự tẩy rửa; nó thậm chí có thể nhanh hơn với chức năng bên trong nếu bạn gọi nó nhiều lần.
kindall

7
Vâng, bạn sẽ cần nhiều cuộc gọi đến chức năng bên trong. Nếu bạn gọi nó trong một vòng lặp, hoặc chỉ hơn một vài lần, lợi ích của việc có một tên địa phương cho hàm sẽ bắt đầu lớn hơn chi phí tạo hàm. Trong các thử nghiệm của tôi, điều này xảy ra khi bạn gọi hàm bên trong khoảng 3-4 lần. Tất nhiên, bạn có thể nhận được lợi ích tương tự (không mất nhiều chi phí) bằng cách xác định tên địa phương cho hàm, ví dụ method_b = self._method_bvà sau đó gọi method_bđể tránh tra cứu thuộc tính lặp đi lặp lại. (Nó xảy ra rằng tôi đã thực hiện RẤT NHIỀU thời gian của công cụ gần đây. :)
kindall

3
@kindall: Đúng, đúng vậy. Tôi đã sửa đổi bài kiểm tra thời gian của mình để nó thực hiện 30 cuộc gọi đến chức năng phụ và kết quả đã quay lại. Tôi sẽ xóa câu trả lời của mình sau khi bạn có cơ hội thấy câu trả lời này. Cảm ơn sự giác ngộ.
martineau

2
Tuy nhiên, tôi chỉ muốn giải thích -1 vì đây là câu trả lời có nhiều thông tin: Tôi đã đưa ra -1 vì nó tập trung vào sự khác biệt hiệu năng tầm thường (trong hầu hết các kịch bản, việc tạo đối tượng mã sẽ chỉ mất một phần thời gian thực hiện của hàm ). Điều quan trọng cần xem xét trong những trường hợp này là liệu có chức năng nội tuyến có thể cải thiện khả năng đọc và bảo trì mã hay không, điều mà tôi nghĩ nó thường làm vì bạn không phải tìm kiếm / cuộn qua các tệp để tìm mã liên quan.
Blixt

2
@Blixt: Tôi nghĩ rằng logic của bạn không hoàn hảo (và bỏ phiếu không công bằng) bởi vì rất khó có khả năng một phương thức khác của cùng một lớp sẽ rất "xa" so với một phương thức khác trong cùng một lớp ngay cả khi nó không được lồng vào nhau (và cực kỳ khó xảy ra ở trong một tập tin khác). Ngoài ra, câu đầu tiên trong câu trả lời của tôi là "Bạn không thực sự đạt được nhiều bằng cách làm điều này" chỉ ra đó là một sự khác biệt không đáng kể.
martineau 7/11/2015

27

Một chức năng bên trong của một chức năng thường được sử dụng để đóng cửa .

(Có rất nhiều tranh cãi về chính xác những gì làm cho một đóng cửa đóng cửa .)

Đây là một ví dụ sử dụng tích hợp sum(). Nó định nghĩa startmột lần và sử dụng nó từ đó về sau:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

Đang sử dụng:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

Tích hợp đóng cửa python

functools.partial là một ví dụ về việc đóng cửa.

Từ các tài liệu python , nó gần tương đương với:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(Kudos gửi tới @ user225312 bên dưới để biết câu trả lời. Tôi thấy ví dụ này dễ hiểu hơn và hy vọng sẽ giúp trả lời bình luận của @ mango.)


-1 có, chúng thường được sử dụng như đóng cửa. Bây giờ đọc lại câu hỏi. Về cơ bản, ông hỏi liệu khái niệm mà ông thể hiện có thể được sử dụng cho trường hợp b. Nói với anh ta rằng điều này thường được sử dụng cho trường hợp a không phải là một câu trả lời tồi, nhưng là câu trả lời sai cho câu hỏi này. Ông quan tâm liệu đó có phải là một điều tốt để làm ở trên để đóng gói chẳng hạn.
Mayou36

@ Mayou36 Để công bằng, câu hỏi khá mở - thay vì cố gắng trả lời mọi trường hợp có thể, tôi nghĩ tốt nhất nên tập trung vào một câu hỏi. Ngoài ra câu hỏi không rõ ràng lắm. Ví dụ, nó ngụ ý đóng cửa trong ví dụ thứ hai, mặc dù đó có thể không phải là ý nghĩa.
CivilFan

@ Mayou36 "Về cơ bản, anh ấy hỏi liệu khái niệm anh ấy thể hiện có thể được sử dụng cho trường hợp b không." Không, câu hỏi hỏi nếu nó nên được sử dụng. OP đã biết nó có thể được sử dụng.
CivilFan

1
tốt, "nếu phương thức_b là bắt buộc và chỉ được gọi từ phương thức_a, tôi có nên khai báo phương thức_b bên trong phương thức_a không?" là khá rõ ràng và không có gì để làm với đóng cửa, phải không? Vâng, tôi đồng ý, tôi đã sử dụng có thể theo cách nên . Nhưng điều đó không liên quan đến việc đóng cửa ... Tôi chỉ ngạc nhiên khi có rất nhiều câu trả lời về việc đóng cửa và cách sử dụng chúng khi OP hỏi một câu hỏi hoàn toàn khác.
Mayou36

@ Mayou36 Nó có thể giúp điều chỉnh lại câu hỏi bạn nhìn thấy nó như thế nào và mở ra một câu hỏi khác để giải quyết nó.
CivilFan

17

Nói chung, không, không xác định các chức năng bên trong các chức năng.

Trừ khi bạn có một lý do thực sự tốt. Mà bạn không.

Tại sao không?

Có gì một lý do thực sự tốt để xác định các chức năng bên trong chức năng?

Khi những gì bạn thực sự muốn là một đóng cửa dingdang .


1
Câu trả lời này là dành cho bạn @ Mayou36.
CivilFan

2
vâng, cảm ơn, đây là câu trả lời tôi đang tìm kiếm Điều này trả lời câu hỏi theo cách tốt nhất có thể. Mặc dù nó không nghiêm túc nói cái này hay cái kia, nhưng nó làm giảm đáng kể các định nghĩa nội tuyến bằng cách nêu rõ lý do. Đó là cách một câu trả lời (pythonic :)) nên được!
Mayou36

10

Thật sự tốt khi khai báo một chức năng bên trong một chức năng khác. Điều này đặc biệt hữu ích tạo trang trí.

Tuy nhiên, theo nguyên tắc thông thường, nếu chức năng phức tạp (hơn 10 dòng), có thể là một ý tưởng tốt hơn để khai báo nó ở cấp độ mô-đun.


2
Điều đó có thể, nhưng tôi đồng ý, bạn cần một lý do chính đáng để làm điều đó. Sẽ là nhiều trăn hơn khi sử dụng dấu gạch dưới trước cho một chức năng chỉ được sử dụng trong lớp của bạn.
chmullig

3
Trong một lớp, có, nhưng chỉ trong một hàm thì sao? Đóng gói là phân cấp.
Paul Draper

@PaulDraper "Đóng gói là phân cấp" - Không! Ai nói vậy? Đóng gói là một khoản tiền gốc rộng hơn nhiều so với chỉ một số thừa kế.
Mayou36

7

Tôi tìm thấy câu hỏi này vì tôi muốn đặt ra một câu hỏi tại sao có tác động hiệu năng nếu một người sử dụng các hàm lồng nhau. Tôi đã chạy thử nghiệm các chức năng sau bằng Python 3.2.5 trên Máy tính xách tay Windows với bộ xử lý Intel Core i5-2530M Quad Core 2,5 GHz

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

Tôi đã đo 20 lần sau đây, cũng cho hình vuông1, hình vuông2 và hình vuông5:

s=0
for i in range(10**6):
    s+=square0(i)

và nhận được kết quả như sau

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0không có chức năng lồng nhau, square1có một chức năng lồng nhau, square2có hai chức năng lồng nhau và square5có năm chức năng lồng nhau. Các hàm lồng nhau chỉ được khai báo nhưng không được gọi.

Vì vậy, nếu bạn đã xác định 5 chức năng lồng nhau trong một hàm mà bạn không gọi thì thời gian thực hiện của hàm là hai lần của hàm mà không có hàm lồng nhau. Tôi nghĩ nên thận trọng khi sử dụng các hàm lồng nhau.

Tệp Python cho toàn bộ bài kiểm tra tạo đầu ra này có thể được tìm thấy tại ideone .


5
Sự so sánh bạn làm không thực sự hữu ích. Nó giống như thêm các câu lệnh giả vào hàm và nói rằng nó chậm hơn. Ví dụ về martineau thực sự sử dụng các hàm được đóng gói và tôi không nhận thấy bất kỳ sự khác biệt hiệu suất nào bằng cách chạy ví dụ của mình.
kon tâm lý

-1 xin vui lòng đọc lại câu hỏi. Mặc dù bạn có thể nói rằng nó có thể chậm hơn một chút, nhưng anh ấy hỏi liệu có nên làm điều đó không, chủ yếu là vì hiệu suất mà là do thực tiễn chung.
Mayou36

@ Mayou36 xin lỗi, ba năm sau khi câu hỏi và câu trả lời được đăng tôi sẽ không.
phép lạ173

Ok, thật không may, vẫn không có câu trả lời được chấp nhận (hoặc tốt).
Mayou36

4

Đó chỉ là một nguyên tắc về API phơi sáng.

Sử dụng python, đó là một ý tưởng tốt để tránh API phơi sáng ở ngoài không gian (mô-đun hoặc lớp), chức năng là một nơi đóng gói tốt.

Nó có thể là một ý tưởng tốt. khi bạn đảm bảo

  1. chức năng bên trong CHỈ được sử dụng bởi chức năng bên ngoài.
  2. chức năng nội bộ có một tên tốt để giải thích mục đích của nó bởi vì mã nói chuyện.
  3. mã không thể hiểu trực tiếp bởi các đồng nghiệp của bạn (hoặc người đọc mã khác).

Mặc dù, Lạm dụng kỹ thuật này có thể gây ra vấn đề và ngụ ý một lỗ hổng thiết kế.

Chỉ từ exp ​​của tôi, Có thể hiểu nhầm câu hỏi của bạn.


4

Vì vậy, cuối cùng, câu hỏi chủ yếu là việc triển khai python thông minh đến mức nào hay không, đặc biệt là trong trường hợp hàm bên trong không phải là một bao đóng mà chỉ đơn giản là một hàm trợ giúp chỉ cần trợ giúp.

Trong thiết kế dễ hiểu, chỉ có các chức năng khi cần thiết và không được hiển thị ở nơi khác là thiết kế tốt cho dù chúng được nhúng trong một mô-đun, một lớp như một phương thức, hoặc bên trong một chức năng hoặc phương thức khác. Khi được thực hiện tốt, họ thực sự cải thiện sự rõ ràng của mã.

Và khi chức năng bên trong là một bao đóng cũng có thể giúp làm rõ khá nhiều ngay cả khi chức năng đó không được đưa ra khỏi chức năng chứa để sử dụng ở nơi khác.

Vì vậy, tôi thường nói rằng hãy sử dụng chúng nhưng hãy chú ý đến hiệu suất có thể đạt được khi bạn thực sự quan tâm đến hiệu suất và chỉ xóa chúng nếu bạn thực hiện hồ sơ thực tế cho thấy chúng bị loại bỏ tốt nhất.

Không tối ưu hóa sớm chỉ sử dụng "BAD bên trong" trong tất cả các mã python bạn viết. Xin vui lòng.


Như được hiển thị bởi các câu trả lời khác, bạn không thực sự có lượt truy cập hiệu suất.
Mayou36

1

Hoàn toàn ổn khi làm theo cách đó, nhưng trừ khi bạn cần sử dụng bao đóng hoặc trả lại chức năng mà tôi có thể đặt ở cấp mô-đun. Tôi tưởng tượng trong ví dụ mã thứ hai bạn có nghĩa là:

...
some_data = method_b() # not some_data = method_b

nếu không, some_data sẽ là hàm.

Có nó ở cấp mô-đun sẽ cho phép các hàm khác sử dụng phương thức_b () và nếu bạn đang sử dụng một cái gì đó như Sphinx (và autodoc) cho tài liệu, nó cũng sẽ cho phép bạn viết tài liệu phương thức_b.

Bạn cũng có thể muốn xem xét chỉ đưa chức năng vào hai phương thức trong một lớp nếu bạn đang làm một cái gì đó có thể được đại diện bởi một đối tượng. Điều này cũng chứa logic nếu đó là tất cả những gì bạn đang tìm kiếm.


1

Làm một cái gì đó như:

def some_function():
    return some_other_function()
def some_other_function():
    return 42 

nếu bạn chạy some_function()thì nó sẽ chạy some_other_function()và trả về 42.

EDIT: Ban đầu tôi đã nói rằng bạn không nên xác định một chức năng bên trong một chức năng khác nhưng điều đó chỉ ra rằng đôi khi nó thực tế để làm điều này.


Tôi đánh giá cao những nỗ lực mà những người ở trên bạn đã đưa vào câu trả lời của họ, nhưng của bạn là trực tiếp và đi vào vấn đề. Đẹp.
C0NFUS3D

1
Tại sao? Tại sao bạn không nên làm điều đó? Đó không phải là sự đóng gói tuyệt vời sao? Tôi đang thiếu bất kỳ đối số. -1
Mayou36

@ Mayou36 Tôi không biết đóng gói là gì tại thời điểm viết bình luận của mình, tôi cũng không thực sự biết nó là gì bây giờ. Tôi chỉ nghĩ rằng nó không tốt để làm. Bạn có thể giải thích tại sao nó có ích khi định nghĩa một hàm bên trong một hàm khác, thay vì chỉ định nghĩa nó bên ngoài nó không?
mdlp0716

2
Vâng tôi có thể. Bạn có thể tra cứu khái niệm đóng gói, nhưng tóm lại: ẩn thông tin không cần thiết và chỉ tiết lộ cho người dùng những gì cần biết. Điều đó có nghĩa là, việc xác định some_other_function bên ngoài chỉ cần thêm một cái gì đó vào không gian tên thực sự bị ràng buộc chặt chẽ với hàm đầu tiên. Hoặc suy nghĩ về các biến: tại sao bạn cần biến cục bộ so với biến toàn cục? Xác định tất cả các biến bên trong một hàm, nếu có thể, là cách tốt hơn sau đó sử dụng các biến toàn cục cho các biến chỉ được sử dụng bên trong một hàm này . Đó là tất cả về việc giảm sự phức tạp cuối cùng.
Mayou36

0

Bạn có thể sử dụng nó để tránh xác định các biến toàn cục. Điều này cung cấp cho bạn một sự thay thế cho các thiết kế khác. 3 thiết kế trình bày một giải pháp cho một vấn đề.

A) Sử dụng các chức năng không có toàn cầu

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

B) Sử dụng các chức năng với toàn cầu

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C) Sử dụng các chức năng bên trong chức năng khác

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

Giải pháp C) cho phép sử dụng các biến trong phạm vi của hàm ngoài mà không cần phải khai báo chúng trong hàm bên trong. Có thể hữu ích trong một số tình huống.


0

Chức năng Trong chức năng python

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
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.