Jeśli funkcja A jest wymagana tylko przez funkcję B, czy A powinno być zdefiniowane w B? [Zamknięte]


147

Prosty przykład. Dwie metody, jedna wywoływana z drugiej:

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

def method_b(arg):
    return some_data

W Pythonie możemy zadeklarować defwewnątrz innego def. Więc jeśli method_bjest to wymagane i wywoływane tylko z method_a, czy powinienem zadeklarować w method_bśrodku method_a? lubię to :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Czy powinienem tego unikać?


7
Nie powinieneś musieć definiować funkcji wewnątrz innej, chyba że robisz coś NAPRAWDĘ funky. Opisz jednak, co próbujesz zrobić, abyśmy mogli udzielić bardziej pomocnej odpowiedzi
inspectorG4dget

6
Czy zdajesz sobie sprawę, że drugi przykład jest inny, ponieważ nie dzwonisz method_b ? (@inspector: Ściśle rzecz biorąc, musisz to robić, ale jest to niezwykle przydatne, gdy zaczynasz zajmować się programowaniem funkcjonalnym, w szczególności zamknięciami).

3
@delnan: Myślę, że chodziło o „Ty nie musisz, ściśle rzecz biorąc, ale ...”
martineau

4
Przypadki użycia funkcji wewnętrznych są wspaniale podsumowane w linku: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Jeśli twoje zastosowanie nie pasuje do żadnego z przypadków, lepiej tego unikaj.

1
Świetne pytanie, ale nie ma prawdziwej odpowiedzi, jak się wydaje ...
Mayou36

Odpowiedzi:


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

Czy tego szukałeś? Nazywa się to zamknięciem .


14
To o wiele lepsze wyjaśnienie.
Usunąłem

dlaczego po prostu nie zrobić def sum (x, y): return x + y?
mango

4
@mango: To tylko zabawkowy przykład, aby przekazać koncepcję - w rzeczywistym użyciu do_it()prawdopodobnie byłoby nieco bardziej skomplikowane niż to, co można obsłużyć za pomocą jakiejś arytmetyki w pojedynczej returninstrukcji.
martineau

2
@mango W odpowiedzi na Twoje pytanie podano nieco bardziej przydatny przykład. stackoverflow.com/a/24090940/2125392
CivFan

10
Nie odpowiada na pytanie.
Hunsu

49

Tak naprawdę niewiele zyskujesz, robiąc to, w rzeczywistości spowalnia to method_a, ponieważ definiuje i rekompiluje inną funkcję za każdym razem, gdy jest wywoływana. Biorąc to pod uwagę, prawdopodobnie lepiej byłoby po prostu poprzedzić nazwę funkcji podkreśleniem, aby wskazać, że jest to metoda prywatna - tj _method_b.

Przypuszczam, że może chcesz to zrobić, jeśli definicja funkcji zagnieżdżonej za zmieniać za każdym razem z jakiegoś powodu, ale może wskazywać na błąd w projekcie. Powiedział, że jest ważny powód, aby to zrobić, aby umożliwić funkcja zagnieżdżona używać argumentów, które zostały przekazane do funkcji zewnętrznej, ale nie wprost na nich przerzucone, który czasami pojawia się podczas pisania dekoratorów funkcyjnych, na przykład. To właśnie jest pokazane w zaakceptowanej odpowiedzi, mimo że dekorator nie jest definiowany ani używany.

Aktualizacja:

Oto dowód na to, że zagnieżdżanie ich jest wolniejsze (przy użyciu Pythona 3.6.1), chociaż w tym trywialnym przypadku nie za dużo:

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

Uwaga Dodałem kilka selfargumentów do twoich przykładowych funkcji, aby uczynić je bardziej podobnymi do prawdziwych metod (chociaż method_b2nadal nie jest technicznie metodą Testklasy). Również funkcja zagnieżdżona jest faktycznie wywoływana w tej wersji, w przeciwieństwie do twojej.


21
W rzeczywistości nie kompiluje w pełni funkcji wewnętrznej za każdym razem, gdy wywoływana jest funkcja zewnętrzna, chociaż musi utworzyć obiekt funkcji, co zajmuje trochę czasu. Z drugiej strony nazwa funkcji staje się lokalna, a nie globalna, więc wywołanie funkcji jest szybsze. W moich próbach, pod względem czasu, jest to w zasadzie pranie przez większość czasu; może być nawet szybszy z funkcją wewnętrzną, jeśli wywołasz ją wiele razy.
kindall

7
Tak, będziesz potrzebować wielu wywołań funkcji wewnętrznej. Jeśli wywołujesz ją w pętli lub tylko kilka razy, korzyść z posiadania lokalnej nazwy funkcji zacznie przewyższać koszt jej utworzenia. W moich próbach dzieje się tak, gdy wywołujesz funkcję wewnętrzną około 3-4 razy. Oczywiście możesz uzyskać tę samą korzyść (bez prawie takich samych kosztów), definiując lokalną nazwę funkcji, np. method_b = self._method_bA następnie wywołując, method_baby uniknąć powtarzających się wyszukiwań atrybutów. (Zdarza się, że ostatnio robię DUŻO czasu. :)
kindall

3
@kindall: Tak, to prawda. Zmodyfikowałem mój test czasu, więc wykonałem 30 wywołań funkcji dodatkowej i wyniki się odwróciły. Usuwam swoją odpowiedź, gdy będziesz miał okazję ją zobaczyć. Dzięki za oświecenie.
martineau

2
Chciałem tylko wyjaśnić moje -1, ponieważ jest to jednak pouczająca odpowiedź: dałem -1, ponieważ skupia się na trywialnej różnicy w wydajności (w większości scenariuszy tworzenie obiektu kodu zajmie tylko ułamek czasu wykonania funkcji ). W takich przypadkach ważne jest, aby rozważyć, czy posiadanie funkcji wbudowanej może poprawić czytelność kodu i łatwość utrzymania, co moim zdaniem często robi, ponieważ nie trzeba przeszukiwać / przewijać plików, aby znaleźć odpowiedni kod.
Blixt

2
@Blixt: Myślę, że twoja logika jest błędna (i głosowanie negatywne jest niesprawiedliwe), ponieważ jest bardzo mało prawdopodobne, że inna metoda tej samej klasy byłaby bardzo „daleko” od innej w tej samej klasie, nawet jeśli nie jest zagnieżdżona (i jest bardzo mało prawdopodobne znajdować się w innym pliku). Również pierwsze zdanie mojej odpowiedzi brzmi: „Robiąc to niewiele zyskujesz”, co wskazuje, że jest to trywialna różnica.
martineau

27

Funkcja wewnątrz funkcji jest powszechnie używana do domknięć .

(Istnieje wiele sporów co do tego, co dokładnie sprawia , że zamknięcie jest zamknięciem ).

Oto przykład użycia wbudowanego sum(). Definiuje startraz i używa go odtąd:

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

W użyciu:

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

Wbudowane zamknięcie w języku Python

functools.partial jest przykładem zamknięcia.

Z dokumentacji Pythona jest to mniej więcej odpowiednik:

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

(Uznanie dla @ user225312 poniżej za odpowiedź. Uważam, że ten przykład jest łatwiejszy do zrozumienia i mam nadzieję, że pomogę odpowiedzieć na komentarz @ mango.)


-1 tak, są powszechnie używane jako zamknięcia. Teraz ponownie przeczytaj pytanie. Zasadniczo zapytał, czy koncepcję, którą pokazuje, można wykorzystać w przypadku b. Mówienie mu, że jest to często używane w przypadku a, nie jest złą odpowiedzią, ale niewłaściwą odpowiedzią na to pytanie. Interesuje się, czy wykonanie powyższej czynności jest dobre, na przykład w przypadku hermetyzacji.
Mayou36

@ Mayou36 Szczerze mówiąc, pytanie jest raczej otwarte - zamiast próbować odpowiadać na każdą możliwą sprawę, pomyślałem, że najlepiej skupić się na jednym. Również pytanie nie jest zbyt jasne. Na przykład w drugim przykładzie oznacza to zamknięcie, chociaż prawdopodobnie nie o to chodziło.
CivFan

@ Mayou36 "Zasadniczo zapytał, czy koncepcję, którą pokazuje, można wykorzystać w przypadku b." Nie, pytanie brzmi, czy należy go użyć. OP już wie, że można go użyć.
CivFan

1
cóż, "jeśli metoda method_b jest wymagana i wywoływana tylko z metody method_a, czy powinienem zadeklarować metodę method_b wewnątrz metody method_a?" jest całkiem jasne i nie ma nic wspólnego z zamknięciami, prawda? Tak, zgadzam się, użyłem can tak, jak powinienem . Ale to nie ma nic wspólnego z zamknięciami ... Jestem po prostu zaskoczony, że tak wielu odpowiada na temat zamknięć i tego, jak z nich korzystać, gdy OP zadał zupełnie inne pytanie.
Mayou36

@ Mayou36 Pomocne może być przeformułowanie pytania, jak to widzisz, i otwarcie kolejnego pytania, aby go rozwiązać.
CivFan

17

Generalnie nie, nie definiuj funkcji wewnątrz funkcji.

Chyba że masz naprawdę dobry powód. Czego nie robisz.

Dlaczego nie?

Jaki jest naprawdę dobry powód, aby definiować funkcje wewnątrz funkcji?

Kiedy to, czego naprawdę chcesz, to zamknięcie dingdang .


1
Ta odpowiedź jest dla Ciebie @ Mayou36.
CivFan

2
tak, dzięki, to jest odpowiedź, której szukałem. To odpowiada na pytanie w najlepszy możliwy sposób. Chociaż nie określa ściśle jednej lub drugiej, znacznie zniechęca do definiowania wbudowanych, podając przyczyny. Taka powinna być (pythonowa :)) odpowiedź!
Mayou 36

10

Właściwie dobrze jest zadeklarować jedną funkcję wewnątrz innej. Jest to szczególnie przydatne przy tworzeniu dekoratorów.

Jednak z reguły, jeśli funkcja jest złożona (więcej niż 10 linii), lepszym pomysłem może być zadeklarowanie jej na poziomie modułu.


2
Jest to możliwe, ale zgadzam się, potrzebujesz dobrego powodu, aby to zrobić. Bardziej w Pythonie byłoby użycie poprzedzającego podkreślenia dla funkcji, która miała być używana tylko w twojej klasie.
chmullig

3
W klasie, tak, ale co tylko w ramach funkcji ? Hermetyzacja jest hierarchiczna.
Paul Draper,

@PaulDraper „Hermetyzacja jest hierarchiczna” - Nie! Kto to mówi? Hermetyzacja to znacznie szersza zasada niż zwykłe dziedziczenie.
Mayou36

7

Znalazłem to pytanie, ponieważ chciałem zadać pytanie, dlaczego użycie funkcji zagnieżdżonych ma wpływ na wydajność. Przeprowadziłem testy następujących funkcji przy użyciu Pythona 3.2.5 na notebooku z systemem Windows z czterordzeniowym procesorem Intel i5-2530M 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

Zmierzyłem następujące 20 razy, również dla kwadratu1, kwadratu2 i kwadratu5:

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

i otrzymałem następujące wyniki

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

square0nie ma funkcji zagnieżdżonej, square1ma jedną funkcję zagnieżdżoną, square2ma dwie funkcje zagnieżdżone i square5ma pięć funkcji zagnieżdżonych. Zagnieżdżone funkcje są tylko deklarowane, ale nie są wywoływane.

Więc jeśli zdefiniowałeś 5 zagnieżdżonych funkcji w funkcji, której nie wywołujesz, to czas wykonania funkcji jest dwukrotnie dłuższy niż funkcji bez funkcji zagnieżdżonej. Myślę, że należy zachować ostrożność podczas korzystania z funkcji zagnieżdżonych.

Plik Pythona dla całego testu, który generuje te dane wyjściowe, można znaleźć pod adresem ideone .


5
Porównanie, które robisz, nie jest zbyt przydatne. To jak dodanie fikcyjnych instrukcji do funkcji i powiedzenie, że jest wolniejsza. Przykład martineau faktycznie używa hermetyzowanych funkcji i nie zauważam żadnej różnicy w wydajności, uruchamiając jego przykład.
kon psych

-1 proszę ponownie przeczytać pytanie. Chociaż można powiedzieć, że może być nieco wolniejszy, zapytał, czy warto to zrobić, nie głównie ze względu na wyniki, ale ze względu na ogólną praktykę.
Mayou36

@ Mayou36 przepraszam, trzy lata po wysłaniu pytania i odpowiedzi nie będę.
cud173

Ok, niestety nadal nie ma dostępnej zaakceptowanej (lub dobrej) odpowiedzi.
Mayou36

4

To tylko zasada dotycząca interfejsów API ekspozycji.

Używając Pythona, dobrym pomysłem jest unikanie ekspozycji API w przestrzeni kosmicznej (moduł lub klasa), funkcja jest dobrym miejscem do hermetyzacji.

To mógłby być dobry pomysł. kiedy zapewniasz

  1. Funkcja wewnętrzna jest używana TYLKO przez funkcję zewnętrzną.
  2. Funkcja insider ma dobrą nazwę, aby wyjaśnić jej cel, ponieważ kod mówi.
  3. kod nie może bezpośrednio zrozumieć twoich współpracowników (lub innego czytnika kodów).

Mimo to, nadużywanie tej techniki może powodować problemy i sugeruje wadę projektową.

Tylko z mojego doświadczenia, może źle zrozumiałem twoje pytanie.


4

W końcu jest to w dużej mierze pytanie o to, jak inteligentna jest implementacja Pythona, szczególnie w przypadku, gdy funkcja wewnętrzna nie jest zamknięciem, ale po prostu potrzebnym pomocnikiem w funkcji.

W czystym, zrozumiałym projekcie, w którym funkcje są tylko tam, gdzie są potrzebne i nie są ujawniane gdzie indziej, jest dobrym projektem, niezależnie od tego, czy są one osadzone w module, klasie jako metodzie, czy w innej funkcji lub metodzie. Dobrze wykonane naprawdę poprawiają przejrzystość kodu.

A kiedy funkcja wewnętrzna jest zamknięciem, które również może pomóc w przejrzystości, nawet jeśli ta funkcja nie jest zwracana z funkcji zawierającej do użycia w innym miejscu.

Powiedziałbym więc, że generalnie używaj ich, ale pamiętaj o możliwym spadku wydajności, gdy faktycznie martwisz się o wydajność, i usuwaj je tylko wtedy, gdy wykonasz faktyczne profilowanie, które pokazuje, że najlepiej je usunąć.

Nie wykonuj przedwczesnej optymalizacji, używając po prostu „ZŁE funkcje wewnętrzne” w całym kodzie Pythona, który piszesz. Proszę.


Jak pokazują inne odpowiedzi, tak naprawdę nie masz hitów wydajnościowych.
Mayou36

1

Robienie tego w ten sposób jest całkowicie w porządku, ale chyba że musisz użyć zamknięcia lub zwrócić funkcji, prawdopodobnie umieściłbym to na poziomie modułu. Wyobrażam sobie, że w drugim przykładzie kodu masz na myśli:

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

w przeciwnym razie some_data będzie funkcją.

Posiadanie go na poziomie modułu pozwoli innym funkcjom używać method_b (), a jeśli używasz czegoś takiego jak Sphinx (i autodoc) do dokumentacji, pozwoli ci to również udokumentować method_b.

Możesz również rozważyć umieszczenie funkcji w dwóch metodach w klasie, jeśli robisz coś, co może być reprezentowane przez obiekt. Zawiera również logikę, jeśli to wszystko, czego szukasz.


1

Zrób coś takiego:

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

jeśli miałbyś uruchomić, some_function()to uruchomi się some_other_function()i zwróci 42.

EDYCJA: Początkowo stwierdziłem, że nie powinno się definiować funkcji wewnątrz innej, ale wskazano, że czasami jest to praktyczne.


Doceniam wysiłek, jaki ci powyżej włożyli w udzielenie odpowiedzi, ale twoja była bezpośrednia i na temat. Miły.
C0NFUS3D

1
Czemu? Dlaczego nie miałbyś tego robić? Czy to nie jest świetne zamknięcie? Brakuje mi argumentów. -1
Mayou36

@ Mayou36 Nie wiedziałem, czym jest hermetyzacja w momencie pisania mojego komentarza, ani tak naprawdę nie wiem, co to jest teraz. Po prostu pomyślałem, że to niedobrze. Czy możesz wyjaśnić, dlaczego warto byłoby zdefiniować funkcję wewnątrz innej, zamiast definiować ją po prostu poza nią?
mdlp0716

2
Tak, mogę. Możesz spojrzeć na pojęcie hermetyzacji, ale w skrócie: ukryj informacje, które nie są potrzebne, i ujawnij użytkownikowi tylko to, co powinien wiedzieć. Co oznacza, że ​​zdefiniowanie some_other_function na zewnątrz po prostu dodaje coś więcej do przestrzeni nazw, która jest w rzeczywistości ściśle powiązana z pierwszą funkcją. Lub pomyśl w kategoriach zmiennych: dlaczego potrzebujesz zmiennych lokalnych, a nie zmiennych globalnych? Zdefiniowanie wszystkich zmiennych wewnątrz funkcji, jeśli to możliwe, jest o wiele lepsze niż używanie zmiennych globalnych dla zmiennych używanych tylko w tej jednej funkcji . Ostatecznie chodzi o zmniejszenie złożoności.
Mayou36

0

Możesz go użyć, aby uniknąć definiowania zmiennych globalnych. Daje to alternatywę dla innych projektów. 3 projekty przedstawiające rozwiązanie problemu.

A) Używanie funkcji bez globali

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) Używanie funkcji z globalnymi

_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) Używanie funkcji wewnątrz innej funkcji

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

Rozwiązanie C) pozwala na użycie zmiennych w zakresie funkcji zewnętrznej bez konieczności deklarowania ich w funkcji wewnętrznej. Może się przydać w niektórych sytuacjach.


0

Funkcja W funkcji 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))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.