views:

190

answers:

5

In the following code segment:

try:
    raise Bob()
except Fred:
    print "blah"

How is the comparison of Bob and Fred implemented?

From playing around it seems to be calling isinstance underneath, is this correct?

I'm asking because I am attempting to subvert the process, specifically I want to be able to construct a Bob such that it gets caught by execpt Fred even though it isn't actually an instance of Fred or any of its subclasses.

A couple of people have asked why I'm trying to do this...

We have a RMI system, that is built around the philosophy of making it as seamless as possible, here's a quick example of it in use, note that there is no socket specific code in the RMI system, sockets just provided a convenient example.

import remobj
socket = remobj.RemObj("remote_server_name").getImport("socket")
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 0))
print "listening on port:", s.getsockname()[1]
s.settimeout(10)
try:
    print "received:", s.recv(2048)
except socket.timeout:
    print "timeout"

Now in this particular example the except doesn't work as expected because the raised object is not an instance of socket.timeout, it's an instance of one of our proxy helper classes.

+1  A: 

I want to be able to construct a Bob such that it gets caught by execpt Fred even though it isn't actually an instance of Fred or any of its subclasses.

Well, you can just catch 'Exception' - but this is not very pythonic. You should attempt to catch the correct exception and then fall back on the general Exception (which all exceptions are subclassed from) as a last resort. If this does not work for you then something has gone terribly wrong in your design phase.

See this note from Code Like A Pythonista

Note: Always specify the exceptions to catch. Never use bare except clauses. Bare except clauses will catch unexpected exceptions, making your code exceedingly difficult to debug.

However, one of the idioms in the Zen of Python is

Special cases aren't special enough to break the rules. Although practicality beats purity.

Shane C. Mason
+4  A: 

I believe that your guess is correct in how the comparison works, and the only way to intercept that is to add Fred as a base class to Bob. For example:

# Assume both Bob and Fred are derived from Exception
>>> class Bob(Bob, Fred):
...     pass
... 
>>> try:
...     raise Bob()
... except Fred:
...     print 'blah'
blah

As far as I know, this is the only way to make it work as you wrote it. However, if you simply rewrote the except: line as

... except (Bob, Fred):

It would catch both Bob and Fred, without requiring the modification of the definition of Bob.

bluejeansummer
A: 
>>> class Fred(Exception):
    pass
>>> class Bob(Fred):
    pass
>>> issubclass(Bob, Fred)
True
>>> issubclass(Fred, Bob)
False
>>> try:
    raise Bob()
except Fred:
    print("blah")


blah

So basically the exception is caught because, Bob is subclass of Fred, I am assuming, either they must have implemented a logic similar to issubclass(Bob, Fred)

See, is this how you want to implement, watever you want. Ofcourse not in the init, but some other method.

>>> class Bob(Exception):
    def __init__(self):
     raise Fred

>>> try:
    b = Bob()
except Fred:
    print('blah')


blah
roopesh
A: 

In CPython at least, it looks like there's a COMPARE_OP operation with type 10 (exception match). There's unlikely anything you can do to hack around that calculation.

>>> import dis
>>> def foo():
...     try:
...             raise OSError()
...     except Exception, e:
...             pass
... 
>>> dis.dis(foo)
  2           0 SETUP_EXCEPT            13 (to 16)

  3           3 LOAD_GLOBAL              0 (OSError)
              6 CALL_FUNCTION            0
              9 RAISE_VARARGS            1
             12 POP_BLOCK           
             13 JUMP_FORWARD            21 (to 37)

  4     >>   16 DUP_TOP             
             17 LOAD_GLOBAL              1 (Exception)
             20 COMPARE_OP              10 (exception match)
             23 JUMP_IF_FALSE            9 (to 35)
             26 POP_TOP             
             27 POP_TOP             
             28 STORE_FAST               0 (e)
             31 POP_TOP             

  5          32 JUMP_FORWARD             2 (to 37)
        >>   35 POP_TOP             
             36 END_FINALLY         
        >>   37 LOAD_CONST               0 (None)
             40 RETURN_VALUE        
Sufian
+1  A: 

I'm not clear how hiding your exception in socket.timeout adhears to the "seamless" philosophy? What's wrong with catching the expected exception as it's defined?

try:
    print "received:", s.recv(2048)
except socket.timeout:
    print "timeout"
except our_proxy_helper_class:
    print 'crap!'

Or, if you really want to catch it as socket.timeout, why not just raise socket.timeout in our_proxy_helper_class?

raise socket.timeout('Method x timeout')

So when you raise socket.timeout in our_proxy_helper_class it should be caught by 'except socket.timeout'.

monkut
By seamless I mean the only visible difference between local and remote code should be the import. If you had imported socket locally with "import socket" then you would except on socket.timeout, so ideally you should do the same when it is imported remotely.
tolomea
In the example socket.timeout isn't a class. It looks and acts like a class as long as you don't do any introspection. But it is actually another proxy helper instance. I now believe that this is the core problem and I am investigating metaclasses as a way of turning it into an actual class. Then I will be able to raise the exception as an instance of that class and everything should work as expected.
tolomea