为什么Python代码使用len()函数而不是length方法?


204

我知道python具有len()用于确定字符串大小的函数,但是我想知道为什么它不是字符串对象的方法。

更新资料

好吧,我意识到我很尴尬地犯了错误。__len__()实际上是字符串对象的方法。在字符串对象上使用len函数在Python中看到面向对象的代码似乎很奇怪。此外,看到__len__名字而不是len 也很奇怪。

Answers:


180

字符串确实有一个length方法: __len__()

Python中的协议是在具有一定长度并使用内置len()函数的对象上实现此方法,该内置函数会为您调用该方法,类似于您实现__iter__()和使用内置iter()函数的方法(或在后面调用方法)的场景)在可迭代的对象上。

有关更多信息,请参见模拟容器类型

这是有关Python协议主题的好书:Python和最小惊讶原则


124
令我惊讶的是使用该产品的原因len。他们认为,强迫人们执行.__len__比强迫人们执行要容易.len()。它是同一件事,看起来干净。如果该语言要进行面向对象编程__len__,那么世界的意思是len(..)
替代

38
len,str等可以与高阶函数(例如map,reduce和filter)一起使用,而无需定义函数或lambda来调用方法。并非所有内容都围绕OOP,即使在Python中也是如此。
Evicatos

11
而且,通过使用协议,他们可以提供实现事物的替代方法。例如,您可以使用__iter__或仅创建一个可迭代的对象__getitem__,并且iter(x)将以任何一种方式工作。你可以创建一个可用的功能于布尔上下文对象具有__bool____len__bool(x)将工作无论哪种方式。等等。我认为Armin在链接后的文章中对此进行了很好的解释-但是,即使他没有这么做,但由于一个经常公开与核心开发人员发生
分歧的

8
@Evicatos:+1表示“不是所有事情都围绕OOP展开”,但我会以“ 尤其是在Python中” 结尾,甚至没有。Python(不同于Java,Ruby或Smalltalk)不会尝试成为“纯OOP”语言。它被明确设计为一种“多范式”语言。列表推导不是可迭代的方法。zip是顶级函数,而不是zip_with方法。等等。仅仅因为一切都是对象,并不意味着成为对象是每件事最重要的事情。
abarnert 2014年

7
那篇文章写得太差了,在尝试阅读之后,我感到困惑和愤怒。“例如,在Python 2.x中,Tuple类型不会公开任何非特殊方法,但是您可以使用它来创建字符串:”整个过程是几乎难以理解的句子的组合。
MirroredFate 2016年

93

吉姆对这个问题的回答可能会有所帮助。我在这里复制。引用Guido van Rossum:

首先,出于HCI的原因,我选择len(x)而不是x.len()(def __len __()来得很晚)。实际上,两个HCI相互交织在一起:

(a)对于某些运算,前缀表示法比后缀读得更好-前缀(和infix!)运算符在数学中有很长的传统,喜欢在视觉上帮助数学家思考问题的表示法。将我们将x *(a + b)之类的公式重写为x a + x b 的简便性与使用原始OO符号做相同事情的笨拙性进行比较。

(b)当我读到说len(x)的代码时,我知道它是在问某物的长度。这告诉我两件事:结果是整数,参数是某种容器。相反,当我阅读x.len()时,我必须已经知道x是某种实现接口或从具有标准len()的类继承的容器。当未实现映射的类具有get()或keys()方法,或者非文件类具有write()方法时,我们有时会感到困惑。

用另一种方式说同样的事情,我将“ len”视为内置操作。我不想失去那个。/.../


37

有一种len方法:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>

34

Python是一种务实的编程语言,并为原因len()是一个功能,而不是一个方法strlistdict等务实。

len()内置函数直接处理的内置类型:CPython的执行len()实际返回的值ob_size字段中PyVarObject的C结构代表任意可变大小的内置存储器中的对象。这是很多比调用一个方法快-无属性的查找需要发生。获取集合中的项目数是一种常见的操作,必须对这些基本类型多样为提高工作效率strlistarray.array等。

但是,为了提高一致性,当应用len(o)到用户定义的类型时,Python会o.__len__()作为后备调用。 __len____abs__和所有其他特殊的记录方法的Python数据模型可以很容易地创建对象,其行为像内置插件,使表现力和高度一致的API,我们称之为“Python化”。

通过实现特殊的方法,您的对象可以支持迭代,重载infix运算符,在with块中管理上下文等。您可以将数据模型视为一种使用Python语言本身作为框架的方式,您可以在其中无缝集成所创建的对象。

第二个原因,通过报价从吉多·范罗苏姆等支撑这一个,是它更容易阅读和写len(s)s.len()

该表示法len(s)与带有前缀表示法的一元运算符一致,例如abs(n)len()的使用频率比更高abs(),并且应该易于编写。

可能还有一个历史原因:在Python之前的ABC语言中(在其设计中很有影响力),有一个一元运算符,#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
处理对象时,显而易见的事情当然就是方法。
彼得·库珀

4
@Peter:我愿意付给任何有照片证据的人20美元,让他们将您的评论贴在Guido的背上。如果额头上有$ 50,
错误

1
是的,Python设计师坚持教条,但他们自己不尊重自己的教条。
bitek

2
$ python -c'导入此'| grep明显
Ed Randall

4

这里有一些很好的答案,因此在我给出自己的名字之前,我想重点介绍一下我在这里读过的一些宝石(无红宝石双关语)。

  • Python并不是纯粹的OOP语言,它是一种通用的多范式语言,它使程序员能够使用他们最熟悉的范式和/或最适合其解决方案的范式。
  • Python具有一流的功能,因此len实际上是一个对象。另一方面,Ruby没有一流的功能。因此,len函数对象具有自己的方法,可以通过运行进行检查dir(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

这里的其余答案缺少一些内容:len函数检查__len__方法是否返回非负数intlen作为函数的事实意味着类无法重写此行为以避免检查。因此,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通过将其重新分配为全局变量来“覆盖” 函数,但是比起覆盖类中方法的代码,这样做的代码明显更可疑。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.