Возможно ли изменить поведение len()?

Мне известно о создании настраиваемого метода __repr__ или __add__ (и т.д.), чтобы изменить поведение операторов и функций. Есть ли метод переопределения для len?

Например:

class Foo:
    def __repr__(self):
        return "A wild Foo Class in its natural habitat."

foo = Foo()

print(foo)         # A wild Foo Class in its natural habitat.
print(repr(foo))   # A wild Foo Class in its natural habitat.

Это можно сделать для len, со списком? Обычно это будет выглядеть так:

foo = []
print(len(foo))    # 0

foo = [1, 2, 3]
print(len(foo))    # 3

Что делать, если я хочу оставить типы поиска вне счета? Вот так:

class Bar(list):
    pass

foo = [Bar(), 1, '']
print(len(foo))    # 3

count = 0
for item in foo:
    if not isinstance(item, Bar):
        count += 1

print(count)       # 2

Есть ли способ сделать это из подкласса list?

Ответ 1

Да, реализуем метод __len__:

def __len__(self):
    return 42

Демо:

>>> class Foo(object):
...     def __len__(self):
...         return 42
... 
>>> len(Foo())
42

Из документации:

Вызывается для реализации встроенной функции len(). Должен возвращать длину объекта, целое число >= 0. Кроме того, объект, который не определяет метод __bool__() и метод __len__() возвращает ноль, считается ложным в булевом контексте.

В вашем конкретном случае:

>>> class Bar(list):
...     def __len__(self):
...         return sum(1 for ob in self if not isinstance(ob, Bar))
... 
>>> len(Bar([1, 2, 3]))
3
>>> len(Bar([1, 2, 3, Bar()]))
3

Ответ 2

Да, так же, как вы уже обнаружили, что вы можете переопределить поведение вызова функции repr(), реализуя магический метод __repr__, вы можете указать поведение из len() вызов функции путем реализации (неожиданное удивление), затем __len__ magic:

>>> class Thing:
...     def __len__(self):
...         return 123
...     
>>> len(Thing())
123

Педант может упомянуть, что вы не изменяете поведение len(), вы изменяете поведение своего класса. len делает то же самое, что и всегда, включая проверку атрибута __len__ в аргументе.

Ответ 3

Помните: Python динамически и Duck Typed.

Если он действует как то, что может иметь длину;

class MyCollection(object):

    def __len__(self):
        return 1234

Пример:

>>> obj = MyCollection()
>>> len(obj)
1234

если он не действует так, как будто он имеет длину; KABOOM!

class Foo(object):

    def __repr___(self):
        return "<Foo>"

Пример:

>>> try:
...     obj = Foo()
...     len(obj)
... except:
...     raise
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: object of type 'Foo' has no len()

От Typing:

Python использует утиную печать и набирает объекты, но нетипизированная переменная имена. Ограничения типа не проверяются во время компиляции; скорее, операции над объектом могут потерпеть неудачу, означая, что данный объект не подходящего типа. Несмотря на динамическую типизацию, Python строго типизированный, запрещающий операции, которые не определены четко (для например, добавление числа в строку), а не молча чтобы понять их.

Пример:

>>> x = 1234
>>> s = "1234"
>>> x + s
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Ответ 4

Вы можете просто добавить метод __len__ к вашему классу.

class Test:
    def __len__(self):
        return 2

a=Test()
len(a) # --> 2