Полиморфизм с вызываемыми в Python - PullRequest
2 голосов
/ 27 февраля 2012

У меня есть интерфейсный класс iResource и несколько подклассов, каждый из которых реализует метод request. Функции запроса используют сокетный ввод / вывод для других машин, поэтому имеет смысл запускать их асинхронно, чтобы эти другие машины могли работать параллельно.

Проблема в том, что когда я запускаю поток с iResource.request и указываю ему подкласс в качестве первого аргумента, он вызывает метод суперкласса. Если я попытаюсь запустить его с «type (a) .request» и «a» в качестве первого аргумента, я получу «» для значения типа (a). Любые идеи, что это значит и как получить истинный тип метода? Можно ли как-то официально объявить абстрактный метод в Python?

РЕДАКТИРОВАТЬ: Включая код.

def getSocialResults(self, query=''):
    #for a in self.types["social"]: print type(a)
    tasks = [type(a).request for a in self.types["social"]]
    argss = [(a, query, 0) for a in self.types["social"]]

    grabbers = executeChainResults(tasks, argss)

    return igrabber.cycleGrabber(grabbers)

«executeChainResults» принимает список «задач» вызываемых объектов и список «argss» аргументов-кортежей и предполагает, что каждый из них возвращает список. Затем он выполняет каждый в отдельном потоке и объединяет списки результатов. Я могу опубликовать этот код, если это необходимо, но у меня не было проблем с ним, поэтому я пока опущу его.

Объекты "a" определенно не относятся к типу iResource, поскольку у них есть единственный конструктор, который просто генерирует исключение. Однако замена «type (a) .request» на «iResource.request» вызывает метод базового класса. Кроме того, вызов "self.types [" social "] [0] .request" напрямую работает нормально, но приведенный выше код дает мне: "объект типа 'instance' не имеет атрибута 'request'".

Раскомментируя закомментированную строку, печатает <type 'instance'> несколько раз.

Ответы [ 2 ]

2 голосов
/ 27 февраля 2012

Вы можете просто использовать сам объект связанного метода:

tasks = [a.request for a in self.types["social"]]
#        ^^^^^^^^^
grabbers = executeChainResults(tasks, [(query, 0)] * len(tasks))
#                                     ^^^^^^^^^^^^^^^^^^^^^^^^^

Если вы настаиваете на вызове ваших методов через базовый класс, вы также можете сделать это следующим образом:

from abc import ABCMeta
from functools import wraps

def virtualmethod(method):
    method.__isabstractmethod__ = True
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        return getattr(self, method.__name__)(*args, **kwargs)
    return wrapper

class IBase(object):
    __metaclass__ = ABCMeta
    @virtualmethod
    def my_method(self, x, y):
        pass

class AddImpl(IBase):
    def my_method(self, x, y):
        return x + y

class MulImpl(IBase):
    def my_method(self, x, y):
        return x * y

items = [AddImpl(), MulImpl()]

for each in items:
    print IBase.my_method(each, 3, 4)

b = IBase()  #  <-- crash

Результат:

7
12
Traceback (most recent call last):
  File "testvirtual.py", line 30, in <module>
    b = IBase()
TypeError: Can't instantiate abstract class IBase with abstract methods my_method

Python не поддерживает интерфейсы, как, например, Java.Но с помощью модуля abc вы можете гарантировать, что определенные методы должны быть реализованы в подклассах.Обычно вы делаете это с помощью декоратора abc.abstractmethod(), но вы все равно не можете вызвать метод подклассов через базовый класс, как вы и предполагали.У меня когда-то был похожий вопрос, и у меня была идея декоратора virtualmethod().Это довольно просто.По сути, он делает то же самое, что и abc.abstratmethod(), но также перенаправляет вызов метода подклассов.Особенности модуля abc можно найти в документах и в PEP3119 .

Кстати: я предполагаю, что вы используете Python> = 2.6.

1 голос
/ 27 февраля 2012

Ссылка на «<type "instance" >» вы получаете, когда вы используете «класс старого стиля» в Python, то есть: классы, не производные от иерархии типов «объект».Классы старого стиля не должны работать с несколькими новыми функциями языка, включая дескрипторы и другие.И, среди прочего, - вы не можете извлечь атрибут (или метод) из класса старого стиля, используя то, что вы делаете:

>>> class C(object):
...    def c(self): pass
... 
>>> type (c)
<class '__main__.C'>
>>> c = C()
>>> type(c).c
<unbound method C.c>
>>> class D: #not inheriting from object: old style class
...   def d(self): pass
... 
>>> d = D()
>>> type(d).d
>>> type(d)
<type 'instance'>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'instance' has no attribute 'd'
>>> 

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

Что касается вашего другого наблюдения: «Проблема в том, что когда я запускаюпоток с iResource.request и предоставляющий ему подкласс в качестве первого аргумента, он вызовет метод суперкласса. "

Кажется, что" правильная "вещь, которую он должен сделать, это именно то, что:

>>> class A(object):
...   def b(self):
...     print "super"
... 
>>> class B(A):
...   def b(self):
...     print "child"
... 
>>> b = B()
>>> A.b(b)
super
>>> 

Здесь я вызываю метод в классе «A», давая ему специализированный экземпляр «A» - метод все еще тот же, что и в классе «A».

...