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笔记:第九章 元编程的更多相关文章

  1. Python Cookbook 笔记--12章并发编程

    <Python Cookbook(第3版)中文版> 1.队列queue的有些方法是线程不安全的,在多线程中最好别用 2.需要限制一段代码的并发访问量时,用信号量.不要把信号量当做普通的锁来 ...

  2. Android群英传笔记——第九章:Android系统信息和安全机制

    Android群英传笔记--第九章:Android系统信息和安全机制 本书也正式的进入尾声了,在android的世界了,不同的软件,硬件信息就像一个国家的经济水平,军事水平,不同的配置参数,代表着一个 ...

  3. o'Reill的SVG精髓(第二版)学习笔记——第九章

    第九章:文本 9.1 字符:在XML文档中,字符是指带有一个数字值的一个或多个字节,数字只与Unicode标准对应. 符号:符号(glyph)是指字符的视觉呈现.每个字符都可以用很多不同的符号来呈现. ...

  4. 《Interest Rate Risk Modeling》阅读笔记——第九章:关键利率久期和 VaR 分析

    目录 第九章:关键利率久期和 VaR 分析 思维导图 一些想法 有关现金流映射技术的推导 第九章:关键利率久期和 VaR 分析 思维导图 一些想法 在解关键方程的时候施加 \(L^1\) 约束也许可以 ...

  5. 《Java编程思想》笔记 第九章 接口

    1.抽象类和抽象方法 抽象方法,仅有方法声明没有方法体 abstract class AbstractClass{ abstract void f(); //没有 {} } 只要有一个或者多个抽象方法 ...

  6. 《UNIX环境高级编程》(APUE) 笔记第九章 - 进程关系

    9 - 进程关系 GitHub 地址 1. 进程组 每个进程除了有一个 进程 ID 外,还属于一个 进程组 .进程组是一个或多个进程的 集合 ,通常,它们是在同一作业中结合起来的,同一进程组中的各进程 ...

  7. 《Python基础教程(第二版)》学习笔记 -> 第九章 魔法方法、属性和迭代器

    准备工作 >>> class NewStyle(object): more_code_here >>> class OldStyle: more_code_here ...

  8. C primer plus 读书笔记第九章

    本章的标题是函数.C的设计原则是把函数作为程序的构成模块. 1.函数概述 函数的定义:函数是用于完成特定任务的程序代码的自包含单元. 使用函数的原因:1.函数的使用可以省去重复代码的编写.2.使得程序 ...

  9. 《DOM Scripting》学习笔记-——第九章 CSS-DOM

    本章内容: 一.style属性 二.如何检索样式信息 三.如何改变样式 属性: 包含位置信息:parentNode , nextSibling , previousSibling , childNod ...

随机推荐

  1. Beat our dice game and get the flag 击败我们的骰子游戏拿到旗子

    文件名:ebCTF-Teaser-BIN100-Dice.exe 话不多说 用PEID一看没壳 拖进OD 让我们摇出31337这五个数字才能拿到正确的flag cmp dword ptr ss:[eb ...

  2. 可视化限流管理,Sentinel 控制台启动和接入

    Sentinel 的使用可以分为核心库和控制台两个部分. 核心库不依赖任何框架/库,集成了主流框架,可以进行单机限流降级等功能, 控制台Dashboard提供了可视化的管理限流规则.对集群进行监控,集 ...

  3. (转) fuzzing XSS filter

    //转自isno在wooyun知识库所写 题记:这是09年自己写的总结文章,之后多年也不搞这个了,技术显然是过时了,但我觉得思路还是有用的,算抛砖引玉吧,各位见笑 0x00 前言 这是一篇学习总结,首 ...

  4. BZOJ4559 成绩比较

    题目传送门 分析: 我们可以先试着求一下,对于单个学科,有多少种分配方案可以使B神排名为R 对于第i个学科 \(~~~~g(i)=\sum_{j=1}^{H_i}j^{n-R_i}(H_i-j)^{R ...

  5. qt中的拖拽及其使用技巧

    关于qt中的拖放操作,首先可以看这篇官方文档:http://doc.qt.io/qt-5.5/dnd.html 一.QDrag 首先是创建QDrag,可以在mousePressEvent或者mouse ...

  6. 团队项目——Alpha1版本

    团队项目-Alpha版本发布1 一.格式描述 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/ ...

  7. keras冒bug

    使用keras做vgg16的迁移学习实验,在实现的过程中,冒各种奇怪的bug,甚至剪贴复制还是出问题. 解决方案: 当使用组合keras和tensorflow.keras时.由于版本不一致问题导致很多 ...

  8. Django自定义认证系统

    官网教程:https://docs.djangoproject.com/en/2.2/topics/auth/customizing/ app下的model.py from django.db imp ...

  9. mysql --->mysql 外键总结

    mysql 外键总结 1.设置外键MySQL ERROR 1005 错误 MySQL ERROR 1005 (主要是约束不一样导致的)例如: 1.两表外键的引用类型不一样,如主键是int外键是char ...

  10. springboot2.x整合spring-data-jpa的问题

    今天使用springboot整合spring-data-jpa遇到一些问题,直接使用JpaRepository的getOne()方法是会报错的.报错信息为:org.hibernate.LazyInit ...