関数Aが関数Bでのみ必要な場合、AをB内で定義する必要がありますか?[閉まっている]


147

簡単な例。2つのメソッド、1つは別のメソッドから呼び出されます。

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

Pythonでは、def別の内部で宣言できdefます。それで、にmethod_bのみ必要でありmethod_a、からのみ呼び出される場合、method_b内部で宣言する必要がありmethod_aますか?このような :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

それとも私はこれを避けるべきですか?


7
本当にファンキーなことをしているのでなければ、別の関数の内部で関数を定義する必要はありません。ただし、何をしようとしているのか詳しく説明してください。より役立つ回答を提供できます
inspectorG4dget

6
電話 をかけないので、2番目の例が違うことに気づきましたmethod_bか?(@inspector:厳密に言えば必要がありますが、関数型プログラミング、特にクロージャーに少し入ったときに非常に役立ちます)。

3
@delnan:「厳密に言えば、必要ありませんが...」という意味だったと思います
martineau '29年

4
内部関数の使用例はリンクで見事に要約されていますhttps : //realpython.com/blog/python/inner-functions-what-are-they-good-for/。使用がいずれのケースにも当てはまらない場合は、避けてください。

1
すばらしい質問ですが、本当の答えはありません...
Mayou36

回答:


136
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

これはあなたが探していたものですか?それはクロージャーと呼ばます。


14
これはもっと良い説明です。私は私の答えを削除しました
pyfunc '28年

なぜdef sum(x、y)を実行しないのですか:x + yを返しますか?
マンゴー2014年

4
@mango:これは概念を伝えるための単なるおもちゃの例です。実際の使用do_it()では、単一のreturnステートメントで演算によって処理できるものよりもおそらく少し複雑になります。
martineau 14

2
@mango少し便利な例で質問に回答しました。stackoverflow.com/a/24090940/2125392
CivFan

10
質問の答えにはなりません。
Hunsu、2015年

49

これを実行しても、それほど多くのことは得られません。実際method_aには、呼び出されるたびに他の関数を定義して再コンパイルするため、速度が低下します。それを考えると、関数名の前にアンダースコアを付けるだけで、プライベートメソッドであることを示す方がよいでしょう_method_b

ネストされた関数の定義が何らかの理由で毎回異なる場合は、これを実行したいと思うかもしれませんが、それは設計の欠陥を示している可能性があります。といえ、ネストされた関数が外部関数に渡されたが明示的に渡されなかった引数を使用できるようにするためには、これを行う正当な理由あります。これは、デコレータが定義または使用されていないにもかかわらず、受け入れられた回答に示されているものです。

更新:

これらの入れ子が遅い(Python 3.6.1を使用)ことの証明は次のとおりです。

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

self実際のメソッドのようにするために、サンプル関数にいくつかの引数を追加しました(ただし、method_b2技術的にはTestクラスのメソッドではありません)。また、ネストされた関数は実際にはそのバージョンでは呼び出されません。


21
実際には、外部関数が呼び出されるたびに内部関数が完全にコンパイルされるわけではありませんが、関数オブジェクトを作成する必要があるため、少し時間がかかります。一方、関数名はグローバルではなくローカルになるため、関数を呼び出す方が高速です。私の試用では、時間的には、ほとんどの場合基本的に洗浄です。何度も呼び出すと、内部関数の方が高速になる場合があります。
キンドル

7
ええ、内部関数を複数回呼び出す必要があります。ループで呼び出す場合、またはほんの数回以上呼び出す場合は、関数にローカル名を付けることの利点は、関数を作成するコストを上回ります。私の試験では、これは内部関数を3〜4回呼び出すときに発生します。もちろん、関数にローカル名を定義することで、同じコスト(ほとんど同じくらいのコストなし)を得ることができます。たとえばmethod_b = self._method_b、呼び出しmethod_bて、繰り返される属性ルックアップを回避します。(私は最近、もののタイミングのLOTを
たくさんし

3
@kindall:はい、そうです。タイミングテストを変更して、2次関数を30回呼び出し、結果が反転しました。この返信を見る機会があったら、私は私の答えを削除します。悟りをありがとう。
martineau

2
それでも有益な回答なので、-1を説明したいと思います。これは、わずかなパフォーマンスの違いに焦点を当てているため、-1を与えました(ほとんどのシナリオでは、コードオブジェクトの作成は、関数の実行時間の一部しかかかりません。 )。これらの場合に考慮すべき重要なことは、インライン関数を使用するとコードの可読性と保守性が向上するかどうかです。関連するコードを見つけるためにファイルを検索/スクロールする必要がないため、多くの場合そうなります。
Blixt

2
@Blixt:同じクラスの別のメソッドがネストされていなくても、同じクラスの別のメソッドから非常に「遠い」可能性が非常に低いため(そして可能性が非常に低いため)、ロジックに欠陥がある(そして不当に反対投票する)と思います別のファイルにあることを確認してください)。また、私の答えの最初の文は「これを実行してもそれほど多くの利益を得られない」であり、これは些細な違いだと指摘しています。
martineau 2015年

27

関数内の関数は、一般的にクロージャに使用されます。

(がある競合の多くは、上で正確に何を作る閉鎖閉鎖。)

組み込みのを使用した例を次に示しsum()ます。それはstart一度定義し、それ以降それを使用します:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

使用中で:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

組み込みのPythonクロージャー

functools.partial クロージャの例です。

python docsから、それはおおよそ次と同等です:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(回答については、下記の@ user225312を称賛してください。この例はわかりやすいと思います。うまくいけば、@ mangoのコメントへの回答にも役立ちます。)


-1はい、それらは一般的にクロージャとして使用されます。質問をもう一度読んでください。彼は基本的に、彼が示す概念をケースbに使用できるかどうか尋ねました。これはケースaによく使用されることを彼に伝えることは悪い答えではなく、この質問に対しては間違った答えです。例えば、カプセル化のために上記を行うことが良いことかどうか彼は興味を持っています。
Mayou36

@ Mayou36公平にするために、質問はかなり自由回答式です-可能なすべてのケースに答えようとするのではなく、私は1つに焦点を合わせるのが最善だと思いました。また、質問はあまり明確ではありません。たとえば、2番目の例では、それが意図したものではない可能性があるとしても、それは閉鎖を意味します。
CivFan 2017

@ Mayou36「彼は基本的に、彼が示す概念をケースbに使用できるかどうか尋ねました。」それは場合は、[いいえ、質問を尋ねる必要があります使用すること。OPは使用できることをすでに認識しています。
CivFan 2017

1
「method_bが必要で、method_aからのみ呼び出される場合、method_a内でmethod_bを宣言する必要がありますか?」非常に明確で、クロージャーとは関係ありませんよね?はい、私は私が使用し、同意缶の方法の中ですべき。しかし、それはクロージャーとは関係ありません... OPが完全に異なる質問をしたとき、クロージャーとクロージャーについての多くの回答が驚いただけです。
Mayou36

@ Mayou36質問の見方を変え、別の質問を開いて対処することが役立つ場合があります。
CivFan 2017

17

一般的に、いいえ、関数内で関数を定義しないでください。

本当に正当な理由がない限り。あなたはそうしません。

何故なの?

関数内で関数を定義する本当に良い理由何ですか?

何をすると、実際にしたいことはdingdangのある閉鎖


1
この答えはあなたのためです@ Mayou36。
CivFan 2017

2
はい、ありがとうございます。これが私が探していた答えです。これは、可能な限り最良の方法で質問に答えます。どちらか一方を厳密に言っているわけではありませんが、理由を述べることでインライン定義を大幅に落とします。これが(pythonic :))の答えです!
Mayou36

10

ある関数を別の関数の内部で宣言することは実際には問題ありません。これは、デコレータの作成に特に役立ちます。

ただし、経験則として、関数が複雑な場合(10行を超える場合)、モジュールレベルで宣言することをお勧めします。


2
それは可能ですが、同意するに違いありません。クラス内でのみ使用することを目的とした関数に先行するアンダースコアを使用する方が、より多くのpythonになります。
chmullig '28年

3
クラス内ではありますが、関数内だけについてはどうですか?カプセル化は階層的です。
Paul Draper

@PaulDraper「カプセル化は階層的」-いいえ!誰が言ったの?カプセル化は、単なる継承よりもはるかに広範なプリンシパルです。
Mayou36

7

私がこの質問を見つけたのは、ネストされた関数を使用するとパフォーマンスに影響が出る理由を質問したかったからです。クワッドコア2.5 GHz Intel i5-2530Mプロセッサを搭載したWindowsノートブックでPython 3.2.5を使用して以下の機能のテストを実行しました

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

次の20回を、square1、square2、square5についても測定しました。

s=0
for i in range(10**6):
    s+=square0(i)

次の結果を得た

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0入れ子関数がなく、square11つの入れ子関数があり、square22つの入れ子関数があり、square55つの入れ子関数があります。ネストされた関数は宣言されるだけで、呼び出されません。

したがって、呼び出さない関数で5つのネストされた関数を定義した場合、関数の実行時間は、ネストされた関数がない場合の2倍になります。入れ子関数を使用する場合は注意が必要です。

この出力を生成するテスト全体のPythonファイルは、ideoneにあります。


5
あなたが行う比較は本当に役に立ちません。これは、関数にダミーのステートメントを追加して、遅いと言っているようなものです。martineauの例は実際にカプセル化された関数を使用しており、彼の例を実行してもパフォーマンスの違いはわかりません。
kon psych 2015

-1質問をもう一度読んでください。少し遅いかもしれませんが、主にパフォーマンスのためではなく、一般的な練習のために、それを行うのは良い考えかどうか尋ねました。
Mayou36

@ Mayou36申し訳ありませんが、質問と回答が投稿されてから3年が経過しました。
miracle173

申し訳ありませんが、残念ながら、まだ有効な回答はありません。
Mayou36

4

これは、公開APIに関する原則にすぎません。

Pythonを使用して、外部空間(モジュールまたはクラス)でAPIを公開しないようにすることをお勧めします。関数はカプセル化に適した場所です。

それは良い考えかもしれません。確認すると

  1. 内部関数は外部関数でのみ使用されます。
  2. インサイダー関数は、コードが語るので、その目的を説明するのに良い名前を持っています。
  3. コードは、同僚(または他のコードリーダー)が直接理解することはできません。

とはいえ、この悪用は問題を引き起こす可能性があり、設計上の欠陥を意味します。

ちょうど私の経験から、多分あなたの質問を誤解しています。


4

したがって、最終的には、特に内部関数がクロージャーではなく、単に関数内でヘルパーのみが必要な場合は、Python実装がどれほど賢いかについての問題です。

モジュール、メソッドとしてのクラス、または別の関数やメソッドの内部に埋め込まれるかどうかに関係なく、必要な場所にのみ関数があり、他の場所に公開されていない、きれいに理解できるデザインでは、優れたデザインです。うまくやると、コードの明快さが本当に向上します。

そして、内部関数がクロージャーである場合、その関数が他の場所で使用するために包含関数から返されない場合でも、明確さをかなり助けることができます。

したがって、一般的にはそれらを使用すると言いますが、実際にパフォーマンスに懸念がある場合はパフォーマンスヒットの可能性があることに注意し、削除するのが最も良いことを示す実際のプロファイリングを行う場合にのみ削除してください。

作成するすべてのPythonコード全体で「内部関数BAD」を使用するだけでは、時期尚早の最適化を行わないでください。お願いします。


他の回答で示されているように、実際にはパフォーマンスへの影響はありません。
Mayou36

1

その方法でそれを行うのはまったく問題ありませんが、クロージャーを使用したり、関数を返す必要がない限り、おそらくモジュールレベルに配置します。私はあなたが意味する2番目のコード例を想像します:

...
some_data = method_b() # not some_data = method_b

それ以外の場合は、some_dataが関数になります。

モジュールレベルで使用すると、他の関数でmethod_b()を使用できるようになり、ドキュメントにSphinx(およびautodoc)などを使用している場合は、method_bもドキュメント化できます。

また、オブジェクトで表現できるものを実行する場合は、機能をクラスの2つのメソッドに配置することを検討することもできます。それがあなたが探しているすべてであるならば、これはロジックもうまく含みます。


1

次のようなことをしてください:

def some_function():
    return some_other_function()
def some_other_function():
    return 42 

実行するsome_function()としたら実行されますsome_other_function()て42を返します。

編集:私は最初にあなたが別の中で関数を定義するべきではないと述べましたが、これを時々行うことが実用的であることが指摘されました。


私はあなたが彼らの答えに力を注いだ上記の努力に感謝しますが、あなたの努力は率直で的を射ていました。いいね。
C0NFUS3D 2015

1
どうして?なぜそんなことをしてはいけないのですか?それは素晴らしいカプセル化ではありませんか?引数がありません。-1
Mayou36

@ Mayou36私がコメントを書いた時点では、どのようなカプセル化が行われていたのかわかりませんでした。やるのは良くないと思いました。関数を外部で定義するだけでなく、内部で定義することが有益である理由を説明できますか?
mdlp0716 2017

2
はい、できます。カプセル化の概念を調べることもできますが、簡単に言うと、不要な情報を非表示にし、知る必要があるものだけをユーザーに公開します。つまり、外部でsome_other_functionを定義すると、名前空間に何かが追加され、実際には最初の関数に密接にバインドされます。または、変数の観点から考えます。なぜローカル変数とグローバル変数が必要なのですか?関数内のすべての変数を定義することは、可能であれば、この1つの関数内でのみ使用される変数にグローバル変数を使用するよりも優れています。最終的には、複雑さを軽減することがすべてです。
Mayou36

0

これを使用して、グローバル変数の定義を回避できます。これにより、他の設計の代替案が得られます。問題の解決策を提示する3つの設計。

A)グローバルなしの関数の使用

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

B)グローバルで関数を使用する

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C)別の関数内で関数を使用する

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

ソリューションC)では、内部関数で変数を宣言する必要なく、外部関数のスコープで変数を使用できます。状況によっては役立つ場合があります。


0

関数python関数

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.