Si la función A es requerida solo por la función B, ¿debería A definirse dentro de B? [cerrado]

147

Ejemplo simple. Dos métodos, uno llamado desde otro:

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

def method_b(arg):
    return some_data

En Python podemos declarar defdentro de otro def. Entonces, si method_bse requiere y se llama solo desde method_a, ¿debo declarar method_bdentro method_a? Me gusta esto :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

¿O debería evitar hacer esto?

nukl
fuente
77
No debería necesitar definir una función dentro de otra a menos que esté haciendo algo REALMENTE original. Sin embargo, explique lo que está tratando de hacer, para que podamos brindarle una respuesta más útil
inspectorG4dget
66
¿Te das cuenta de que el segundo ejemplo es diferente porque no llamas method_b ? (@inspector: lo necesita, estrictamente hablando, pero es inmensamente útil cuando entra en un poco de programación funcional, en particular cierres).
3
@delnan: Creo que quisiste decir "No es necesario, estrictamente hablando, pero ..."
Martineau
44
Los casos de uso para funciones internas se resumen maravillosamente en el enlace: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Si su uso no se ajusta a ninguno de los casos, mejor evítelo.
1
Muy buena pregunta, pero hay una respuesta real como parece ...
Mayou36

Respuestas:

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

Esto es lo que estabas buscando? Se llama cierre .

usuario225312
fuente
14
Esta es una explicación mucho mejor. He eliminado mi respuesta
pyfunc
¿por qué no simplemente hacer def sum (x, y): return x + y?
mango
44
@mango: Este es solo un ejemplo de juguete para transmitir el concepto: en el uso real, lo que do_it()hace probablemente sería un poco más complicado de lo que puede ser manejado por alguna aritmética en una sola returndeclaración.
Martineau
2
@mango Respondió su pregunta con un ejemplo un poco más útil. stackoverflow.com/a/24090940/2125392
CivFan
10
No responde la pregunta.
Hunsu
49

Realmente no ganas mucho haciendo esto, de hecho se ralentiza method_aporque definirá y recompilará la otra función cada vez que se llame. Dado eso, probablemente sería mejor simplemente prefijar el nombre de la función con guión bajo para indicar que es un método privado, es decir _method_b.

Supongo que es posible que desee hacer esto si la definición de la función anidada varía cada vez por alguna razón, pero eso puede indicar una falla en su diseño. Dicho esto, no es una razón válida para hacerlo para permitir la función anidada utilizar argumentos que se pasan a la función exterior, pero no pasa explícitamente a ellos, que a veces ocurre cuando se escribe decoradores de función, por ejemplo. Es lo que se muestra en la respuesta aceptada, aunque un decorador no se define ni se utiliza.

Actualizar:

Aquí hay pruebas de que anidarlos es más lento (usando Python 3.6.1), aunque ciertamente no es mucho en este caso 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

Tenga en cuenta que agregué algunos selfargumentos a sus funciones de muestra para que sean más como métodos reales (aunque method_b2técnicamente todavía no es un método deTest clase). También la función anidada en realidad se llama en esa versión, a diferencia de la suya.

Martineau
fuente
21
En realidad, no compila completamente la función interna cada vez que se llama a la función externa, aunque tiene que crear un objeto de función, lo que lleva un poco de tiempo. Por otro lado, el nombre de la función se convierte en local en lugar de global, por lo que es más rápido llamar a la función. En mis pruebas, en el tiempo es básicamente un lavado la mayor parte del tiempo; incluso puede ser más rápido con la función interna si la llamas muchas veces.
poco
77
Sí, necesitarás múltiples llamadas a la función interna. Si lo está llamando en un bucle, o solo más de un puñado de veces, el beneficio de tener un nombre local para la función comenzará a superar el costo de crear la función. En mis pruebas, esto sucede cuando llamas a la función interna unas 3-4 veces. Por supuesto, podría obtener el mismo beneficio (sin casi el mismo costo) definiendo un nombre local para la función, por ejemplo, method_b = self._method_by luego llamar method_bpara evitar las búsquedas repetidas de atributos. (Sucede que he estado haciendo MUCHO tiempo últimamente. :)
poco el
3
@kindall: Sí, eso es cierto. Modifiqué mi prueba de tiempo para que hiciera 30 llamadas a la función secundaria y los resultados cambiaron. Eliminaré mi respuesta después de que haya tenido la oportunidad de ver esta respuesta. Gracias por la iluminación.
Martineau
2
Solo quería explicar mi -1 porque esta es una respuesta informativa: le di un -1 porque se enfoca en una diferencia de rendimiento trivial (en la mayoría de los escenarios, la creación del objeto de código solo tomará una fracción del tiempo de ejecución de la función ) Lo que es importante tener en cuenta en estos casos es si tener una función en línea puede mejorar la legibilidad y la facilidad de mantenimiento del código, lo que creo que a menudo sucede porque no tiene que buscar / desplazarse por los archivos para encontrar el código relevante.
Blixt
2
@Blixt: Creo que su lógica es defectuosa (y voto negativo injusto) porque es extremadamente improbable que otro método de la misma clase esté muy "lejos" de otro en la misma clase, incluso cuando no está anidado (y extremadamente improbable) estar en un archivo diferente). Además, la primera oración en mi respuesta es "Realmente no ganas mucho haciendo esto", lo que señala que es una diferencia trivial.
Martineau
27

Una función dentro de una función se usa comúnmente para cierres .

(Existe una gran controversia sobre qué es exactamente lo que hace que un cierre sea un cierre ).

Aquí hay un ejemplo usando el incorporado sum(). Define startuna vez y lo usa a partir de entonces:

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

En 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

Cierre de pitón incorporado

functools.partial Es un ejemplo de cierre.

De los documentos de Python , es más o menos 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

(Felicitaciones a @ user225312 a continuación para obtener la respuesta. Este ejemplo me resulta más fácil de entender y espero que ayude a responder el comentario de @ mango).

CivFan
fuente
-1 sí, se usan comúnmente como cierres. Ahora relee la pregunta. Básicamente preguntó si el concepto que muestra puede usarse para el caso b. Decirle que esto se usa a menudo para el caso a no es una mala respuesta, pero es incorrecta para esta pregunta. Le interesa saber si es bueno hacer lo anterior para la encapsulación, por ejemplo.
Mayou36
@ Mayou36 Para ser justos, la pregunta es bastante abierta, en lugar de tratar de responder a todos los casos posibles, pensé que era mejor concentrarse en uno. Además, la pregunta no es muy clara. Por ejemplo, implica el cierre en el segundo ejemplo, aunque probablemente eso no sea lo que se quiso decir.
CivFan
@ Mayou36 "Básicamente preguntó si el concepto que muestra puede usarse para el caso b". No, la pregunta pregunta si debería usarse. OP ya sabe que se puede usar.
CivFan
1
bueno, "si se requiere método_b y se llama solo desde método_a, ¿debería declarar método_b dentro de método_a?" es bastante claro y no tiene nada que ver con los cierres, ¿verdad? Sí, estoy de acuerdo, usé can en la forma de debería . Pero eso no tiene que ver con los cierres ... Estoy sorprendido de que tantos respondan sobre los cierres y cómo usarlos cuando el OP hizo una pregunta completamente diferente.
Mayou36
@ Mayou36 Podría ser útil reformular la pregunta como la ve, y abrir otra pregunta para abordarla.
CivFan
17

Generalmente, no, no defina funciones dentro de funciones.

A menos que tenga una muy buena razón. Lo cual no tienes.

Por qué no?

¿Cuál es una buena razón para definir funciones dentro de funciones?

Cuando lo que realmente quieres es un cierre dingdang .

CivFan
fuente
1
Esta respuesta es para ti @ Mayou36.
CivFan
2
sí, gracias, esta es la respuesta que estaba buscando. Esto responde la pregunta de la mejor manera posible. Aunque no dice estrictamente uno u otro, desalienta las definiciones en línea al exponer razones. ¡Así debería ser una respuesta (pitónica :))!
Mayou36
10

De hecho, está bien declarar una función dentro de otra. Esto es especialmente útil para crear decoradores.

Sin embargo, como regla general, si la función es compleja (más de 10 líneas), podría ser una mejor idea declararla en el nivel del módulo.

vz0
fuente
2
Es posible, pero estoy de acuerdo, necesitas una buena razón para hacerlo. Sería más python usar un guión bajo anterior para una función que estaba destinada a usarse solo dentro de su clase.
chmullig
3
Dentro de una clase, sí, pero ¿qué pasa solo dentro de una función ? La encapsulación es jerárquica.
Paul Draper
@PaulDraper "La encapsulación es jerárquica" - ¡No! ¿Quién dice eso? La encapsulación es un principio mucho más amplio que solo una herencia.
Mayou36
7

Encontré esta pregunta porque quería plantear una pregunta por qué hay un impacto en el rendimiento si uno usa funciones anidadas. Ejecuté pruebas para las siguientes funciones usando Python 3.2.5 en un portátil con Windows con un procesador Intel Core 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

Medí las siguientes 20 veces, también para square1, square2 y square5:

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

y obtuve los siguientes resultados

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

square0no tiene función anidada, square1tiene una función anidada, square2tiene dos funciones anidadas ysquare5 tiene cinco funciones anidadas. Las funciones anidadas solo se declaran pero no se llaman.

Entonces, si ha definido 5 funciones anidadas en una función que no llama, entonces el tiempo de ejecución de la función es el doble de la función sin una función anidada. Creo que debería ser cauteloso al usar funciones anidadas.

El archivo Python para toda la prueba que genera esta salida se puede encontrar en ideone .

milagro173
fuente
55
La comparación que haces no es realmente útil. Es como agregar declaraciones ficticias en la función y decir que es más lento. Ejemplo de martineau en realidad usa las funciones encapsuladas y no noto ninguna diferencia de rendimiento al ejecutar su ejemplo.
kon psych
-1 por favor relee la pregunta. Aunque se puede decir que tal vez sea un poco más lento, preguntó si es una buena idea hacerlo, no principalmente por el rendimiento sino por la práctica general.
Mayou36
@ Mayou36 lo siento, tres años después de que se publicara la pregunta y la respuesta, no lo haré.
milagro173
Ok, desafortunadamente, todavía no hay una respuesta aceptada (o buena) disponible.
Mayou36
4

Es solo un principio sobre las API de exposición.

Usando python, es una buena idea evitar la exposición API en el espacio exterior (módulo o clase), la función es un buen lugar de encapsulación.

Podría ser una buena idea cuando te aseguras

  1. La función interna SOLO es utilizada por la función externa.
  2. la función interna tiene un buen nombre para explicar su propósito porque el código habla.
  3. sus colegas (u otro lector de códigos) no pueden entender el código directamente.

Aunque, el abuso de esta técnica puede causar problemas e implica un defecto de diseño.

Solo desde mi exp. Quizás malinterprete su pregunta.

chao787
fuente
4

Entonces, al final, se trata en gran medida de cuán inteligente es o no la implementación de Python, particularmente en el caso de que la función interna no sea un cierre, sino simplemente un ayudante necesario en función.

En un diseño limpio y comprensible, tener funciones solo donde se necesitan y no estar expuestas en otro lugar es un buen diseño, ya sea que estén integradas en un módulo, una clase como método o dentro de otra función o método. Cuando se hace bien, realmente mejoran la claridad del código.

Y cuando la función interna es un cierre que también puede ayudar con bastante claridad, incluso si esa función no se devuelve de la función de contención para su uso en otro lugar.

Por lo tanto, diría que generalmente los usa, pero tenga en cuenta el posible impacto en el rendimiento cuando realmente le preocupa el rendimiento y solo elimínelos si realiza un perfil real que muestre que es mejor eliminarlos.

No haga una optimización prematura simplemente usando "funciones internas MALAS" en todo el código de Python que escriba. Por favor.


fuente
Como se muestra en otras respuestas, en realidad no tienes resultados de rendimiento.
Mayou36
1

Está perfectamente bien hacerlo de esa manera, pero a menos que necesite usar un cierre o devolver la función, probablemente lo pondría en el nivel del módulo. Me imagino que en el segundo ejemplo de código quieres decir:

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

de lo contrario, some_data será la función.

Tenerlo en el nivel del módulo permitirá que otras funciones usen method_b () y si está usando algo como Sphinx (y autodoc) para la documentación, también le permitirá documentar method_b.

También es posible que desee considerar simplemente poner la funcionalidad en dos métodos en una clase si está haciendo algo que pueda ser representable por un objeto. Esto también contiene lógica si eso es todo lo que estás buscando.

mikelikespie
fuente
1

Haz algo como:

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

si tuvieras que correr some_function(), entonces correríasome_other_function() y devolvería 42.

EDITAR: Originalmente dije que no deberías definir una función dentro de otra, pero se señaló que a veces es práctico hacerlo.

mdlp0716
fuente
Aprecio el esfuerzo que los que están arriba de ustedes han puesto en sus respuestas, pero el suyo fue directo y al grano. Agradable.
C0NFUS3D
1
¿Por qué? ¿Por qué no deberías hacer eso? ¿No es una gran encapsulación? Me faltan argumentos. -1
Mayou36
@ Mayou36 No sabía qué era la encapsulación en el momento de escribir mi comentario, ni sé realmente qué es ahora. Solo pensé que no era bueno hacerlo. ¿Puede explicar por qué sería beneficioso definir una función dentro de otra, en lugar de solo definirla fuera de ella?
mdlp0716
2
Sí, puedo. Puede buscar el concepto de encapsulación, pero en resumen: oculte la información que no es necesaria y solo exponga al usuario lo que necesita saber. Lo que significa que definir algo_otra_función afuera simplemente agrega algo más al espacio de nombres que en realidad está estrechamente vinculado a la primera función. O piense en términos de variables: ¿por qué necesita variables locales versus variables globales? Definir todas las variables dentro de una función, si es posible, es mucho mejor que usar variables globales para las variables que solo se usan dentro de esta función . Se trata de reducir la complejidad al final.
Mayou36
0

Puede usarlo para evitar definir variables globales. Esto le brinda una alternativa para otros diseños. 3 diseños que presentan una solución a un problema.

A) Usar funciones sin globals

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) Usar funciones con globales

_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) Usar funciones dentro de otra función

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 solución C) permite utilizar variables en el ámbito de la función externa sin tener la necesidad de declararlas en la función interna. Puede ser útil en algunas situaciones.

Elmex80s
fuente
0

Función en la función 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))
usuario12069064
fuente