Si la fonction A n'est requise que par la fonction B, doit-on définir A à l'intérieur de B? [fermé]

147

Exemple simple. Deux méthodes, l'une appelée depuis une autre:

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

def method_b(arg):
    return some_data

En Python, nous pouvons déclarer defdans un autre def. Donc, si method_best requis et appelé uniquement depuis method_a, dois-je déclarer à l' method_bintérieur method_a? comme ça :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Ou devrais-je éviter de faire ça?

nukl
la source
7
Vous ne devriez pas avoir besoin de définir une fonction dans une autre, sauf si vous faites quelque chose de VRAIMENT génial. Cependant, veuillez préciser ce que vous essayez de faire, afin que nous puissions vous fournir une réponse plus utile
inspectorG4dget
6
Vous rendez-vous compte que le deuxième exemple est différent, parce que vous n'appelez pas method_b? (@inspector: Vous en avez besoin, à proprement parler, mais c'est extrêmement utile lorsque vous entrez dans un peu de programmation fonctionnelle, en particulier des fermetures).
3
@delnan: Je pense que vous vouliez dire "Vous n'avez pas besoin, à proprement parler, mais ..."
martineau
4
Les cas d'utilisation des fonctions internes sont merveilleusement résumés dans le lien: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Si votre utilisation ne rentre dans aucun des cas, mieux vaut l'éviter.
1
Grande question mais pas de vraie réponse comme il semble ...
Mayou36

Réponses:

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

Est ce que c'est ce que vous recherchiez? Cela s'appelle une fermeture .

user225312
la source
14
C'est une bien meilleure explication. J'ai supprimé ma réponse
pyfunc
pourquoi ne pas simplement faire def sum (x, y): return x + y?
mango
4
@mango: Ceci est juste un exemple de jouet pour transmettre le concept - en utilisation réelle, ce do_it()qui est probablement un peu plus compliqué que ce qui peut être géré par une certaine arithmétique dans une seule returninstruction.
martineau le
2
@mango a répondu à votre question avec un exemple un peu plus utile. stackoverflow.com/a/24090940/2125392
CivFan
10
Cela ne répond pas à la question.
Hunsu
49

Vous ne gagnez pas vraiment grand-chose en faisant cela, en fait cela ralentit method_acar il définira et recompilera l'autre fonction à chaque fois qu'elle sera appelée. Compte tenu de cela, il serait probablement préférable de simplement préfixer le nom de la fonction avec un trait de soulignement pour indiquer que c'est une méthode privée - c'est-à-dire _method_b.

Je suppose que vous voudrez peut -être faire cela si la définition de la fonction imbriquée variait à chaque fois pour une raison quelconque, mais cela peut indiquer un défaut dans votre conception. Cela dit, il existe une raison valable de le faire pour permettre à la fonction imbriquée d'utiliser des arguments qui ont été passés à la fonction externe mais qui ne leur ont pas été explicitement transmis, ce qui se produit parfois lors de l'écriture de décorateurs de fonction, par exemple. C'est ce qui est montré dans la réponse acceptée bien qu'un décorateur ne soit pas défini ou utilisé.

Mettre à jour:

Voici la preuve que leur imbrication est plus lente (en utilisant Python 3.6.1), bien que certes pas de beaucoup dans ce cas trivial:

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

Remarque J'ai ajouté quelques selfarguments à vos exemples de fonctions pour les rendre plus comme des méthodes réelles (bien que ce method_b2ne soit toujours pas techniquement une méthode de la Testclasse). La fonction imbriquée est également appelée dans cette version, contrairement à la vôtre.

Martineau
la source
21
Il ne compile pas complètement la fonction interne à chaque fois que la fonction externe est appelée, bien qu'il doive créer un objet fonction, ce qui prend un peu de temps. D'un autre côté, le nom de la fonction devient local plutôt que global, il est donc plus rapide d'appeler la fonction. Dans mes essais, en termes de temps, c'est essentiellement un lavage la plupart du temps; il peut même être plus rapide avec la fonction interne si vous l'appelez plusieurs fois.
quelque sorte le
7
Ouais, vous aurez besoin de plusieurs appels à la fonction interne. Si vous l'appelez en boucle, ou juste plus d'une poignée de fois, l'avantage d'avoir un nom local pour la fonction commencera à l'emporter sur le coût de création de la fonction. Dans mes essais, cela se produit lorsque vous appelez la fonction intérieure environ 3-4 fois. Bien sûr, vous pourriez obtenir le même avantage (sans presque autant de coût) en définissant un nom local pour la fonction, par exemple method_b = self._method_b, puis en appelant method_bpour éviter les recherches d'attributs répétées. (Il se trouve que j'ai fait BEAUCOUP de timing ces derniers temps. :)
quelque sorte le
3
@kindall: Oui, c'est vrai. J'ai modifié mon test de synchronisation pour qu'il fasse 30 appels à la fonction secondaire et les résultats se sont retournés. Je supprimerai ma réponse une fois que vous aurez eu la chance de voir cette réponse. Merci pour l'illumination.
martineau
2
Je voulais juste expliquer mon -1 car il s'agit néanmoins d'une réponse informative: j'ai donné un -1 car cela met l'accent sur une différence de performance triviale (dans la plupart des scénarios, la création d'un objet code ne prendra qu'une fraction du temps d'exécution de la fonction ). Ce qui est important à considérer dans ces cas est de savoir si le fait d'avoir une fonction en ligne peut améliorer la lisibilité et la maintenabilité du code, ce que je pense que c'est souvent le cas parce que vous n'avez pas à rechercher / faire défiler les fichiers pour trouver le code pertinent.
Blixt le
2
@Blixt: Je pense que votre logique est imparfaite (et injuste) car il est extrêmement improbable qu'une autre méthode de la même classe soit très "loin" d'une autre de la même classe même si elle n'est pas imbriquée (et extrêmement improbable être dans un autre fichier). De plus, la toute première phrase de ma réponse est "Vous ne gagnez pas vraiment beaucoup en faisant cela", ce qui montre que c'est une différence insignifiante.
martineau
27

Une fonction à l'intérieur d'une fonction est couramment utilisée pour les fermetures .

(Il y a beaucoup de désaccord sur ce qui fait exactement d' une fermeture une fermeture .)

Voici un exemple utilisant le fichier sum(). Il définit startune fois et l'utilise à partir de là:

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

Utilisé:

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

Fermeture en python intégrée

functools.partial est un exemple de fermeture.

D'après la documentation python , c'est à peu près équivalent à:

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

(Félicitations à @ user225312 ci-dessous pour la réponse. Je trouve cet exemple plus facile à comprendre et j'espère que cela aidera à répondre au commentaire de @ mango.)

CivFan
la source
-1 oui, ils sont couramment utilisés comme fermetures. Relisez maintenant la question. Il a essentiellement demandé si le concept qu'il montrait pouvait être utilisé pour le cas b. Lui dire que cela est souvent utilisé pour le cas a n'est pas une mauvaise réponse, mais la mauvaise pour cette question. Il se demande si c'est une bonne chose de faire ce qui précède pour l'encapsulation par exemple.
Mayou36
@ Mayou36 Pour être honnête, la question est plutôt ouverte - plutôt que d'essayer de répondre à tous les cas possibles, j'ai pensé qu'il valait mieux se concentrer sur un seul. La question n'est pas non plus très claire. Par exemple, cela implique la fermeture dans le deuxième exemple, même si ce n'est probablement pas ce que cela voulait dire.
CivFan du
@ Mayou36 "Il a essentiellement demandé si le concept qu'il montre pouvait être utilisé pour le cas b." Non, la question demande s'il doit être utilisé. OP sait déjà qu'il peut être utilisé.
CivFan
1
bien, "si method_b est requis et appelé uniquement depuis method_a, dois-je déclarer method_b dans method_a?" est assez clair et n'a rien à voir avec les fermetures, non? Oui, je suis d'accord, j'ai utilisé can à la manière de devrait . Mais cela n'a pas à voir avec les fermetures ... Je suis juste étonné que tant de réponses sur les fermetures et comment les utiliser lorsque le PO a posé une question complètement différente.
Mayou36
@ Mayou36 Il pourrait être utile de reformuler la question comment vous la voyez et d'ouvrir une autre question pour y répondre.
CivFan
17

En général, non, ne définissez pas les fonctions à l'intérieur des fonctions.

Sauf si vous avez une très bonne raison. Ce que vous ne faites pas.

Pourquoi pas?

Quelle est vraiment une bonne raison de définir des fonctions à l'intérieur de fonctions?

Quand ce que vous voulez vraiment, c'est une fermeture dingdang .

CivFan
la source
1
Cette réponse est pour vous @ Mayou36.
CivFan
2
oui, merci, c'est la réponse que je cherchais. Cela répond à la question de la meilleure façon possible. Bien qu'il ne dise pas strictement l'un ou l'autre, il décourage grandement les définitions en ligne en énonçant des raisons. C'est ainsi qu'une réponse (pythonique :)) devrait être!
Mayou36
10

Il est en fait bien de déclarer une fonction dans une autre. Ceci est particulièrement utile pour créer des décorateurs.

Cependant, en règle générale, si la fonction est complexe (plus de 10 lignes), il peut être préférable de la déclarer au niveau du module.

vz0
la source
2
C'est possible, mais je suis d'accord, vous avez besoin d'une bonne raison pour le faire. Il serait plus python d'utiliser un trait de soulignement précédent pour une fonction qui était destinée à être utilisée uniquement dans votre classe.
chmullig
3
Au sein d'une classe, oui, mais qu'en est-il uniquement au sein d'une fonction ? L'encapsulation est hiérarchique.
Paul Draper
@PaulDraper "L'encapsulation est hiérarchique" - Non! Qui dit ça? L'encapsulation est un principe beaucoup plus large qu'un simple héritage.
Mayou36
7

J'ai trouvé cette question parce que je voulais poser une question pourquoi il y a un impact sur les performances si l'on utilise des fonctions imbriquées. J'ai effectué des tests pour les fonctions suivantes en utilisant Python 3.2.5 sur un ordinateur portable Windows avec un processeur Intel 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

J'ai mesuré les 20 fois suivantes, également pour square1, square2 et square5:

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

et a obtenu les résultats suivants

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

square0n'a pas de fonction imbriquée, square1a une fonction imbriquée, square2a deux fonctions imbriquées et square5a cinq fonctions imbriquées. Les fonctions imbriquées sont uniquement déclarées mais pas appelées.

Donc, si vous avez défini 5 fonctions imbriquées dans une fonction que vous n'appelez pas, le temps d'exécution de la fonction est le double de la fonction sans fonction imbriquée. Je pense qu'il faut être prudent lors de l'utilisation de fonctions imbriquées.

Le fichier Python pour l'ensemble du test qui génère cette sortie se trouve sur ideone .

miracle173
la source
5
La comparaison que vous faites n'est pas vraiment utile. C'est comme ajouter des instructions factices dans la fonction et dire que c'est plus lent. Exemple de martineau utilise en fait les fonctions encapsulées et je ne remarque aucune différence de performances en exécutant son exemple.
kon psych
-1 veuillez relire la question. Bien que vous puissiez dire que c'est peut-être un peu plus lent, il a demandé si c'était une bonne idée de le faire, pas principalement à cause de la performance, mais de la pratique générale.
Mayou36
@ Mayou36 désolé, trois ans après que la question et la réponse aient été publiées, je ne le ferai pas.
miracle173 du
Ok, malheureusement, il n'y a toujours pas de réponse acceptée (ou bonne) disponible.
Mayou36
4

C'est juste un principe sur les API d'exposition.

En utilisant python, c'est une bonne idée d'éviter l'exposition des API dans l'espace (module ou classe), la fonction est un bon endroit d'encapsulation.

Cela pourrait être une bonne idée. quand tu t'assures

  1. la fonction interne est UNIQUEMENT utilisée par la fonction externe.
  2. La fonction d'initié a un bon nom pour expliquer son but parce que le code parle.
  3. le code ne peut pas être compris directement par vos collègues (ou un autre lecteur de code).

Même si, une utilisation abusive de cette technique peut causer des problèmes et implique un défaut de conception.

Juste à partir de mon exp, peut-être mal compris votre question.

chao787
la source
4

Donc, en fin de compte, il s'agit en grande partie de savoir à quel point l'implémentation de python est intelligente ou non, en particulier dans le cas où la fonction interne n'est pas une fermeture mais simplement une aide nécessaire uniquement.

Dans une conception claire et compréhensible, avoir des fonctions uniquement là où elles sont nécessaires et non exposées ailleurs est une bonne conception, qu'elles soient intégrées dans un module, une classe en tant que méthode, ou dans une autre fonction ou méthode. Lorsqu'elles sont bien faites, elles améliorent vraiment la clarté du code.

Et lorsque la fonction interne est une fermeture, cela peut également aider un peu à la clarté, même si cette fonction n'est pas renvoyée par la fonction conteneur pour une utilisation ailleurs.

Je dirais donc que généralement, utilisez-les, mais soyez conscient de l'impact possible sur les performances lorsque vous êtes réellement préoccupé par les performances et ne les supprimez que si vous effectuez un profilage réel qui montre qu'il vaut mieux les supprimer.

Ne faites pas d'optimisation prématurée en utilisant simplement des «fonctions internes BAD» dans tout le code python que vous écrivez. S'il vous plaît.


la source
Comme le montrent d'autres réponses, vous n'avez pas vraiment de succès de performance.
Mayou36
1

C'est parfaitement normal de le faire de cette façon, mais à moins que vous n'ayez besoin d'utiliser une fermeture ou de renvoyer la fonction, je mettrais probablement au niveau du module. J'imagine que dans le deuxième exemple de code, vous voulez dire:

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

sinon, some_data sera la fonction.

L'avoir au niveau du module permettra à d'autres fonctions d'utiliser method_b () et si vous utilisez quelque chose comme Sphinx (et autodoc) pour la documentation, cela vous permettra également de documenter method_b.

Vous pouvez également envisager de simplement placer la fonctionnalité dans deux méthodes dans une classe si vous faites quelque chose qui peut être représentable par un objet. Cela contient également de la logique si c'est tout ce que vous recherchez.

mikelikespie
la source
1

Faites quelque chose comme:

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

si vous deviez l'exécuter, some_function()il fonctionnerait alorssome_other_function() et renvoie 42.

EDIT: J'ai initialement déclaré que vous ne devriez pas définir une fonction à l'intérieur d'une autre, mais il a été souligné qu'il est parfois pratique de le faire.

mdlp0716
la source
J'apprécie les efforts que ceux ci-dessus ont déployés pour obtenir leurs réponses, mais la vôtre était directe et pertinente. Agréable.
C0NFUS3D
1
Pourquoi? Pourquoi ne devriez-vous pas faire cela? N'est-ce pas une excellente encapsulation? Il me manque des arguments. -1
Mayou36
@ Mayou36 Je ne savais pas ce qu'était l'encapsulation au moment de la rédaction de mon commentaire, et je ne sais pas non plus ce que c'est maintenant. Je pensais juste que ce n'était pas bon à faire. Pouvez-vous expliquer pourquoi il serait avantageux de définir une fonction à l'intérieur d'une autre, au lieu de la définir simplement à l'extérieur?
mdlp0716
2
Oui je peux. Vous pouvez rechercher le concept d'encapsulation, mais en bref: cachez les informations qui ne sont pas nécessaires et n'exposez à l'utilisateur que ce qu'il faut savoir. Ce qui signifie que la définition de some_other_function à l'extérieur ajoute simplement quelque chose de plus dans l'espace de noms qui est en fait étroitement lié à la première fonction. Ou pensez en termes de variables: pourquoi avez-vous besoin de variables locales par rapport à des variables globales? Définir toutes les variables à l'intérieur d'une fonction, si possible, est bien meilleur que d'utiliser des variables globales pour les variables utilisées uniquement dans cette fonction . Il s'agit de réduire la complexité à la fin.
Mayou36
0

Vous pouvez l'utiliser pour éviter de définir des variables globales. Cela vous donne une alternative aux autres modèles. 3 designs présentant une solution à un problème.

A) Utilisation de fonctions sans globaux

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) Utilisation de fonctions avec des globaux

_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) Utilisation de fonctions dans une autre fonction

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 solution C) permet d'utiliser des variables dans le cadre de la fonction externe sans avoir besoin de les déclarer dans la fonction interne. Peut être utile dans certaines situations.

Elmex80s
la source
0

Fonction In function 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))
user12069064
la source