ความแตกต่างระหว่าง len () และ. __ len __ ()?


107

มีความแตกต่างระหว่างการโทรlen([1,2,3])หรือ[1,2,3].__len__()?

หากไม่มีความแตกต่างที่มองเห็นได้จะมีการทำอะไรที่แตกต่างกันเบื้องหลัง?


คำตอบ:


106

lenคือฟังก์ชั่นรับความยาวของคอลเลกชัน มันทำงานโดยเรียก__len__เมธอดของวัตถุ __something__แอตทริบิวต์มีความพิเศษและมักจะตรงตามสายตาและโดยทั่วไปไม่ควรเรียกโดยตรง

มีการตัดสินใจเมื่อนานมาแล้วการกำหนดความยาวของบางสิ่งควรเป็นฟังก์ชันไม่ใช่รหัสวิธีการโดยให้เหตุผลว่าความlen(a)หมายของผู้เริ่มต้นจะชัดเจน แต่a.len()จะไม่ชัดเจนเท่าที่ควร เมื่อ Python เริ่ม__len__ไม่มีอยู่จริงและlenเป็นสิ่งพิเศษที่ใช้ได้กับออบเจ็กต์บางประเภท ไม่ว่าสถานการณ์นี้จะทำให้เราสมเหตุสมผลหรือไม่ก็ตามก็อยู่ที่นี่ต่อไป


67

มักจะเป็นกรณีที่พฤติกรรม "ทั่วไป" ของบิวท์อินหรือตัวดำเนินการคือการเรียก (ด้วยไวยากรณ์ที่แตกต่างและดีกว่า) วิธีการใช้เวทมนตร์ที่เหมาะสม (ที่มีชื่อเหมือน__whatever__) บนวัตถุที่เกี่ยวข้อง บ่อยครั้งที่ตัวดำเนินการในตัวหรือตัวดำเนินการมี "มูลค่าเพิ่ม" (สามารถใช้เส้นทางที่แตกต่างกันได้ขึ้นอยู่กับวัตถุที่เกี่ยวข้อง) - ในกรณีของlenvs __len__มันเป็นเพียงการตรวจสอบความมีสติในตัวที่ขาดหายไปจาก วิธีมายากล:

>>> class bah(object):
...   def __len__(self): return "an inch"
... 
>>> bah().__len__()
'an inch'
>>> len(bah())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

เมื่อคุณเห็นการโทรไปยังlenบิวท์อินคุณแน่ใจว่าหากโปรแกรมดำเนินการต่อหลังจากนั้นแทนที่จะเพิ่มข้อยกเว้นการโทรจะส่งคืนจำนวนเต็มไม่เป็นลบและน้อยกว่า 2 ** 31 - เมื่อ คุณเห็นการโทรหาxxx.__len__()คุณไม่มีความมั่นใจ (ยกเว้นว่าผู้เขียนโค้ดไม่คุ้นเคยกับ Python หรือไม่ดี ;-)

บิวท์อินอื่น ๆ ให้มูลค่าเพิ่มที่มากกว่าการตรวจสอบความถูกต้องและการอ่านค่า ด้วยการออกแบบ Python ทั้งหมดอย่างสม่ำเสมอเพื่อให้ทำงานผ่านการโทรไปยังบิวด์อินและการใช้งานตัวดำเนินการไม่ผ่านการเรียกใช้วิธีการใช้เวทมนตร์โปรแกรมเมอร์จะไม่ต้องจำภาระในการจดจำว่ากรณีใดเป็นกรณีใด (บางครั้งข้อผิดพลาดหลุดเข้ามา: จนถึง 2.5 คุณต้องโทรfoo.next()- ใน 2.6 ในขณะที่ยังคงใช้งานได้เพื่อความเข้ากันได้แบบย้อนกลับคุณควรโทรหาnext(foo)และใน3.*วิธีการมายากลจะถูกตั้งชื่ออย่างถูกต้อง__next__แทนที่จะเป็น "อ๊ะ - ตา" next! - ).

ดังนั้นกฎทั่วไปไม่ควรเรียกใช้เมธอดมายากลโดยตรง (แต่โดยอ้อมเสมอผ่านบิวท์อิน) เว้นแต่คุณจะรู้แน่ชัดว่าทำไมคุณถึงต้องทำเช่นนั้น (เช่นเมื่อคุณลบล้างเมธอดดังกล่าวในคลาสย่อยหาก คลาสย่อยจำเป็นต้องเลื่อนไปสู่ซูเปอร์คลาสที่ต้องทำผ่านการเรียกอย่างชัดเจนไปยังเมธอดมายากล)


ฉันเป็นผู้ใช้ Python ระดับเริ่มต้น (ไม่ใช่โปรแกรมเมอร์มือใหม่คิดว่า) และฉันไม่แน่ใจว่า "เมื่อคุณเห็นการเรียกไปยัง len ในตัวคุณแน่ใจว่าหากโปรแกรมดำเนินต่อไปหลังจากนั้นแทนที่จะเพิ่มข้อยกเว้น" ฉันลองสิ่งนี้: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))และพิมพ์I am stringสองครั้ง ช่วยอธิบายเพิ่มเติมได้ไหม
Darek Nędza

4
@ DarekNędzaนี่ไม่มีส่วนเกี่ยวข้องกับข้างต้นซึ่งเกี่ยวกับ builtin len คุณเพิ่งกำหนดฟังก์ชัน len ของคุณซึ่งแน่นอนว่าสามารถส่งคืนสิ่งที่คุณต้องการได้ OP พูดถึง builtin len ซึ่งเรียกใช้ method__len__พิเศษ(ไม่ใช่ function) บนวัตถุที่กำลังพิจารณา
Veky

@Veky ฉันจะแน่ใจได้อย่างไรว่าฉันกำลังเรียกใช้ฟังก์ชันในตัวlenไม่ใช่ฟังก์ชันอื่น ๆ (เช่นในตัวอย่างของฉัน) ที่มีชื่อเดียวกัน - len. ไม่มีคำเตือนเช่น "คุณกำลังกำหนดฟังก์ชัน len ในตัวใหม่" หรืออะไรทำนองนี้ ในความคิดของฉันฉันไม่แน่ใจเกี่ยวกับสิ่งที่อเล็กซ์ระบุไว้ในคำตอบของเขา
Darek Nędza

3
อเล็กซ์อย่างชัดเจนกล่าวว่าถ้าคุณโทร builtin, แล้วคุณแน่ใจว่า ..._ เขาไม่ได้พูดอะไรเกี่ยวกับการแน่ใจว่าคุณกำลังโทรหา builtin แต่ถ้าคุณต้องการทราบสิ่งนั้นคุณสามารถ: len in vars(__builtins__).values().
Veky

1
น่าเศร้าที่นี่เป็นอีกตัวอย่างหนึ่งของการไม่มีคลาสพื้นฐานทั่วไปสำหรับอ็อบเจ็กต์ใน Python การสลับบริบททางไวยากรณ์เป็นเรื่องปกติ ในบางกรณีมันเป็นสำนวนทั่วไปที่จะใช้วิธีการขีดล่างในอีกวิธีหนึ่งควรใช้บางอย่างเช่นฟังก์ชันเพื่อทำบางสิ่งที่เหมือนกันกับวัตถุจำนวนมาก ก็แปลกเช่นกันเพราะวัตถุจำนวนมากไม่มีการใช้ความหมายสำหรับ len บางครั้งแบบจำลองวัตถุจะเป็น C ++ มากกว่าเช่นห้องครัวจม ..
uchuugaka

28

คุณสามารถคิดว่า len () เทียบเท่ากับ

def len(x):
    return x.__len__()

ข้อดีอย่างหนึ่งคือช่วยให้คุณสามารถเขียนสิ่งต่างๆเช่น

somelist = [[1], [2, 3], [4, 5, 6]]
map(len, somelist) 

แทน

map(list.__len__, somelist)

หรือ

map(operator.methodcaller('__len__'), somelist)

แม้ว่าจะมีพฤติกรรมที่แตกต่างกันเล็กน้อย ตัวอย่างเช่นในกรณีของ ints

>>> (1).__len__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__len__'
>>> len(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()

2
ผมถือว่าคุณหมายถึงแทนoperator.methodcaller operator.attrgetter
Elazar

5

คุณสามารถตรวจสอบเอกสาร Pythond :

>>> class Meta(type):
...    def __getattribute__(*args):
...       print "Metaclass getattribute invoked"
...       return type.__getattribute__(*args)
...
>>> class C(object):
...     __metaclass__ = Meta
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print "Class getattribute invoked"
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.