2016-04-11 9 views
17

Есть ли способ сделать len() работать с методами экземпляра без изменения класса?Создание len() работы с методами экземпляра

Пример моей проблемы:

>>> class A(object): 
...  pass 
... 
>>> a = A() 
>>> a.__len__ = lambda: 2 
>>> a.__len__() 
2 
>>> len(a) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: object of type 'A' has no len() 

Примечание:

  • различные экземпляры A будут иметь различные __len__ методы, прикрепленные
  • я не могу изменить класс A
+1

Вашей проблемой является ** **, когда вы определяете '__len__', попробуйте определив его как реальный метод на классе вместо атрибут экземпляра. – jesseroberts

+1

@PadraicCunningham Смотрите мой измененный вопрос. Это не позволяет использовать разные методы '__len__' для разных экземпляров' A'. – ARF

+1

@ARF, я полагаю, вы не можете изменить класс? –

ответ

6

Это на самом деле можно, не изменяя класс, основанный на этом answer Алекс Мартелли:

class A(object): 
    def __init__(self, words): 
     self.words = words 

    def get_words(self): 
     return self.words 


a = A("I am a") 
b = A("I am b") 

def make_meth(inst, _cls, meth, lm): 
    inst.__class__ = type(_cls.__name__, (_cls,), {meth: lm}) 

make_meth(a, A, "__len__", lambda self: 12) 
make_meth(b, A, "__len__", lambda self: 44) 

print(len(b)) 
print(len(a)) 
print(a.get_words()) 
print(b.get_words()) 

Если мы запустим код:

In [15]: a = A("I am a")  
In [16]: b = A("I am b")  
In [17]: make_meth(a, A, "__len__", lambda self: 12) 
In [18]: make_meth(b, A, "__len__", lambda self: 44) 
In [19]: print(len(b)) 
44  
In [20]: print(len(a)) 
12 

In [21]: print(a.get_words()) 
I am a  
In [22]: print(b.get_words()) 
I an b 

Согласно последней части последней части связанный ответ, вы можете добавлять любые методы в каждом конкретном случае, используя inst.specialmethod после того, как вы использовали inst.__class__ = type(...:

In [34]: b.__class__.__str__ = lambda self: "In b" 

In [35]: print(str(a)) 
<__main__.A object at 0x7f37390ae128> 

In [36]: print(str(b)) 
In b 
+0

Переназначение класса объекта? Я полагаю, он должен работать, если экземпляры A имеют '__dict__' (иначе вы получите TypeError). У него есть несколько других последствий, которые могут смущать всех, кто должен иметь дело с объектом, модифицированным таким образом, но, возможно, не более сложным, чем изменение поведения '__len__' на чужом классе уже есть. – user2357112

+0

@ user2357112, да, он работает, не меняя класс, который был требованием OP, что они не добавляли к вопросу, я не сказал, что это лучшее решение, но это показывает, что это возможно. –

+0

@ARF, это должно быть 'print (a.get_words())' с s. Я отредактировал ответ и забыл добавить s –

5

You должны определить специальный метод ods во время определения класса:

class A(object): 
    def __len__(self): 
     return self.get_len() 

a = A() 
a.get_len = lambda: 2 
print len(a) 
+0

Я действительно хочу видеть комментарии downvoters? –

+0

Неверно, что «вам нужно определить специальные методы во время определения класса». 'class C: pass', за которым следует ' c = C() C .__ len__ = lambda self: 2 print (len (c)) 'works –

+0

@TerryJanReedy: OP использует классы нового стиля. Вы используете классы старого стиля. Так в чем смысл? – Daniel

26

№ Python всегда ищет специальные методы в классе объекта. Для этого есть несколько веских причин, один из которых заключается в том, что repr(A) должен использовать type(A).__repr__ вместо A.__repr__, который предназначен для обработки экземпляров A вместо самого класса A.

Если вы хотите различные экземпляры A вычислить их len по-другому, рассмотреть возможность __len__ делегата другому методу:

class A(object): 
    def __len__(self): 
     return self._len() 

a = A() 
a._len = lambda: 2 
+2

Хороший ответ, учитывает требование OPs о том, что разные экземпляры используют другой метод определения len. Хотя почему это требование выходит за рамки меня ... – jesseroberts

+4

Я действительно хочу видеть комментарии спутника-спутника? –

+5

Любой метод, имя которого начинается и заканчивается на '__', потребует такого лечения. Из-за двойных подчеркиваний они иногда называются методами «dunder». «Грязные дела и их начальник!» – kindall

10

Специальные методы, такие как __len__ (двойное подчеркивание или «Dunder» методы) должны быть определены на классе. Они не будут работать, если они определены только в экземпляре.

В экземпляре можно определить методы, отличные от dunder. Тем не менее, вы должны преобразовать свою функцию в метод экземпляра, добавив к ней обертку, с которой передается self. (Обычно это делается при доступе к методу, так как метод, определенный в классе, является дескриптором, который возвращает . обертка) Это может быть сделано следующим образом:

a.len = (lambda self: 2).__get__(a, type(a)) 

Объединяя эти идеи, мы можем написать __len__() на класс, который делегирует в len(), что мы можем определить на экземпляре:

class A(object): 
    def __len__(self): 
     return self.len() 

a = A() 
a.len = (lambda self: 2).__get__(a, type(a)) 

print(len(a)) # prints 2 

You может фактически упростить это в вашем случае, потому что вам не нужно self, чтобы r eturn ваша постоянная 2. Поэтому вы можете просто назначить a.len = lambda: 2. Однако, если вам нужно self, вам необходимо создать оболочку метода.

+0

Можете ли вы объяснить немного больше об обертке? @ user2357112 ответ, кажется, дает мне доступ к 'self' экземпляра даже без оболочки. – ARF

+0

@ ARF: Нет, я просто оставил это, потому что ваш пример 'lambda' на самом деле не пытается получить доступ к' self'. Если вам действительно нужно 'self', вам нужно либо перейти к нему по-другому (возможно, с переменной закрытия или' functools.partial'), либо вам нужно будет сделать '__get__' вещь. – user2357112

+0

@ user2357112 Спасибо, после некоторых игр с ним я теперь понимаю проблему обертки. – ARF

1

Вы не указали, используете ли вы Python 2.x или 3.x, и в этом случае это зависит! В 3.x на ваш вопрос уже были ответы другие люди. В 2.x ваш вопрос на самом деле имеет простой ответ: прекратите получать объект! (Конечно, если вы будете следовать этому подходу, вы не сможете выполнить обновление до 3.x, пока не найдете другой способ сделать это, поэтому не делайте то, что я предлагаю, если у вас нет планов по обновлению в ближайшее время.)

Type "copyright", "credits" or "license()" for more information. 
>>> class A:pass 

>>> a=A() 
>>> a.__len__ = lambda: 2 
>>> len(a) 
2 
>>> 

Наслаждайтесь :)