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.