2015-01-06 3 views
9

Общий вопрос для тех, кто знает внутренние функции определения функций лучше, чем я.Выполнение встроенных определений функций Python

В общем, есть динамика торговля от делать что-то вроде этого:

def my_function(): 
    def other_function(): 
     pass 

    # do some stuff 
    other_function() 

Versus:

def other_function(): 
    pass 

def my_function(): 
    # do some stuff 
    other_function() 

Я видел разработчиков встроенных функций перед держать небольшое, одноразовое использование функции, близкой к коду, который на самом деле его использует, но я всегда задавался вопросом, было ли ограничение производительности (или вычисления) памяти для выполнения чего-то подобного.

Мысли?

+0

Что делает ваше профилирующее шоу? https://docs.python.org/3/library/profile.html –

+1

Обычно я использую встроенные функции, когда хочу закрыть. Я также использую их, когда встроенная функция не имеет возможности использовать вне своей закрывающей функции. – acushner

+0

@tristan, я меньше интересовался вычислительной производительностью, большей внутренней памятью, я думаю? Но об этом было бы интересно узнать. –

ответ

7

Разделение больших функций на более читаемые, более мелкие функции является частью написания кода Pythonic - должно быть очевидно, что вы пытаетесь выполнить, а более мелкие функции легче читать, проверять наличие ошибок, поддерживать и повторно использовать.

Как всегда, вопросы, «которые имеют лучшую производительность», всегда должны решаться profiling the code, то есть они часто зависят от подписи методов и того, что делает ваш код.

например. если вы передаете большой словарь в отдельную функцию вместо ссылки на локальный фрейм, вы получите разные характеристики производительности, чем вызов функции void от другого.

Например, вот некоторые тривиальное поведение:

import profile 
import dis 

def callee(): 
    for x in range(10000): 
     x += x 
    print("let's have some tea now") 

def caller(): 
    callee() 


profile.run('caller()') 

let's have some tea now 
     26 function calls in 0.002 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     2 0.000 0.000 0.000 0.000 :0(decode) 
     2 0.000 0.000 0.000 0.000 :0(getpid) 
     2 0.000 0.000 0.000 0.000 :0(isinstance) 
     1 0.000 0.000 0.000 0.000 :0(range) 
     1 0.000 0.000 0.000 0.000 :0(setprofile) 
     2 0.000 0.000 0.000 0.000 :0(time) 
     2 0.000 0.000 0.000 0.000 :0(utf_8_decode) 
     2 0.000 0.000 0.000 0.000 :0(write) 
     1 0.002 0.002 0.002 0.002 <ipython-input-3-98c87a49b247>:4(callee) 
     1 0.000 0.000 0.002 0.002 <ipython-input-3-98c87a49b247>:9(caller) 
     1 0.000 0.000 0.002 0.002 <string>:1(<module>) 
     2 0.000 0.000 0.000 0.000 iostream.py:196(write) 
     2 0.000 0.000 0.000 0.000 iostream.py:86(_is_master_process) 
     2 0.000 0.000 0.000 0.000 iostream.py:95(_check_mp_mode) 
     1 0.000 0.000 0.002 0.002 profile:0(caller()) 
     0 0.000    0.000   profile:0(profiler) 
     2 0.000 0.000 0.000 0.000 utf_8.py:15(decode) 

против

import profile 
import dis 

def all_in_one(): 
    def passer(): 
     pass 
    passer() 
    for x in range(10000): 
     x += x 
    print("let's have some tea now")  

let's have some tea now 
     26 function calls in 0.002 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     2 0.000 0.000 0.000 0.000 :0(decode) 
     2 0.000 0.000 0.000 0.000 :0(getpid) 
     2 0.000 0.000 0.000 0.000 :0(isinstance) 
     1 0.000 0.000 0.000 0.000 :0(range) 
     1 0.000 0.000 0.000 0.000 :0(setprofile) 
     2 0.000 0.000 0.000 0.000 :0(time) 
     2 0.000 0.000 0.000 0.000 :0(utf_8_decode) 
     2 0.000 0.000 0.000 0.000 :0(write) 
     1 0.002 0.002 0.002 0.002 <ipython-input-3-98c87a49b247>:4(callee) 
     1 0.000 0.000 0.002 0.002 <ipython-input-3-98c87a49b247>:9(caller) 
     1 0.000 0.000 0.002 0.002 <string>:1(<module>) 
     2 0.000 0.000 0.000 0.000 iostream.py:196(write) 
     2 0.000 0.000 0.000 0.000 iostream.py:86(_is_master_process) 
     2 0.000 0.000 0.000 0.000 iostream.py:95(_check_mp_mode) 
     1 0.000 0.000 0.002 0.002 profile:0(caller()) 
     0 0.000    0.000   profile:0(profiler) 
     2 0.000 0.000 0.000 0.000 utf_8.py:15(decode) 

Два используют один и тот же Numbe r вызовов функций, и нет разницы в производительности, что подтверждает мое утверждение о том, что действительно важно проверить в определенных обстоятельствах.

Вы можете видеть, что у меня есть неиспользованный импорт для модуля disassembly. Это еще один полезный модуль, который позволит вам увидеть, что делает ваш код (попробуйте dis.dis(my_function)).Я бы опубликовал профиль тестового кода, который я сгенерировал, но он только покажет вам больше деталей, которые не имеют отношения к решению проблемы или не узнают, что на самом деле происходит в вашем коде.

+0

Я согласен с вашими таймингами, но странно, что вы использовали 'profile.run (f)' вместо, скажем, 'min (timeit.Timer (f) .repeat (10, 200))'. 'profile' хорош, говоря вам примерно то, что дорогостоящие части программы, но не так хорошо рассказывать вам, как быстро программа из-за накладных расходов. – Veedrac

+0

@Veedrac В приведенном тривиальном примере важнее понять, что происходит, чем знать, как быстро неиспользуемая функция запускает кучу раз. То есть, знание разницы во времени выполнения между этими двумя функциями не имеет значения. Знание того, как разобраться, почему один метод быстрее для более реалистичного кода _is_ важен. –

+0

Я не уверен, что я следую; что я собираюсь извлечь из следа? – Veedrac

5

Использование timeit на мой макинтош, кажется, благоволит определения функции на уровне модуля (немного), и, очевидно, результаты могут варьироваться от одного компьютера к другому ...:

>>> import timeit 
>>> def fun1(): 
... def foo(): 
...  pass 
... foo() 
... 
>>> def bar(): 
... pass 
... 
>>> def fun2(): 
... bar() 
... 
>>> timeit.timeit('fun1()', 'from __main__ import fun1') 
0.2706329822540283 
>>> timeit.timeit('fun2()', 'from __main__ import fun2') 
0.23086285591125488 

Обратите внимание, что эта разница (~ 10%), так что это действительно не будет иметь большого значения во время выполнения вашей программы, если это не будет в очень плотном цикле.

Самая частая причина для определения функции внутри другой - это получить локальные переменные функции out в закрытии. Если вам не требуется закрытие, вы должны выбрать вариант, который проще всего прочитать. (Мои предпочтения почти всегда включают функцию на уровне модуля).

+0

Вы пробовали оптимизацию с помощью 'Python -O'? Это будет более справедливое сравнение, потому что функции не могут считаться встроенными, если только в режиме оптимизации компилятора. –

+0

Я тестировал его с помощью оптимизации, и результат был таким же, как вы сообщили. –