Diferença entre len () e .__ len __ ()?

100

Existe alguma diferença entre ligar len([1,2,3])ou [1,2,3].__len__()?

Se não houver diferença visível, o que é feito de maneira diferente nos bastidores?

Marca
fonte
2
Consulte stackoverflow.com/questions/496009/…
Crescent Fresh

Respostas:

102

lené uma função para obter o comprimento de uma coleção. Ele funciona chamando o __len__método de um objeto . __something__os atributos são especiais e geralmente mais do que aparenta, e geralmente não devem ser chamados diretamente.

Foi decidido em algum momento atrás, obter o comprimento de algo deve ser uma função e não um código de método, raciocinando que len(a)o significado seria claro para iniciantes, mas a.len()não seria tão claro. Quando Python começou __len__nem existia e lenera uma coisa especial que funcionava com alguns tipos de objetos. Quer a situação que isso nos deixa faça sentido ou não, ela veio para ficar.

Mike Graham
fonte
66

É comum que o comportamento "típico" de um operador interno ou seja chamar (com uma sintaxe diferente e mais agradável) métodos mágicos adequados (aqueles com nomes como __whatever__) nos objetos envolvidos. Freqüentemente, o operador integrado ou tem "valor agregado" (é capaz de seguir caminhos diferentes dependendo dos objetos envolvidos) - no caso de lenvs __len__, é apenas um pouco de verificação de sanidade no integrado que está faltando no método mágico:

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

Ao ver uma chamada para o lenintegrado, você tem certeza de que, se o programa continuar depois disso, em vez de levantar uma exceção, a chamada retornou um número inteiro não negativo e menor que 2 ** 31 - quando você vê uma chamada para xxx.__len__(), você não tem certeza (exceto que o autor do código não está familiarizado com Python ou não é bom ;-).

Outros recursos integrados fornecem ainda mais valor agregado além das simples verificações de integridade e legibilidade. Ao projetar uniformemente todo o Python para funcionar por meio de chamadas a builtins e uso de operadores, nunca por meio de chamadas a métodos mágicos, os programadores são poupados do fardo de lembrar qual caso é qual. (Às vezes, um erro ocorre: até o 2.5, você tinha que chamar foo.next()- no 2.6, embora isso ainda funcione para compatibilidade com versões anteriores, você deve chamar next(foo), e em 3.*, o método mágico é nomeado corretamente em __next__vez de "oops-ey" next! - )

Portanto, a regra geral deve ser nunca chamar um método mágico diretamente (mas sempre indiretamente por meio de um embutido), a menos que você saiba exatamente por que precisa fazer isso (por exemplo, quando você está substituindo tal método em uma subclasse, se o a subclasse precisa se submeter à superclasse, o que deve ser feito por meio de uma chamada explícita ao método mágico).

Alex Martelli
fonte
Eu sou um usuário iniciante de Python (não o pensamento de programador iniciante) e não tenho certeza sobre "Quando você vê uma chamada para o len embutido, você tem certeza que, se o programa continuar depois disso, em vez de levantar uma exceção". Eu tentei isso: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))e foi impresso I am stringduas vezes. Você pode explicar mais?
Darek Nędza
4
@ DarekNędza Isso não tem nada a ver com o acima, que é sobre len embutido. Você acabou de definir sua função len, que pode retornar o que você quiser. OP falou sobre len embutido, que chama __len__um método especial (não uma função) no objeto em consideração.
Veky
@Veky Como posso ter certeza de que estou chamando uma função interna e lennão alguma outra função (como no meu exemplo) que tinha o mesmo nome - len. Não há nenhum aviso como "Você está redefinindo a função interna len" ou algo assim. Na minha opinião, não tenho certeza do que Alex afirmou em sua resposta.
Darek Nędza
3
Alex disse explicitamente se você está chamando de builtin, então você tem certeza ..._. Ele não disse nada sobre ter certeza de que você está ligando para builtin Mas se você quer saber isso, você pode: len in vars(__builtins__).values().
Veky
1
Infelizmente, este é outro exemplo da falta de uma classe base comum para objetos em Python. A troca de contexto sintática sempre foi louca. Em alguns casos, é um idioma comum usar um método de sublinhado, em outros, deve-se usar algo como uma função para fazer algo comum a muitos objetos. Também é estranho porque muitos objetos não têm uso semântico para len. Às vezes, o modelo de objeto é mais como C ++, pia de cozinha ..
uchuugaka
28

Você pode pensar em len () como sendo aproximadamente equivalente a

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

Uma vantagem é que permite que você escreva coisas como

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

ao invés de

map(list.__len__, somelist)

ou

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

No entanto, há um comportamento ligeiramente diferente. Por exemplo, no caso de 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()
John La Rooy
fonte
2
Presumo que você queira dizer em operator.methodcallervez de operator.attrgetter.
Elazar de
5

Você pode verificar a documentação do 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
Dmytro Ozarkiv
fonte