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

Можно ли объявить функцию в 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 были определены ранее.

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


person Nathan Fellman    schedule 19.10.2009    source источник


Ответы (17)


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

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

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

def foo():
    bar()

def bar():
    foo()

Функции Python анонимны, как и значения, но их можно привязать к имени.

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

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

person RichN    schedule 19.10.2009
comment
короче говоря, если у вас есть if __name__ == '__main__': main () в качестве последней строки в вашем скрипте, все будет хорошо! - person Filipe Pina; 03.08.2011
comment
@FilipePina Я не понял вашего комментария - почему вы не можете указать последнюю строку кода просто main()? - person Sanjay Manohar; 08.04.2013
comment
@SanjayManohar: чтобы не выполнить его на import your_module - person jfs; 19.06.2013
comment
Хочу добавить - иногда можно обойти эти проблемы с помощью лямбда-выражений, поскольку они оцениваются позже. - person Joe; 26.02.2015
comment
W.r.t. анонимный, вы действительно имеете в виду первоклассные объекты. - person danielm; 22.07.2015
comment
@RichN: Надеюсь, вам понравились мои правки. Если бы не путаница между declare и define, я бы оставил это в покое. Я думаю, ваш ответ можно было бы обойтись без объяснения анонимных ценностей, так как это сбивает людей с толку и создает впечатление, будто вы не понимаете, о чем говорите. Изменения времени выполнения в таблице символов не имеют отношения к вопросу, и вам следовало пропустить его. - person Borodin; 27.05.2016
comment
@FilipePina: Что вы имеете в виду, говоря, что все будет хорошо? Какую проблему это должно решить? - person HelloGoodbye; 19.09.2018
comment
@HelloGoodbye не совсем уверен, что у меня было за 7 лет назад, но, вероятно, что-то вокруг того OP имеет эту строку вне функции. Если код находится внутри функции и последняя строка скрипта / модуля вызывает точку входа (обычно main()), не нужно беспокоиться о том, что функции порядка объявлены, поскольку в этой точке (последняя строка) все были определены - person Filipe Pina; 21.09.2018
comment
@FilipePina Да, 7 лет - это довольно много. В любом случае, возможно, это решит проблему, когда вы просто используете функции, но я заканчиваю свой скрипт if __name__ == '__main__': main() (хотя и не в одной строке), и у меня такая же проблема, как у OP. Я пытаюсь получить доступ к функции из класса, который я определил выше функции; возможно, это влияет на поведение. - person HelloGoodbye; 21.09.2018
comment
@HelloGoodbye Боюсь, я не могу комментировать, не видя фактического кода, но если вы вызываете функцию только внутри другой функции, то она всегда после того, как она была объявлена ​​... - person Filipe Pina; 24.09.2018
comment
@FilipePina Что вы имеете в виду, говоря, что это всегда после объявления? К чему оба они относятся? - person HelloGoodbye; 25.09.2018
comment
Ваша проблема не может быть решена, потому что это все равно что попросить переменную, которая не была объявлена. Что ж, это может быть решено на языках, которые отделяют объявление объектов от их определений. - person Gene Callahan; 11.01.2019
comment
То, как здесь показан фрагмент кода, сбивает с толку. Мое первое впечатление заключалось в том, что это означало сказать, что нечто подобное на самом деле могло быть сделано - тогда как цель состоит в том, чтобы показать, что такая конструкция невозможна из-за ошибки, объясненной впоследствии. - person shasan; 03.07.2019
comment
Это пропускает лодку. OP задает гораздо более простой вопрос, чем вы отвечаете. Правильный ответ заключается в том, чтобы вызвать вызов внутри функции, а не на верхнем уровне, а затем, поскольку интерпретатор рассматривает вызов функции как часть определения, а не как немедленную инструкцию, рекурсивные определения совершенно прекрасны, не требуя каких-либо предварительных объявлений. Пример foo / bar прекрасен и НЕ является примером неудачной конструкции. - person DragonLord; 29.09.2020

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

Так что

foo()

def foo():
    print "Hi!"

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

def bar():
    foo()

def foo():
    print "Hi!"

bar()

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

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

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

person Vanya    schedule 19.10.2009
comment
+1 самый прямой ответ, с концепцией краеугольного камня: Паскаль = определять выше, Python = определять раньше. - person Bob Stein; 26.10.2014
comment
Это правильный ответ, он также объясняет, почему решение if __name__=="__main__": работает. - person 00prometheus; 24.11.2015
comment
Если я правильно понимаю, это означает, что пример OP spam () и egg () в порядке, как написано. Это верно? - person krubo; 03.09.2018
comment
@krubo да ладно как написано - person lxop; 13.03.2019
comment
У меня это не работает. Мне нужно, чтобы foo{} был определен последним в файле, и я получаю сообщение об ошибке при вызове bar(). - person ysap; 02.06.2021

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

if __name__=="__main__":
   main()

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

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

person jldupont    schedule 19.10.2009
comment
+1 наиболее практичный ответ: если вы поместите этот код в низ самого внешнего исходного файла, вы можете определять в любом порядке. - person Bob Stein; 26.10.2014
comment
Отличный совет; действительно помогает мне; поскольку я гораздо больше предпочитаю программирование сверху вниз, а не снизу вверх. - person GhostCat; 13.08.2015
comment
В чем смысл строки if name == main: Я могу просто написать myMain () (или другое имя основной функции) в конце файла. - person ThomasRones; 24.01.2021
comment
Не будет работать, если вы укажете класс как тип. например. class X: def som(self, y: Y) ... class Y: ... - person Phani Rithvij; 28.03.2021

Если вызов 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 () в конце файла.

person BJ Homer    schedule 19.10.2009

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

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

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.

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

person Piotr Czapla    schedule 19.10.2009

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

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

# 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, чтобы увидеть, как эти два оператора сравниваются, у них были бы точно такие же вычислительные затраты.

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

person KGardevoir    schedule 22.02.2014

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

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

def main():
  make_omelet()
  eat()

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

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

# ...

main()
person funroll    schedule 29.05.2012

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

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

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

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

person unutbu    schedule 19.10.2009
comment
ну, это если вы делаете только один проход по коду. Некоторые компиляторы (и я понимаю, что python в интерпретации) выполняют два прохода, так что эти вещи можно понять. форвард-объявление или, по крайней мере, какое-то открытие с заданной областью видимости было бы действительно хорошо. - person Mark Lakewood; 26.08.2011

# 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!
person jmurphy61    schedule 29.04.2019

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

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')
person user10488833    schedule 11.10.2018
comment
но вам нужно инкапсулировать весь свой код с if name, чтобы он не выполнялся два раза - person arivero; 14.10.2020
comment
Правильно, сейчас я много использую: '' 'if __name__ ==' __main__ ': from test import func func () else: def func (): print (' Func works ')' '' Так и у конечного пользователя предпочтение. - person user10488833; 04.11.2020

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

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

Также не используйте cmp, который устарел. Используйте key и укажите функцию «меньше чем».

person Mike Graham    schedule 20.10.2009
comment
Как мне предоставить функцию «меньше чем»? - person Nathan Fellman; 20.10.2009
comment
Вместо cmp_configs вы должны определить функцию, которая принимает два аргумента и возвращает True, если первый меньше второго, и False в противном случае. - person Mike Graham; 25.10.2009
comment
Для тех из нас, кто имеет опыт работы в стиле C, нет ничего неразумного в выполнении логики до определения функций. Подумайте: многопроходный компилятор. Иногда нужно время, чтобы адаптироваться к новым языкам :) - person Luke H; 08.03.2012

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

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

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

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

person S.Lott    schedule 19.10.2009
comment
У меня такая ситуация. Я пытаюсь передать типы в декораторе функций, а типы определены ниже по модулю. Я не могу переместить нарушающие типы вверх, потому что это нарушит цепочку наследования. - person Joe; 26.02.2015
comment
Я исправил это, передав в мой декоратор лямбду вместо фактического типа; но я бы не знал, как это исправить в противном случае (это не потребовало бы от меня перестановки моего наследования) - person Joe; 26.02.2015

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

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

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.

person PaulMcG    schedule 19.10.2009

TL; DR: Python не нуждается в форвардных объявлениях. Просто поместите вызовы функций в определения функций def, и все будет в порядке.

def foo(count):
    print("foo "+str(count))
    if(count>0):
        bar(count-1)

def bar(count):
    print("bar "+str(count))
    if(count>0):
        foo(count-1)

foo(3)
print("Finished.")

рекурсивная функция definitions, отлично дает:

foo 3
bar 2
foo 1
bar 0
Finished.

Тем не мение,

bug(13)

def bug(count):
    print("bug never runs "+str(count))

print("Does not print this.")

прерывает вызов верхнего уровня функции, которая еще не была определена, и дает:

Traceback (most recent call last):
  File "./test1.py", line 1, in <module>
    bug(13)
NameError: name 'bug' is not defined

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

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

def uncalled():
    wild_eyed_undefined_function()
    print("I'm not invoked!")

print("Only run this one line.")

и он работает отлично (!), с выводом

Only run this one line.

Ключевым моментом является разница между определениями и вызовами.

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

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

Дело о if __ main __ - это идиома, основанная на этом принципе, но вы должны понимать почему, а не просто слепо следовать ей.

Конечно, есть гораздо более сложные темы, касающиеся лямбда-функций и динамического повторного связывания имен функций, но это не то, о чем просил OP. Кроме того, они могут быть решены с использованием тех же принципов: (1) defs определяют функцию, они не вызывают свои строки; (2) возникают проблемы, когда вы вызываете несвязанный символ функции.

person DragonLord    schedule 28.09.2020

Python не поддерживает форвардные объявления, но обычным обходным путем для этого является использование следующего условия в конце вашего скрипта / кода:

if __name__ == '__main__': main()

При этом он сначала прочитает весь файл, а затем оценит условие и вызовет функцию main (), которая сможет вызвать любую объявленную вперед функцию, поскольку она уже сначала прочитала весь файл. Это условие использует специальную переменную __name__, которая возвращает значение __main__ всякий раз, когда мы запускаем код Python из текущего файла (когда код был импортирован как модуль, тогда __name__ возвращает имя модуля).

person Mikhail    schedule 05.12.2020

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

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

Обработчик может принимать аргумент 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)
person obesechicken13    schedule 04.08.2013
comment
это кажется очень непифоничным. Python должен справляться с подобными вещами сам по себе. - person Nathan Fellman; 04.08.2013
comment
перечитайте принятый ответ. Python не требует, чтобы функция определялась до тех пор, пока вы ее не вызовете, а не просто используете ее в определении. - person tacaswell; 04.08.2013

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

Вход

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.

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

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

person Satish Reddy Venkannagari    schedule 11.03.2018
comment
Разве print_lyrics() не вызывается (используется) в строке 1 до его определения? Я скопировал этот фрагмент кода и попытался запустить его, и он выдает ошибку NameError: name 'print_lyrics' is not defined в строке 1. Вы можете это объяснить? - person Bugs Buggy; 17.07.2018