Чи можливо переадресувати оголошення в 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тоді викликте нову функцію.

Вашу проблему неможливо вирішити, тому що це як попросити отримати змінну, яка не була оголошена.


47
коротше, якщо у вас є, якщо __name__ == '__main__': main () як останній рядок у вашому сценарії, все буде просто добре!
Філіпе Піна

3
@FilipePina Я не зрозумів ваш коментар - чому ви не можете просто поставити останній рядок коду main()?
Санджай Манохар

11
@SanjayManohar: щоб уникнути його виконанняimport your_module
jfs

2
Хочу додати - іноді ці проблеми можна обійти, використовуючи лямбда, оскільки вони оцінюються пізніше.
Джо

2
Wrt "анонімний", ви справді маєте на увазі "першокласні об'єкти".
danielm

119

Що ви можете зробити, це ввімкнути виклик у свою функцію.

Так що

foo()

def foo():
    print "Hi!"

зламається, але

def bar():
    foo()

def foo():
    print "Hi!"

bar()

буде працювати належним чином.

Загальне правило в Pythonце НЕ що функція повинна бути визначена вище в коді (як в Pascal), але вона повинна бути визначена до її використання.

Сподіваюся, що це допомагає.


20
+1 найбільш пряма відповідь, з концепцією ключового каменю: Pascal = визначити вище, Python = визначити раніше.
Боб Штейн

1
Це правильна відповідь, вона також пояснює, чому if __name__=="__main__":рішення працює.
00прометей

2
Якщо я правильно розумію, це означає, що приклад спаму () та яєць () ОП добре, як написано. Це правильно?
крубо

1
@krubo так, це добре, як написано
lxop

92

Якщо ви запустили свій сценарій за допомогою наступного:

if __name__=="__main__":
   main()

то вам, мабуть, не доведеться турбуватися про такі речі, як "вперед заява". Розумієте, перекладач завантажив би всі ваші функції, а потім запустив вашу головну () функцію. Звичайно, переконайтесь, що ви також імпортуєте правильний ;-)

Подумайте про це, я ніколи не чув такого поняття, як "декларація вперед" в python ... але потім, я можу помилитися ;-)


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модуль, щоб побачити, як ці два твердження порівнюються, вони мають точно однакові обчислювальні витрати.

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


9

У 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.

Можна подумати, що тіло функції - це лише інший сценарій, який буде інтерпретований, як тільки ви викликаєте функцію.


7

Ні, я не вірю, що є якийсь спосіб переадресувати оголошення в Python.

Уявіть, що ви інтерпретатор Python. Коли ви потрапите на лінію

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

або ви знаєте, що таке cmp_configs, чи ні. Для того, щоб продовжити, ви повинні знати cmp_configs. Не має значення, чи є рекурсія.


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()

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!

3

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

Також замініть це розуміння списку експрес-генератором (тобто print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))

Крім того, не використовуйте cmp, що застаріло. Використовуйте keyта надайте функцію, меншу, ніж


Як я можу забезпечити функцію, меншу за функцію?
Натан Фелман

Замість cmp_configs ви б визначили функцію, яка бере два аргументи і повертає True, якщо перший менше другого, а False - інакше.
Майк Грехем

Для тих, хто з нас походить із C-подібного фону, немає нічого необґрунтованого у виконанні логіки до визначення функцій. Подумайте: "компілятор багато пропусків". Іноді потрібен певний час, щоб адаптуватися до нових мов :)
Лука H

3

Імпортуйте сам файл. Припустимо, що файл називається test.py:

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')

1

"просто реорганізуйте свій код, щоб у мене не виникло цієї проблеми." Правильно. Легко зробити. Завжди працює.

Ви завжди можете надати функцію до її посилання.

"Однак є випадки, коли це, мабуть, неминуче, наприклад, коли реалізуються деякі форми рекурсії"

Не бачу, як це можливо навіть віддалено. Надайте приклад місця, де ви не можете визначити функцію до її використання.


У мене така ситуація. Я намагаюся передати типи в декоратор функцій, і типи визначаються далі за модулем. Я не можу переміщувати види порушень, оскільки це порушило б ланцюжок спадкування.
Джо

Я зафіксував це, передавши лямбда своєму декоратору замість фактичного типу; але я б не знав, як це виправити інакше (це не вимагає від мене перестановки спадщини)
Джо

0

Тепер почекай хвилинку. Коли ваш модуль доходить до оператора друку у вашому прикладі, до того, як 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змінна, визначена перед тим, як досягти цього рядка.

[EDIT - ця наступна частина виявляється невірною, оскільки значення аргументу за замовчуванням буде присвоєно під час компіляції функції, і це значення буде використано, навіть якщо ви зміните значення cmp_configs пізніше.]

На щастя, будучи настільки приємним для типу Python, не важливо, що ви визначаєте як cmp_configs, так що ви можете просто передмовити це твердження:

cmp_configs = None

І компілятор буде радий. Просто не забудьте оголосити справжню, cmp_configsперш ніж коли-небудь звертатись fn.


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

що здається дуже непітонічним. Python повинен сам обробляти подібні речі.
Натан Фелман

перечитайте прийняту відповідь. Python не потребує визначення функції, поки ви не викликаєте її, а не просто використовуєте її у визначенні.
tacaswell

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

Як згадував Б. Дж. Гомер у вищезазначених коментарях, загальне правило в Python полягає не в тому, що функція повинна визначатися вище в коді (як у Паскалі), а в тому, що вона повинна бути визначена перед її використанням.

Сподіваюся, що це допомагає.


2
Чи не print_lyrics()називається (використовується) у рядку 1 до того, як він буде визначений? Я скопіював цей фрагмент коду і спробував запустити його, і він дає мені помилку NameError: name 'print_lyrics' is not definedв рядку 1. Чи можете ви пояснити це?
Bugs Buggy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.