En Wed, 29 Apr 2009 15:27:48 -0300, mrstevegross <mrstevegr...@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

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to