2

I have an interface class called iResource, and a number of subclasses, each of which implement the "request" method. The request functions use socket I/O to other machines, so it makes sense to run them asynchronously, so those other machines can work in parallel.

The problem is that when I start a thread with iResource.request and give it a subclass as the first argument, it'll call the superclass method. If I try to start it with "type(a).request" and "a" as the first argument, I get "" for the value of type(a). Any ideas what that means and how to get the true type of the method? Can I formally declare an abstract method in Python somehow?

EDIT: Including code.

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" takes a list "tasks" of callables and a list "argss" of args-tuples, and assumes each returns a list. It then executes each in a separate thread, and concatenates the lists of results. I can post that code if necessary, but I haven't had any problems with it so I'll leave it out for now.

The objects "a" are DEFINITELY not of type iResource, since it has a single constructor that just throws an exception. However, replacing "type(a).request" with "iResource.request" invokes the base class method. Furthermore, calling "self.types["social"][0].request" directly works fine, but the above code gives me: "type object 'instance' has no attribute 'request'".

Uncommenting the commented line prints <type 'instance'> several times.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
xanderflood
  • 826
  • 2
  • 12
  • 22

2 Answers2

2

You can just use the bound method object itself:

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

If you insist on calling your methods through the base class you could also do it like this:

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

Result:

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 doesn't support interfaces as e.g. Java does. But with the abc module you can ensure that certain methods must be implemented in subclasses. Normally you would do this with the abc.abstractmethod() decorator, but you still could not call the subclasses method through the base class, like you intend. I had a similar question once and I had the idea of the virtualmethod() decorator. It's quite simple. It essentially does the same thing as abc.abstratmethod(), but also redirects the call to the subclasses method. The specifics of the abc module can be found in the docs and in PEP3119.

BTW: I assume you're using Python >= 2.6.

pillmuncher
  • 10,094
  • 2
  • 35
  • 33
1

The reference to "<type "instance" >" you get when you are using an "old style class" in Python - i.e.: classes not derived from the "object" type hierarchy. Old style classes are not supposed to work with several of the newer features of the language, including descriptors and others. AND, among other things, - you can't retrieve an attribute (or method) from the class of an old style class using what you are doing:

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

Therefore, just make your base class inherit from "object" instead of "nothing" and check if you still get the error message when requesting the "request" method from type(a) :

As for your other observation: "The problem is that when I start a thread with iResource.request and give it a subclass as the first argument, it'll call the superclass method."

It seems that the "right" thing for it to do is exactly that:

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

Here, I call a method in the class "A" giving it an specialized instance of "A" - the method is still the one in class "A".

jsbueno
  • 99,910
  • 10
  • 151
  • 209