La funzione annidata è un buon approccio quando richiesto da una sola funzione? [chiuso]


149

Diciamo che a function Aè richiesto solo da function B, A dovrebbe essere definito all'interno di B?

Semplice esempio. Due metodi, uno chiamato da un altro:

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

def method_b(arg):
    return some_data

In Python possiamo dichiarare defdentro un altro def. Quindi, se method_bè richiesto e chiamato solo da method_a, devo dichiarare method_ball'interno method_a? come questo :

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

    some_data = method_b(arg)

O dovrei evitare di farlo?


7
Non dovresti aver bisogno di definire una funzione all'interno di un'altra a meno che tu non stia facendo qualcosa di DAVVERO funky. Tuttavia, ti preghiamo di approfondire ciò che stai cercando di fare, in modo che possiamo fornire una risposta più utile
inspector

6
Ti rendi conto che il secondo esempio è diverso, perché non chiami method_b ? (@inspector: è necessario, in senso stretto, ma è immensamente utile quando si entra in un po 'di programmazione funzionale, in particolare chiusure).

3
@delnan: Penso che volessi dire " Non è necessario, a rigor di termini, ma ..."
martineau

Come menzionato da @delnan, questo è comune nel caso delle chiusure, quindi non penso che si qualifichi come funky; tuttavia, a meno che le chiusure non siano necessarie (cosa che immagino non siano in questo caso), inserire una funzione all'interno di un'altra non sembra necessario, efficiente o ordinato. A meno che tu non abbia bisogno di chiusure, mi atterrei al primo schema.
Trevor

4
I casi d'uso per le funzioni interne sono riassunti meravigliosamente nel link: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Se il tuo utilizzo non rientra in nessuna delle custodie, meglio evitarlo.

Risposte:


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

È questo quello che stavi cercando? Si chiama chiusura .


perché non solo def sum (x, y): return x + y?
mango

4
@mango: Questo è solo un esempio di giocattolo per trasmettere il concetto: nell'uso reale ciò che do_it()fa sarebbe presumibilmente un po 'più complicato di ciò che può essere gestito da alcuni aritmetici in una singola returnaffermazione.
martineau

2
@mango ha risposto alla tua domanda con un esempio leggermente più utile. stackoverflow.com/a/24090940/2125392
CivFan

12
Non risponde alla domanda.
Hunsu

Bella risposta ma dov'è la domanda? Non qui. OP ha chiesto se si può fare quanto sopra se method_b è usato solo da method_a. Non ha chiesto altro. Nessuna chiusura.
Mayou36

49

Non guadagni davvero molto in questo modo, infatti rallenta method_aperché definirà e ricompilerà l'altra funzione ogni volta che viene chiamata. Detto questo, probabilmente sarebbe meglio anteporre al nome della funzione un carattere di sottolineatura per indicare che si tratta di un metodo privato, ad es _method_b.

Suppongo che potresti volerlo fare se la definizione della funzione annidata variava ogni volta per qualche motivo, ma ciò potrebbe indicare un difetto nel tuo design. Detto questo, non v'è un motivo valido per fare questo per consentire la funzione annidata di usare argomenti che sono stati passati alla funzione di esterno, ma non esplicitamente trasferito su di loro, che a volte si verifica quando si scrive decoratori di funzione, per esempio. È ciò che viene mostrato nella risposta accettata sebbene un decoratore non venga definito o utilizzato.

Aggiornare:

Ecco la prova che annidarli è più lento (usando Python 3.6.1), anche se certamente non di molto in questo caso banale:

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

Nota ho aggiunto alcuni selfargomenti alle tue funzioni di esempio per renderle più simili a metodi reali (sebbene method_b2non sia ancora tecnicamente un metodo della Testclasse). Anche la funzione annidata viene effettivamente chiamata in quella versione, a differenza della tua.


21
In realtà non compila completamente la funzione interna ogni volta che viene chiamata la funzione esterna, sebbene debba creare un oggetto funzione, il che richiede un po 'di tempo. D'altra parte, il nome della funzione diventa locale piuttosto che globale, quindi è più veloce chiamare la funzione. Nelle mie prove, dal punto di vista del tempo è fondamentalmente un lavaggio la maggior parte del tempo; potrebbe anche essere più veloce con la funzione interna se la chiami molte volte.
kindall

7
Sì, avrai bisogno di più chiamate alla funzione interna. Se lo chiami in un ciclo, o solo più di una manciata di volte, il vantaggio di avere un nome locale per la funzione inizierà a superare il costo di creazione della funzione. Nelle mie prove, questo accade quando chiami la funzione interna circa 3-4 volte. Ovviamente, potresti ottenere lo stesso vantaggio (senza un costo quasi uguale) definendo un nome locale per la funzione, ad es. method_b = self._method_bE quindi chiamare method_bper evitare le ripetute ricerche di attributi. (Succede che ho fatto un sacco di tempi di roba ultimamente :).
Kindall

3
@kindall: Sì, è vero. Ho modificato il mio test di temporizzazione in modo che abbia effettuato 30 chiamate alla funzione secondaria ei risultati si sono invertiti. Eliminerò la mia risposta dopo che avrai avuto la possibilità di vedere questa risposta. Grazie per l'illuminazione.
martineau

3
Volevo solo spiegare il mio -1 perché questa è comunque una risposta informativa: ho dato questo -1 perché si concentra su una banale differenza di prestazioni (nella maggior parte degli scenari la creazione dell'oggetto codice richiederà solo una frazione del tempo di esecuzione della funzione ). Ciò che è importante considerare in questi casi è se avere una funzione inline può migliorare la leggibilità e la manutenibilità del codice, cosa che penso spesso faccia perché non è necessario cercare / scorrere i file per trovare il codice pertinente.
Blixt

2
@Blixt: penso che la tua logica sia imperfetta (e ingiusta) perché è estremamente improbabile che un altro metodo della stessa classe sia molto "lontano" da un altro della stessa classe anche quando non è annidato (ed estremamente improbabile essere in un file diverso). Anche la primissima frase della mia risposta è "Non si guadagna molto in questo modo", il che sottolinea che è una differenza banale.
martineau

27

Una funzione all'interno di una funzione è comunemente usata per le chiusure .

(Ci sono molte controversie su ciò che rende esattamente una chiusura una chiusura .)

Ecco un esempio utilizzando il built-in sum(). Definisce startuna volta e lo utilizza da quel momento in poi:

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

In uso:

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

Chiusura in pitone incorporata

functools.partial è un esempio di chiusura.

Dalla documentazione di Python , è più o meno equivalente a:

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

(Complimenti a @ user225312 di seguito per la risposta. Trovo questo esempio più facile da capire e, si spera, aiuterà a rispondere al commento di @ mango.)


-1 sì, sono comunemente usati come chiusure. Ora rileggi la domanda. Fondamentalmente ha chiesto se il concetto che mostra può essere utilizzato per il caso b. Dirgli che questo è spesso usato per il caso a non è una cattiva risposta, ma quella sbagliata per questa domanda. Gli interessa se sia una buona cosa fare quanto sopra per l'incapsulamento, ad esempio.
Mayou36

@ Mayou36 Per essere onesti, la domanda è piuttosto aperta - piuttosto che cercare di rispondere a ogni possibile caso, ho pensato che fosse meglio concentrarsi su uno. Anche la domanda non è molto chiara. Ad esempio, implica la chiusura nel secondo esempio, anche se probabilmente non è quello che si intendeva.
CivFan

@ Mayou36 "In pratica ha chiesto se il concetto che mostra può essere utilizzato per il caso b." No, la domanda chiede se dovrebbe essere usato. OP sa già che può essere utilizzato.
CivFan

1
beh, "se method_b è richiesto e chiamato solo da method_a, dovrei dichiarare method_b all'interno di method_a?" è abbastanza chiaro e non ha nulla a che fare con le chiusure, giusto? Sì, sono d'accordo, ho usato can nel modo in cui dovrei . Ma questo non ha a che fare con le chiusure ... Sono solo sorpreso che così tanti rispondano sulle chiusure e su come usarle quando l'OP ha posto una domanda completamente diversa.
Mayou36

@ Mayou36 Potrebbe essere utile riformulare la domanda come la vedi e aprire un'altra domanda per affrontarla.
CivFan

18

Generalmente no, non definire funzioni all'interno di funzioni.

A meno che tu non abbia davvero una buona ragione. Quale non lo fai.

Perchè no?

Qual è davvero un buon motivo per definire le funzioni all'interno delle funzioni?

Quando quello che vuoi veramente è una chiusura dingdang .


10

In realtà va bene dichiarare una funzione all'interno di un'altra. Questo è particolarmente utile per creare decoratori.

Tuttavia, come regola pratica, se la funzione è complessa (più di 10 righe) potrebbe essere un'idea migliore dichiararla a livello di modulo.


2
È possibile, ma sono d'accordo, hai bisogno di una buona ragione per farlo. Sarebbe più python usare un carattere di sottolineatura precedente per una funzione che doveva essere utilizzata solo all'interno della tua classe.
chmullig

3
All'interno di una classe, sì, ma solo all'interno di una funzione ? L'incapsulamento è gerarchico.
Paul Draper

@PaulDraper "L'incapsulamento è gerarchico" - No! Chi lo dice? L'incapsulamento è un principio molto più ampio di una semplice eredità.
Mayou36

8

Ho trovato questa domanda perché volevo porre una domanda sul perché c'è un impatto sulle prestazioni se si utilizzano funzioni annidate. Ho eseguito test per le seguenti funzioni utilizzando Python 3.2.5 su un notebook Windows con un processore Intel i5-2530M Quad Core da 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

Ho misurato le seguenti 20 volte, anche per quadrato1, quadrato2 e quadrato5:

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

e ha ottenuto i seguenti risultati

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

square0non ha una funzione nidificata, square1ha una funzione nidificata, square2ha due funzioni nidificate e square5ha cinque funzioni nidificate. Le funzioni annidate vengono solo dichiarate ma non chiamate.

Quindi, se hai definito 5 funzioni annidate in una funzione che non chiami, il tempo di esecuzione della funzione è il doppio di quello della funzione senza una funzione annidata. Penso che dovrebbe essere cauto quando si utilizzano funzioni annidate.

Il file Python per l'intero test che genera questo output può essere trovato su ideone .


5
Il confronto che fai non è molto utile. È come aggiungere istruzioni fittizie nella funzione e dire che è più lento. Esempio di martineau utilizza effettivamente le funzioni incapsulate e non ho notato alcuna differenza di prestazioni eseguendo il suo esempio.
kon psych

-1 per favore rileggi la domanda. Sebbene tu possa dire che forse è leggermente più lento, ha chiesto se è una buona idea farlo, non principalmente per le prestazioni ma per la pratica generale.
Mayou36

4

È solo un principio sulle API di esposizione.

Usando python, è una buona idea evitare l'API di esposizione nello spazio (modulo o classe), la funzione è un buon luogo di incapsulamento.

Potrebbe essere una buona idea. quando ti assicuri

  1. la funzione interna viene utilizzata SOLO dalla funzione esterna.
  2. la funzione insider ha un buon nome per spiegare il suo scopo perché il codice parla.
  3. il codice non può essere compreso direttamente dai tuoi colleghi (o da altri lettori di codici).

Anche se, abusare di questa tecnica può causare problemi e implica un difetto di progettazione.

Solo dalla mia esperienza, forse fraintendo la tua domanda.


4

Quindi, alla fine, è in gran parte una questione su quanto sia intelligente o meno l'implementazione di Python, in particolare nel caso in cui la funzione interna non sia una chiusura ma semplicemente un aiuto in funzione necessario.

In un design pulito e comprensibile avere funzioni solo dove sono necessarie e non esposte altrove è un buon design sia che siano incorporate in un modulo, una classe come metodo o all'interno di un'altra funzione o metodo. Se fatti bene, migliorano davvero la chiarezza del codice.

E quando la funzione interna è una chiusura, questo può anche aiutare con chiarezza un po 'anche se quella funzione non viene restituita dalla funzione contenitore per essere utilizzata altrove.

Quindi direi che generalmente li usi ma sii consapevole del possibile impatto sulle prestazioni quando sei effettivamente preoccupato per le prestazioni e rimuovili solo se esegui una profilazione effettiva che mostra che è meglio rimuoverli.

Non ottimizzate prematuramente usando solo "funzioni interne BAD" in tutto il codice Python che scrivete. Per favore.


Come mostrato da altre risposte, non hai davvero risultati positivi.
Mayou36

1

È perfettamente OK farlo in questo modo, ma a meno che non sia necessario utilizzare una chiusura o restituire la funzione, probabilmente metterei a livello di modulo. Immagino che nel secondo esempio di codice intendi:

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

in caso contrario, some_data sarà la funzione.

Averlo a livello di modulo consentirà ad altre funzioni di usare method_b () e se stai usando qualcosa come Sphinx (e autodoc) per la documentazione, ti permetterà di documentare anche method_b.

Potresti anche considerare di mettere la funzionalità in due metodi in una classe se stai facendo qualcosa che può essere rappresentabile da un oggetto. Questo contiene bene anche la logica se è tutto ciò che stai cercando.


1

Fai qualcosa come:

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

se si dovesse eseguire some_function(), verrà eseguito some_other_function()e restituirà 42.

EDIT: inizialmente ho affermato che non dovresti definire una funzione all'interno di un'altra, ma è stato sottolineato che a volte è pratico farlo.


Apprezzo lo sforzo che coloro sopra di te hanno fatto per le loro risposte, ma il tuo è stato diretto e pertinente. Bello.
C0NFUS3D

1
Perché? Perché non dovresti farlo? Non è un ottimo incapsulamento? Mi mancano argomenti. -1
Mayou36

@ Mayou36 Non sapevo cosa fosse l'incapsulamento al momento della stesura del mio commento, né so veramente cosa sia adesso. Ho solo pensato che non fosse buono da fare. Puoi spiegare perché sarebbe utile definire una funzione all'interno di un'altra, invece di definirla semplicemente al di fuori di essa?
mdlp0716

2
Sì posso. Puoi cercare il concetto di incapsulamento, ma in breve: nascondi le informazioni che non sono necessarie ed esponi all'utente solo ciò che è necessario sapere. Il che significa che la definizione di some_other_function all'esterno aggiunge semplicemente qualcosa di più nello spazio dei nomi che è in realtà strettamente legato alla prima funzione. Oppure pensa in termini di variabili: perché hai bisogno di variabili locali rispetto a variabili globali? Definire tutte le variabili all'interno di una funzione, se possibile, è molto meglio quindi utilizzare le variabili globali per le variabili utilizzate solo all'interno di questa funzione . Alla fine si tratta di ridurre la complessità.
Mayou36

0

Puoi usarlo per evitare di definire variabili globali. Questo ti offre un'alternativa per altri design. 3 progetti che presentano una soluzione a un problema.

A) Utilizzo di funzioni senza 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) Utilizzo di funzioni con globali

_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) Utilizzo di funzioni all'interno di un'altra funzione

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

La soluzione C) consente di utilizzare le variabili nell'ambito della funzione esterna senza la necessità di dichiararle nella funzione interna. Potrebbe essere utile in alcune situazioni.


0

Funzione Nella funzione 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))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.