生成器Generator
def countdown(num):
print('Starting')
while num > 0:
yield num
num -= 1
# this will not print 'Starting'
cd = countdown(3)
# this will print 'Starting' and the first value
print(next(cd))
# will print the next values
print(next(cd))
print(next(cd))
# this will raise a StopIteration
print(next(cd))
迭代器使用方式
# you can iterate over a generator object with a for in loop
cd = countdown(3)
for x in cd:
print(x)
# you can use it for functions that take iterables as input
cd = countdown(3)
sum_cd = sum(cd)
print(sum_cd)
cd = countdown(3)
sorted_cd = sorted(cd)
print(sorted_cd)
生成器表达式
# generator expression
mygenerator = (i for i in range(1000) if i % 2 == 0)
print(sys.getsizeof(mygenerator), "bytes")
# list comprehension
mylist = [i for i in range(1000) if i % 2 == 0]
print(sys.getsizeof(mylist), "bytes")
# 120bytes
# 4272 bytes
生成器概念
类可以实现生成器作为一个可迭代对象,它需要实现
__iter__
方法和__next__
方法,使得类对象可迭代。此外,还需要注意记录迭代次数,以及最后raise一个StopIteration异常。
class firstn:
def __init__(self, n):
self.n = n
self.num = 0
def __iter__(self):
return self
def __next__(self):
if self.num < self.n:
cur = self.num
self.num += 1
return cur
else:
raise StopIteration()
firstn_object = firstn(1000000)
print(sum(firstn_object))
装饰器Decorators
装饰器的典型使用场景有:
- 计算函数执行时间
- 用于调试,打印出函数的参数和调用信息
- 作为函数的参数校验
- 以插件的形式注册函数
- 降低代码执行速度来测试网络,如使用sleep函数
- 缓存代码执行结果Memoization
- 附加信息或者更新状态
装饰器就是一个语法糖,如下装饰器就类似
target = somedecorator(target)
装饰器一个特性就是将被装饰函数替换为其他函数
@somedecorator
def target():
print("running target")
简单装饰器模板
import functools
def my_decorator(func):
@functools.wraps(func) # 保持被装饰函数的属性
def wrapper(*args, **kwargs):
# Do something before
result = func(*args, **kwargs)
# Do something after
return result
return wrapper
带参数的装饰器函数,可以认为是两层函数,在简单装饰器外部套一个函数来扩展装饰器的行为。
即两层闭包,一个持有外部环境变量的函数就是闭包。
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet('Alex')
# 输出
"""
Hello Alex
Hello Alex
Hello Alex
"""
层叠装饰器
装饰器的执行顺序是decorator(func),从外到内执行,如果有多个装饰器堆叠在一起,按照decorator2(docorator1(func))的执行顺序。
其更清晰的执行顺序是:
- func = decorator1(func)
- func = decorator2(func)
- func()
多个装饰器装饰函数时,有个规律是从下到上包裹(装饰)函数,在装饰的过程中执行装饰器函数和内部闭包wrapper函数之间代码,闭包函数知识作为一个对象被返回,在装饰过程中并不执行。
而在执行被装饰函数的过程中,从上到下执行wrapper函数内部的代码。
如下代码多个装饰器,其装饰的顺序和wrapper执行的顺序相反。
# 装饰过程
say_hello = start_end_decorator_2(start_end_decorator_1(say_hello))
say_hello()
多装饰器实验
import functools
# a decorator function that prints debug information about the wrapped function
def start_end_decorator_2(func):
print('Start decorator2')
@functools.wraps(func)
def wrapper2(*args, **kwargs):
print('Exec wrapper 2')
result = func(*args, **kwargs)
print('End wrapper 2')
return result
return wrapper2
def start_end_decorator_1(func):
print('Start decorator1')
@functools.wraps(func)
def wrapper1(*args, **kwargs):
print('Exec wrapper 1')
result = func(*args, **kwargs)
print('End wrapper 1')
return result
return wrapper1
@start_end_decorator_2
@start_end_decorator_1
def say_hello(name):
greeting = f'Hello {name}'
print(greeting)
return greeting
"""
相当于
func = start_end_decorator_1(func)
此时func是下面这个wrapper1函数
def wrapper1(*args, **kwargs):
print('Exec wrapper 1')
result = func(*args, **kwargs)
print('End wrapper 1')
return result
再经过下一个装饰器start_end_decorator_2
func再次被替换
func = start_end_decorator_2(start_end_decorator_1(func))
"""
say_hello(name='Alex')
# exec result
"""
Start decorator1
Start decorator2
Exec wrapper 2
Exec wrapper 1
Hello Alex
End wrapper 1
End wrapper 2
"""
装饰器执行时间
装饰器需要区分导入时和运行时,
装饰器一个特性就是装饰的过程在import时执行,当import代码时,装饰器立刻执行,将被装饰函数变为另一个函数。
registry = []
def register(func):
print("running register(%s)" % func)
registry.append(func)
return func
@register
def f1():
print("running f1")
@register
def f2():
print("running f2")
def f3():
print("running f3")
def main():
print("running main")
print("registry ->", registry)
f1()
f2()
f3()
if __name__ == "__main__":
main()
# import该模块的输出如下
"""
running register(<function f1 at 0x7f4105056af0>)
running register(<function f2 at 0x7f4105056c10>)
"""
# 执行main函数的输出如下
# 在运行时,被装饰函数才开始执行
"""
running register(<function f1 at 0x7f65b62d70d0>)
running register(<function f2 at 0x7f65b62d7160>)
running main
registry -> [<function f1 at 0x7f65b62d70d0>, <function f2 at 0x7f65b62d7160>]
running f1
running f2
running f3
"""
类装饰器
可以使用类作为装饰器,因此,需要首先实现魔法方法
__call__
,使得对象是callable可调用的,类装饰器典型用处是保存状态,如函数被调用次数。我们使用functools.update_wrapper()
而不是functools.wraps
来持久化被装饰器函数信息。
import functools
class CountCalls:
# the init needs to have the func as argument and stores it
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
# extend functionality, execute function, and return the result
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(num):
print("Hello!")
say_hello(5)
say_hello(5)
# result
"""
Call 1 of 'say_hello'
Hello!
Call 2 of 'say_hello'
Hello!
"""
Context Managers
上下文管理器用于资源管理,允许你方便的分配和释放资源
Python内置的关键字with用于处理上下文管理器,上下文管理器典型的用途有:
- 打开和关闭文件
- 打开和关闭数据库连接
- 获得和释放锁
from threading import Lock
lock = Lock()
# error-prone:
lock.acquire()
# do stuff
# lock should always be released!
lock.release()
# Better:
with lock:
# do stuff
实现一个上下文管理器类
为了支持with关键字,需要在类中实现
__enter__
和__exit__
方法,当Python执行到with语句,会执行__enter__
方法,此时应该获取资源并返回,而当离开上下文环境时,将执行__exit__
方法,此时应该释放资源。
class ManagedFile:
def __init__(self, filename):
print('init', filename)
self.filename = filename
def __enter__(self):
print('enter')
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
if self.file:
self.file.close()
print('exit')
with ManagedFile('notes.txt') as f:
print('doing stuff...')
f.write('some todo...')
处理异常
当异常产生时,Python将异常类型、值和traceback信息传递给
__exit__
方法,它可以处理该异常。如果__exit__
方法返回了除True之外的任何值,则由with语句引发异常。
class ManagedFile:
def __init__(self, filename):
print('init', filename)
self.filename = filename
def __enter__(self):
print('enter')
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
if self.file:
self.file.close()
print('exc:', exc_type, exc_value)
print('exit')
# No exception
with ManagedFile('notes.txt') as f:
print('doing stuff...')
f.write('some todo...')
print('continuing...')
print()
# Exception is raised, but the file can still be closed
with ManagedFile('notes2.txt') as f:
print('doing stuff...')
f.write('some todo...')
f.do_something()
print('continuing...')
也可以在
__exit__
方法中处理异常,并返回True
class ManagedFile:
def __init__(self, filename):
print('init', filename)
self.filename = filename
def __enter__(self):
print('enter')
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
if self.file:
self.file.close()
if exc_type is not None:
print('Exception has been handled')
print('exit')
return True
with ManagedFile('notes2.txt') as f:
print('doing stuff...')
f.write('some todo...')
f.do_something()
print('continuing...')
用生成器实现一个上下文管理器
与其写一个类,也可以写一个生成器函数,并用
contextlib.contextmanager
来装饰它。为了实现这个目的,函数必须在
try
语句段中yield
资源,而在finally
语句中实现类似__exit__
的功能,即释放资源。
from contextlib import contextmanager
@contextmanager
def open_managed_file(filename):
f = open(filename, 'w')
try:
yield f
finally:
f.close()
with open_managed_file('notes.txt') as f:
f.write('some todo...')
生成器首先获取资源,然后暂时挂起执行流程,并
yeild
返回资源,资源可以被调用者使用,当调用着离开with上下文,生成器接着执行后续的finally
语句,释放资源。
Python中的解引用
Python中的
*
号具有多种作用:
- Use
*args
for variable-length arguments- Use
**kwargs
for variable-length keyword arguments- Use
*,
followed by more function parameters to enforce keyword-only arguments
def my_function(*args, **kwargs):
for arg in args:
print(arg)
for key in kwargs:
print(key, kwargs[key])
my_function("Hey", 3, [0, 1, 2], name="Alex", age=8)
# Parameters after '*' or '*identifier' are keyword-only parameters and may only be passed using keyword arguments.
def my_function2(name, *, age):
print(name)
print(age)
# my_function2("Michael", 5) --> this would raise a TypeError
my_function2("Michael", age=5)
Python函数传参以及深拷贝浅拷贝
在Python中,赋值语句
obj_b = obj_a
不产生真正的对象拷贝,只创建一个新的变量和obj_a具有相同的引用,因此当你想要产生可变对象的真正的拷贝,并在不影响原来对象的情况下修改拷贝对象时,需要格外小心。可以使用copy模块产生真正的拷贝,然而,对于混合/嵌套对象,浅拷贝和深拷贝有重要的区别,
浅拷贝
只有一层深,对于比一层深的嵌套对象是源对象的引用,因此修改会导致源对象的更改
深拷贝
一份完全独立的拷贝,递归产生源对象中所有嵌套对象的拷贝
赋值操作
会产生源对象的一个引用,修改会导致源对象的变更
list_a = [1, 2, 3, 4, 5]
list_b = list_a
list_a[0] = -10
print(list_a)
print(list_b)
"""
[-10, 2, 3, 4, 5]
[-10, 2, 3, 4, 5]
"""
浅拷贝
浅拷贝只有一层深度,修改第一层不会影响源对象,使用
copy.copy()
方法或者对象特定的拷贝方法或者拷贝构造函数
import copy
list_a = [1, 2, 3, 4, 5]
list_b = copy.copy(list_a)
# not affects the other list
list_b[0] = -10
print(list_a)
print(list_b)
但是在嵌套对象中,修改第二层或者更深层次的数据时,会影响到源对象,因为在第二层时拷贝的是引用,而不是值。
import copy
list_a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
list_b = copy.copy(list_a)
# affects the other!
list_a[0][0]= -10
print(list_a)
print(list_b)
"""
[[-10, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
[[-10, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
"""
对于列表,类似的浅拷贝方法还有
# shallow copies
list_b = list(list_a)
list_b = list_a[:]
list_b = list_a.copy()
深拷贝
深拷贝是一份完全独立的克隆,使用
copy.deepcopy
方法实现
import copy
list_a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
list_b = copy.deepcopy(list_a)
# not affects the other
list_a[0][0]= -10
print(list_a)
print(list_b)
"""
[[-10, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
"""
对象的深拷贝和浅拷贝
可以使用
copy
模块实现对特定对象的深拷贝或者浅拷贝
- 赋值只拷贝对象引用
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# Only copies the reference
p1 = Person('Alex', 27)
p2 = p1
p2.age = 28
print(p1.age)
print(p2.age)
"""
28
28
"""
- 浅拷贝拷贝一层
# shallow copy
import copy
p1 = Person('Alex', 27)
p2 = copy.copy(p1)
p2.age = 28
print(p1.age)
print(p2.age)
"""
27
28
"""
- 深拷贝可以完整拷贝
class Company:
def __init__(self, boss, employee):
self. boss = boss
self.employee = employee
# shallow copy will affect nested objects
boss = Person('Jane', 55)
employee = Person('Joe', 28)
company = Company(boss, employee)
company_clone = copy.copy(company)
company_clone.boss.age = 56
print(company.boss.age)
print(company_clone.boss.age)
"""
56
56
"""
# deep copy will not affect nested objects
boss = Person('Jane', 55)
employee = Person('Joe', 28)
company = Company(boss, employee)
company_clone = copy.deepcopy(company)
company_clone.boss.age = 56
print(company.boss.age)
print(company_clone.boss.age)
"""
55
56
"""
函数传参
在C语言中传参有显式的值传递和地址传递,在Python中也有类似的机制。
Python中数据类型存在可变和不可变的区别,即mutable和immutable。
对于可变类型,例如列表,由于传递的是列表的引用,列表可以在一个方法中被修改
# immutable objects -> no change
def foo(x):
x = 5 # x += 5 also no effect since x is immutable and a new variable must be created
var = 10
print('var before foo():', var)
foo(var)
print('var after foo():', var)
"""
var before foo(): 10
var after foo(): 10
"""
- 可变对象
# mutable objects -> change
def foo(a_list):
a_list.append(4)
my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
"""
my_list before foo(): [1, 2, 3]
my_list after foo(): [1, 2, 3, 4]
"""
- 重新绑定一个可变对象的引用
# Rebind a mutable reference -> no change
def foo(a_list):
# 赋值操作产生一个新的局部变量
a_list = [50, 60, 70] # a_list is now a new local variable within the function
a_list.append(50)
my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
- 区分+=和=
# another example with rebinding references:
def foo(a_list):
a_list += [4, 5] # this chanches the outer variable
def bar(a_list):
a_list = a_list + [4, 5] # this rebinds the reference to a new local variable
my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)
my_list = [1, 2, 3]
print('my_list before bar():', my_list)
bar(my_list)
print('my_list after bar():', my_list)
"""
my_list before foo(): [1, 2, 3]
my_list after foo(): [1, 2, 3, 4, 5]
my_list before bar(): [1, 2, 3]
my_list after bar(): [1, 2, 3]
"""