web-dev-qa-db-ja.com

Pythonではラムダ関数とネスト関数( 'def')のどちらを使用する方が望ましいですか?

私は主にラムダ関数を使用しますが、時々同じ動作を提供するように見えるネストされた関数を使用します。

以下は、別の関数内で見つかった場合に機能的に同じことを行う簡単な例です。

ラムダ関数

>>> a = lambda x : 1 + x
>>> a(5)
6

入れ子関数

>>> def b(x): return 1 + x

>>> b(5)
6

どちらか一方を使用する利点はありますか? (パフォーマンス?可読性?制限?一貫性?など)

それも重要ですか?そうでない場合、Pythonの原則に違反しています:

「それを行うための明白な方法は1つ(できれば1つだけ)が必要です」

89
Ray Vega

lambdaを名前に割り当てる必要がある場合は、代わりにdefを使用してください。 defsは割り当ての単なる構文上のシュガーなので、結果は同じであり、はるかに柔軟で読みやすいです。

lambdasは、一度だけ使用し、名前のない関数を捨てる関数に使用できます。

ただし、このユースケースは非常にまれです。名前のない関数オブジェクトを渡す必要はほとんどありません。

ビルトインmap()およびfilter()は関数オブジェクトを必要としますが、リスト内包表記および生成式は一般にそれらの関数より読みやすく、ラムダを必要とせずに、すべてのユースケースをカバーします。

本当に小さな関数オブジェクトが必要な場合は、operator.addの代わりにlambda x, y: x + yのようなoperatorモジュール関数を使用する必要があります

まだカバーされていないlambdaが必要な場合は、読みやすくするためにdefを書くことを検討してください。関数がoperatorモジュールの関数よりも複雑な場合、defの方がおそらく優れています。

そのため、実際の良いlambdaユースケースは非常にまれです。

91
nosklo

実際には、私には2つの違いがあります。

最初は、彼らが何をし、何を返すかについてです。

  • defは何も返さないキーワードで、ローカル名前空間に「名前」を作成します。

  • lambdaは、関数オブジェクトを返すキーワードであり、ローカル名前空間に「名前」を作成しません。

したがって、関数オブジェクトを取る関数を呼び出す必要がある場合、pythonコードの1行でそれを行う唯一の方法はラムダを使用することです。defと同等の方法はありません。

一部のフレームワークでは、これは実際には非常に一般的です。例えば、私は Twisted をたくさん使っているので、

d.addCallback(lambda result: setattr(self, _someVariable, result))

は非常に一般的であり、ラムダにより簡潔です。

2番目の違いは、実際の機能が許可されていることです。

  • 'def'で定義された関数には、任意のpythonコードを含めることができます
  • 'lambda'で定義された関数は式に評価される必要があるため、print、import、raiseなどのステートメントを含めることはできません...

例えば、

def p(x): print x

期待どおりに動作しますが、

lambda x: print x

はSyntaxErrorです。

もちろん、回避策があります-printsys.stdout.writeに、またはimport__import__に置き換えてください。ただし、通常は、その場合は関数を使用する方が適切です。

このインタビューでは Guido van Rossumは、「lambda」をPythonに入れたくないと言います。

Q。Pythonで一番嫌いな機能は何ですか?

私は時々投稿を受け入れるのが速すぎて、後でそれが間違いだと気付きました。 1つの例は、ラムダ関数などの関数型プログラミング機能の一部です。 lambdaは、小さな匿名関数を作成できるキーワードです。 map、filter、reduceなどの組み込み関数は、リストなどのシーケンスタイプに対して関数を実行します。

実際には、うまくいきませんでした。 Pythonには、ローカルとグローバルの2つのスコープしかありません。ラムダが定義されたスコープ内の変数にアクセスしたいことが多いため、ラムダ関数の作成が苦痛になりますが、これを回避する方法はありますが、ややこしいものです。多くの場合、Pythonでは、ラムダ関数をいじるのではなくforループを使用する方がはるかに簡単です。必要なことを実行する組み込み関数が既に存在する場合のみです。

私見、Iambdasは時々便利ですが、通常は読みやすさを犠牲にして便利です。これが何をするのか教えてください:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

私はそれを書きました、そして、それを理解するのに1分かかりました。これはProject Eulerによるものです-私はネタバレを嫌うので、どの問題を言っているのかはわかりませんが、0.124秒で実行されます:)

21
Chris Lawlor

N = 1000の場合、関数とラムダを呼び出すタイミングがいくつかあります。

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop
9
Andy Hayden

Noskloのアドバイスに同意します。関数に名前を付ける必要がある場合は、defを使用します。コードの短い断片を別の関数に渡すだけの場合のために、lambda関数を予約しています。

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
6
Dan Lenski

パフォーマンス:

lambdaを使用した関数の作成は、defを使用した関数の作成よりもやや高速です。違いは、defがlocalsテーブルに名前エントリを作成するためです。結果の関数の実行速度は同じです。


読みやすさ:

Lambda関数は、ほとんどのPythonユーザーにとって読みにくいですが、状況によってははるかに簡潔です。非機能ルーチンから機能ルーチンへの変換を検討してください。

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

ご覧のとおり、lambdaバージョンは短く、「簡単」です。つまり、元の非機能バージョンにlambda v:を追加するだけで、機能バージョンに変換できます。また、はるかに簡潔です。ただし、多くのPythonユーザーはラムダ構文に混乱するため、長さや実際の複雑さを失ったものは、仲間のコーダーから混乱して取り戻される可能性があります。


制限事項:

  • lambda関数は、変数名に割り当てられていない限り、一度しか使用できません。
  • 変数名に割り当てられたlambda関数は、def関数よりも利点がありません。
  • lambda関数は、ピクルスするのが困難または不可能な場合があります。
  • def関数の名前は、適度に説明的で一意であるか、少なくともスコープ内で未使用になるように慎重に選択する必要があります。

一貫性:

Pythonは、手続き型のより単純な客観的なセマンティクスを優先して、関数型プログラミングの規則をほとんど避けています。 lambda演算子は、このバイアスとは正反対です。さらに、すでに普及しているdefの代替として、lambda関数は構文に多様性を追加します。一貫性が低いと考える人もいます。


既存の機能:

他の人が述べたように、フィールドでのlambdaの多くの使用は、operatorまたは他のモジュールのメンバーに置き換えることができます。例えば:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

既存の関数を使用すると、多くの場合、コードが読みやすくなります。


Pythonの原則:「明白な方法が1つ、できれば1つだけあるべきです」

これは 単一の真実の源 教義に似ています。残念なことに、単一の自明な原則は、真の指導原則というよりも、常にPythonに対する切望的な願望でした。 Pythonの非常に強力な配列内包表記を検討してください。機能的には、mapおよびfilter関数と同等です。

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambdadefは同じです。

意見の問題ではありますが、一般的な使用を意図したPython言語で、明らかに何かを壊さないものは何でも "Pythonic"で十分だと思います。

5
Pi Marillion

他の回答に同意する一方で、読みやすい場合もあります。 lambdaが便利な例です。ユースケースで、N次元 defaultdict に遭遇し続けます。
例を次に示します。

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

2番目の次元にdefを作成するよりも読みやすいと思います。これは、高次元ではさらに重要です。

4
Jonathan

ラムダの主な用途は、常に単純なコールバック関数と、引数として関数を必要とするmap、reduce、filterです。リスト内包表記が標準になり、次のように追加できる場合:

x = [f for f in range(1, 40) if f % 2]

ラムダを日常的に使用する実際のケースを想像するのは困難です。その結果、ラムダを避け、ネストされた関数を作成します。

3
apg

ラムダの重要な制限は、式以外には何も含めることができないことです。ラムダ式がdef 'ed関数ほどリッチなボディを持つことができないため、些細な副作用以外の何かを生成することはほとんど不可能です。

そうは言っても、Luaは私の匿名の関数の広範囲な使用に向けたプログラミングスタイルに影響を与えました。それに加えて、私はmap/reduceをリストの内包表記やジェネレーターを考慮しない方法で抽象演算子と考える傾向があります。ほとんどの場合、それらの演算子を使用して実装決定を明示的に延期する場合と同じです。

編集:これはかなり古い質問であり、この問題に関する私の意見は多少変更されました。

まず、変数にlambda式を割り当てることに強く偏っています。 as pythonはそのための特別な構文を持っています(ヒント、def)。それに加えて、ラムダの多くの用途は、名前を取得しなくても、事前に定義された(より効率的な)実装があります。たとえば、問題の例は(1).__add__に短縮でき、lambdaまたはdefにラップする必要はありません。 operatoritertools、およびfunctoolsモジュールのいくつかの組み合わせで用途を満足させることができます。

より好ましい:ラムダ関数またはネストされた関数(def)?

ラムダを通常の関数(式で作成される)よりも使用することには1つの利点があり、いくつかの欠点があります。そのため、ラムダではなくdefキーワードを使用して関数を作成することを好みます。

最初のポイント-それらは同じタイプのオブジェクトです

ラムダは、通常の関数と同じタイプのオブジェクトになります

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

ラムダは関数であるため、ファーストクラスのオブジェクトです。

ラムダと関数の両方:

  • 引数として渡すことができます(通常の関数と同じ)
  • 外部関数内で作成されると、その外部関数のローカルに対するクロージャーになります

しかし、ラムダにはデフォルトで、関数が完全な関数定義構文を介して取得するものがいくつか欠落しています。

ランバの__name__'<lambda>'です

ラムダは結局のところ匿名関数なので、自分の名前を知りません。

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

したがって、ラムダは名前空間でプログラムで検索できません。

これにより、特定のことが制限されます。たとえば、fooはシリアル化されたコードで検索できますが、lは次のことができません。

>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>: 
attribute lookup <lambda> on __main__ failed

fooは問題なく検索できます-独自の名前を知っているためです:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

ラムダには注釈もドキュメント文字列もありません

基本的に、ラムダは文書化されていません。よりよく文書化するためにfooを書き直しましょう:

def foo() -> int:
    """a nullary function, returns 0 every time"""
    return 0

現在、fooにはドキュメントがあります。

>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:

foo() -> int
    a nullary function, returns 0 every time

一方、ラムダに同じ情報を提供する同じメカニズムはありません。

>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda (...)

しかし、私たちはそれらをハッキングすることができます:

>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda ) -> in
    nullary -> 0

しかし、おそらくヘルプの出力を台無しにするいくつかのエラーがあります。

ラムダは式のみを返すことができます

ラムダは複雑なステートメントを返すことはできず、式のみを返すことができます。

>>> lambda: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

確かに式はかなり複雑になる可能性があり、very hardを試してみると、おそらくラムダで同じことを達成できますが、追加された複雑さは、明確なコードを書くことのより多くの不利益です。

明快さと保守性のためにPythonを使用します。ラムダの過剰使用はそれに対して機能します。

onlyラムダの利点:単一の式で作成可能

これが唯一の可能な利点です。式を使用してラムダを作成できるため、関数呼び出し内でラムダを作成できます。

関数呼び出し内で関数を作成すると、他の場所で作成された名前検索(安価な)を回避できます。

ただし、Pythonは厳密に評価されるため、名前のルックアップを回避することを除いて、パフォーマンスを向上させることはできません。

非常に単純な式の場合、ラムダを選択できます。

また、対話型Pythonを実行するときにラムダを使用する傾向があります。 timeit.repeatを呼び出すときにコンストラクターに引数を渡したい場合、次の種類のコード形式を使用します。

import timeit

def return_nullary_lambda(return_value=0):
    return lambda: return_value

def return_nullary_function(return_value=0):
    def nullary_fn():
        return return_value
    return nullary_fn

そしていま:

>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304

上記のわずかな時間差は、return_nullary_functionでの名前検索に起因すると考えられます。非常に無視できることに注意してください。

結論

ラムダは、特異な点を作るためにコード行を最小限に抑えたい非公式の状況に適しています。

ラムダは、後から来るコードの編集者に明確さを必要とするより公式な状況、特に自明ではない場合には悪いです。

オブジェクトに適切な名前を付けることになっています。オブジェクトにno nameがある場合、どうすればよいでしょうか?

これらのすべての理由から、defではなくlambdaを使用して関数を作成することを好みます。

3
Aaron Hall
  • 計算時間。
  • 名前のない関数。
  • 1つの機能を実現し、多くの機能を使用します。

簡単な例を考えると、

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements
2
bhargav patel

ラムダをローカルスコープ内の変数に割り当てる場合は、defを使用することもできます。これは、読みやすく、将来より簡単に拡張できるためです。

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

または

def fun(a, b): return a ** b # more readable
map(fun, someList)
1
too much php

私が見つけたラムダの用途の1つは、デバッグメッセージです。

ラムダは遅延評価できるため、次のようなコードを使用できます。

log.debug(lambda: "this is my message: %r" % (some_data,))

おそらく高価な代わりに:

log.debug("this is my message: %r" % (some_data,))

現在のログレベルが原因でデバッグ呼び出しが出力を生成しない場合でも、フォーマット文字列を処理します。

もちろん、説明したとおりに動作するためには、使用中のロギングモジュールが「レイジーパラメーター」としてラムダをサポートする必要があります(私のロギングモジュールと同様)。

同じアイデアは、オンデマンドコンテンツの価値を作成するための遅延評価の他のケースにも適用できます。

たとえば、次のカスタム三項演算子:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

の代わりに:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

ラムダでは、条件で選択された式のみが評価され、ラムダでは評価されません。

もちろん、ラムダの代わりに単純に関数を使用することもできますが、短い式ではラムダは(c)学習者です。

1
Glushiator

Noskloに同意します。ちなみに、一度使用、捨てる関数を使用しても、ほとんどの場合、オペレーターモジュールから何かを使用したいだけです。

E.G:

このシグネチャを持つ関数があります:myFunction(data、callback function)。

2つの要素を追加する関数を渡します。

ラムダを使用して:

myFunction(data, (lambda x, y : x + y))

Pythonicの方法:

import operator
myFunction(data, operator.add)

または、これは単純な例ですが、リストおよび辞書用のアイテムセッター/ゲッターなど、オペレーターモジュールが提供するものはたくさんあります。すごくかっこいい。

0
e-satis