概念解释:
方法重载:
又称多分派(multiple-dispatch),旨在实现在一个类中出现多个参数数据类型或者个数不同的同名方法而存在。在其他语言中例如C++中实现多分派实际上非常容易(直接定义方法同名,参数类型个数不同即可),但是由于python是一门动态语言的缘故,不得不说,动态语言有其优势,但是这是一把双刃剑(有利有弊),从而导致多分派的实现是男上加男(嘿嘿:))。
函数注解:
即函数参数注解和返回值注解,就是大家在编程中常用或者常见的例如:
def spam(x:int,y:int)->int:
return x+y
中对参数(或返回值)数据类型进行描述的关键字,即函数注解。函数注解仅仅可以提示程序员该函数使用参数或者返回值的数据类型类型,并不会有明确的或者说严格的数据类型限制或者检查。
假如说可以这样?(现实很残酷)
class Spam:
def __init__(self,x=None):
pass
def __init__(self,*args):
pass
这样习惯了C++的童鞋们在这里百撕不得姐(嘿嘿:));但是真的要这样吗?其实我们是质疑的,因为(*args,**kwargs)不能吃干饭!!(说明动态语言理解还是不到位);为什么要说多分派,因为为了和其他语言进行概念上的统一。
如何做到?
利用元类和描述符来实现
实例(参考他人资料,讲得很好):
import inspect
import types
class MultiMethod:
def __init__(self,name):
self._method = {}
self.__name__ = name
def register(self,meth):
sig = inspect.signature(meth)
types = []
for name,parm in sig.parameters.items():
if name == 'self':
continue
if parm.annotation is inspect.Parameter.empty:
raise TypeError('Argument {} must be annotated with a type'.format(name))
if not isinstance(parm.annotation,type):
raise TypeError('Argument {} must be a type'.format(name))
if parm.default is not inspect.Parameter.empty:
self._method[tuple(types)] = meth
types.append(parm.annotation)
self._method[tuple(types)] = meth
def __call__(self, *args):
types = tuple(type(arg) for arg in args[1:])
meth = self._method.get(types,None)
if meth:
return meth(*args)
else:
raise TypeError('No Matching method for types {}'.format(types))
def __get__(self, instance, owner):
if instance is not None:
return types.MethodType(self,instance)
else:
return self
class MutiDict(dict):
def __setitem__(self, key, value):
if key in self:
current_value = self[key]
if isinstance(current_value,MultiMethod):
current_value.register(value)
else:
mvalue = MultiMethod(key)
mvalue.register(current_value)
mvalue.register(value)
super(MutiDict, self).__setitem__(key,mvalue)
else:
super(MutiDict, self).__setitem__(key,value)
class MutiMeta(type):
def __new__(cls, clsname,bases,clsdict):
return type.__new__(cls,clsname,bases,dict(clsdict))
@classmethod
def __prepare__(metacls, name, bases):
return MutiDict()
#要使用这个类,可以如下:
class Spam(metaclass=MutiMeta):
def bar(self,x:int,y:int):
print('bar 1 : ', x, y)
def bar(self, s:str, n:int = 0):
print('bar 2 : ',s, n)
import time
class Date(metaclass=MutiMeta):
def __init__(self,year:int, month:int, day:int):
self.year = year
self.month = month
self.day = day
def __init__(self):
t = time.localtime()
self.__init__(t.tm_year,t.tm_mon,t.tm_mday)
s = Spam()
s.bar(2,3)#bar 1 : 2 3
s.bar('heloo',20)#bar 2 : heloo 20
#如果这样会报错:
s.bar('hello',2.3)
'''
Traceback (most recent call last):
File "F:/PycharmProjects/class_obj/class_one.py", line 1134, in <module>
s.bar('hello',2.3)
File "F:/PycharmProjects/class_obj/class_one.py", line 1083, in __call__
raise TypeError('No Matching method for types {}'.format(types))
TypeError: No Matching method for types (<class 'str'>, <class 'float'>)
'''
date = Date(2020,2,14)
date_local_time = Date()
关键技术描述讲解(尽管是他人见解,但是梳理过后发现成长很多,感谢原著作者!):
元类MutiMeta使用__prepare__()方法提供一个定制化字典,将其作为MultiDict的一个类实例。
MultiDict会检查条目是否已经存在,如果已经存在,则重复的条目会被合并到MultiMethod的一个类实例中去。
MultiMethod的类实例会通过一个从类型签名到函数的映射关系来将方法收集到一起。在构建额时候通过函数注解来收集这些签名并构建出映射关系。这些都是在MultiMethod.register()中完成的。对于这个映射,一个至关重要的地方是实现了方法重载。
为了让MultiMethod表现的像一个可调用对象,实现了__call__()方法,该方法通过所有的参数(除了self之外)构建出一个类型元祖,然后再内部的映射关系中找到对应的方法并调用它。实现__get__()方法是为了让MultiMethod的类实例能够在类定义中正常工作。在给出的实例中,get()被用来创建合适的绑定方法。
这样便可以实现类似其他语言中的方法重载。难受。以上方法具有几个局限性:对关键字参数不支持(不支持的原因是因为关键字参数顺序问题,得用另外一种方法去实现)、对继承支持不佳:
class A:
pass
class B(A):
pass
class C:
pass
class Spam(metaclass=MutiMeta):
def foo(self, x:A):
print('Foo 1:',x)
def foo(self, x:C):
print('Foo 2:',x)
s = Spam()
a = A()
s.foo(a)
c = C()
s.foo(c)
b = B()
s.foo(b)
'''
Traceback (most recent call last):
File "F:/PycharmProjects/class_obj/class_one.py", line 1166, in <module>
s.foo(b)
File "F:/PycharmProjects/class_obj/class_one.py", line 1083, in __call__
raise TypeError('No Matching method for types {}'.format(types))
TypeError: No Matching method for types (<class '__main__.B'>,)
'''
如上的内容你会发现在正常编程中所用甚少,有些同学可能会觉得这东西没什么用,但是,里边提及的重要的几个技术(元类,描述符、函数注解等),对后续开发会大有裨益。
同样,也可以通过装饰器来完成类似的功能,但是局限性并未消除:
import types
class multimethod:
def __init__(self,func):
self._method = {}
self.__name__ = func.name
self._default = func
def match(self, *types):
def register(func):
ndefaults = len(func.__defaults__) if func.__defaults__ else 0
for n in range(ndefaults):
self._method[types[:len(types)-n]] = func
return self
return register
def __call__(self, *args):
types = tuple(type(arg) for arg in args[1:])
meth = self._method.get(types,None)
if meth:
return meth(*args)
else:
return self._default(*args)
def __get__(self, instance, cls):
if instance is not None:
return types.MethodType(self, instance)
else:
return self
class Spam:
@multimethod
def bar(self,*args):
raise TypeError('No matching method for bar')
@bar.match(int,int)
def bar(self, x, y):
print('bar 1 :', x, y)
@bar.match(str,int)
def bar(self, s, n=0):
print('bar 2 :', s, n)
自此,所有内容结束。真晕是吧?理解技术最关键!