Se a função A é requerida apenas pela função B, A deve ser definido dentro de B? [fechadas]

147

Exemplo simples. Dois métodos, um chamado de outro:

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

def method_b(arg):
    return some_data

Em Python, podemos declarar defdentro de outro def. Então, se method_bfor necessário e chamado apenas de method_a, devo declarar method_bdentro method_a? como isso :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Ou devo evitar fazer isso?

nukl
fonte
7
Você não precisa definir uma função dentro de outra, a menos que esteja fazendo algo REALMENTE descolado. No entanto, por favor elaborar sobre o que você está tentando fazer, para que possamos fornecer uma resposta mais útil
inspectorG4dget
6
Você percebe que o segundo exemplo é diferente, porque você não liga method_b ? (@inspector: Você precisa, estritamente falando, mas é imensamente útil quando você entra em um pouco de programação funcional, em especial fechamentos).
3
@delnan: Eu acho que você quis dizer "Você não precisa, estritamente falando, mas ..."
martineau
4
Os casos de uso para funções internas estão maravilhosamente resumidos no link: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Se o seu uso não se encaixar em nenhum dos casos, é melhor evitá-lo.
1
Ótima pergunta, mas nenhuma resposta real como parece ...
Mayou36

Respostas:

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

Não era isso que você estava procurando? É chamado de fechamento .

user225312
fonte
14
Esta é uma explicação muito melhor. Eu apaguei minha resposta
pyfunc
por que não fazer def sum (x, y): retornar x + y?
manga
4
@ manga: Este é apenas um exemplo de brinquedo para transmitir o conceito - em uso real, o do_it()que presumivelmente seria um pouco mais complicado do que o que pode ser tratado por alguma aritmética em uma única returndeclaração.
martineau 02/09
2
@mango Respondeu sua pergunta com um exemplo um pouco mais útil. stackoverflow.com/a/24090940/2125392
CivFan
10
Não responde à pergunta.
Hunsu
49

Você não ganha muito fazendo isso, na verdade, fica mais lento method_aporque define e recompila a outra função toda vez que é chamada. Dado isso, provavelmente seria melhor prefixar o nome da função com sublinhado para indicar que é um método privado - ou seja _method_b.

Eu suponho que você pode querer fazer isso se a definição da função aninhada variado cada vez, por algum motivo, mas isso pode indicar uma falha em seu projeto. Dito isto, não é uma razão válida para fazer isso para permitir que a função aninhada de usar argumentos que foram passados para a função externa, mas não passaram explicitamente a eles, o que por vezes ocorre quando se escreve decoradores de função, por exemplo. É o que está sendo mostrado na resposta aceita, embora um decorador não esteja sendo definido ou usado.

Atualizar:

Aqui está a prova de que o aninhamento é mais lento (usando o Python 3.6.1), embora, reconhecidamente, não seja muito neste 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

Observe que adicionei alguns selfargumentos às suas funções de amostra para torná-las mais parecidas com métodos reais (embora method_b2ainda não seja tecnicamente um método da Testclasse). Além disso, a função aninhada é realmente chamada nessa versão, diferente da sua.

Martineau
fonte
21
Na verdade, ele não compila totalmente a função interna toda vez que a função externa é chamada, embora precise criar um objeto de função, o que leva um pouco de tempo. Por outro lado, o nome da função se torna local e global, por isso é mais rápido chamar a função. Nos meus ensaios, em termos de tempo, é basicamente uma lavagem na maior parte do tempo; pode até ser mais rápido com a função interna, se você a chamar várias vezes.
Kindall
7
Sim, você precisará de várias chamadas para a função interna. Se você está chamando isso em um loop, ou apenas mais do que algumas vezes, o benefício de ter um nome local para a função começará a superar o custo de criação da função. Nos meus ensaios, isso acontece quando você chama a função interna cerca de 3-4 vezes. Obviamente, você pode obter o mesmo benefício (sem quase o mesmo custo) definindo um nome local para a função, por exemplo, method_b = self._method_be depois ligue method_bpara evitar as repetidas pesquisas de atributo. (Acontece que eu tenho feito MUITO tempo ultimamente. :)
kindall
3
@kindall: Sim, é verdade. Modifiquei meu teste de temporização para fazer 30 chamadas para a função secundária e os resultados foram alterados. Excluirei minha resposta depois que você tiver a chance de ver essa resposta. Obrigado pela iluminação.
27511 martineau
2
Só queria explicar meu -1 porque, apesar disso, é uma resposta informativa: dei -1 como um porque coloca o foco em uma diferença de desempenho trivial (na maioria dos cenários, a criação do objeto de código leva apenas uma fração do tempo de execução da função ) O que é importante considerar nesses casos é se ter uma função embutida pode melhorar a legibilidade e a manutenção do código, o que acho que costuma acontecer porque você não precisa pesquisar / rolar arquivos para encontrar o código relevante.
Blixt
2
@Blixt: Eu acho que sua lógica é falha (e com voto negativo injusto) porque é extremamente improvável que outro método da mesma classe esteja muito "longe" de outro da mesma classe, mesmo quando não está aninhado (e extremamente improvável) estar em um arquivo diferente). Também a primeira frase da minha resposta é "Você realmente não ganha muito fazendo isso", o que indica que é uma diferença trivial.
9788 martineau #
27

Uma função dentro de uma função é comumente usada para fechamentos .

(Há muita controvérsia sobre o que exatamente faz de um fechamento um fechamento .)

Aqui está um exemplo usando o built-in sum(). Ele define startuma vez e o utiliza a partir de então:

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

Em 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

Fechamento python embutido

functools.partial é um exemplo de fechamento.

Nos documentos python , é aproximadamente 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

(Parabéns a @ user225312 abaixo para obter a resposta. Acho este exemplo mais fácil de descobrir e espero ajudar a responder o comentário de @ mango.)

CivFan
fonte
-1 Sim, eles são comumente usados ​​como fechamento. Agora releia a pergunta. Ele basicamente perguntou se o conceito que ele mostra pode ser usado no caso b. Dizer a ele que isso costuma ser usado no caso a não é uma resposta ruim, mas a errada para essa pergunta. Ele está interessado em saber se é bom fazer o acima para encapsulamento, por exemplo.
Mayou36
@ Mayou36 Para ser justo, a pergunta é bastante aberta - em vez de tentar responder a todos os casos possíveis, achei melhor focar em um. Além disso, a questão não é muito clara. Por exemplo, isso implica fechamento no segundo exemplo, mesmo que não seja o que isso significava.
CivFan
@ Mayou36 "Ele basicamente perguntou se o conceito que ele mostra pode ser usado no caso b". Não, a pergunta pergunta se deve ser usada. O OP já sabe que pode ser usado.
26417 CivFan
1
bem, "se method_b for necessário e chamado apenas de method_a, devo declarar method_b dentro de method_a?" é bem claro e não tem nada a ver com fechamentos, certo? Sim, eu concordo, eu usei lata da maneira que deveria . Mas isso não tem a ver com fechamentos ... Estou surpreso que muitos respondam sobre fechamentos e como usá-los quando o OP fez uma pergunta completamente diferente.
Mayou36
@ Mayou36 Pode ajudar a reformular a pergunta como você a vê e abrir outra pergunta para resolvê-la.
26517 CivFan
17

Geralmente, não, não define funções dentro de funções.

A menos que você tenha uma boa razão. O que você não faz.

Por que não?

Qual é realmente uma boa razão para definir funções dentro de funções?

Quando o que você realmente quer é um fechamento bobo .

CivFan
fonte
1
Esta resposta é para você @ Mayou36.
precisa saber é o seguinte
2
Sim, obrigado, esta é a resposta que eu estava procurando. Isso responde à pergunta da melhor maneira possível. Embora não diga estritamente um ou outro, desencoraja bastante as definições em linha, indicando os motivos. É assim que uma resposta (pythonic :)) deve ser!
usar o seguinte código
10

É realmente bom declarar uma função dentro de outra. Isso é especialmente útil para criar decoradores.

No entanto, como regra geral, se a função for complexa (mais de 10 linhas), pode ser uma ideia melhor declará-la no nível do módulo.

vz0
fonte
2
É possível, mas eu concordo, você precisa de um bom motivo para fazê-lo. Seria mais python usar um sublinhado anterior para uma função que se destinava a ser usada apenas dentro da sua classe.
chmullig
3
Dentro de uma classe, sim, mas e dentro de uma função ? O encapsulamento é hierárquico.
Paul Draper
@PaulDraper "O encapsulamento é hierárquico" - Não! Quem diz isso? O encapsulamento é um princípio muito mais amplo do que apenas uma herança.
Mayou36
7

Eu encontrei essa pergunta porque queria fazer uma pergunta por que há um impacto no desempenho se alguém usa funções aninhadas. Eu executei testes para as seguintes funções usando o Python 3.2.5 em um notebook Windows com um processador Intel Core i5-2530M de 2,5 GHz e quatro núcleos

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

Eu medi as seguintes 20 vezes, também para quadrado1, quadrado2 e quadrado5:

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

e obteve os seguintes 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]
>>> 

square0não possui função aninhada, square1possui uma função aninhada, square2possui duas funções aninhadas e square5possui cinco funções aninhadas. As funções aninhadas são declaradas apenas, mas não chamadas.

Portanto, se você definiu 5 funções aninhadas em uma função que você não chama, o tempo de execução da função é o dobro da função sem uma função aninhada. Eu acho que deve ser cauteloso ao usar funções aninhadas.

O arquivo Python para todo o teste que gera essa saída pode ser encontrado em ideone .

miracle173
fonte
5
A comparação que você faz não é realmente útil. É como adicionar instruções fictícias à função e dizer que é mais lento. O exemplo de martineau realmente usa as funções encapsuladas e não percebo nenhuma diferença de desempenho executando seu exemplo.
Kon psych
-1, releia a pergunta. Embora você possa dizer que talvez seja um pouco mais lento, ele perguntou se é uma boa ideia fazer isso, não principalmente por causa do desempenho, mas da prática geral.
Mayou36
@ Mayou36 desculpe, três anos após a pergunta e a resposta terem sido postadas, não vou.
miracle173
Ok, infelizmente, ainda não há uma resposta aceita (ou boa) disponível.
Mayou36
4

É apenas um princípio sobre APIs de exposição.

Usando python, é uma boa idéia evitar a API de exposição no espaço sideral (módulo ou classe), a função é um bom local de encapsulamento.

Poderia ser uma boa ideia. quando você garantir

  1. a função interna é usada SOMENTE pela função externa.
  2. A função insider tem um bom nome para explicar sua finalidade, porque o código fala.
  3. o código não pode ser entendido diretamente por seus colegas (ou outro leitor de código).

Mesmo assim, o abuso dessa técnica pode causar problemas e implica uma falha de design.

Apenas da minha exp, talvez não entenda sua pergunta.

chao787
fonte
4

Portanto, no final, é em grande parte uma questão sobre o quão inteligente é ou não a implementação do python, particularmente no caso da função interna não ser um fechamento, mas simplesmente uma função in necessária apenas para auxiliar.

No design compreensível e limpo, com funções apenas onde são necessárias e não expostas a outros lugares, é bom design, sejam elas incorporadas a um módulo, uma classe como método ou dentro de outra função ou método. Quando bem feitos, eles realmente melhoram a clareza do código.

E quando a função interna é um fechamento, também pode ajudar bastante com clareza, mesmo que essa função não seja retornada da função que o contém para uso em outros lugares.

Então, eu diria que geralmente os utiliza, mas esteja ciente do possível impacto no desempenho quando você estiver realmente preocupado com o desempenho e os remova somente se você criar um perfil real que mostre que é melhor removê-los.

Não faça otimização prematura usando apenas "funções internas BAD" em todo o código python que você escreve. Por favor.


fonte
Conforme mostrado por outras respostas, você realmente não tem hits de desempenho.
Mayou36
1

Não há problema em fazê-lo dessa maneira, mas, a menos que você precise usar um fechamento ou retornar a função que eu provavelmente colocaria no nível do módulo. Eu imagino no segundo exemplo de código que você quer dizer:

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

caso contrário, some_data será a função.

Tê-lo no nível do módulo permitirá que outras funções usem method_b () e se você estiver usando algo como Sphinx (e autodoc) para documentação, permitirá que você documente também method_b.

Você também pode considerar apenas colocar a funcionalidade em dois métodos em uma classe se estiver fazendo algo que possa ser representável por um objeto. Isso também contém lógica, se é tudo o que você procura.

mikelikespie
fonte
1

Faça algo como:

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

se você executasse, some_function()ele executaria some_other_function()e retornaria 42.

Edição: Eu originalmente afirmei que você não deve definir uma função dentro de outra, mas foi apontado que é prático fazer isso algumas vezes.

mdlp0716
fonte
Agradeço o esforço daqueles que você colocou em suas respostas, mas a sua foi direta e direta. Agradável.
C0NFUS3D
1
Por quê? Por que você não deveria fazer isso? Não é um grande encapsulamento? Faltam argumentos. -1
Mayou36
@ Mayou36 Eu não sabia o que era o encapsulamento no momento em que escrevi meu comentário, nem sei realmente o que é agora. Eu apenas pensei que não é bom fazer. Você pode explicar por que seria benéfico definir uma função dentro de outra, em vez de apenas defini-la fora dela?
Mdlp0716
2
Sim eu posso. Você pode procurar o conceito de encapsulamento, mas resumindo: oculte as informações que não são necessárias e exponha apenas ao usuário o que é necessário saber. O que significa que a definição de alguma outra função fora adiciona algo mais ao espaço de nome, que na verdade está fortemente ligado à primeira função. Ou pense em termos de variáveis: por que você precisa de variáveis ​​locais versus variáveis ​​globais? Definir todas as variáveis ​​dentro de uma função, se possível, é muito melhor do que usar variáveis ​​globais para variáveis usadas somente dentro dessa função . É uma questão de reduzir a complexidade no final.
usar o seguinte código
0

Você pode usá-lo para evitar a definição de variáveis ​​globais. Isso oferece uma alternativa para outros designs. 3 projetos apresentando uma solução para um problema.

A) Usando funções sem globais

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) Usando funções com globais

_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) Usando funções dentro de outra função

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

A solução C) permite usar variáveis ​​no escopo da função externa sem a necessidade de declará-las na função interna. Pode ser útil em algumas situações.

Elmex80s
fonte
0

Função Na função 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
fonte