先来说说ruby的元编程
ruby中的动态方法只要跟send, define_method, method_missing这几个方法相关。

 

send
class SendClass
def get_one_name
'one_name'
end
def get_two_name
'two_name'
end
def get_three_name
'three_name'
end
end
s = SendClass.new
puts s.send(:get_one_name) #one_name
puts s.send(:get_two_name) #two_name
puts s.send(:get_three_name)#three**name
puts s.send(:get_four_name) #undefined method `get_four_name'

send方法至少要有一个参数,这个参数就是要调用的方法名,如果找不到这个方法,就会报undefined method的错误,send方法用处也是比较多的,如果有一个对象有很多属性,但是调用这些属性方法时,如果不知道要调用的是哪个属性,就可以用这个方法。

但是send调用的这些方法还是必须存在的呀,并不是一些动态的方法,只是动态调用,那么有没有可能动态定义方法呢?别急,这就是我们要介绍的define_method方法。

define_method
class SendClass
#def get_one_name
# 'one_name'
#end
#def get_two_name
# 'two_name'
#end
#def get_three_name
# 'three_name'
#end
[:one_name, :two_name, :three_name].each do |name|
define_method("get_#{name}"){
name
}
end
end
s = SendClass.new
puts s.send(:get_one_name) #one_name
puts s.send(:get_two_name) #two_name
puts s.send(:get_three_name)#three_name
puts s.send(:get_four_name) #undefined method `get_four_name'

看吧,达到了同样的效果,只用了一条语句就定义了几个方法,那么还有没有其他方法能达到这样的效果呢?答案是肯定的,那就是method_missing。
method_missing
method_missing是一个魔鬼方法,使用不当的情况下,会发生很多意想不到的问题。不过它还是很有用的,在介绍它之前,我们先会议一下方法的查找,遵循右上法则,直到找到BasicObject中。那么我们看看BasicObject有没有这个方法。

1.9.3-p551 :016 > BasicObject.method(:method_missing)
=> #

找到了,method_missing方法是定义在BasicObject方法内,那么它是怎么使用的呢?
实际上我们调用一个方法,如果对象无法找不到这个方法,就会调用method_missing方法,我们举个例子:

class SendClass
def get_one_name
'one_name'
end
def get_two_name
'two_name'
end
def get_three_name
'three_name'
end
def method_missing(name, *argc)
'call method_missing'
end
end
s = SendClass.new
puts s.send(:get_one_name) #one_name
puts s.send(:get_two_name) #two_name
puts s.send(:get_three_name)#three_name
puts s.send(:get_four_name) #call method_missing

我们基于这个原理实现一下动态方法:
[ruby] view plain copy

在CODE上查看代码片派生到我的代码片

class SendClass
def method_missing(name, *argc)
if [:one_name, :two_name, :three_name].include?(name)
name
else #处理不了的方法就让父类处理
super
end
end
end
s = SendClass.new
puts s.one_name #one_name
puts s.two_name #two_name
puts s.three_name #three_name
puts s.four_name #undefined method `four_name'

一般情况下会是send, define_method, method_missing三个方法同时使用:

class SendClass
[:one_name, :two_name, :three_name].each do |name|
define_method("get_#{name}"){
name
}
end
def method_missing(name, *argc)
if self.respond_to?(name)
send(:name)
else #处理不了的方法就让父类处理
super
end
end
end
s = SendClass.new
puts s.get_one_name #one_name
puts s.get_two_name #two_name
puts s.get_three_name #three_name
puts s.get_four_name #undefined method `get_four_name'

其他
在最后,我们讲解一些细节
走到method_missing的方法并不是真正的方法
举个例子:

 

class SendClass
def method_missing(name, *argc)
if [:one_name, :two_name, :three_name].include?(name)
name
else #处理不了的方法就让父类处理
super
end
end
end
s = SendClass.new
puts s.respond_to?(:one_name)#false
puts s.respond_to?(:two_name)#false
puts s.respond_to?(:three_name)#false
method_missing方法是方法调用找不到对应方法的时候就会调用method_missing方法,但是这些方法不会真正被定义,只能算是一个异常机制。
method_missing有性能问题
method_missing虽然好用,但是会有一定的性能损失,为什么呢?还记得我们说到的方法查找吗,遵循右上的原则,最终找不到的时候才会报错。
class BasicObject
def method_missing(name, *argc)
puts 'call BasicObject method_missing'
super
end
end
class Object
def method_missing(name, *argc)
puts 'call Object method_missing'
super
end
end
class SendClass
def method_missing(name, *argc)
if [:one_name, :two_name, :three_name].include?(name)
name
else #处理不了的方法就让父类处理
puts 'call SendClass method_missing'
super
end
end
end
s = SendClass.new
puts s.four_name

执行这段代码:

call SendClass method_missing
call Object method_missing
call BasicObject method_missing
send.rb:4:in method_missing': class or module required (TypeError)
from send.rb:11:inmethod_missing'
from send.rb:21:in method_missing'
from send.rb:28:in'

这就是方法查找,如果four_name方法在对象链中都找不到,就会查找对象链中method_missing方法,逐个调用,直到真正找不到这个方法。方法查找是有一定的性能损失,所以说mthod_missing方法是一定的性能损失。
真正方法的优先级高于method_missing方法
如果一个对象有这个方法,肯定不会调用method_missing方法,这个就不介绍了,这也是method_missing方法的用途,如果低于method_missing方法,那么method_missing还有什么用呢?

接下来说说python的

1.动态派发,其实这是一种很直观的做法,就是在调用类方法时候,找不到调用的方法,则会默认调用一个魔术方法,需要在魔术方法里处理实际的调用。

ruby里是用method_missing实现,python里面则是getattr,其实python里面还有一个getattribute方法,区别是前者只在类属性不存在的时候调用。有一点注意的是,在python里,方法也是一种对象,所以调用方法也是先获取类属性。下面是一个例子,为了简单,返回的是lambda,而不是一个函数,缺点就是没那么直观,要返回一个可调用对象而不是直接调用某个方法。

 

class A:
def getattr(self,name):
return lambda x:print(name)
a=A()
a.x
<function A.__getattr__.. at 0x02F13B28>
a.call()
Traceback (most recent call last):
File "<pyshell#15>", line 1, in
a.call()
TypeError: () missing 1 required positional argument: 'x'
a.call(1)
call

2.给对象增加属性,可以给对象实例通过setattr来设置属性,或者可以直接给类增加属性。和方法一相比,区别就是增加了属性,可以在dir里看到对象的属性。看到下面的代码也可以看出实例和类设置属性的区别,实例设置属性其实并不是对象的方法,而只是一个函数类型的属性。给类设置函数类型的属性之后,类实例化之后,会当做实例的方法。所以实例设置属性的时候,是不需要传递self,类方法设置的时候,函数需要传入self参数。类实例设置属性时候,如果需要获取self,可以在某个方法,比如init设置属性的时候,通过作用域传入到函数对象里面。

class A:
pass
dir(a)
['class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattr', 'getattribute', 'gt', 'hash', 'init', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref']
def m(x):
print(x)
setattr(a,'m',m)
dir(a)
['class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattr', 'getattribute', 'gt', 'hash', 'init', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', 'm']
a.m
a.m()
Traceback (most recent call last):
File "<pyshell#24>", line 1, in
a.m()
TypeError: m() missing 1 required positional argument: 'x'
a.m(10)
10
A.m=m
a.m
b=A()
dir(b)
['class', 'delattr', 'dict', 'dir', 'doc', 'eq', 'format', 'ge', 'getattr', 'getattribute', 'gt', 'hash', 'init', 'le', 'lt', 'module', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', 'm']
b.m
<bound method m of >
b.m(1)
Traceback (most recent call last):
File "<pyshell#31>", line 1, in
b.m(1)
TypeError: m() takes 1 positional argument but 2 were given