Pythonの入れ子関数


83

このようなPythonコードでどのようなメリットや影響が得られるでしょうか。

class some_class(parent_class):
    def doOp(self, x, y):
        def add(x, y):
            return x + y
        return add(x, y)

私はこれをオープンソースプロジェクトで見つけました。ネストされた関数の内部で何か便利なことをしていますが、それを呼び出す以外はまったく何もしていません。(実際のコードはここにあります。)なぜ誰かがこのようにコーディングするのでしょうか?外側の通常の関数ではなく、ネストされた関数内にコードを記述することには、いくつかの利点または副作用がありますか?


4
これはあなたが見つけた実際のコードですか、それともあなたが作成した単純な例ですか?
MAK

これは単純化された例です。実際のコードはここで見つけることができます:bazaar.launchpad.net/%7Eopenerp/openobject-server/trunk/...
Hosamアリー

2
リンク(HEADを指す)が正確ではなくなりました。試してみてください:bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/...
クレイグ・マックイーン

あなたは正しいクレイグです。ありがとうございました。
Hosam Aly

回答:


111

通常、あなたはクロージャを作るためにそれをします:

def make_adder(x):
    def add(y):
        return x + y
    return add

plus5 = make_adder(5)
print(plus5(12))  # prints 17

内部関数は、囲んでいるスコープから変数(この場合はローカル変数x)にアクセスできます。囲んでいるスコープから変数にアクセスしていない場合、それらは実際には異なるスコープを持つ通常の関数にすぎません。


5
そのために私はパーシャルを好むでしょう:plus5 = functools.partial(operator.add, 5)。デコレータはクロージャのより良い例です。
Jochen Ritzel 2009年

4
感謝しますが、私が投稿したスニペットでわかるように、ここではそうではありません。ネストされた関数は、単に外部関数で呼び出されています。
Hosam Aly

57

内部関数の作成がほとんど関数発生器の定義である関数発生器を除いて、ネストされた関数を作成する理由は、読みやすさを向上させるためです。外部関数によってのみ呼び出される小さな関数がある場合は、定義をインライン化するので、その関数が何をしているのかを判断するためにスキップする必要はありません。後日関数を再利用する必要がある場合は、いつでも内部メソッドをカプセル化メソッドの外に移動できます。

おもちゃの例:

import sys

def Foo():
    def e(s):
        sys.stderr.write('ERROR: ')
        sys.stderr.write(s)
        sys.stderr.write('\n')
    e('I regret to inform you')
    e('that a shameful thing has happened.')
    e('Thus, I must issue this desultory message')
    e('across numerous lines.')
Foo()

4
唯一の問題は、内部関数にアクセスできないため、ユニットテストが困難になる場合があることです。
チャレンジャー5

1
彼らはとても素敵に見えます!
テーパー

FWIY @ Challenger5内部関数がプライベートクラス関数に類似している場合、それらはとにかくユニットテストされません。定義を問題のメソッドに近づけながら、メソッドを読みやすくするためにこれを開始しました。
ミッチェルカリー2018年

26

内部メソッドを使用することの潜在的な利点の1つは、外部メソッドのローカル変数を引数として渡さずに使用できることです。

def helper(feature, resultBuffer):
  resultBuffer.print(feature)
  resultBuffer.printLine()
  resultBuffer.flush()

def save(item, resultBuffer):

  helper(item.description, resultBuffer)
  helper(item.size, resultBuffer)
  helper(item.type, resultBuffer)

次のように書くことができますが、これは間違いなく読みやすくなります

def save(item, resultBuffer):

  def helper(feature):
    resultBuffer.print(feature)
    resultBuffer.printLine()
    resultBuffer.flush()

  helper(item.description)
  helper(item.size)
  helper(item.type)

8

私はそのようなコードの正当な理由を想像することはできません。

たぶん、他のOpsのように、古いリビジョンの内部機能には理由がありました。

たとえば、これは少し意味があります。

class some_class(parent_class):
    def doOp(self, op, x, y):
        def add(x, y):
            return x + y
        def sub(x,y):
            return x - y
        return locals()[op](x,y)

some_class().doOp('add', 1,2)

ただし、内部関数は代わりに( "private")クラスメソッドである必要があります。

class some_class(object):
    def _add(self, x, y):
        return x + y
    def doOp(self, x, y):
        return self._add(x,y)

うん、多分doOpは...引数に使用する演算子を指定した文字列を取った
Skilldrick

1
@ArtOfWarfareは、単にモジュールを使用することをお勧めします。クラスは州向けです。
aehlke 2013年

6

ローカルメソッドの背後にある考え方は、ローカル変数に似ています。より大きな名前空間を汚染しないでください。ほとんどの言語はそのような機能を直接提供しないため、明らかに利点は限られています。


1

コードがまさにこのようなものでしたか?このようなことを行う通常の理由は、パーシャル(ベイクインされたパラメーターを持つ関数)を作成するためです。外部関数を呼び出すと、パラメーターを必要としないcallableが返されるため、パラメーターを渡すことができない場所に保存して使用できます。ただし、投稿したコードはそれを行いません。呼び出し可能ではなく、関数をすぐに呼び出して結果を返します。あなたが見た実際のコードを投稿することは役に立つかもしれません。


1
質問へのコメントに元のコードへのリンクを追加しました。ご覧のとおり、私の例は単純化された例ですが、それでもほとんど同じです。
Hosam Aly
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.