أيهما أفضل للاستخدام في بايثون:وظائف لامدا أو وظائف متداخلة ("def")؟

StackOverflow https://stackoverflow.com/questions/134626

سؤال

أستخدم في الغالب وظائف لامدا ولكن أحيانًا أستخدم وظائف متداخلة يبدو أنها توفر نفس السلوك.

فيما يلي بعض الأمثلة التافهة حيث تقوم بنفس الشيء وظيفيًا إذا تم العثور عليها ضمن وظيفة أخرى:

وظيفة لامدا

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

وظيفة متداخلة

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

>>> b(5)
6

هل هناك مزايا لاستخدام واحد على الآخر؟(أداء؟مقروئية؟محددات؟تناسق؟إلخ.)

هل يهم حتى؟إذا لم يكن الأمر كذلك، فهذا ينتهك مبدأ Pythonic:

"يجب أن تكون هناك طريقة واحدة واضحة للقيام بذلك، ويفضل أن تكون واحدة فقط".

هل كانت مفيدة؟

المحلول

إذا كنت بحاجة إلى تعيين lambda إلى الاسم، استخدم أ def بدلاً من. defs هي مجرد سكر نحوي لمهمة ما، وبالتالي فإن النتيجة هي نفسها، وهي أكثر مرونة وسهولة في القراءة.

lambdaيمكن استخدامها ل استخدام مرة واحدة، ورمي بعيدا الوظائف التي لن يكون لها اسم.

ومع ذلك، فإن حالة الاستخدام هذه نادرة جدًا.نادرًا ما تحتاج إلى تمرير كائنات دالة غير مسماة.

البناء map() و filter() تحتاج كائنات وظيفة، ولكن قائمة الفهم و تعبيرات المولد تكون عمومًا أكثر قابلية للقراءة من تلك الوظائف ويمكنها تغطية جميع حالات الاستخدام، دون الحاجة إلى استخدام لامدا.

بالنسبة للحالات التي تحتاج فيها حقًا إلى كائن دالة صغير، يجب عليك استخدام operator وظائف الوحدة النمطية، مثل operator.add بدلاً من lambda x, y: x + y

إذا كنت لا تزال بحاجة إلى بعض lambda لم تتم تغطيتها، قد تفكر في كتابة أ def, ، فقط لتكون أكثر قابلية للقراءة.إذا كانت الوظيفة أكثر تعقيدًا من تلك الموجودة في operator الوحدة، أ def ربما يكون أفضل.

لذا، العالم الحقيقي جيد lambda حالات الاستخدام نادرة جدًا.

نصائح أخرى

من الناحية العملية، بالنسبة لي هناك اختلافان:

الأول: ما يفعلونه وما يرجعون:

  • def هي كلمة أساسية لا تُرجع أي شيء وتقوم بإنشاء "اسم" في مساحة الاسم المحلية.

  • lambda هي كلمة أساسية تقوم بإرجاع كائن دالة ولا تنشئ "اسمًا" في مساحة الاسم المحلية.

وبالتالي، إذا كنت بحاجة إلى استدعاء دالة تأخذ كائن دالة، فإن الطريقة الوحيدة للقيام بذلك في سطر واحد من كود بايثون هي باستخدام لامدا.لا يوجد ما يعادلها مع def.

في بعض الأطر، يعد هذا أمرًا شائعًا جدًا؛على سبيل المثال، أنا استخدم ملتوية الكثير، وهكذا تفعل شيئا من هذا القبيل

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

شائع جدًا، وأكثر إيجازًا مع لامدا.

والفرق الثاني يتعلق بما يُسمح للوظيفة الفعلية بالقيام به.

  • يمكن أن تحتوي الوظيفة المحددة بـ "def" على أي كود بايثون
  • يجب أن يتم تقييم الدالة المعرفة بـ "lambda" إلى تعبير، وبالتالي لا يمكن أن تحتوي على عبارات مثل print أو import أو rise أو ...

على سبيل المثال،

def p(x): print x

يعمل كما هو متوقع، في حين

lambda x: print x

هو خطأ في بناء الجملة.

وبطبيعة الحال، هناك حلول بديلة - بديل print مع sys.stdout.write, ، أو import مع __import__.ولكن عادةً ما يكون من الأفضل لك استخدام وظيفة ما في هذه الحالة.

في هذه المقابلة، يقول جويدو فان روسوم إنه يتمنى لو لم يسمح لـ "lambda" بالدخول إلى بايثون:

"س.ما هي ميزة بايثون التي لا ترضيك على الإطلاق؟

في بعض الأحيان كنت أتسرع في قبول المساهمات، وأدركت لاحقًا أن ذلك كان خطأً.أحد الأمثلة على ذلك هو بعض ميزات البرمجة الوظيفية، مثل وظائف لامدا.لامدا هي كلمة أساسية تتيح لك إنشاء وظيفة مجهولة صغيرة؛تعمل الوظائف المضمنة مثل الخريطة والتصفية والتقليل على تشغيل وظيفة عبر نوع تسلسل، مثل القائمة.

ومن الناحية العملية، لم يسير الأمر على ما يرام.لدى بايثون نطاقان فقط:المحلية والعالمية.وهذا يجعل كتابة وظائف لامدا أمرًا مؤلمًا، لأنك غالبًا ما ترغب في الوصول إلى المتغيرات في النطاق الذي تم تعريف لامدا فيه، ولكن لا يمكنك ذلك بسبب النطاقين.هناك طريقة للتغلب على هذا، لكنه شيء من الحماقة.غالبًا ما يبدو من الأسهل في لغة بايثون استخدام حلقة for بدلاً من العبث بوظائف لامدا.تعمل الخريطة والأصدقاء بشكل جيد فقط عندما تكون هناك وظيفة مضمنة بالفعل تقوم بما تريد.

IMHO، يمكن أن يكون Iambdas مناسبًا في بعض الأحيان، ولكنه عادةً ما يكون مناسبًا على حساب سهولة القراءة.هل يمكن أن تخبرني ماذا يفعل هذا:

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

لقد كتبت ذلك، واستغرق مني دقيقة لمعرفة ذلك.هذا من مشروع أويلر - لن أذكر أي مشكلة لأنني أكره حرق المعلومات، لكنه يتم تنفيذه خلال 0.124 ثانية :)

بالنسبة إلى 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

وأنا أتفق مع نصيحة nosklo:إذا كنت بحاجة إلى إعطاء اسم للوظيفة، استخدم def.أنا أحجز lambda وظائف للحالات التي أقوم فيها فقط بتمرير مقتطف مختصر من التعليمات البرمجية إلى وظيفة أخرى، على سبيل المثال:

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

أداء:

إنشاء وظيفة مع lambda يكون أسرع قليلا من إنشائه مع def.الفرق يرجع إلى def إنشاء إدخال اسم في جدول السكان المحليين.الوظيفة الناتجة لها نفس سرعة التنفيذ.


مقروئية:

تعد وظائف 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: إلى الإصدار الأصلي غير الوظيفي للتحويل إلى الإصدار الوظيفي.إنها أيضًا أكثر إيجازًا.لكن تذكر أن الكثير من مستخدمي بايثون سوف يرتبكون بسبب بناء جملة لامدا، لذا فإن ما تخسره من حيث الطول والتعقيد الحقيقي قد يتم استعادته مرة أخرى في حالة من الارتباك من زملائك المبرمجين.


محددات:

  • lambda يمكن استخدام الوظائف مرة واحدة فقط، ما لم يتم تعيينها لاسم متغير.
  • lambda الوظائف المخصصة لأسماء المتغيرات ليس لها أي ميزة def المهام.
  • lambda يمكن أن تكون الوظائف صعبة أو مستحيلة المخلل.
  • def يجب أن يتم اختيار أسماء الوظائف بعناية لتكون وصفية وفريدة من نوعها بشكل معقول أو على الأقل غير مستخدمة في النطاق.

تناسق:

تتجنب بايثون في الغالب اصطلاحات البرمجة الوظيفية لصالح دلالات موضوعية إجرائية وأبسط.ال lambda يقف المشغل في تناقض مباشر مع هذا التحيز.علاوة على ذلك، كبديل لما هو سائد بالفعل def, ، ال lambda تضيف الوظيفة التنوع إلى بناء الجملة الخاص بك.قد يعتبر البعض ذلك أقل اتساقا.


الوظائف الموجودة مسبقًا:

كما لاحظ آخرون، العديد من الاستخدامات lambda في الميدان يمكن استبداله بأعضاء operator أو وحدات أخرى.على سبيل المثال:

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

يمكن أن يؤدي استخدام الوظيفة الموجودة مسبقًا إلى جعل التعليمات البرمجية أكثر قابلية للقراءة في كثير من الحالات.


مبدأ البايثونية:"يجب أن تكون هناك طريقة واحدة واضحة للقيام بذلك، ويفضل أن تكون واحدة فقط"

وهذا مشابه ل مصدر واحد للحقيقة عقيدة.لسوء الحظ، كان مبدأ "الطريقة الوحيدة الواضحة للقيام بذلك" دائمًا بمثابة طموح حزين لبايثون، وليس مبدأ توجيهيًا حقيقيًا.فكر في فهم المصفوفة القوية جدًا في بايثون.وهي مكافئة وظيفيا ل map و filter المهام:

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

lambda و def هي نفسها.

إنها مسألة رأي، ولكن أود أن أقول إن أي شيء في لغة بايثون مخصص للاستخدام العام والذي لا يكسر أي شيء بشكل واضح هو "بايثوني" بدرجة كافية.

على الرغم من الاتفاق مع الإجابات الأخرى، إلا أنه في بعض الأحيان يكون أكثر قابلية للقراءة.وهنا مثال حيث lambda يكون مفيدًا، في حالة الاستخدام، أواجه دائمًا بُعدًا N defaultdict.
هنا مثال:

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

أجد أنه أكثر قابلية للقراءة من إنشاء ملف def للبعد الثاني .وهذا أكثر أهمية بالنسبة للأبعاد الأعلى.

كان الاستخدام الأساسي لامدا دائمًا هو وظائف رد الاتصال البسيطة، وللخريطة والتقليل والتصفية، والتي تتطلب وظيفة كوسيطة.مع أن فهم القائمة أصبح هو القاعدة، والإضافة المسموح بها كما في:

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

من الصعب تخيل حالة حقيقية لاستخدام لامدا في الاستخدام اليومي.ونتيجة لذلك، أود أن أقول، تجنب لامدا وإنشاء وظائف متداخلة.

من القيود المهمة على lambdas أنها لا يمكن أن تحتوي على أي شيء غير التعبير.يكاد يكون من المستحيل أن تنتج تعبيرات لامدا أي شيء بخلاف الآثار الجانبية التافهة، حيث لا يمكن أن يكون لها جسم غني مثل جسم defوظيفة إد.

ومع ذلك، فقد أثرت Lua في أسلوب برمجتي نحو الاستخدام المكثف للوظائف المجهولة، وأقوم بتوزيع الكود الخاص بي بها.علاوة على ذلك، أميل إلى التفكير في الخريطة/التقليل كعوامل تشغيل مجردة بطرق لا أعتبرها عبارة عن فهم القائمة أو المولدات، كما لو كنت أقوم بتأجيل قرار التنفيذ بشكل صريح باستخدام تلك العوامل.

يحرر: هذا سؤال قديم جدًا، وقد تغيرت آرائي حول هذا الموضوع إلى حد ما.

أولاً، أنا متحيز بشدة ضد تعيين أ lambda التعبير إلى متغير؛نظرًا لأن python لديه بناء جملة خاص لذلك (تلميح، def).بالإضافة إلى ذلك، فإن العديد من استخدامات lambda، حتى عندما لا تحصل على اسم، لها تطبيقات محددة مسبقًا (وأكثر كفاءة).على سبيل المثال، يمكن اختصار المثال المعني إلى just (1).__add__, ، دون الحاجة إلى لفه في lambda أو def.يمكن تلبية العديد من الاستخدامات الشائعة الأخرى بمزيج من operator, itertools و functools وحدات.

أكثر تفضيلا:وظائف لامدا أو وظائف متداخلة (def)?

هناك ميزة واحدة لاستخدام لامدا على وظيفة عادية (يتم إنشاؤها في تعبير)، والعديد من العيوب.لهذا السبب، أفضّل إنشاء وظائف باستخدام ملف def الكلمة الأساسية بدلاً من مع lambdas.

النقطة الأولى - إنهم نفس النوع من الكائنات

تنتج دالة لامدا نفس نوع الكائن كدالة عادية

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

نظرًا لأن لامدا هي وظائف، فهي كائنات من الدرجة الأولى.

كل من لامدا والوظائف:

  • يمكن تمريرها كوسيطة (مثل وظيفة عادية)
  • عندما يتم إنشاؤها داخل وظيفة خارجية تصبح بمثابة إغلاق على السكان المحليين لتلك الوظائف الخارجية

لكن لامدا، بشكل افتراضي، تفتقد بعض الأشياء التي تحصل عليها الوظائف من خلال بناء جملة تعريف الوظيفة الكاملة.

لامبا __name__ يكون '<lambda>'

Lambdas هي وظائف مجهولة المصدر، لذا فهي لا تعرف اسمها.

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

وبالتالي لا يمكن البحث عن lambda برمجيًا في مساحة الاسم الخاصة بهم.

وهذا يحد من أشياء معينة.على سبيل المثال، 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>

لا تحتوي Lambdas على تعليقات توضيحية ولا مستندات

في الأساس، لم يتم توثيق لامدا.دعونا نعيد الكتابة 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

حيث أننا لا نملك نفس الآلية لإعطاء نفس المعلومات إلى lambdas:

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

ولكن من المحتمل أن يكون هناك خطأ ما في إفساد مخرجات المساعدة.

يمكن لـ Lambdas إرجاع تعبير فقط

لا تستطيع Lambdas إرجاع بيانات معقدة، بل تعبيرات فقط.

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

من المسلم به أن التعبيرات يمكن أن تكون معقدة إلى حد ما، وإذا حاولت جداً من الصعب أن تفعل الشيء نفسه باستخدام لامدا، ولكن التعقيد الإضافي يضر أكثر بكتابة تعليمات برمجية واضحة.

نحن نستخدم لغة بايثون من أجل الوضوح وقابلية الصيانة.الإفراط في استخدام لامدا يمكن أن يعمل ضد ذلك.

ال فقط الاتجاه الصعودي لامدا:يمكن إنشاؤها في تعبير واحد

وهذا هو الاتجاه الصعودي الوحيد الممكن.نظرًا لأنه يمكنك إنشاء تعبير لامدا باستخدام تعبير، فيمكنك إنشاؤه داخل استدعاء دالة.

يؤدي إنشاء دالة داخل استدعاء دالة إلى تجنب البحث عن الاسم (غير المكلف) مقابل البحث الذي تم إنشاؤه في مكان آخر.

ومع ذلك، بما أن بايثون يتم تقييمها بشكل صارم، فلا يوجد أي مكاسب أخرى في الأداء للقيام بذلك بخلاف تجنب البحث عن الاسم.

للحصول على تعبير بسيط جدًا، قد أختار لامدا.

أميل أيضًا إلى استخدام lambdas عند التعامل مع لغة 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 - لاحظ أنه كذلك جداً ضئيلة.

خاتمة

تعد Lambdas مفيدة للمواقف غير الرسمية حيث تريد تقليل سطور التعليمات البرمجية لصالح توضيح نقطة واحدة.

تعتبر Lambdas سيئة في المواقف الأكثر رسمية حيث تحتاج إلى توضيح لمحرري التعليمات البرمجية الذين سيأتون لاحقًا، خاصة في الحالات التي تكون فيها هذه الأمور غير تافهة.

نحن نعلم أنه من المفترض أن نعطي الأشياء أسماء جيدة.كيف يمكننا أن نفعل ذلك عندما يكون الكائن لا اسم؟

لكل هذه الأسباب، أفضل إنشاء وظائف باستخدام def بدلا من مع lambda.

  • وقت الحساب.
  • وظيفة بدون اسم.
  • لتحقيق وظيفة واحدة والعديد من وظائف الاستخدام.

وبالنظر إلى مثال بسيط،

# 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

إذا كنت ستقوم فقط بتعيين lambda لمتغير في النطاق المحلي، فيمكنك أيضًا استخدام 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)

لقد وجدت استخدامًا واحدًا للامدا ...موجود في رسائل التصحيح.

نظرًا لأنه يمكن تقييم lambdas بتكاسل، فيمكنك الحصول على رمز مثل هذا:

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)

مع lambdas سيتم تقييم التعبير المحدد بواسطة الشرط فقط، وبدون lambdas سيتم تقييم كليهما.

بالطبع يمكنك ببساطة استخدام الدوال بدلاً من لامدا، ولكن بالنسبة للتعبيرات القصيرة، تكون لامدا (ج) أصغر حجمًا.

وأنا أتفق مع نوسكلو.بالمناسبة، حتى مع أ استخدام مرة واحدة، ورمي بعيدا الوظيفة، في معظم الأوقات تريد فقط استخدام شيء ما من وحدة المشغل.

على سبيل المثال :

لديك وظيفة بهذا التوقيع:myFunction(البيانات، وظيفة رد الاتصال).

تريد تمرير دالة تضيف عنصرين.

باستخدام لامدا:

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

الطريقة البايثونية :

import operator
myFunction(data, operator.add)

أو بالطبع هذا مثال بسيط، ولكن هناك الكثير من الأشياء التي توفرها وحدة المشغل، بما في ذلك أدوات ضبط العناصر/حروف القائمة والإملاء.رائع حقا.

لامدا مفيد لتوليد وظائف جديدة:

def somefunc(x): return lambda y: x+y
f = somefunc(10)
f(2)
>>> 12
f(4)
>>> 14

والفرق الرئيسي هو أنه لا يمكنك استخدامها def وظائف مضمنة، والتي هي في رأيي حالة الاستخدام الأكثر ملاءمة لـ a lambda وظيفة.على سبيل المثال، عند فرز قائمة من الكائنات:

my_list.sort(key=lambda o: o.x)

لذلك أقترح الإبقاء على استخدام lambdas في هذا النوع من العمليات التافهة، والتي لا تستفيد أيضًا من التوثيق التلقائي المقدم من خلال تسمية الوظيفة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top