An idea for method_missing

Gabriel Genellina gagsl-py2 at yahoo.com.ar
Thu Apr 30 00:12:25 EDT 2009


En Wed, 29 Apr 2009 15:27:48 -0300, mrstevegross <mrstevegross at gmail.com>  
escribió:

> I was exploring techniques for implementing method_missing in Python.
> I've seen a few posts out there on the subject... One tricky aspect is
> if it's possible to not just intercept a method_missing call, but
> actually dynamically add a new function to an existing class. I
> realized you can modify the Class.__dict__ variable to actually add a
> new function. Here's how to do it:
>
> class Foo:
>   def __init__(self):
>     self.foo = 3
>
>   def method_missing(self, attr, *args):
>     print attr, "is called with %d args:" % len(args),
>     print args
>
>   def __getattr__(self, attr):
>     print "Adding a new attribute called:", attr
>     def callable(*args):
>       self.method_missing(attr, *args[1:])
>     Foo.__dict__[attr] = callable
>     return lambda *args :callable(self, *args)

You're adding a function (a closure, actually) to some *class* that refers  
to a specific *instance* of it. This doesn't work as expected; let's add  
some verbosity to show the issue:

class Foo:
   def __init__(self, foo):
     self.foo = foo

   def method_missing(self, attr, *args):
     print 'Foo(%r)' % self.foo, attr, "is called with %d args:" %  
len(args),
     print args
   # __getattr__ stays the same

f = Foo('f')
f.go_home(1,2,3)
g = Foo('g')
g.go_home('boo')

# output:
Adding a new attribute called: go_home
Foo('f') go_home is called with 3 args: (1, 2, 3)
Foo('f') go_home is called with 1 args: ('boo',)

One would expect Foo('g') on the last line. Worse:

py> print f
Adding a new attribute called: __str__
Foo('f') __str__ is called with 0 args: ()

Traceback (most recent call last):
   File "<pyshell#21>", line 1, in <module>
     print f
TypeError: __str__ returned non-string (type NoneType)
py> if f: print "Ok!"

Adding a new attribute called: __nonzero__
Foo('f') __nonzero__ is called with 0 args: ()

Traceback (most recent call last):
   File "<pyshell#28>", line 1, in <module>
     if f: print "Ok!"
TypeError: __nonzero__ should return an int

Almost anything you want to do with a Foo instance will fail, because  
Python relies on the existence or not of some __magic__ methods to  
implement lots of things.

The code below tries to overcome this problems. Still, in Python, unlike  
Ruby, there is no way to distinguish between a "method" and an "attribute"  
-- both are looked up exactly the same way. __getattr__ already is,  
roughly, the equivalent of method_missing (well, attribute_missing, I'd  
say).

class Foo(object):
   def __init__(self, foo):
     self.foo = foo

   def method_missing(self, attr, *args, **kw):
     print 'Foo(%r)' % self.foo, attr, "is called with %d args:" %  
len(args),
     print args

   def __getattr__(self, attr):
     if attr[:2]==attr[-2:]=='__':
         raise AttributeError(attr)
     print "Adding a new attribute called:", attr
     def missing(self, *args, **kw):
         return self.method_missing(attr, *args, **kw)
     setattr(type(self), attr, missing)
     return missing.__get__(self, type(self))

f = Foo('f')
f.go_home(1,2,3)
f.go_home(2,3,4)
g = Foo('g')
g.go_home('boo')
f.go_home('f again')
print f # works fine now
print f.foox # oops! typos have unexpected side effects :(
print len(f) # should raise TypeError

-- 
Gabriel Genellina




More information about the Python-list mailing list