共计 6669 个字符,预计需要花费 17 分钟才能阅读完成。
提到元这个字,你兴许会想到元数据,元数据就是形容数据自身的数据,元类就是类的类,相应的元编程就是形容代码自身的代码,元编程就是对于创立操作源代码 (比方批改、生成或包装原来的代码) 的函数和类。次要技术是应用装璜器、元类、描述符类。
本文的次要目标是向大家介绍这些元编程技术,并且给出实例来演示它们是怎么定制化源代码的行为。
装璜器
装璜器就是函数的函数,它承受一个函数作为参数并返回一个新的函数,在不扭转原来函数代码的状况下为其减少新的性能,比方最罕用的计时装璜器:
from functools import wraps | |
def timeit(logger=None): | |
"""耗时统计装璜器,单位是秒,保留 4 位小数""" | |
def decorator(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
start = time.time() | |
result = func(*args, **kwargs) | |
end = time.time() | |
if logger: | |
logger.info(f"{func.__name__} cost {end - start :.4f} seconds") | |
else: | |
print(f"{func.__name__} cost {end - start :.4f} seconds") | |
return result | |
return wrapper | |
return decorator |
(注:比方下面应用 @wraps(func)
注解是很重要的,它能保留原始函数的元数据) 只须要在原来的函数下面加上 @timeit()
即可为其减少新的性能:
@timeit() | |
def test_timeit(): | |
time.sleep(1) | |
test_timeit() | |
#test_timeit cost 1.0026 seconds |
下面的代码跟上面这样写的成果是一样的:
test_timeit = timeit(test_timeit) | |
test_timeit() |
装璜器的执行程序
当有多个装璜器的时候,他们的调用程序是怎么样的?
如果有这样的代码,请问是先打印 Decorator1 还是 Decorator2 ?
from functools import wraps | |
def decorator1(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
print('Decorator 1') | |
return func(*args, **kwargs) | |
return wrapper | |
def decorator2(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
print('Decorator 2') | |
return func(*args, **kwargs) | |
return wrapper | |
@decorator1 | |
@decorator2 | |
def add(x, y): | |
return x + y | |
add(1,2) | |
# Decorator 1 | |
# Decorator 2 |
答复这个问题之前,我先给你打个形象的比喻,装璜器就像函数在穿衣服,离它最近的最先穿,离得远的最初穿,上例中 decorator1 是外套,decorator2 是内衣。
add = decorator1(decorator2(add))
在调用函数的时候,就像脱衣服,先解除最里面的 decorator1,也就是先打印 Decorator1,执行到 return func(*args, **kwargs)
的时候会去解除 decorator2,而后打印 Decorator2,再次执行到 return func(*args, **kwargs)
时会真正执行 add()
函数。
须要留神的是打印的地位,如果打印字符串的代码位于调用函数之后,像上面这样,那输入的后果正好相同:
def decorator1(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
result = func(*args, **kwargs) | |
print('Decorator 1') | |
return result | |
return wrapper | |
def decorator2(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
result = func(*args, **kwargs) | |
print('Decorator 2') | |
return result | |
return wrapper |
装璜器不仅能够定义为函数,也能够定义为类,只有你确保它实现了__call__()
和 __get__()
办法。
元类
Python 中所有类(object)的元类,就是 type
类,也就是说 Python 类的创立行为由默认的 type
类管制,打个比喻,type
类是所有类的先人。咱们能够通过编程的形式来实现自定义的一些对象创立行为。
定一个类继承 type
类 A,而后让其余类的元类指向 A,就能够管制 A 的创立行为。典型的就是应用元类实现一个单例:
class Singleton(type): | |
def __init__(self, *args, **kwargs): | |
self._instance = None | |
super().__init__(*args, **kwargs) | |
def __call__(self, *args, **kwargs): | |
if self._instance is None: | |
self._instance = super().__call__(*args, **kwargs) | |
return self._instance | |
else: | |
return self._instance | |
class Spam(metaclass=Singleton): | |
def __init__(self): | |
print("Spam!!!") |
元类 Singleton 的 __init__
和__new__
办法会在定义 Spam 的期间被执行,而 __call__
办法会在实例化 Spam 的时候执行。
descriptor 类(描述符类)
descriptor 就是任何一个定义了 __get__()
,__set__()
或 __delete__()
的对象,形容器让对象可能自定义属性查找、存储和删除的操作。这里举官网文档 [1] 一个自定义验证器的例子。
定义验证器类,它是一个描述符类,同时还是一个抽象类:
from abc import ABC, abstractmethod | |
class Validator(ABC): | |
def __set_name__(self, owner, name): | |
self.private_name = '_' + name | |
def __get__(self, obj, objtype=None): | |
return getattr(obj, self.private_name) | |
def __set__(self, obj, value): | |
self.validate(value) | |
setattr(obj, self.private_name, value) | |
@abstractmethod | |
def validate(self, value): | |
pass |
自定义验证器须要从 Validator 继承,并且必须提供 validate() 办法以依据须要测试各种束缚。
这是三个实用的数据验证工具:
OneOf 验证值是一组受约束的选项之一。
class OneOf(Validator): | |
def __init__(self, *options): | |
self.options = set(options) | |
def validate(self, value): | |
if value not in self.options: | |
raise ValueError(f'Expected {value!r} to be one of {self.options!r}') |
Number 验证值是否为 int 或 float。依据可选参数,它还能够验证值在给定的最小值或最大值之间。
class Number(Validator): | |
def __init__(self, minvalue=None, maxvalue=None): | |
self.minvalue = minvalue | |
self.maxvalue = maxvalue | |
def validate(self, value): | |
if not isinstance(value, (int, float)): | |
raise TypeError(f'Expected {value!r} to be an int or float') | |
if self.minvalue is not None and value < self.minvalue: | |
raise ValueError(f'Expected {value!r} to be at least {self.minvalue!r}' | |
) | |
if self.maxvalue is not None and value > self.maxvalue: | |
raise ValueError(f'Expected {value!r} to be no more than {self.maxvalue!r}' | |
) |
String 验证值是否为 str。依据可选参数,它能够验证给定的最小或最大长度。它还能够验证用户定义的 predicate。
class String(Validator): | |
def __init__(self, minsize=None, maxsize=None, predicate=None): | |
self.minsize = minsize | |
self.maxsize = maxsize | |
self.predicate = predicate | |
def validate(self, value): | |
if not isinstance(value, str): | |
raise TypeError(f'Expected {value!r} to be an str') | |
if self.minsize is not None and len(value) < self.minsize: | |
raise ValueError(f'Expected {value!r} to be no smaller than {self.minsize!r}' | |
) | |
if self.maxsize is not None and len(value) > self.maxsize: | |
raise ValueError(f'Expected {value!r} to be no bigger than {self.maxsize!r}' | |
) | |
if self.predicate is not None and not self.predicate(value): | |
raise ValueError(f'Expected {self.predicate} to be true for {value!r}' | |
) |
理论利用时这样写:
class Component: | |
name = String(minsize=3, maxsize=10, predicate=str.isupper) | |
kind = OneOf('wood', 'metal', 'plastic') | |
quantity = Number(minvalue=0) | |
def __init__(self, name, kind, quantity): | |
self.name = name | |
self.kind = kind | |
self.quantity = quantity |
形容器阻止有效实例的创立:
>>> Component('Widget', 'metal', 5) # Blocked: 'Widget' is not all uppercase | |
Traceback (most recent call last): | |
... | |
ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' | |
>>> Component('WIDGET', 'metle', 5) # Blocked: 'metle' is misspelled | |
Traceback (most recent call last): | |
... | |
ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'} | |
>>> Component('WIDGET', 'metal', -5) # Blocked: -5 is negative | |
Traceback (most recent call last): | |
... | |
ValueError: Expected -5 to be at least 0 | |
>>> Component('WIDGET', 'metal', 'V') # Blocked: 'V' isn't a number | |
Traceback (most recent call last): | |
... | |
TypeError: Expected 'V' to be an int or float | |
>>> c = Component('WIDGET', 'metal', 5) # Allowed: The inputs are valid |
最初的话
对于 Python 的元编程,总结如下:
如果心愿某些函数领有雷同的性能,心愿不扭转原有的调用形式、不写反复代码、易保护,能够应用装璜器来实现。
如果心愿某一些类领有某些雷同的个性,或者在类定义实现对其的管制,咱们能够自定义一个元类,而后让它类的元类指向该类。
如果心愿实例的属性领有某些独特的特点,就能够自定义一个描述符类。
以上就是本次分享的所有内容,如果你感觉文章还不错,欢送关注公众号:Python 编程学习圈,每日干货分享,发送“J”还可支付大量学习材料。或是返回编程学习网,理解更多编程技术常识。