Por que o código Python usa a função len () em vez de um método length?


204

Eu sei que python tem uma len()função que é usada para determinar o tamanho de uma string, mas eu queria saber por que não é um método do objeto string.

Atualizar

Ok, eu percebi que estava embaraçosamente enganado. __len__()é realmente um método de um objeto string. Parece estranho ver código orientado a objetos em Python usando a função len em objetos de string. Além disso, também é estranho ver __len__como o nome em vez de apenas len.

Respostas:


180

Strings têm um método de comprimento: __len__()

O protocolo no Python é implementar esse método em objetos que possuem um comprimento e usam a len()função interna, que o chama, semelhante à maneira como você implementaria __iter__()e usaria a iter()função interna (ou tenha o método chamado por trás cenas para você) em objetos iteráveis.

Consulte Emulando tipos de contêiner para obter mais informações.

Aqui está uma boa leitura sobre o assunto de protocolos em Python: Python e o princípio da menor surpresa


124
Surpreende-me quão idiota é a razão de usar len. Eles acham que é mais fácil forçar as pessoas a implementar .__len__do que forçar as pessoas a implementar .len(). É a mesma coisa, e um parece muito mais limpo. Se o idioma tiver um POO __len__, o que o mundo está fazendolen(..)
alternativa

38
len, str, etc. podem ser usados ​​com funções de ordem superior como mapear, reduzir e filtrar sem a necessidade de definir uma função ou lambda apenas para chamar um método. Nem tudo gira em torno de OOP, mesmo em Python.
Evicatos

11
Além disso, usando um protocolo, eles podem fornecer maneiras alternativas de implementar as coisas. Por exemplo, você pode criar um iterável com __iter__, ou apenas __getitem__, e iter(x)funcionará de qualquer maneira. Você pode criar um objeto utilizável em contexto booleano com __bool__ou __len__, e bool(x)funcionará de qualquer maneira. E assim por diante. Acho Armin explica isso razoavelmente bem no pós-mas ligada, mesmo que ele não fez, chamando Python idiota por causa de uma explicação fora por um cara que é muitas vezes publicamente em desacordo com os devs centrais não seria exatamente justo ...
abarnert

8
@ Evicatos: +1 para "Nem tudo gira em torno de OOP", mas eu terminaria com " especialmente em Python", nem mesmo . Python (diferente de Java, Ruby ou Smalltalk) não tenta ser uma linguagem "pura OOP"; foi explicitamente projetado para ser uma linguagem "multiparadigma". Compreensões de lista não são métodos em iterables. zipé uma função de nível superior, não um zip_withmétodo. E assim por diante. Só porque tudo é um objeto não significa que ser um objeto é a coisa mais importante sobre cada coisa.
abarnert

7
Esse artigo é tão mal escrito que me sinto confuso e com raiva depois de tentar lê-lo. "No Python 2.x, o tipo Tuple, por exemplo, não expõe métodos não especiais, e você pode usá-lo para criar uma string:" A coisa toda é uma amálgama de frases quase indecifráveis.
MirroredFate

93

A resposta de Jim para essa pergunta pode ajudar; Eu copio aqui. Citando Guido van Rossum:

Primeiro, escolhi len (x) em vez de x.len () por razões de HCI (def __len __ () veio muito mais tarde). Na verdade, existem duas razões entrelaçadas, ambas HCI:

(a) Para algumas operações, a notação de prefixo apenas lê melhor que as operações de prefixo - prefixo (e infixo!) têm uma longa tradição em matemática, que gosta de notações em que o visual ajuda o matemático a pensar em um problema. Compare o modo fácil com o qual reescrevemos uma fórmula como x * (a + b) em x a + x b com a falta de jeito de fazer a mesma coisa usando uma notação OO bruta.

(b) Quando leio o código que diz len (x) eu sei que ele está pedindo o tamanho de algo. Isso me diz duas coisas: o resultado é um número inteiro e o argumento é algum tipo de contêiner. Pelo contrário, quando leio x.len (), já tenho que saber que x é algum tipo de contêiner implementando uma interface ou herdando de uma classe que possui um len () padrão. Testemunhe a confusão que ocasionalmente temos quando uma classe que não está implementando um mapeamento tem um método get () ou keys () ou algo que não é um arquivo tem um método write ().

Dizendo a mesma coisa de outra maneira, vejo 'len' como uma operação interna. Eu odiaria perder isso. /… /


37

Existe um lenmétodo:

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

34

Python é uma linguagem de programação pragmática, e as razões para len()ser uma função e não um método de str, list, dictetc. são pragmáticos.

A len()função interna lida diretamente com os tipos internos: a implementação do CPython len()na verdade retorna o valor do ob_sizecampo na PyVarObjectestrutura C que representa qualquer objeto interno de tamanho variável na memória. Isso é muito mais rápido do que chamar um método - nenhuma pesquisa de atributo precisa acontecer. Obtendo o número de itens em uma coleção é uma operação comum e deve trabalhar de forma eficiente para tais tipos básicos e diversos como str, list, array.arrayetc.

No entanto, para promover consistência, ao se aplicar len(o)a um tipo definido pelo usuário, o Python chama o.__len__()como fallback. __len__, __abs__e todos os outros métodos especiais documentados no Python Data Model facilitam a criação de objetos que se comportam como os incorporados, habilitando as APIs expressivas e altamente consistentes que chamamos de "Pythonic".

Ao implementar métodos especiais, seus objetos podem oferecer suporte à iteração, sobrecarregar operadores de infixos, gerenciar contextos em withblocos etc.

Uma segunda razão, apoiada em citações de Guido van Rossum como essa , é que é mais fácil ler e escrever do len(s)que s.len().

A notação len(s)é consistente com operadores unários com notação de prefixo, como abs(n). len()é usado com mais frequência abs()e merece ser tão fácil de escrever.

Também pode haver uma razão histórica: na linguagem ABC que precedeu o Python (e foi muito influente em seu design), havia um operador unário escrito como o #sque significava 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
O óbvio ao trabalhar com um objeto é, é claro, um método.
Peter Cooper

4
@ Peter: Eu pagaria US $ 20 a qualquer pessoa com foto que colasse seu comentário nas costas de Guido. US $ 50 se estiver na testa.
bug

1
Sim, os designers do Python aderem ao dogma, mas eles próprios não respeitam seu próprio dogma.
Bitek

2
$ python -c 'importa isso' | grep obvio
Ed Randall

4

Há ótimas respostas aqui e, antes de dar as minhas, gostaria de destacar algumas das gemas (sem trocadilhos de rubi) que li aqui.

  • O Python não é uma linguagem pura de OOP - é uma linguagem de múltiplos paradigmas de propósito geral que permite ao programador usar o paradigma com o qual se sente mais confortável e / ou o paradigma mais adequado para sua solução.
  • Python tem funções de primeira classe, então lené realmente um objeto. Ruby, por outro lado, não possui funções de primeira classe. Portanto, o lenobjeto de função possui seus próprios métodos que você pode inspecionar executando dir(len).

Se você não gosta da maneira como isso funciona em seu próprio código, é trivial reimplementar os contêineres usando seu método preferido (veja o exemplo abaixo).

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

Você também pode dizer

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

Usando o Python 2.7.3.


9
lenA função já foi mencionada nas respostas anteriores. Qual é o sentido dessa "resposta", então?
Piotr Dobrogost

0

Algo está faltando no restante das respostas aqui: a lenfunção verifica se o __len__método retorna um resultado não negativo int. O fato de lenser uma função significa que as classes não podem substituir esse comportamento para evitar a verificação. Como tal, len(obj)fornece um nível de segurança que obj.len()não pode.

Exemplo:

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

Obviamente, é possível "substituir" a lenfunção reatribuindo-a como uma variável global, mas o código que faz isso é muito mais obviamente suspeito do que o código que substitui um método em uma classe.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.