python进阶手记(一)
pythonic
什么是pythonic?
简单来说就是具有强烈python风格的代码,直观、易读、简洁、优雅。那如何才能做到pythonic,可以从这几点入手:
可变类型与不可变类型
-
str(不可变字符串,在多线程环境是安全的,增改大量数据时效率很低),与之对应的是
io.StringIO
(可变字符串) -
python2写中文记得加u -------> u"权利导致腐败,绝对权利导致绝对腐败"
-
bytes
(不可变字节串),与之对应的是io.BytesIO
(可变字节串,常用于xlwt导出Excel报表)
在多线程环境中,能使用不变类型,就不要使用可变类型,不变类型相比可变类型更安全。
另外创建不变类型的开销远小于创建可变类型的开销(前提是不变类型不需要频繁的变动),这可用timeit
和sys.getsizeof()
进行时间和空间上的测试进行验证。
迭代器
实现了迭代器协议的对象叫迭代器,迭代器协议是两个魔术方法:
-
__ iter__
:返回迭代器对象 -
__ next__
:从迭代器对象中迭代出值,之被取完后,报StopIteration异常
for循环原理:for
循环在工作时,首先会调用可迭代对象内置的__iter__
方法拿到一个迭代器对象,然后再调用该迭代器对象的__next__
取出一个值,执行循环体完成一次循环,周而复始,直到捕捉StopIteration
异常,结束迭代。
迭代器版斐波拉契数列示例:
class FibonacciSequence:
def __init__(self, num):
self.num = num
self.index = 0
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.index < self.num:
self.a, self.b = self.b, self.a + self.b
self.index += 1
return self.a
raise StopIteration()
if __name__ == "__main__":
fib = FibonacciSequence(20)
for i in fib:
print(i)
迭代器版计算文件哈希摘要:
import hashlib
hash_md5 = hashlib.md5()
with open('法治之光.pdf', 'rb') as file:
file_iter = iter(lambda: file.read(4096), b'')
for data in file_iter:
hash_md5.update(data)
print(hash_md5.hexdigest())
优点:
在取值时属于惰性计算,迭代器对象表示的是一个数据流,可以只在需要时才去调用__next__
来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点:
除非取尽,否则无法获取迭代器的长度
生成器
生成器是语法升级版的迭代器, 若函数体包含yield
关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象。
生成器版斐波拉契数列示例:
def fibonacci_sequence(num):
a, b = 0, 1
for _ in range(num):
a, b = b, a + b
yield a
if __name__ == "__main__":
gen_fib = fibonacci_sequence(20)
for value in gen_fib:
print(value)
使用生成器可模拟出协程的效果,那什么是协程?
协程----> 可协作的子程序,用于实现协作式并发,让cpu高效率运转
在python编程中,并发编程可分为:多进程、多线程、异步编程(异步I/O),多进程适合计算密集型任务(可以使用多cpu和cpu的多核特性),多线程和异步编程适合I/O密集型任务,但后者更优,协程是一种用户态的轻量级线程,协程的调度完全由用户控制。 而线程的调度只有拥有最高权限的内核空间才可以完成,所以线程的切换涉及到用户空间和内核空间的切换,也就是特权模式切换,相比协程,开销更大。
将生成器对象obj变成协程对象,首先需要预激,使用obj.send(None)
或next(obj)
使用yield模拟协程版的流式平均值计算示例:
def flow_average():
total, count = 0, 0
avg_value = None
while True:
curr_value = yield avg_value
total += curr_value
count += 1
avg_value = total / count
if __name__ == "__main__":
obj = flow_average()
obj.send(None) # 预激
print(obj.send(10))
print(obj.send(20))
print(obj.send(30))
装饰器
首先回顾下设计模式中的七大原则:
-
开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
-
里氏(Liskov)替换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
-
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
-
接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端(高层的模块或代码)不应该依赖那些它不需要的接口。
-
迪米特法则: 一个软件实体应当尽可能少地与其他实体发生相互作用。解耦,依赖越少越好。
-
单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
-
合成聚合复用原则:尽量使用对象组合/聚合, 而不是继承关系达到软件复用的目的
装饰器可以在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能,它符合开放封闭原则。装饰器的思想并不是设计模式中的装饰器模式而是代理模式,用代理对象去执行被代理对象的行为,在这个过程中可以增加横切关注功能(与被代理对象业务正交的功能)
常用的标准库中的装饰器:
-
staticmethod
/classmethod
/property
-
contextmanager
/lru_cache
-
total_ordering
/unique
示例一:给多个函数添加标记并计算执行时间的装饰器
import time
from functools import wraps # 保留原函数的属性(如注释说明)
def timer(parameter):
def outer_wrapper(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
stop = time.time()
print("the {} run time is :".format(parameter), stop - start)
return wrapper
return outer_wrapper
@timer(parameter='task1')
def task1():
time.sleep(2)
print("in the task1")
@timer(parameter='task2')
def task2():
time.sleep(2)
print("in the task2")
if __name__ == "__main__":
task1()
task2()
示例二:有一个通过网络获取数据的函数(可能会因为网络原因出现异常),写一个装饰器,让这个函数在出现异常时,可以重试指定的次数,并在每次重试之前随机延迟一段时间
function版
from functools import wraps
from random import random
from time import sleep
# 位置参数,*,命名关键字参数
def request_to_try(*, number_of_attempts=3, max_wait_secs=5, errors=(Exception,)):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(number_of_attempts):
try:
return func(*args, **kwargs)
except errors:
sleep(random() * max_wait_secs)
return None
return wrapper
return decorate
class版
class RequestToTry:
def __init__(self, *, number_of_attempts=3, max_wait_secs=5, errors=(Exception,)):
self.number_of_attempts = number_of_attempts
self.max_wait_secs = max_wait_secs
self.errors = errors
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(self.number_of_attempts):
try:
return func(*args, **kwargs)
except self.errors:
sleep(random() * self.max_wait_secs)
return None
示例三:装饰器版单例模式:
from functools import wraps
from threading import RLock
def singleton(cls):
instances = {}
lock = RLock()
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
with lock:
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class LightRuleLaw:
def __init__(self, name, sex):
self.name = name
self.sex = sex
def __str__(self):
return "{}:{}".format(self.name, self.sex)
light1 = LightRuleLaw("罗翔", "男")
light2 = LightRuleLaw("张三", "男")
print(light1, light2) # 罗翔:男 罗翔:男
lru_cache(Least Recently Used)的作用:缓存的置换策略,以空间换时间
from functools import lru_cache
@lru_cache()
def fib(num):
if num in (1, 2):
return 1
return fib(num - 1) + fib(num - 2)
for n in range(1, 121):
print(f'{n}:{fib(n)}')
total_ordering:类中至少要实现一种可比较大小的方法
unique:枚举唯一值
from enum import Enum
from enum import unique
@unique
class Suits(Enum):
SPADE, HEART, CLUB, DIAMOND = range(4)
for suite in Suits:
print(suite)
Django和Flask框架中的装饰器
- csrf_exempt / cache_page / method_decorator / atomic
- route / before_request / after_request / teardown_request
Django和Flask的中间件(拦截过滤器)
- Django中间件
- Flask的中间件
推荐django中间件的写法:
def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
return middleware
上下文管理器,需在类中实现下面两个魔术方法:
- __ enter__ 进入上下文前
- __ exit__ 离开上下文后
class Foo:
def __enter__(self):
print("我悄悄地来")
return "一片云彩"
def __exit__(self, exc_type, exc_val, exc_tb):
print("我悄悄地走")
foo = Foo()
with foo as msg:
print(msg)
print("日落日出,万事无常")
contextmanager:可用于上下文管理的装饰器
import time
from contextlib import contextmanager
def fac(num):
if num == 0:
return 1
return num * fac(num - 1)
@contextmanager
def record_time():
start_time = time.time()
try:
yield "good time"
finally:
end_time = time.time()
print(f'执行时间:{end_time-start_time}ms')
with record_time() as a:
print(a)
print(fac(5))
面向对象
三大支柱:
- 封装:隐藏实现细节,暴露简单的调用接口
- 继承:是一种创建新类的方式之一,可以解决类与类之间的代码重用性问题
- 多态:可以在不用考虑对象具体类型的情况下而直接使用对象
- is-a:继承关系
- has-a:关联(我与朋友之间的关系)、聚合(电脑与cpu)、合成(人与大脑)
- use-a:依赖(人要临时过河,人与船的关系)
强弱程度依次为:组合>聚合>关联>依赖
- S:The Single Responsibility Principle 单一职责
- O:The Open Closed Principle 开放封闭
- L:Liskov Substitution Principle 里氏替换
- I:The Interface Segregation Principle 接口分离
- D:The Dependency Inversion Principle 依赖倒置
python进阶好书推荐
本文地址:https://blog.csdn.net/qq_35289736/article/details/107375720
上一篇: Python生成器(Generator)的原理和使用
下一篇: scrapy 爬虫