Можно ли заранее объявить функцию в Python?

189

Можно ли заранее объявить функцию в Python? Я хочу отсортировать список, используя мою собственную cmpфункцию, прежде чем он будет объявлен.

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

Я организовал свой код, чтобы поместить определение cmp_configsметода после вызова. Это терпит неудачу с этой ошибкой:

NameError: name 'cmp_configs' is not defined

Есть ли способ "заявить" cmp_configs метод до его использования? Это сделало бы мой код чище?

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

Рассмотрим случай, когда в Python необходимо объявить функцию вперед:

def spam():
    if end_condition():
        return end_result()
    else:
        return eggs()

def eggs():
    if end_condition():
        return end_result()
    else:
        return spam()

Где end_conditionи end_resultбыли предварительно определены.

Является ли единственным решением реорганизовать код и всегда ставить определения перед вызовами?

Натан Феллман
источник

Ответы:

76

Если вы не хотите определять функцию до ее использования, а определить ее впоследствии невозможно, как насчет определения ее в каком-то другом модуле?

Технически вы все еще определяете это сначала, но это чисто.

Вы можете создать рекурсию следующим образом:

def foo():
    bar()

def bar():
    foo()

Функции Python анонимны так же, как значения анонимны, но они могут быть связаны с именем.

В приведенном выше коде foo()не вызывается функция с именем foo, она вызывает функцию, которая оказывается привязанной к имени fooв тот момент, когда выполняется вызов. Можно переопределить fooгде-то еще, а barзатем вызвать новую функцию.

Ваша проблема не может быть решена, потому что это как запросить переменную, которая не была объявлена.

RichN
источник
47
Короче говоря, если у вас в качестве последней строки в вашем скрипте есть __name__ == '__main__': main (), все будет просто отлично!
Филипе Пина
3
@FilipePina Я не понял вашего комментария - почему вы не можете поместить последнюю строку кода так просто main()?
Санджай Манохар
11
@SanjayManohar: чтобы избежать его выполненияimport your_module
JFS
2
Я хотел бы добавить - иногда можно обойти эти проблемы, используя лямбды, поскольку они оцениваются позже.
Джо
2
«Аноним», вы действительно имеете в виду «первоклассные объекты».
Даниэль
119

Что вы можете сделать, так это превратить вызов в собственную функцию.

Так что

foo()

def foo():
    print "Hi!"

сломается, но

def bar():
    foo()

def foo():
    print "Hi!"

bar()

будет работать правильно.

Общее правило Pythonзаключается не в том, что функция должна быть определена выше в коде (как в Pascal), а в том, что она должна быть определена до ее использования.

Надеюсь, это поможет.

Ваня
источник
20
+1 самый прямой ответ, с концепцией краеугольного камня: Pascal = определить выше, Python = определить раньше.
Боб Стейн
1
Это правильный ответ, он также объясняет, почему if __name__=="__main__":решение работает.
00promeheus
2
Если я правильно понимаю, это означает, что пример OP spam () и eggs () в порядке, как написано. Это правильно?
Крубо
1
@krubo да, все хорошо, как написано
lxop
92

Если вы запускаете свой скрипт с помощью следующего:

if __name__=="__main__":
   main()

тогда вам, вероятно, не нужно беспокоиться о таких вещах, как «предварительное объявление». Видите ли, интерпретатор загрузит все ваши функции и затем запустит вашу функцию main (). Конечно, убедитесь, что все операции импорта тоже правильные ;-)

Если подумать, я никогда не слышал такого понятия, как «предварительное объявление» в python ... но опять же, я могу ошибаться ;-)

jldupont
источник
14
+1 самый практичный ответ: если вы поместите этот код внизу самого исходного исходного файла, то вы можете определить его в любом порядке.
Боб Стейн
2
Отличный совет; действительно помогает мне; поскольку я гораздо больше предпочитаю программирование «сверху вниз», чем снизу вверх.
GhostCat
10

Если вызов cmp_configs находится внутри собственного определения функции, у вас все будет хорошо. Я приведу пример.

def a():
  b()  # b() hasn't been defined yet, but that's fine because at this point, we're not
       # actually calling it. We're just defining what should happen when a() is called.

a()  # This call fails, because b() hasn't been defined yet, 
     # and thus trying to run a() fails.

def b():
  print "hi"

a()  # This call succeeds because everything has been defined.

В целом, размещение вашего кода внутри функций (таких как main ()) решит вашу проблему; просто вызовите main () в конце файла.

Би Джей Гомер
источник
10

Я извиняюсь за возрождение этой темы, но здесь не обсуждалась стратегия, которая может быть применима.

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

# We want to call a function called 'foo', but it hasn't been defined yet.
function_name = 'foo'
# Calling at this point would produce an error

# Here is the definition
def foo():
    bar()

# Note that at this point the function is defined
    # Time for some reflection...
globals()[function_name]()

Таким образом, таким образом мы определили, какую функцию мы хотим вызвать до того, как она будет определена, по сути, предварительное объявление. В python утверждение globals()[function_name]()такое же, как foo()если бы function_name = 'foo'по причинам, рассмотренным выше, поскольку python должен искать каждую функцию перед ее вызовом. Если использовать timeitмодуль, чтобы увидеть, как эти два оператора сравниваются, они имеют одинаковую вычислительную стоимость.

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

KGardevoir
источник
9

В питоне нет такой вещи, как предварительное объявление. Вы просто должны убедиться, что ваша функция объявлена ​​до того, как она понадобится. Обратите внимание, что тело функции не интерпретируется, пока функция не будет выполнена.

Рассмотрим следующий пример:

def a():
   b() # won't be resolved until a is invoked.

def b(): 
   print "hello"

a() # here b is already defined so this line won't fail.

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

Петр Чапла
источник
7

Нет, я не верю, что в Python есть способ объявить функцию вперед.

Представьте, что вы интерпретатор Python. Когда вы доберетесь до линии

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

либо вы знаете, что такое cmp_configs, либо нет. Чтобы продолжить, вы должны знать cmp_configs. Неважно, есть ли рекурсия.

unutbu
источник
9
хорошо, если вы делаете только один проход по коду. Некоторые компиляторы (и я понимаю, что Python интерпретируется) делают два прохода, чтобы эти вещи можно было понять. предварительное объявление, или, по крайней мере, какое-то открытие в области видимости, было бы действительно хорошо.
Марк Лейквуд
7

Иногда алгоритм проще всего понять сверху вниз, начиная с общей структуры и углубляясь в детали.

Вы можете сделать это без предварительных деклараций:

def main():
  make_omelet()
  eat()

def make_omelet():
  break_eggs()
  whisk()
  fry()

def break_eggs():
  for egg in carton:
    break(egg)

# ...

main()
funroll
источник
4
# declare a fake function (prototype) with no body
def foo(): pass

def bar():
    # use the prototype however you see fit
    print(foo(), "world!")

# define the actual function (overwriting the prototype)
def foo():
    return "Hello,"

bar()

Вывод:

Hello, world!
jmurphy61
источник
3

Вы не можете заранее объявить функцию в Python. Если у вас есть логика, выполняемая до того, как вы определили функции, у вас, вероятно, возникнут проблемы. Поместите свое действие в if __name__ == '__main__'конец скрипта (выполнив функцию, которую вы называете «основной», если она нетривиальна), и ваш код станет более модульным, и вы сможете использовать его как модуль, если вам когда-нибудь понадобится к.

Кроме того, замените это понимание списка генератором express (то есть, print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))

Кроме того, не используйте cmp, что не рекомендуется. Используйте keyи предоставьте менее чем функцию.

Майк Грэм
источник
Как мне предоставить функцию меньше, чем?
Натан Феллман
Вместо cmp_configs вы определяете функцию, которая принимает два аргумента и возвращает True, если первый меньше второго, и False в противном случае.
Майк Грэм
Для тех из нас, кто пришел из C-подобного фона, нет ничего неразумного в выполнении логики до определения функций. Подумайте: «многопроходный компилятор». Иногда требуется некоторое время, чтобы адаптироваться к новым языкам :)
Лука Х
3

Импортируйте сам файл. Предполагая, что файл называется test.py:

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')
user10488833
источник
1

«просто реорганизовать мой код, чтобы у меня не было этой проблемы». Верный. Легко сделать. Всегда работает.

Вы всегда можете предоставить функцию до ее ссылки.

«Однако бывают случаи, когда это, вероятно, неизбежно, например, при реализации некоторых форм рекурсии»

Не вижу, как это возможно даже отдаленно. Пожалуйста, приведите пример места, где вы не можете определить функцию до ее использования.

С. Лотт
источник
У меня такая ситуация. Я пытаюсь передать типы в декораторе функции, и типы определяются ниже по модулю. Я не могу переместить оскорбительные типы вверх, потому что это нарушит цепочку наследования.
Джо
Я исправил это, передав лямбду своему декоратору вместо фактического типа; но я бы не знал, как это исправить иначе (это не потребовало бы, чтобы я переставил свои наследства)
Джо,
0

Теперь подожди минутку. Когда ваш модуль достигает оператора print в вашем примере, до того как cmp_configsон был определен, что именно вы ожидаете от него?

Если ваша публикация вопроса с использованием печати действительно пытается представить что-то вроде этого:

fn = lambda mylist:"\n".join([str(bla)
                         for bla in sorted(mylist, cmp = cmp_configs)])

тогда нет необходимости определять cmp_configs перед выполнением этого оператора, просто определите его позже в коде, и все будет хорошо.

Теперь, если вы пытаетесь сослаться cmp_configsна лямбду в качестве значения по умолчанию для аргумента, тогда это другая история:

fn = lambda mylist,cmp_configs=cmp_configs : \
    "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

Теперь вам нужна cmp_configsпеременная, определенная до того, как вы достигнете этой строки.

[РЕДАКТИРОВАТЬ - следующая часть оказывается неверной, поскольку значение аргумента по умолчанию будет назначено при компиляции функции, и это значение будет использоваться, даже если вы измените значение cmp_configs позже.]

К счастью, Python быть настолько типа размещения , как это, не важно , что вы определяете , как cmp_configs, так что вы можете просто предисловие с этим утверждением:

cmp_configs = None

И компилятор будет счастлив. Просто не забудьте объявить реальное, cmp_configsпрежде чем вы когда-либо призывать fn.

PaulMcG
источник
-1

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

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

Обработчик может принять аргумент nameOfMethodToCall. Затем использует кучу операторов if для вызова правильного метода.

Это решит вашу проблему.

def foo():
    print("foo")
    #take input
    nextAction=input('What would you like to do next?:')
    return nextAction

def bar():
    print("bar")
    nextAction=input('What would you like to do next?:')
    return nextAction

def handler(action):
    if(action=="foo"):
        nextAction = foo()
    elif(action=="bar"):
        nextAction = bar()
    else:
        print("You entered invalid input, defaulting to bar")
        nextAction = "bar"
    return nextAction

nextAction=input('What would you like to do next?:')

while 1:
    nextAction = handler(nextAction)
obesechicken13
источник
это кажется очень нелепым. Python должен обрабатывать такие вещи сам по себе.
Натан Феллман
перечитайте принятый ответ. Python не нужна функция , которая будет определена , пока вы не вызовете его, а не просто использовать его в определении.
Такасвелл
-3

Да, мы можем проверить это.

вход

print_lyrics() 
def print_lyrics():

    print("I'm a lumberjack, and I'm okay.")
    print("I sleep all night and I work all day.")

def repeat_lyrics():
    print_lyrics()
    print_lyrics()
repeat_lyrics()

Вывод

I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Как упомянул BJ Homer в приведенных выше комментариях, общее правило в Python заключается не в том, что функция должна быть определена выше в коде (как в Pascal), а в том, что она должна быть определена до ее использования.

Надеюсь, это поможет.

Сатиш Редди Венканнагари
источник
2
Не print_lyrics()вызывается (используется) в строке 1, прежде чем он будет определен? Я скопировал этот кусок кода и попытался запустить его, и он выдает ошибку NameError: name 'print_lyrics' is not definedв строке 1. Вы можете это объяснить?
Багги Багги