python3-cookbook笔记:第九章 元编程
python3-cookbook中每个小节以问题、解决方案和讨论三个部分探讨了Python3在某类问题中的最优解决方式,或者说是探讨Python3本身的数据结构、函数、类等特性在某类问题上如何更好地使用。这本书对于加深Python3的理解和提升Python编程能力的都有显著帮助,特别是对怎么提高Python程序的性能会有很好的帮助,如果有时间的话强烈建议看一下。
本文为学习笔记,文中的内容只是根据自己的工作需要和平时使用写了书中的部分内容,并且文中的示例代码大多直接贴的原文代码,当然,代码多数都在Python3.6的环境上都验证过了的。不同领域的编程关注点也会有所不同,有兴趣的可以去看全文。
python3-cookbook:https://python3-cookbook.readthedocs.io/zh_CN/latest/index.html
9.2 创建装饰器时保留函数元信息
通常,在定义装饰器函数时,总是应该记得使用functools.wraps来注解被包装的函数,虽然在平时不加这个wraps装饰器感觉也工作的很好,但其实不加的话被装饰函数的元信息是被丢失了的,比如__name__、__doc__等信息,为了被装饰函数的元信息不被丢失,就需要加上这个wraps装饰器。
需要注意的是wraps应该放在包装原始函数的那个函数定义之上,即参数为func的函数的返回值函数定义之上,特别是带参数的装饰器定义之中更要注意。
import time
from functools import wraps def timethis(func):
"""普通的装饰器""" def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result return wrapper def timethis_wraps(func):
"""有wraps的装饰器""" @wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result return wrapper @timethis
def countdown(n):
"""timethis装饰函数"""
while n > 0:
n -= 1 @timethis_wraps
def countdown_wraps(n):
"""timethis_wraps装饰函数"""
while n > 0:
n -= 1 countdown(1000)
print(countdown.__name__)
print(countdown.__doc__) countdown_wraps(1000)
print(countdown_wraps.__name__)
print(countdown_wraps.__doc__)
countdown 0.0
wrapper
None
countdown_wraps 0.0
countdown_wraps
timethis_wraps装饰函数
9.6 带可选参数的装饰器
你想定义一个装饰器,但使用的时候既可以传递参数给它,也可以不传任何参数给它,那么可以参考以下示例,利用functools.partial来实现。
import logging
from functools import wraps, partial # 星号*的作用是迫使其后面的参数,即level,name,message三个参数,都必须使用关键字参数的形式传参
def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
# 此处partial的作用为返回一个函数,但它的level,name,message三个参数是已经指定好了的
# 使用的时候只需要传入剩下的参数即可,相当于:def logged(func=None):...
return partial(logged, level=level, name=name, message=message) logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__ # 使用wraps装饰器保证func函数的元信息是正确完整的
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs) return wrapper # 不传参示例
@logged
def add(x, y):
return x + y # 传参示例
@logged(level=logging.CRITICAL, name='example')
def spam():
print('Spam!') print(add(2, 3)) # 输出:5
spam() # 输出:Spam!
9.8 将装饰器定义为类的一部分
如果需要在装饰器中记录信息或绑定信息时,那么可以考虑将装饰器定义在类中,需要注意的是装饰器方法为类方法和实例方法时使用的也是对应的类或者实例,而且functools.wraps包装的函数是不用传递额外的cls或self参数的。
from functools import wraps class A:
# 实例方法
def decorator1(self, func):
# wrapper函数不用定义参数self
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs) return wrapper # 类方法
@classmethod
def decorator2(cls, func):
# wrapper函数不用定义参数cls
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs) return wrapper a = A() # 使用实例进行调用
@a.decorator1
def spam():
pass # 使用类进行调用
@A.decorator2
def grok():
pass
9.21 避免重复的属性方法
如果在类中定义许多重复的对于属性的逻辑代码,可以考虑如下示例进行简化代码:需要检查name属性是否为str类型,检查age属性是否为int类型。
普通方法定义属性的检查:
class Person:
def __init__(self, name ,age):
self.name = name
self.age = age @property
def name(self):
return self._name @name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('name must be a string')
self._name = value @property
def age(self):
return self._age @age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError('age must be an int')
self._age = value
简化后的代码:
# 此函数返回一个属性对象,用于检查赋的值是否为指定的类型
# 在类中使用这个函数时,跟把这个函数中的代码放到类中定义是一样的,作用就只是把重复使用的代码提出来了
def typed_property(name, expected_type):
storage_name = '_' + name @property
def prop(self):
return getattr(self, storage_name) @prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError('{} must be a {}'.format(name, expected_type))
setattr(self, storage_name, value) return prop class Person:
name = typed_property('name', str)
age = typed_property('age', int) def __init__(self, name, age):
self.name = name
self.age = age
使用functools.partial改良的代码:
from functools import partial String = partial(typed_property, expected_type=str)
Integer = partial(typed_property, expected_type=int) class Person:
name = String('name')
age = Integer('age') def __init__(self, name, age):
self.name = name
self.age = age
9.22 定义上下文管理器的简单方法
自定义上下文管理器有两种方式,一种是使用contextlib.contextmanager装饰器,但是这种方式应该只用来定义函数的上下文管理器,如果是对象,比如文件、网络连接或者锁等,就应该使用另一种方式,即给对应的类实现__enter__()方法和__exit__()方法,在进入with语句时会调用__enter__()方法,退出时调用__exit__()方法,对象的方式容易懂一点,所以这里就只给出函数方式的示例。
from contextlib import contextmanager @contextmanager
def list_transaction(orig_list):
working = list(orig_list)
yield working
# 如果没有报错,对列表orig_list的任何修改才会生效
orig_list[:] = working items = [1, 2, 3]
with list_transaction(items) as working:
working.append(4)
working.append(5)
print(items) items = [1, 2, 3]
with list_transaction(items) as working:
working.append(4)
working.append(5)
raise RuntimeError('oops!')
print(items)
[1, 2, 3, 4, 5]
Traceback (most recent call last):
File "Z:/Projects/Daily Test/test.py", line 120, in <module>
raise RuntimeError('oops!')
RuntimeError: oops!
9.23 在局部变量域中执行代码
在使用exec()时,都应该想一下是否有更好的方案或者可以替代的方案。当然,如果是必须要使用exec(),那么需要注意exec()执行时,它使用的局部变量域是拷贝自真实局部变量域,而不是直接使用的真实局部变量域,如果需要获取exec局部变量域中的值,可以使用locals(),locals()返回一个真实局部变量域的拷贝,也是指向的exec()拷贝的局部变量域。
参考以下测试代码和示例:
>>> a = 13
>>> exec('b = a + 1')
>>> b
14
>>> def test():
a = 13
exec('b = a + 1')
print(b) >>> # b并没有在真实局部变量域中,所以会报错
>>> test()
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
test()
File "<pyshell#1>", line 4, in test
print(b)
NameError: name 'b' is not defined
>>>
>>> def test1():
x = 0
exec('x += 1')
print(x) >>> test1() # x的值并没有被修改
0
>>>
>>> def test2():
x = 0
loc = locals()
print('before:', loc)
exec('x += 1')
print('after:', loc)
print('x =', x)
# 想要获取修改后的值,可以从locals()中获取
x = loc['x']
print('x =', x)
# 注意,再次运行locals()时,原来的值会被覆盖掉
x = 444
loc = locals()
print('new:', loc) >>> test2()
before: {'x': 0}
after: {'x': 1, 'loc': {...}}
x = 0
x = 1
new: {'x': 444, 'loc': {...}}
>>>
python3-cookbook笔记:第九章 元编程的更多相关文章
- Python Cookbook 笔记--12章并发编程
<Python Cookbook(第3版)中文版> 1.队列queue的有些方法是线程不安全的,在多线程中最好别用 2.需要限制一段代码的并发访问量时,用信号量.不要把信号量当做普通的锁来 ...
- Android群英传笔记——第九章:Android系统信息和安全机制
Android群英传笔记--第九章:Android系统信息和安全机制 本书也正式的进入尾声了,在android的世界了,不同的软件,硬件信息就像一个国家的经济水平,军事水平,不同的配置参数,代表着一个 ...
- o'Reill的SVG精髓(第二版)学习笔记——第九章
第九章:文本 9.1 字符:在XML文档中,字符是指带有一个数字值的一个或多个字节,数字只与Unicode标准对应. 符号:符号(glyph)是指字符的视觉呈现.每个字符都可以用很多不同的符号来呈现. ...
- 《Interest Rate Risk Modeling》阅读笔记——第九章:关键利率久期和 VaR 分析
目录 第九章:关键利率久期和 VaR 分析 思维导图 一些想法 有关现金流映射技术的推导 第九章:关键利率久期和 VaR 分析 思维导图 一些想法 在解关键方程的时候施加 \(L^1\) 约束也许可以 ...
- 《Java编程思想》笔记 第九章 接口
1.抽象类和抽象方法 抽象方法,仅有方法声明没有方法体 abstract class AbstractClass{ abstract void f(); //没有 {} } 只要有一个或者多个抽象方法 ...
- 《UNIX环境高级编程》(APUE) 笔记第九章 - 进程关系
9 - 进程关系 GitHub 地址 1. 进程组 每个进程除了有一个 进程 ID 外,还属于一个 进程组 .进程组是一个或多个进程的 集合 ,通常,它们是在同一作业中结合起来的,同一进程组中的各进程 ...
- 《Python基础教程(第二版)》学习笔记 -> 第九章 魔法方法、属性和迭代器
准备工作 >>> class NewStyle(object): more_code_here >>> class OldStyle: more_code_here ...
- C primer plus 读书笔记第九章
本章的标题是函数.C的设计原则是把函数作为程序的构成模块. 1.函数概述 函数的定义:函数是用于完成特定任务的程序代码的自包含单元. 使用函数的原因:1.函数的使用可以省去重复代码的编写.2.使得程序 ...
- 《DOM Scripting》学习笔记-——第九章 CSS-DOM
本章内容: 一.style属性 二.如何检索样式信息 三.如何改变样式 属性: 包含位置信息:parentNode , nextSibling , previousSibling , childNod ...
随机推荐
- 调试 ambari-server 总结
刚开始debug ambari-server的时候,很多逻辑都是第一次接触.其中有很多知识点还是记录一下的好,做个备忘.这些知识点对于自定义api的开发还是很有作用的. 1. api的子href的最后 ...
- 在eclipse中导入源码
因为初学java有一个源码项目想要导入,在网上找了很多方法试了都不行,后来发现其实是想多了,这里说一个很简洁的方法.* 1.首先点eclipse中的File然后点import, 2. 然后选Gener ...
- 源码的说明 ASP.NET MVC 5框架揭秘.zip
第1章 S101 MVP(SC)模式中Presenter与View之间的交互 S102 迷你版的ASP.NET MVC框架 第2章 S201 通过路由实现请求地址与.aspx页面的映射 S202 基本 ...
- 安全性与收尾工作 创建基本的安全策略 精通ASP-NET-MVC-5-弗瑞曼
- Python 判断小数的函数
需求分析:1.小数点个数可以使用.count()方法2.按照小数点进行分割 例如: 1.98 [1,98]3.正小数:小数点左边是整数,右边也是整数 可以使用.isdigits()方法4.负小数:小数 ...
- C语言 获取系统时间与睡眠时间函数
摘要: 以ms为单位,获取系统时间.睡眠或延迟时间函数的使用方法. #include<stdio.h> #include <time.h> #include <sys/t ...
- 矩阵matrix变换的用法(css3属性transform: matrix)
参数 2D矩阵的表示 matrix(a,b,c,d,e,f),其中6个参数在矩阵的分布: -- -- | a c e | | b d f | | 0 0 1 | -- -- 在CSS3中矩阵的原始值是 ...
- asp获取隐藏域的json 并解析
方法粗糙,适用度适中. var data2 = document.getElementById("hd_data02"); var val = data2.value; var o ...
- Kubernetes 服务自动发现CoreDNS
前言 Service服务,是一个概念,逻辑通过selector标签代理指定后端pod.众所周知,pod生命周期短,状态不稳定,pod错误异常后新生成的Pod IP会发生变化,之前Pod的访问方式均不可 ...
- Spring注解开发系列Ⅵ --- AOP&事务
注解开发 --- AOP AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,横向重复,纵向抽取.详细的AO ...