说说Python的元编程

时间:2022-10-03 17:49:23

说说Python的元编程

提到元这个字,你也许会想到元数据,元数据就是描述数据本身的数据,元类就是类的类,相应的元编程就是描述代码本身的代码,元编程就是关于创建操作源代码(比如修改、生成或包装原来的代码)的函数和类。主要技术是使用装饰器、元类、描述符类。本文的主要目的是向大家介绍这些元编程技术,并且给出实例来演示它们是怎样定制化源代码的行为。

装饰器

装饰器就是函数的函数,它接受一个函数作为参数并返回一个新的函数,在不改变原来函数代码的情况下为其增加新的功能,比如最常用的计时装饰器:

  1. from functools import wraps 
  2.  
  3. def timeit(logger=None): 
  4.     ""
  5.     耗时统计装饰器,单位是秒,保留 4 位小数 
  6.     ""
  7.  
  8.     def decorator(func): 
  9.         @wraps(func) 
  10.         def wrapper(*args, **kwargs): 
  11.             start = time.time() 
  12.             result = func(*args, **kwargs) 
  13.             end = time.time() 
  14.             if logger: 
  15.                 logger.info(f"{func.__name__} cost {end - start :.4f} seconds"
  16.             else
  17.                 print(f"{func.__name__} cost {end - start :.4f} seconds"
  18.             return result 
  19.  
  20.         return wrapper 
  21.  
  22.     return decorator 

(注:比如上面使用 @wraps(func) 注解是很重要的, 它能保留原始函数的元数据) 只需要在原来的函数上面加上 @timeit() 即可为其增加新的功能:

  1. @timeit() 
  2. def test_timeit(): 
  3.     time.sleep(1) 
  4.  
  5. test_timeit() 
  6. #test_timeit cost 1.0026 seconds 

上面的代码跟下面这样写的效果是一样的:

  1. test_timeit = timeit(test_timeit) 
  2. test_timeit() 

装饰器的执行顺序

当有多个装饰器的时候,他们的调用顺序是怎么样的?

假如有这样的代码,请问是先打印 Decorator1 还是 Decorator2 ?

  1. from functools import wraps 
  2.  
  3. def decorator1(func): 
  4.     @wraps(func) 
  5.     def wrapper(*args, **kwargs): 
  6.         print('Decorator 1'
  7.         return func(*args, **kwargs) 
  8.     return wrapper 
  9.  
  10. def decorator2(func): 
  11.     @wraps(func) 
  12.     def wrapper(*args, **kwargs): 
  13.         print('Decorator 2'
  14.         return func(*args, **kwargs) 
  15.     return wrapper 
  16.  
  17. @decorator1 
  18. @decorator2 
  19. def add(x, y): 
  20.     return x + y 
  21.  
  22. add(1,2) 
  23.  
  24. # Decorator 1 
  25. # Decorator 2 

回答这个问题之前,我先给你打个形象的比喻,装饰器就像函数在穿衣服,离它最近的最先穿,离得远的最后穿,上例中 decorator1 是外套,decorator2 是内衣。

  1. add = decorator1(decorator2(add)) 

在调用函数的时候,就像脱衣服,先解除最外面的 decorator1,也就是先打印 Decorator1,执行到 return func(*args, **kwargs) 的时候会去解除 decorator2,然后打印 Decorator2,再次执行到 return func(*args, **kwargs) 时会真正执行 add() 函数。

需要注意的是打印的位置,如果打印字符串的代码位于调用函数之后,像下面这样,那输出的结果正好相反:

  1. def decorator1(func): 
  2.     @wraps(func) 
  3.     def wrapper(*args, **kwargs): 
  4.         result = func(*args, **kwargs) 
  5.         print('Decorator 1'
  6.         return result 
  7.     return wrapper 
  8.  
  9. def decorator2(func): 
  10.     @wraps(func) 
  11.     def wrapper(*args, **kwargs): 
  12.         result = func(*args, **kwargs) 
  13.         print('Decorator 2'
  14.         return result 
  15.     return wrapper 

装饰器不仅可以定义为函数,也可以定义为类,只要你确保它实现了__call__() 和 __get__() 方法。

关于装饰器的其他用法,可以参考前文:

  • 我是装饰器
  • 再谈装饰器

元类

Python 中所有类(object)的元类,就是 type 类,也就是说 Python 类的创建行为由默认的 type 类控制,打个比喻,type 类是所有类的祖先。我们可以通过编程的方式来实现自定义的一些对象创建行为。

定一个类继承 type 类 A,然后让其他类的元类指向 A,就可以控制 A 的创建行为。典型的就是使用元类实现一个单例:

  1. class Singleton(type): 
  2.     def __init__(self, *args, **kwargs): 
  3.         self._instance = None 
  4.         super().__init__(*args, **kwargs) 
  5.  
  6.     def __call__(self, *args, **kwargs): 
  7.         if self._instance is None: 
  8.             self._instance = super().__call__(*args, **kwargs) 
  9.             return self._instance 
  10.         else
  11.             return self._instance 
  12.  
  13.  
  14. class Spam(metaclass=Singleton): 
  15.     def __init__(self): 
  16.         print("Spam!!!"

元类 Singleton 的__init__和__new__ 方法会在定义 Spam 的期间被执行,而 __call__方法会在实例化 Spam 的时候执行。

如果想更好的理解元类,可以阅读Python黑魔法之metaclass

descriptor 类(描述符类)

descriptor 就是任何一个定义了 __get__(),__set__()或 __delete__()的对象,描述器让对象能够自定义属性查找、存储和删除的操作。这里举官方文档[1]一个自定义验证器的例子。

定义验证器类,它是一个描述符类,同时还是一个抽象类:

  1. from abc import ABC, abstractmethod 
  2.  
  3. class Validator(ABC): 
  4.  
  5.     def __set_name__(self, owner, name): 
  6.         self.private_name = '_' + name 
  7.  
  8.     def __get__(self, obj, objtype=None): 
  9.         return getattr(obj, self.private_name) 
  10.  
  11.     def __set__(self, obj, value): 
  12.         self.validate(value) 
  13.         setattr(obj, self.private_name, value) 
  14.  
  15.     @abstractmethod 
  16.     def validate(self, value): 
  17.         pass 

自定义验证器需要从 Validator 继承,并且必须提供 validate() 方法以根据需要测试各种约束。

这是三个实用的数据验证工具:

OneOf 验证值是一组受约束的选项之一。

  1. class OneOf(Validator): 
  2.  
  3.     def __init__(self, *options): 
  4.         self.options = set(options) 
  5.  
  6.     def validate(self, value): 
  7.         if value not in self.options: 
  8.             raise ValueError(f'Expected {value!r} to be one of {self.options!r}'

Number 验证值是否为 int 或 float。根据可选参数,它还可以验证值在给定的最小值或最大值之间。

  1. class Number(Validator): 
  2.  
  3.     def __init__(self, minvalue=None, maxvalue=None): 
  4.         self.minvalue = minvalue 
  5.         self.maxvalue = maxvalue 
  6.  
  7.     def validate(self, value): 
  8.         if not isinstance(value, (intfloat)): 
  9.             raise TypeError(f'Expected {value!r} to be an int or float'
  10.         if self.minvalue is not None and value < self.minvalue: 
  11.             raise ValueError( 
  12.                 f'Expected {value!r} to be at least {self.minvalue!r}' 
  13.             ) 
  14.         if self.maxvalue is not None and value > self.maxvalue: 
  15.             raise ValueError( 
  16.                 f'Expected {value!r} to be no more than {self.maxvalue!r}' 
  17.             ) 

String 验证值是否为 str。根据可选参数,它可以验证给定的最小或最大长度。它还可以验证用户定义的 predicate。

  1. class String(Validator): 
  2.  
  3.     def __init__(self, minsize=None, maxsize=None, predicate=None): 
  4.         self.minsize = minsize 
  5.         self.maxsize = maxsize 
  6.         self.predicate = predicate 
  7.  
  8.     def validate(self, value): 
  9.         if not isinstance(value, str): 
  10.             raise TypeError(f'Expected {value!r} to be an str'
  11.         if self.minsize is not None and len(value) < self.minsize: 
  12.             raise ValueError( 
  13.                 f'Expected {value!r} to be no smaller than {self.minsize!r}' 
  14.             ) 
  15.         if self.maxsize is not None and len(value) > self.maxsize: 
  16.             raise ValueError( 
  17.                 f'Expected {value!r} to be no bigger than {self.maxsize!r}' 
  18.             ) 
  19.         if self.predicate is not None and not self.predicate(value): 
  20.             raise ValueError( 
  21.                 f'Expected {self.predicate} to be true for {value!r}' 
  22.             ) 

实际应用时这样写:

  1. class Component: 
  2.  
  3.     name = String(minsize=3, maxsize=10, predicate=str.isupper) 
  4.     kind = OneOf('wood''metal''plastic'
  5.     quantity = Number(minvalue=0) 
  6.  
  7.     def __init__(self, name, kind, quantity): 
  8.         self.name = name 
  9.         self.kind = kind 
  10.         self.quantity = quantity 

描述器阻止无效实例的创建:

  1. >>> Component('Widget''metal', 5)      # Blocked: 'Widget' is not all uppercase 
  2. Traceback (most recent call last): 
  3.     ... 
  4. ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' 
  5.  
  6. >>> Component('WIDGET''metle', 5)      # Blocked: 'metle' is misspelled 
  7. Traceback (most recent call last): 
  8.     ... 
  9. ValueError: Expected 'metle' to be one of {'metal''plastic''wood'
  10.  
  11. >>> Component('WIDGET''metal', -5)     # Blocked: -5 is negative 
  12. Traceback (most recent call last): 
  13.     ... 
  14. ValueError: Expected -5 to be at least 0 
  15. >>> Component('WIDGET''metal''V')    # Blocked: 'V' isn't a number 
  16. Traceback (most recent call last): 
  17.     ... 
  18. TypeError: Expected 'V' to be an int or float 
  19.  
  20. >>> c = Component('WIDGET''metal', 5)  # Allowed:  The inputs are valid 

最后的话

关于 Python 的元编程,总结如下:

如果希望某些函数拥有相同的功能,希望不改变原有的调用方式、不写重复代码、易维护,可以使用装饰器来实现。

如果希望某一些类拥有某些相同的特性,或者在类定义实现对其的控制,我们可以自定义一个元类,然后让它类的元类指向该类。

如果希望实例的属性拥有某些共同的特点,就可以自定义一个描述符类。

原文链接: