Динамически назначать специальные методы для объектов, но не классов в Python - PullRequest
4 голосов
/ 25 января 2010

Я бы хотел сделать следующее:

class A(object): pass

a = A()
a.__int__ = lambda self: 3

i = int(a)

К сожалению, это бросает:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string or a number, not 'A'

Кажется, это работает, только если я назначаю «специальный» метод классу A вместо его экземпляра. Есть ли выход?

Один из способов, о котором я думал, был:

def __int__(self):
    # No infinite loop
    if type(self).__int__.im_func != self.__int__.im_func:
        return self.__int__()
    raise NotImplementedError()

Но это выглядит довольно некрасиво.

Спасибо.

Ответы [ 3 ]

8 голосов
/ 25 января 2010

Python всегда ищет специальные методы в классе, а не в экземпляре (за исключением старых, так называемых «устаревших», классов) - они устарели и исчезли в Python 3 из-за странной семантики, которая в основном происходит от поиска специальных методов в экземпляре, так что вы действительно не хотите использовать их, поверьте мне! -).

Чтобы создать специальный класс, экземпляры которого могут иметь специальные методы, независимые друг от друга, вам нужно предоставить каждому экземпляру свой собственный класс - тогда вы можете назначить специальные методы для экземпляра (индивидуального) класса без влияя на другие случаи, и жить долго и счастливо Если вы хотите, чтобы это выглядело так, как будто вы присваиваете атрибуту экземпляр, в то время как фактически присваиваете атрибут индивидуализированного класса для каждого экземпляра, вы, конечно, можете получить это с помощью специальной __setattr__ реализации.

Вот простой случай с явным синтаксисом «присвоить классу»:

>>> class Individualist(object):
...   def __init__(self):
...     self.__class__ = type('GottaBeMe', (self.__class__, object), {})
... 
>>> a = Individualist()
>>> b = Individualist()
>>> a.__class__.__int__ = lambda self: 23
>>> b.__class__.__int__ = lambda self: 42
>>> int(a)
23
>>> int(b)
42
>>> 

и вот необычная версия, где вы «делаете так, чтобы она выглядела так», вы назначаете специальный метод в качестве атрибута экземпляра (в то время как за кулисами он, конечно, все еще идет в класс):

>>> class Sophisticated(Individualist):
...   def __setattr__(self, n, v):
...     if n[:2]=='__' and n[-2:]=='__' and n!='__class__':
...       setattr(self.__class__, n, v)
...     else:
...       object.__setattr__(self, n, v)
... 
>>> c = Sophisticated()
>>> d = Sophisticated()
>>> c.__int__ = lambda self: 54
>>> d.__int__ = lambda self: 88
>>> int(c)
54
>>> int(d)
88
2 голосов
/ 25 января 2010

Единственный выход, который работает для классов нового стиля, - это наличие в классе метода, который вызывает атрибут экземпляра (если он существует):

class A(object):
    def __int__(self):
        if '__int__' in self.__dict__:
            return self.__int__()
        raise ValueError

a = A()
a.__int__ = lambda: 3
int(a)

Обратите внимание, что a.__int__ не будет методом (только функции, которые являются атрибутами класса станут методами), поэтому self не передается неявно.

0 голосов
/ 25 января 2010

Мне нечего добавить о специфике переопределения __int__. Но я заметил одну вещь в вашем образце, которую стоит обсудить.

Когда вы вручную назначаете объекту новые методы, «self» автоматически передается , а не . Я изменил ваш пример кода, чтобы сделать мою точку зрения более ясной:

class A(object): pass

a = A()
a.foo = lambda self: 3

a.foo()

Если вы запустите этот код, он выдаст исключение, потому что вы передали 0 аргументов "foo" и 1 требуется. Если вы удалите «я», он будет работать нормально.

Python автоматически добавляет «self» к аргументам, только если ему пришлось искать метод в классе объекта и найденная им функция является «нормальной» функцией. (Примеры «ненормальных» функций: методы классов, вызываемые объекты, объекты связанных методов.) Если вы прикрепите вызываемые объекты к самому объекту, они автоматически не получат «себя».

Если вы хотите себя, используйте закрытие.

...