Pythonで関数を前方宣言することは可能ですか?


188

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
要するに、あなたが持っているなら if __name__ == '__main__':main()をスクリプトの最後の行としてなら、すべてうまくいきます!
フィリペピナ2011

3
@FilipePinaコメントを理解できませんでした-コードの最後の行を単純に記述できないのはなぜmain()ですか?
Sanjay Manohar

11
@SanjayManohar:それを実行しないようにする import your_module
jfs

2
追加したいのですが、後で評価されるため、ラムダを使用してこれらの問題を回避できる場合があります。
ジョー

2
「匿名」とは、あなたは本当に「ファーストクラスのオブジェクト」を意味します。
ダニエルム

117

できることは、呼び出しを独自の関数にラップすることです。

そのため

foo()

def foo():
    print "Hi!"

壊れますが

def bar():
    foo()

def foo():
    print "Hi!"

bar()

正常に動作します。

の一般的な規則Pythonは、関数をコードの上位で定義する必要があるということではなく(のようにPascal)、使用する前に定義する必要があります。

お役に立てば幸いです。


20
キーストーンのコンセプトで、+ 1の最も直接的な答え:Pascal =より高い定義、Python =より以前の定義。
Bob Stein

1
これは正解if __name__=="__main__":です。ソリューションが機能する理由も説明します。
00prometheus 2015年

2
私が正しく理解している場合、それはOPのspam()とeggs()の例が記述どおり問題ないことを意味します。あれは正しいですか?
krubo 2018

1
@kruboはい、書かれた
とおり

92

次のようにスクリプトをキックスタートすると:

if __name__=="__main__":
   main()

そうすれば、おそらく「前方宣言」のようなことを心配する必要はありません。インタプリタはすべての関数をロードしてから、main()関数を開始します。もちろん、すべてのインポートが正しいことも確認してください;-)

考えてみると、Pythonで「前方宣言」のようなことは聞いたことがありません...でも、私は間違っているかもしれません;-)


14
+1最も実用的な答え:このコードを最も外側のソースファイルの一番下に配置すると、任意の順序で自由に定義できます。
Bob Stein

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この2つのステートメントの比較方法を確認するためにモジュールを使用すると、計算コストは​​まったく同じになります。

もちろん、ここの例は非常に役に立ちませんが、関数を実行する必要があるが、前に宣言する必要がある複雑な構造が必要な場合(または構造的に後から宣言するのはほとんど意味がない場合)、文字列を格納し、後で関数を呼び出してみてください。


8

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
これは、コードを1回だけパスする場合です。一部のコンパイラー(および私はインタープリターでpythonを実現します)は2つのパスを実行するため、これらのことが理解できます。前方宣言、または少なくともある種のスコープ付きディスカバリーは本当に良いでしょう。
Mark Lakewood、

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__'スクリプトの最後にではない場合は「main」という名前の関数を実行して)配置します。コードはよりモジュール化され、必要に応じてモジュールとして使用できますに。

また、そのリスト内包表記をジェネレータ式(つまりprint "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))に置き換えます。

また、cmp廃止予定のを使用しないでください。小keyなり関数を使用および提供します。


小なり関数を提供するにはどうすればよいですか?
Nathan Fellman、

cmp_configsの代わりに、2つの引数を取り、最初の引数が2番目の引数よりも小さい場合はTrueを返し、そうでない場合はFalseを返す関数を定義します。
マイクグラハム、

Cのような背景から来ている私たちにとって、関数が定義される前に実行されるロジックについて不当なことは何もありません。「マルチパスコンパイラ」と考えてください。新しい言語に適応するのに時間がかかることがあります:)
Luke H

3

ファイル自体をインポートします。ファイルの名前がtest.pyであると想定します。

import test

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

1

「この問題が発生しないようにコードを再編成してください。」正しい。簡単です。常に動作します。

関数は、参照する前にいつでも提供できます。

「しかし、これがおそらく避けられない場合があります。たとえば、ある種の再帰を実装する場合などです。」

それがどのようにリモートで可能であるかさえわかりません。使用前に関数を定義できない場所の例を提供してください。


こんな状況です。関数デコレータで型を渡そうとしていますが、型はモジュールのさらに下で定義されています。問題のある型を上に移動することはできません。継承チェーンが壊れてしまうからです。
Joe

実際の型ではなくラムダをデコレータに渡すことで修正しました。しかし、他の方法でそれを修正する方法はわかりません(継承を再配置する必要はありません)
Joe

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


-1

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は、この種のものをすべて単独で処理する必要があります。
Nathan Fellman 2013

受け入れられた回答をもう一度読んでください。Pythonでは、関数を呼び出すまで定義する必要はありません。定義で使用するだけではありません。
tacaswell 2013

-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 defined1行目にエラーが表示されます。これについて説明できますか?
Bugs Buggy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.