首页 > 解决方案 > injecting a python class member function

问题描述

The following code works in python3, but not python2. To be clear, I am trying to inject/monkey patch a member function from another class (Class1) into the target class (Class2) so that the function uses appropriate member attributes from self.

Why does it work in python3, and what else can I do to get the desired behavior in python2?

class Parent:
  def foo(self):
    print("foo of class Parent: instance " + self.__class__.__name__)

  def __init__(self):
    self.d = {"foo": self.__class__.foo}

  def bar(self):
    self.d['foo'](self)
    self.d['foo'] = Child1.foo
    self.d['foo'](self)

class Child1(Parent):
  def foo(self):
    print("foo of class Child1: instance " + self.__class__.__name__)

class Child2(Parent):
  pass

Child2().bar()

标签: pythonfunctional-programming

解决方案


What you're looking to do here is to call the unbound method Child2.foo with a self that isn't a Child2.

This is illegal, and Python 2 will detect that and raise a TypeError that explains exactly what's wrong:

TypeError: unbound method foo() must be called with Child1 instance as first argument (got Child2 instance instead)

In Python 3, there are no unbound method objects; unbound methods are just plain old functions. So they can't check that you're trying to do anything illegal. And then, because you aren't actually using the fact that self is a Child2 inside the foo method, you get away with it.


But you can't inject methods that actually use the Child2-ness of Child2 this way; they'll just end up raising a TypeError or AttributeError or calling the wrong methods. It will only work for methods that had no reason to be methods in the first place.


If you really want this behavior in Python 2 anyway, you can get it by extracting the function out of the unbound method:

    self.d['foo'] = Child1.foo.__func__

(If you want to work with even older 2.x, use im_func instead of __func__ there.)

Now, it's not a method at all—and if you tried to actually bind it to self with the descriptor protocol or by constructing a MethodType instead, you'd get the same old TypeError. But it is a function, and you can call it with whatever argument you want as a function. And, because your function doesn't do anything with that self parameter that requires it to be a Child2, it will work.


While we're at it, you almost certainly want Parent to be a new-style class here. And you probably want to use the same code in Python 2 and Python 3, not different code for the same behavior in both. So:

class Parent(object): # makes it a new-style class in 2.x as well as 3.x
  def foo(self):
    print("foo of class Parent: instance " + self.__class__.__name__)

  def __init__(self):
    self.d = {"foo": self.__class__.foo}

  def bar(self):
    self.d['foo'](self)
    # Gets __func__ if it's an unbound method (2.x), leaves it alone if not (3.x)
    self.d['foo'] = getattr(Child1.foo, '__func__', Child1.foo)
    self.d['foo'](self)

class Child1(Parent):
  def foo(self):
    print("foo of class Child1: instance " + self.__class__.__name__)

class Child2(Parent):
  pass

Child2().bar()

推荐阅读