Zagnieżdżona funkcja w Pythonie

86

Jakie korzyści lub konsekwencje moglibyśmy uzyskać dzięki kodowi Python w ten sposób:

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

Znalazłem to w projekcie open source, robiąc coś pożytecznego wewnątrz zagnieżdżonej funkcji, ale nie robiąc absolutnie nic poza nią, z wyjątkiem wywoływania jej. (Właściwy kod można znaleźć tutaj .) Dlaczego ktoś mógłby zakodować to w ten sposób? Czy istnieje jakaś korzyść lub efekt uboczny pisania kodu wewnątrz funkcji zagnieżdżonej, a nie w zewnętrznej, normalnej funkcji?

Hosam Aly
źródło
4
Czy to jest rzeczywisty kod, który znalazłeś, czy tylko utworzony przez Ciebie uproszczony przykład?
MAK
To uproszczony przykład. Aktualny kod można znaleźć tutaj: bazaar.launchpad.net/%7Eopenerp/openobject-server/trunk/…
Hosam Aly
2
Twój link (wskazujący na HEAD) jest teraz niedokładny. Wypróbuj: bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/ ...
Craig McQueen
Masz rację Craig. Dziękuję Ci.
Hosam Aly

Odpowiedzi:

113

Zwykle robisz to, aby zamknąć :

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

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

Funkcje wewnętrzne mogą uzyskiwać dostęp do zmiennych z otaczającego zakresu (w tym przypadku zmiennej lokalnej x). Jeśli nie uzyskujesz dostępu do żadnych zmiennych z otaczającego zakresu, w rzeczywistości są to zwykłe funkcje z innym zakresem.

Adam Rosenfield
źródło
5
Do tego wolałbym partials: plus5 = functools.partial(operator.add, 5). Dekoratory byłyby lepszym przykładem zamknięć.
Jochen Ritzel
4
Dzięki, ale jak widać w opublikowanym przeze mnie fragmencie, tak nie jest w tym przypadku: funkcja zagnieżdżona jest po prostu wywoływana w funkcji zewnętrznej.
Hosam Aly
58

Oprócz generatorów funkcji, gdzie tworzenie funkcji wewnętrznych jest prawie definicją generatora funkcji, powodem tworzenia funkcji zagnieżdżonych jest poprawa czytelności. Jeśli mam małą funkcję, która będzie wywoływana tylko przez funkcję zewnętrzną, wstawiam definicję, abyś nie musiał przeskakiwać, aby określić, co robi ta funkcja. Zawsze mogę przenieść wewnętrzną metodę poza metodę hermetyzacji, jeśli stwierdzę potrzebę ponownego użycia funkcji w późniejszym terminie.

Przykład zabawki:

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()
Ross Rogers
źródło
4
Jedynym problemem jest to, że czasami może to utrudniać test jednostkowy, ponieważ nie masz dostępu do funkcji wewnętrznej.
Challenger5
1
Wyglądają tak uroczo!
stożek
1
FWIY @ Challenger5, jeśli funkcja wewnętrzna jest analogiczna do funkcji klasy prywatnej, i tak nie są one testowane jednostkowo. Zacząłem to robić, aby uczynić metody bardziej czytelnymi, zachowując definicję zbliżoną do danej metody.
Mitchell Currie
27

Jedną z potencjalnych zalet używania metod wewnętrznych jest to, że umożliwia użycie zmiennych lokalnych metody zewnętrznej bez przekazywania ich jako argumentów.

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)

można zapisać w następujący sposób, co prawdopodobnie brzmi lepiej

def save(item, resultBuffer):

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

  helper(item.description)
  helper(item.size)
  helper(item.type)
Kuba
źródło
8

Nie potrafię sobie wyobrazić żadnego dobrego powodu dla takiego kodu.

Może był powód dla wewnętrznej funkcji w starszych wersjach, podobnie jak w innych Ops.

Na przykład ma to nieco większy sens:

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)

ale wtedy funkcja wewnętrzna powinna być („prywatną”) metodą klasową:

class some_class(object):
    def _add(self, x, y):
        return x + y
    def doOp(self, x, y):
        return self._add(x,y)
Jochen Ritzel
źródło
Tak, może doOp wziął łańcuch, aby określić, którego operatora użyć w argumentach ...
Skilldrick
1
@ArtOfWarfare lepiej jest po prostu używać modułów. Klasy są dla stanu.
aehlke
6

Idea metod lokalnych jest podobna do zmiennych lokalnych: nie zanieczyszczaj większej przestrzeni nazw. Oczywiście korzyści są ograniczone, ponieważ większość języków nie zapewnia również bezpośrednio takiej funkcjonalności.

avguchenko
źródło
1

Czy na pewno kod był dokładnie taki? Normalnym powodem zrobienia czegoś takiego jest utworzenie częściowej - funkcji z wprowadzonymi parametrami. Wywołanie funkcji zewnętrznej zwraca wywołanie, które nie wymaga parametrów, dlatego może być przechowywane i używane gdzieś, niemożliwe jest przekazanie parametrów. Jednak wysłany kod tego nie zrobi - wywołuje funkcję natychmiast i zwraca wynik, a nie wywoływalny. Przydatne może być opublikowanie rzeczywistego kodu, który widziałeś.

Daniel Roseman
źródło
1
Dodałem link do oryginalnego kodu w komentarzu do mojego pytania. Jak widać, mój jest uproszczonym przykładem, ale nadal jest prawie taki sam.
Hosam Aly