ทำไมรหัสไพ ธ อนจึงใช้ฟังก์ชัน len () แทนที่จะเป็นวิธีความยาว


204

ฉันรู้ว่าหลามมีlen()ฟังก์ชั่นที่ใช้ในการกำหนดขนาดของสตริง แต่ฉันสงสัยว่าทำไมมันไม่ได้เป็นวิธีการของวัตถุสตริง

ปรับปรุง

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

คำตอบ:


180

สตริงมีวิธีความยาว: __len__()

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

ดูที่การจำลองชนิดคอนเทนเนอร์สำหรับข้อมูลเพิ่มเติม

นี่คือการอ่านที่ดีเกี่ยวกับเรื่องของโปรโตคอลใน Python: Python และหลักการของ Least ประหลาดใจ


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

38
len, str, ฯลฯ สามารถใช้กับฟังก์ชั่นลำดับสูงกว่าเช่นแผนที่ลดและกรองโดยไม่จำเป็นต้องกำหนดฟังก์ชั่นหรือแลมบ์ดาเพียงเพื่อเรียกวิธีการ ไม่ใช่ทุกสิ่งที่หมุนรอบ OOP แม้แต่ใน Python
Evicatos

11
นอกจากนี้โดยใช้โปรโตคอลพวกเขาสามารถให้วิธีการอื่นในการใช้สิ่งต่าง ๆ ตัวอย่างเช่นคุณสามารถสร้าง iterable ด้วย__iter__หรือมีเพียง__getitem__และiter(x)จะทำงานอย่างใดอย่างหนึ่ง คุณสามารถสร้างวัตถุที่ใช้งานได้ในบริบทกับ__bool__หรือ__len__และbool(x)จะทำงานอย่างใดอย่างหนึ่ง และอื่น ๆ ฉันคิดว่า Armin อธิบายเรื่องนี้ได้ดีพอสมควรในโพสต์ที่เชื่อมโยง - แต่ถึงแม้ว่าเขาจะไม่ได้เรียก Python moronic เพราะคำอธิบายภายนอกโดยคนที่มักจะขัดแย้งกับ devs หลักไม่ยุติธรรม…
abarnert

8
@Evicatos: +1 สำหรับ "ไม่หมุนทุกอย่างรอบตัวผู้ป่วยเอง" แต่ฉันลงท้ายด้วย " โดยเฉพาะอย่างยิ่งในงูใหญ่" ไม่ได้แม้กระทั่ง Python (ไม่เหมือนกับ Java, Ruby หรือ Smalltalk) ไม่พยายามเป็นภาษา "บริสุทธิ์ OOP" มันถูกออกแบบมาอย่างชัดเจนให้เป็นภาษา "หลายกระบวนทัศน์" รายการความเข้าใจไม่ใช่วิธีการที่ทำซ้ำได้ zipเป็นฟังก์ชันระดับบนสุดไม่ใช่zip_withวิธีการ และอื่น ๆ เพียงเพราะทุกอย่างเป็นวัตถุไม่ได้หมายความว่าการเป็นวัตถุเป็นสิ่งที่สำคัญที่สุดของแต่ละสิ่ง
abarnert

7
บทความนั้นเขียนไม่ดีฉันรู้สึกสับสนและโกรธหลังจากพยายามอ่าน "ใน Python 2.x อินสแตนซ์ของ Tuple นั้นไม่ได้เปิดเผยวิธีการพิเศษใด ๆ และคุณสามารถใช้มันเพื่อสร้างสตริงจากมัน:" สิ่งทั้งปวงเป็นการรวมกันของประโยคใกล้ ๆ
MirroredFate

93

คำตอบของจิมสำหรับคำถามนี้อาจช่วยได้ ฉันคัดลอกที่นี่ การอ้างอิง Guido van Rossum:

ก่อนอื่นฉันเลือก len (x) มากกว่า x.len () ด้วยเหตุผลของ HCI (def __len __ () มามากในภายหลัง) มีสองเหตุผลพันจริงทั้ง HCI:

(a) สำหรับการดำเนินการบางอย่างเครื่องหมายคำนำหน้าจะอ่านได้ดีกว่า postfix - คำนำหน้า (และ infix!) การดำเนินการมีประเพณีอันยาวนานในวิชาคณิตศาสตร์ซึ่งชอบสัญลักษณ์ที่ภาพช่วยให้นักคณิตศาสตร์คิดเกี่ยวกับปัญหา เปรียบเทียบความง่ายที่เราเขียนสูตรเช่น x * (a + b) ลงใน x a + x b กับความซุ่มซ่ามของการทำสิ่งเดียวกันโดยใช้สัญกรณ์ OO แบบดิบ

(b) เมื่อฉันอ่านโค้ดที่บอกว่า len (x) ฉันรู้ว่ามันกำลังขอความยาวของบางอย่าง สิ่งนี้บอกฉันสองสิ่ง: ผลลัพธ์คือจำนวนเต็มและอาร์กิวเมนต์เป็นคอนเทนเนอร์ชนิดหนึ่ง ในทางตรงกันข้ามเมื่อฉันอ่าน x.len () ฉันต้องรู้อยู่แล้วว่า x เป็นคอนเทนเนอร์ชนิดหนึ่งที่ใช้อินเทอร์เฟซหรือสืบทอดจากคลาสที่มี len มาตรฐาน () ร่วมเป็นสักขีพยานกับความสับสนที่เรามีในบางครั้งเมื่อคลาสที่ไม่ได้ใช้การแมปมีเมธอด get () หรือ keys () หรือสิ่งที่ไม่ใช่ไฟล์มีเมธอด write ()

พูดแบบเดียวกันฉันเห็น 'len' เป็นการดำเนินการในตัว ฉันเกลียดที่จะแพ้สิ่งนั้น / ... /



34

งูหลามเป็นภาษาการเขียนโปรแกรมในทางปฏิบัติและสาเหตุของการlen()เป็นฟังก์ชั่นและไม่ใช่วิธีของstr, list, dictฯลฯ ในทางปฏิบัติ

len()ในตัวข้อเสนอฟังก์ชั่นโดยตรงกับในตัวชนิด: การดำเนินงาน CPython ของlen()จริงผลตอบแทนที่คุ้มค่าของob_sizeข้อมูลในPyVarObjectstruct Cที่แสดงถึงตัวแปรใด ๆ ขนาดในตัววัตถุในหน่วยความจำ นี้เป็นมากเร็วกว่าวิธีการเรียกใช้ - ไม่มีความต้องการค้นหาแอตทริบิวต์ที่จะเกิดขึ้น ได้รับหมายเลขของรายการในคอลเลกชันคือการดำเนินการร่วมกันและจะต้องทำงานอย่างมีประสิทธิภาพสำหรับประเภทพื้นฐานและมีความหลากหลายเช่นstr, list, array.arrayฯลฯ

อย่างไรก็ตามเพื่อส่งเสริมความสอดคล้องเมื่อใช้len(o)กับประเภทที่ผู้ใช้กำหนด Python จะเรียกo.__len__()ว่าเป็นทางเลือก __len__, __abs__และทุกวิธีพิเศษอื่น ๆ การบันทึกไว้ในหลามรูปแบบข้อมูลให้ง่ายต่อการสร้างวัตถุที่ทำตัวเหมือนตัวอินช่วยให้แสดงออกและสอดคล้องสูง APIs ที่เราเรียกว่า "Pythonic"

โดยการใช้วิธีการพิเศษวัตถุของคุณสามารถรองรับการวนซ้ำตัวดำเนินการมัดเกินจัดการบริบทในwithบล็อก ฯลฯ คุณสามารถคิดว่าตัวแบบข้อมูลเป็นวิธีการใช้ภาษา Python เป็นกรอบซึ่งวัตถุที่คุณสร้างสามารถบูรณาการได้อย่างราบรื่น

เหตุผลที่สองโดยการสนับสนุนจากคำพูดจากกุยรถตู้ซัมเช่นนี้ก็คือว่ามันจะง่ายต่อการอ่านและเขียนมากกว่าlen(s)s.len()

สัญกรณ์มีความสอดคล้องกับผู้ประกอบการที่มีเอกสัญกรณ์คำนำหน้าเช่นlen(s) มีการใช้บ่อยกว่าและมันควรจะเป็นเรื่องง่ายที่จะเขียนabs(n)len()abs()

อาจมีเหตุผลทางประวัติศาสตร์: ในภาษา ABC ที่นำหน้า Python (และมีอิทธิพลอย่างมากในการออกแบบ) มีโอเปอเรเตอร์ unary เขียนตาม#sที่len(s)ต้องการ


12
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.

34
สิ่งที่ชัดเจนเมื่อทำงานกับวัตถุเป็นวิธีการ
Peter Cooper

4
@Peter: ฉันจะจ่าย $ 20 ให้กับทุกคนพร้อมหลักฐานภาพถ่ายที่พวกเขาบันทึกความคิดเห็นของคุณไว้ที่หลังของ Guido $ 50 ถ้ามันอยู่บนหน้าผากของเขา
ข้อผิดพลาด

1
ใช่นักออกแบบ Python ยึดถือหลักคำสอน แต่พวกเขาไม่เคารพความเชื่อของตัวเอง
bitek

2
$ python -c 'นำเข้าสิ่งนี้' | grep ชัดเจน
Ed Randall

4

มีคำตอบที่ดีบางอย่างที่นี่และก่อนที่ฉันจะให้ตัวเองฉันอยากจะเน้นบางอัญมณี (ไม่มีจุดประสงค์ทับทิมทับทิม) ฉันได้อ่านที่นี่

  • Python ไม่ใช่ภาษา OOP บริสุทธิ์ - เป็นวัตถุประสงค์ทั่วไปภาษาหลายกระบวนทัศน์ที่อนุญาตให้โปรแกรมเมอร์ใช้กระบวนทัศน์ที่พวกเขาสบายที่สุดและ / หรือกระบวนทัศน์ที่เหมาะสมที่สุดสำหรับการแก้ปัญหาของพวกเขา
  • Python มีฟังก์ชันชั้นหนึ่งดังนั้นจึงlenเป็นวัตถุจริงๆ ในทางกลับกัน Ruby ไม่มีฟังก์ชั่นชั้นหนึ่ง ดังนั้นวัตถุฟังก์ชั่นมีวิธีการของตัวเองที่คุณสามารถตรวจสอบโดยการเรียกใช้lendir(len)

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

>>> class List(list):
...     def len(self):
...         return len(self)
...
>>> class Dict(dict):
...     def len(self):
...         return len(self)
...
>>> class Tuple(tuple):
...     def len(self):
...         return len(self)
...
>>> class Set(set):
...     def len(self):
...         return len(self)
...
>>> my_list = List([1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'])
>>> my_dict = Dict({'key': 'value', 'site': 'stackoverflow'})
>>> my_set = Set({1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'})
>>> my_tuple = Tuple((1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'))
>>> my_containers = Tuple((my_list, my_dict, my_set, my_tuple))
>>>
>>> for container in my_containers:
...     print container.len()
...
15
2
15
15

0

คุณสามารถพูดได้

>> x = 'test'
>> len(x)
4

ใช้ Python 2.7.3


9
lenฟังก์ชั่นถูกกล่าวถึงแล้วในคำตอบก่อนหน้า ประเด็นของ "คำตอบ" นี้คืออะไร?
Piotr Dobrogost

0

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

ตัวอย่าง:

>>> class A:
...     def __len__(self):
...         return 'foo'
...
>>> len(A())
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
>>> class B:
...     def __len__(self):
...         return -1
... 
>>> len(B())
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    len(B())
ValueError: __len__() should return >= 0

แน่นอนว่ามันเป็นไปได้ที่จะ "แทนที่" lenฟังก์ชั่นโดยการกำหนดใหม่เป็นตัวแปรทั่วโลก แต่รหัสที่ทำนี่เป็นที่น่าสงสัยมากขึ้นอย่างเห็นได้ชัดกว่ารหัสที่แทนที่วิธีการในชั้นเรียน


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.