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 ...
随机推荐
- Nmap使用教程(一)
基本扫描技术 扫描单个网络 nmap 192.168.1.1/www.baidu.com 扫描多个网络/目标 nmap 192.168.1.1 192.168.1.2 #将扫描同个网段内不同的ip地址 ...
- scikit-learn基础
一.scikit-learn基础 sklearn.ensemble模块有两种基于决策树的算法----随机森林和极端随机树
- 指定HTML标签属性 |Specifying HTML Attributes| 在视图中生成输出URL |高级路由特性 | 精通ASP-NET-MVC-5-弗瑞曼
结果呢: <a class="myCSSClass" href="/" id="myAnchorID">This is an o ...
- 链接拼接的方法(用于解决同一个脚本返回两种不同的url链接的问题)
实例一: 上图所示 爬虫返回的链接有一部分带有http前缀,有一部分没有,且也不知道具体哪些链接会出现没有前缀的情况 后面如果通过返回链接进行再次访问,那么肯定会出现报错的问题 思路: 判断 返回值内 ...
- 基于playcanvas的3d模型展示
1.使用基于playcanvas的离线编辑器制作模型效果 2.使用基于playcanvas的开发包读取编辑好的3d模型进行在线3d展示 效果如下:
- 机器学习李航——Adaboost课本例题实现
例8.1Adaboost的例子 注意求D3或者D4的时候只需要把w替换一下就行,记得还得改阈值.这个代码算个半自动的,因为还需要手动改一下. import numpy as np def getA(e ...
- Docker底层架构之联合文件系统
联合文件系统(UnionFS)是一种分层.轻量级并且高性能的文件系统,它支持对文件系统的 修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several di ...
- Maven: 每次更新Maven Project ,JAVA 版本都变为1.5
由于Maven默认编译环境是JAVA 1.5 ,所以我们需要在pom.xml指定编译插件版本号,这样就可以保证更新Maven project版本不变. <!-- java编译插件 --> ...
- gRPC in ASP.NET Core 3.x -- Protocol Buffer(2)Go语言的例子(上)
上一篇文章(大约半年前写的):https://www.cnblogs.com/cgzl/p/11246324.html 建立Go项目 在GOPATH的src下面建立一个文件夹 protobuf-go, ...
- appcompat_v7 res values-v21 error
[2014-11-03 11:30:25 - AndroidApp] appcompat_v7/res/values-v21/styles_base.xml:75: error: Error retr ...