python3 元类编程的一个例子
【引子】
虽然我们可以通过“class”语句来定义“类”,但是要想更加细粒度的控制“类”的创建,要使用元类编程才能实现。
比如说我们要实现这样的一个约束、所有项目中用到的类都应该要为它定义的方法提供文档字符字符串。有两条可行
的路径 1):依赖工程师的自觉、自律 让他们在定义每一个方法的时候都为方法增加文档字符串, 2):通过元类来做一些
控制,如果工程师没有为方法提供文档字符,那么就直接报错,直到工程师为方法提供字符串为止。
【实现1】
依赖工程师的自觉、自律为方法增加文档字符串
class Person(object): name = None def __init__(self,name): self.name=name def say_hello(self): """ print hello My name is xxx ... """ print("hello My name is {self.name}".format(self=self))
我们如何能保证每一个方法都增加了文档字符串呢?我一方面要依靠“自觉”另一方面要依靠“纪律性”,最好要有专门的人来做代码
审核。
【实现2】
通过元类来实现这个约束
第一步:定义一个元类来审核class
class DocMeta(type): """ 检查方法是否有提供文档字符串 """ def __init__(self,name,base,attrs):for key,value in attrs.items(): if key.startswith('__'): #跳过魔术方法 continue if not hasattr(value,"__call__"): #跳过字段 continue #如果能进入到这里、那么一定是方法了、于是检查方法有没有文档字符串. if not getattr(value,"__doc__"): #没有文档字符串的情况下就报错 raise TypeError("{0} must have a docstring".format(key)) super().__init__(name,base,attrs)
第二步:定义一个通用的基类、以后所有要实现这一约束的类都继承自它
class Documented(metaclass=DocMeta): pass
注意这个项的元类是我们刚才定义的“DocMeta”类
第三步:让项目中的类继承自这个基类
class Person(Documented): name = None def __init__(self,name): self.name=name def say_hello(self): """ print hello My name is xxx ... """ print("hello My name is {self.name}".format(self=self))
第四步:和使用普通的类一样使用Person类
if __name__=="__main__": p = Person("welson") p.say_hello() #hello My name is welson
【总结】
以上代码由于在say_hello 方法中提供了文档字符串、如果没有提供的话、在创建Person类的时候就会报错了
Traceback (most recent call last): File "main.py", line 31, in <module> class Person(Documented): File "main.py", line 24, in __init__ raise TypeError("{0} must have a docstring".format(key)) TypeError: say_hello must have a docstring
全部代码如下:
""" Python元类编程的一个例子 """ __version__ = '0.1' __author__ = '蒋乐哥哥' class DocMeta(type): """ 检查方法是否有提供文档字符串 """ def __init__(self,name,base,attrs): for key,value in attrs.items(): if key.startswith('__'): #路过魔术方法 continue if not hasattr(value,"__call__"): #跳过字段 continue #如果能进入到这里、那么一定是方法了、于是检查方法有没有文档字符串. if not getattr(value,"__doc__"): #没有文档字符串的情况下就报错 raise TypeError("{0} must have a docstring".format(key)) super().__init__(name,base,attrs) class Documented(metaclass=DocMeta): pass class Person(Documented): name = None def __init__(self,name): self.name=name def say_hello(self): """ print hello My name is xxx ... """ print("hello My name is {self.name}".format(self=self)) # 在不为方法提供文档字符串的情况下会直接报错 #class Person(Documented): # name = None # def __init__(self,name): # self.name=name # # def say_hello(self): # print("hello My name is {self.name}".format(self=self)) if __name__=="__main__": p = Person("welson") p.say_hello()
----------------------------------------