web-dev-qa-db-fra.com

Si la fonction A n'est requise que par la fonction B, faut-il définir A dans B?

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 def dans une autre def. Donc, si method_b est requis et appelé uniquement à partir de method_a, dois-je déclarer method_b dans method_a? comme ça :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b

Ou devrais-je éviter de faire cela?

127
nukl
>>> 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? C'est ce qu'on appelle une fermeture .

130
user225312

Cela ne vous rapporte pas grand-chose, en fait, cela ralentit method_a car il définira et recompilera l’autre fonction chaque fois qu’elle est appelée. Dans ces conditions, il serait probablement préférable de préfixer le nom de la fonction par un trait de soulignement pour indiquer qu’il s’agit d’une méthode privée - c’est-à-dire _method_b.

Je suppose que vous pourriez vouloir faire cela si la définition de la fonction imbriquée a changé à chaque fois pour une raison quelconque, mais cela peut indiquer une faille dans votre conception. Cela dit, il y a est une raison valable de permettre à la fonction imbriquée d'utiliser des arguments transmis à la fonction externe mais non explicitement, ce qui se produit parfois lors de l'écriture de décorateurs de fonctions, par exemple. . C'est ce qui est indiqué dans la réponse acceptée bien qu'un décorateur ne soit ni défini ni utilisé.

Mettre à jour:

Voici la preuve que leur imbrication est plus lente (avec Python 3.6.1), bien que cela ne soit pas 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é des arguments self à vos exemples de fonctions pour les rendre plus vrais que méthodes (bien que method_b2 ne soit toujours pas techniquement une méthode de la classe Test). De plus, la fonction imbriquée est appelée dans cette version, contrairement à la vôtre.

43
martineau

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

(Il y a un beaucoup de querelles sur quoi exactement fait une fermeture est une fermeture .

Voici un exemple utilisant le sum() intégré. Il définit start une fois et l'utilise ensuite:

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 python intégrée

functools.partial est un exemple de fermeture.

D'après les documents 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

(Merci à @ user225312 ci-dessous pour la réponse. Je trouve cet exemple plus facile à comprendre et, espérons-le, aidera à répondre au commentaire de @ mango.)

24
CivFan

C'est 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.

10
vz0

J'ai trouvé cette question parce que je voulais poser la question de savoir pourquoi il y a un impact sur les performances si l'on utilise des fonctions imbriquées. J'ai exécuté des tests pour les fonctions suivantes à l'aide de Python 3.2.5 sur un ordinateur portable Windows doté d'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 suivants, é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]
>>> 

square0 n'a pas de fonction imbriquée, square1 a une fonction imbriquée, square2 a deux fonctions imbriquées et square5 a cinq fonctions imbriquées. Les fonctions imbriquées sont seulement déclarées mais non 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 correspond au double de la fonction sans fonction imbriquée. Je pense que devrait être prudent lors de l'utilisation de fonctions imbriquées.

Le fichier Python de l’ensemble du test qui génère cette sortie se trouve à l’adresse ideone .

7
miracle173

En fin de compte, la question de savoir si l’implémentation de python est intelligente, en particulier dans le cas où la fonction interne ne constitue pas une fermeture, mais simplement une aide dans la fonction requise uniquement. 

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

Et lorsque la fonction interne est une fermeture, elle peut également aider un peu avec la clarté même si cette fonction n’est pas renvoyée de la fonction conteneur pour une utilisation ultérieure. 

Je dirais donc que, dans l’ensemble, vous les utilisez, mais vous devez être conscient des conséquences négatives sur les performances lorsque vous êtes réellement préoccupé par les performances, et ne les supprimez que si vous effectuez un profilage qui montre qu’il vaut mieux les supprimer. 

Ne faites pas l'optimisation prématurée de simplement utiliser "les fonctions internes BAD" dans tout le code python que vous écrivez. S'il vous plaît. 

4
user1969453

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

En utilisant python, il est judicieux d’éviter les API d’exposition dans l’espace (module ou classe), la fonction est un bon lieu d’encapsulation. 

Cela pourrait être une bonne idée. quand vous vous assurez 

  1. fonction interne estUNIQUEMENTutilisé par la fonction externe. 
  2. fonction d'initié a un bon nom pour expliquer son objectif car 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 l'abus de cette technique peut causer des problèmes et implique un défaut de conception. 

Juste de mon exp, peut-être mal comprendre votre question. 

4
chao787

la réponse de mdlp n'a pas fonctionné pour moi.

Cela a:

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

print some_function()
1
colin382

C'est parfaitement correct de le faire de cette façon, mais à moins que vous n'ayez besoin d'utiliser une fermeture ou de renvoyer la fonction que 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, certaines_données seront la fonction.

Le fait de 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 d'une classe si vous faites quelque chose qui peut être représenté par un objet. Cela contient aussi bien la logique si c'est tout ce que vous recherchez.

1
mikelikespie

Faites quelque chose comme:

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

si vous exécutiez some_function(), il exécuterait alors some_other_function() et renverrait 42.

EDIT: Au départ, j'avais déclaré qu'il ne fallait pas définir une fonction à l'intérieur d'une autre, mais il a été souligné qu'il est pratique de le faire parfois.

1
mdlp0716

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

A) Utiliser des fonctions sans globales

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 globals

_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) Utiliser des 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 l'étendue de la fonction externe sans avoir à les déclarer dans la fonction interne Pourrait être utile dans certaines situations. 

0
Elmex80s