这两天在研究 <Ruby 元编程> ,发现 Ruby 有一个很神奇的特性,就是可以在运行时定义方法,这种技术被成为动态方法(Dynamic Method)。

1
2
3
4
5
6
7
8
9
class MyClass
define_method :my_method do |my_arg|
my_arg * 3
end
end


obj = MyClass.new
print obj.my_method(2)

MyClass Module 中的 define_method() 方法随时可以定义一个方法,只要为其提供方法名,方法参数和方法代码块即可。示例中的 my_method 方法就是被定义为 MyClass 的一个实例方法。
动态方法在抽象类的过程中可以非常显著地减少重复代码量,在动态语言中是一个很有用的特性。同样作为一款非常精致的动态语言,Python 也有创建动态方法的方案,那么如何创建呢?我们先来看一个非常简单的例子:

1
2
3
4
5
6
7
8
9
class MyObj(object):
def __init__(self, val):
self.val = val

def new_method(self, value):
return self.val + value

obj = MyObj(3)
obj.method = new_method

上面的例子中我们像 Ruby 中一样,直接为实例对象赋值一个新函数,企图将新函数定义为实例对象的一个方法类。看上去很优雅,但是实际操作的时候,我们会发现这代码根本运行不了。它会返回一段错误信息。显然是没有成功定义类方法。

1
2
3
4
>>> obj.method(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: new_method() takes exactly 2 arguments (1 given)

Google 一番之后,我发现利用 builtin 中的 types.MethodType 模块可以定义动态方法。我们再看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from types import MethodType


class MyObj(object):
def __init__(self, val):
self.val = val

def new_method(self, value):
return self.val + value

obj = MyObj(3)

obj.method = MethodType(new_method, obj, MyObj)
print(obj.method(5)) # return 8

Worked! 我们通过 MethodType 模块成功为实例对象 obj 定义了一个类方法!
然而我们尝试重新定义一个对象再去调用 method 方法之后却失败了。

1
2
3
4
5
>>> obj2 = MyObj(2)
>>> obj2.method(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObj' object has no attribute 'method'

错误提示没有 method 这个方法。

1
2
3
4
5
6
>>> MyObj.method = MethodType(new_method, None, MyObj)
>>> MyObj.method
<unbound method MyObj.new_method>
>>> obj2 = MyObj(2)
>>> obj2.method(5)
7

重新再用 MethodType 定义一番之后又可以了,显然 MethodType 只是针对实例对象的,基本上就是 Ruby 的 singleton method 。 动态方法属于元编程的一种技巧,在 Python 中其实很少用到。



扩展阅读:

使用 __slots__ 魔术方法限制增加动态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from types import MethodType

class Student(object):

__slots__ = ('name', 'age', 'country')

def __init__(self, name, age):
self.age = age
self.name = name


def set_country(self, country):
self.country = country

if __name__ == "__main__":
s = Student('a', 9)
print(dir(s))

s.set_country = MethodType(set_country, s)
s.set_country('china')
print(s.country)
1
2
3
4
5
6
# result
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'age', 'country', 'name']
Traceback (most recent call last):
File "test_slot2.py", line 19, in <module>
s.set_country = MethodType(set_country, s)
AttributeError: 'Student' object has no attribute 'set_country'


下面还有 JavaScript 版本:

1
2
3
function define_method (target, name, code) {
target[name] = new Function(code);
}