https://docs.python.org/3/reference/compound_stmts.html#function-definitions
基本结构
def func_name(value):
print(f"Hello {value}")
return 0
func_name("World")
函数参数
参数默认值和五种参数类型
https://docs.python.org/3/glossary.html#term-parameter
参数默认值
带默认值的参数必须在参数列表右边
def func_name(a, b=0):
return a + b
print(func_name(1))
位置或关键字参数,positional-or-keyword argument
默认参数类型,传参的时候可以以位置或者关键字形式传递,但位置参数一定在左,关键字参数一定在右
# a、b均为位置或关键字参数,a以位置参数的形式传递,b以关键字参数的形式传递
def func_name(a, b):
return a + b
print(func_name(1, b=2))
仅位置参数,positional-only argument
使用/用于限定其之前的参数为仅位置参数类型
# a、b均为位置参数,c为位置或关键字参数但以位置参数形式传入
def func_name(a, b, /, c):
return a + b + c
print(func_name(1, 2, 3))
仅关键字参数,keyword-only argument
https://peps.python.org/pep-0570/
使用*用于限定其之后的参数为仅关键字参数类型
# a、b为位置或关键字参数但以位置参数传入,c为仅关键字参数
def func_name(a, b, *, c):
return a + b + c
print(func_name(1, 2, c=3))
可变位置参数,var-positional argument
a、b为仅位置参数,args为可变位置参数,*args将多余的位置参数装包为元组args,args命名任意,习惯上以args作为可变位置参数名
def func_name(a, b, *args):
print(a, b, args, type(args)) # 1 2 (3, 4, 5) <class 'tuple'>
func_name(1, 2, 3, 4, 5)
可变关键字参数,var-keyword argument
a、b均为位置或关键字参数,但a、b传入形式不同,**kwargs将多余的关键字参数装包为字典kwargs,kwargs命名任意,习惯上以kwargs作为可变关键字参数名
def func_name(a, b, **kwargs):
print(a, b, kwargs, type(kwargs)) # 1 2 {'c': 3, 'd': 4, 'f': 5} <class 'dict'>
func_name(1, b=2, c=3, d=4, f=5)
注:
整体上,参数传入时必然以位置参数或关键字参数的形式传入,通常联合使用
*args和**kwargs接受任意参数def func_name(*args, **kwargs): print(args, kwargs) func_name(1, b=2, c=3, d=4, f=5)a、b为位置参数,c、d为位置或关键字参数,e、f为关键字参数
def func_name(a, b, /, c, d, *, e, f): print(a, b, c, d, e, f) func_name(1, 2, 3, d=4, e=5, f=6)
函数返回值
Python基于元组的自动装包和拆包在语法上实现了多个返回值形式,实际仍为一个返回值
def func_name(a, b):
return a, b
a, b = func_name(1, 1)
print(a, b) # 1 1
r = func_name(1, 1)
print(r, type(r)) # (1, 1) <class 'tuple'>
Python函数返回值默认为None,不存在没有返回值的情况
def func_name1():
return
print(func_name1()) # None
def func_name2():
pass
print(func_name2()) # None
自动拆包、自动装包
a, b, *c = [1, 2, 3, 4, 5] # 自动装包
print(a, b, c)
a, b, *c = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e'} # 自动装包
print(a, b, c)
def show(a, b, *c, d=0, e=0): # 自动拆包
print(a, b, c, d, e)
show(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) # 自动装包
show(1, 2, *(3, 4, 5, 6, 7, 8, 9, 10), **{'d': 1, 'e': 1}) # 自动拆包+自动装包,自动拆包
原理分析
import dis
import sys
print(sys.version) # 3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
def swap2(a, b):
a, b = b, a
return a, b
dis.dis(swap2)
# 7 0 RESUME 0
#
# 8 2 LOAD_FAST 1 (b)
# 4 LOAD_FAST 0 (a)
# 6 STORE_FAST 1 (b)
# 8 STORE_FAST 0 (a)
#
# 9 10 LOAD_FAST 0 (a)
# 12 LOAD_FAST 1 (b)
# 14 BUILD_TUPLE 2
# 16 RETURN_VALUE
import dis
import sys
print(sys.version) # 3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
def swap1():
a, b = 1, 2
return a, b
dis.dis(swap1)
# 7 0 RESUME 0
#
# 8 2 LOAD_CONST 1 ((1, 2))
# 4 UNPACK_SEQUENCE 2
# 8 STORE_FAST 0 (a)
# 10 STORE_FAST 1 (b)
#
# 9 12 LOAD_FAST 0 (a)
# 14 LOAD_FAST 1 (b)
# 16 BUILD_TUPLE 2
# 18 RETURN_VALUE
匿名函数,lambda表达式
def pow2(n):
return n ** 2
print(pow2(10))
calc = lambda n: n ** 2
print(calc(10))
add = lambda x, y: x + y
l = [-2, 4, -9, 4, 7, 8]
print(max(l, key=lambda n: n * n))
do_nothing = lambda *args, **kwargs: .
闭包函数
- 闭包函数必须有内嵌函数
- 内嵌函数需要引用该嵌套函数上一级中的变量
- 闭包函数必须返回内嵌函数
# 闭包函数通过将函数名作为返回值使得不释放局部变量引用而保留局部变量
# 具有环境变量n的函数power
def nth_power(a):
n = a or 1
def power(base):
return base ** n
return power
_3power = nth_power(3)
print(_3power(2)) # 8
_closure_
def generate_func(start, end):
def print_string(name):
print(start, name, end)
return print_string
hello = generate_func("Hello", "!")
hello('World') # Hello World !
print(hello.__name__) # print_string
print(
hello.__closure__) # (<cell at 0x00000215DB0E7E50: str object at 0x00007FF9311F9900>, <cell at 0x00000215DB0E7EE0: str object at 0x00000215DAD18AB0>)
print(hello.__closure__[0].cell_contents) # !
print(hello.__closure__[1].cell_contents) # Hello
print(id(hello)) # 2292892980448
bye = generate_func("Bye", ".")
bye('World') # Bye World .
print(bye.__name__) # print_string
print(
bye.__closure__) # (<cell at 0x00000215DB0E7DF0: str object at 0x00007FF9311F9BD8>, <cell at 0x00000215DB0E7E20: str object at 0x00000215DACBB4F0>)
print(bye.__closure__[0].cell_contents) # .
print(bye.__closure__[1].cell_contents) # Bye
print(id(bye)) # 2292892978368
global
a = 1
def f1():
a = 2
def f2():
global a
a = 3
print(a) # 全局变量a=3
f2()
print(a) # 局部变量a=2
f1()
print(a) # 全局变量a=3
nonlocal
a = 1
def f1():
a = 2
def f2():
nonlocal a
a = 3
print(a) # 全局变量a=3
f2()
print(a) # 局部变量a=3
f1()
print(a) # 全局变量a=1
装饰器
两个问题
- 是否需要参数
- 是否需要包装
三种成分:
- 参数函数,可选
- 装饰器函数,必选
- 包装函数,可选
1.无需参数,也无需包装
def decorator(f):
do_something(f)
return f
@decorator
def my_func():
pass
使用案例
为函数打自定义标记,用于某些后续流程
def testcase(func) setattr(func, 'test', True) return func将函数注册到某个地方,后续可以集中处理,例如注册中心
registry = [] def registry(func): global registry registry.append(func) return func
2.需要参数,但无需包装
def dec_with_params(x):
def decorator(f):
do_something(f, x)
return f
return decorator
@dec_with_params(123)
def f():
pass
例子,Flask框架中定义路由的装饰器
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule, endpoint, f, **options)
return f
return decorator
3.无需参数,需要包装
def decorator(f):
do_something(f)
def wrapper(*args, **kwargs):
pre_process()
result = f(*args, **kwargs)
post_process()
return result
return wrapper
@decorator
def my_func():
pass
案例,计算函数执行时间
def timing(f):
def wrapper(*args, **kwargs):
t1 = time.perf_counter()
result = f(*args, **kwargs)
print(time.perf_counter() - t1)
return result
return wrapper
@timing
def my_func():
time.sleep(1)
my_func()
4.需要参数,也需要包装,是第二种和第三种的组合
def dec(x):
def inner_dec(f):
do_something(f, x)
def wrapper(*args, **kwargs):
pre_process()
result = f(*args, **kwargs)
post_process()
return result
return wrapper
return inner_dec
三种成分中参数函数和包装函数存在的问题
- 参数函数在使用默认参数时需要加空括号问题
- 包装函数丢失原本函数doc和name问题
以一个任务队列案例来进行分析
tasks = []
def task(f):
global tasks
tasks.append(f)
return f
@task
def play():
return "playing..."
def action():
for task in tasks:
print(task, ":", task())
if __name__ == '__main__':
action()
1)使用装饰器动态添加任务name属性
tasks = []
def task(f):
global tasks
tasks.append(f)
setattr(f, 'name', f.__name__)
return f
@task
def play():
return "playing..."
@task
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
2)实现自定义名称
这种改写有问题,现在有传参了
tasks = []
def task(name=''):
def _task(f):
global tasks
tasks.append(f)
setattr(f, 'name', name or f.__name__)
return f
return _task
@task
def play():
return "playing..."
@task
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
必须要加括号
tasks = []
def task(name=''):
def _task(f):
global tasks
tasks.append(f)
setattr(f, 'name', name or f.__name__)
return f
return _task
@task(name='play2')
def play():
return "playing..."
@task()
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
解决参数函数使用默认参数时必须加括号的问题就要通过判断类型来实现
标准库中lru_cache实现与这里同理
tasks = []
# Python中没有函数重载,大多数重载场景通过灵活的动态类型即可解决
def task(name=''):
# 省去了一层
if callable(name):
return task()(name)
def _task(f):
global tasks
tasks.append(f)
setattr(f, 'name', name or f.__name__)
return f
return _task
@task(name='play2')
def play():
return "playing..."
@task
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
引入一个计数器,这里出现了包装函数的名称问题
tasks = []
def task(name=''):
# 省去了一层
if callable(name):
return task()(name)
def _task(f):
global tasks
tasks.append(f)
setattr(f, 'name', name or f.__name__)
return f
return _task
def count(f):
counter = 0
def wrapper(*args, **kwargs):
nonlocal counter
counter += 1
return f(*args, **kwargs) + ' ' + str(counter)
return wrapper
@task(name='play2')
@count
def play():
return "playing..."
@task
@count
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
action()
action()
标准库中有对应实现,functools.wraps
tasks = []
def update_wrapper(wrapper, wrapped):
setattr(wrapper, '__name__', wrapped.__name__)
setattr(wrapper, '__doc__', wrapped.__doc__)
def wraps(wrapped):
def dec(wrapper):
update_wrapper(wrapper, wrapped)
return wrapper
return dec
def task(name=''):
# 省去了一层
if callable(name):
return task()(name)
def _task(f):
global tasks
tasks.append(f)
setattr(f, 'name', name or f.__name__)
return f
return _task
def count(f):
counter = 0
@wraps(f)
def wrapper(*args, **kwargs):
nonlocal counter
counter += 1
return f(*args, **kwargs) + ' ' + str(counter)
return wrapper
@task(name='play2')
@count
def play():
return "playing..."
@task
@count
def stop():
return "stopping..."
def action():
for task in tasks:
print(task.name, ":", task())
if __name__ == '__main__':
action()
action()
action()
以上就是四种装饰器,装饰器本质上就是嵌套函数的一种使用,而Python中的类也可以发生调用,所以也可以基于类实现装饰器
基于类实现装饰器就是基于__init__方法和__call__方法实现,由于__init__方法一定会返回一个对象,所以基于类实现不了第一种装饰器
需要参数,无需包装
class Dec2:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def __call__(self, f):
return f
无需参数,需要包装
class Dec3:
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
需要参数,需要包装
class Dec4:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.f = None
def __call__(self, f):
self.f = f
return self.__wrapper
def __wrapper(self, *args, **kwargs):
return self.f(*args, **kwargs)
解决参数问题
import time
def outer(func):
def inner(*args, **kwargs):
t1 = time.perf_counter()
res = func(*args, **kwargs)
print(time.perf_counter() - t1)
return res
return inner
def add(a, b):
time.sleep(1)
return a + b
add_plus = outer(add)
print(add_plus(1, 2))
# 1.0007887999818195
# 3
@outer
def sub(a, b):
time.sleep(1)
return a - b
print(sub(1, 2))
# 1.000871800002642
# -1
# 装饰器标准模板
# 装饰器函数\装饰器类,以函数作为装饰器\以类作为装饰器
def wrapper():
def outer(func):
def inner(*args, **kwargs):
res = func(*args, **kwargs)
return res
return inner
return outer
@wrapper()
def func_wrapped():
pass
func_wrapped()
无参装饰器
# 无参装饰器
def debug(func):
def wrapper(*args):
print("[DEBUG]: function {}".format(func.__name__))
return func(*args)
return wrapper
@debug
def add(a, b):
return a + b
add(1, 2)
# [DEBUG]: function add
有参装饰器
# 有参装饰器
def logging(level):
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs): # 参数在此
print("[{}]: function {}".format(level, func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return outer_wrapper
# @logging(level="DEBUG")相当于先调用函数outer_wrapper=logging(level="DEBUG")得到@outer_wrapper
# @outer_wrapper相当于inner_wrapper=outer_wrapper(func)
# 最终执行inner_wrapper
@logging(level="DEBUG")
def add(a, b, *args):
res = a + b
for i in args:
res += i
return res
print(add(1, 2, 3, 4))
# [DEBUG]: function add
# 10
多重装饰器
自上而下,自下而上
def wrapper1(func):
def inner1():
print("inner1 before")
func()
print("inner1 after")
return inner1
def wrapper2(func):
def inner2():
print("inner2 before")
func()
print("inner2 after")
return inner2
def wrapper3(func):
def inner3():
print("inner3 before")
func()
print("inner3 after")
return inner3
@wrapper1
@wrapper2
@wrapper3
def do():
print("fun")
do()
# inner1 before
# inner2 before
# inner3 before
# fun
# inner3 after
# inner2 after
# inner1 after
deprecated
PEP 702 – Marking deprecations using the type system
https://peps.python.org/pep-0702/
from warnings import deprecated
迭代器,iterator
迭代器协议
__iter____next__
可以使用__iter__或内置函数iter获取一个迭代器对象,迭代器第二次遍历不会有任何元素
l = [1, 2, 3]
print(l.__iter__().__next__()) # 1
print(l.__iter__().__next__()) # 1
iterator = iter(l)
for i in iterator:
print(i)
for i in iterator:
print(i)
# 1
# 2
# 3
调用next但是没有元素则会异常StopIteration
l = [1, 2, 3]
iterator = l.__iter__()
print(iterator.__next__()) # 1
print(iterator.__next__()) # 2
print(iterator.__length_hint__()) # 还剩1个元素
print(iterator.__next__()) #
print(iterator.__next__()) # StopIteration
可迭代与迭代器
- 可迭代:
__iter__ - 迭代器:
__iter__、__next__
from collections.abc import Iterable, Iterator
print(isinstance([], Iterator)) # False
print(isinstance([], Iterable)) # True
print(isinstance(range(10), Iterator)) # False
print(isinstance(range(10), Iterable)) # True
class A():
def __iter__(self): pass
def __next__(self): pass
a = A()
print(isinstance(a, Iterator)) # True
print(isinstance(a, Iterable)) # True
for循环语句循环的是可迭代对象的迭代器,每次取值使用next
l = [1, 2, 3]
for i in l:
print(i)
for i in iter(l):
print(i)
for i in range(3):
print(i)
生成器,generator
- 生成器函数 生成器表达式Generator expression
- 含有yield关键字的函数为生成器函数,yield不可与return共用
- 生成器是一种特殊的迭代器,可参考
__generator
def generator():
a = '123456'
for i in a: # 一般写法
yield i
yield from a # 简写
g = generator()
for i in g:
print(i)
# 1
# 2
# 3
# 4
# 5
# 6
# 1
# 2
# 3
# 4
# 5
# 6
不给生成器传参
def customize_generator():
print("generator1")
yield 'a'
print("generator2")
yield 'b'
result = customize_generator()
print(type(result))
print(result)
print(result.__next__()) # a
print(result.__next__()) # b
# print(result.__next__()) # StopIteration
# 生成器只能遍历一遍元素,获取完毕之后必须重新new
# 而对于列表来说,可以使用for i in l: print(i)遍历多次,但是生成器会报异常
给生成器传参
def generator():
print(1)
content = yield 'a'
print(2, content)
content = yield 'b'
print(3, content)
yield
g = generator()
print(g.__next__())
print(g.send("content1")) # 需要给上一个yield位置传参,第一个需要用__next__,同理最后一个yield无法获取到值,不过可以yield空
print(g.send("content2"))
# 1
# a
# 2 content1
# b
# 3 content2
# None
def tractor():
"""
拖拉机
@return:
"""
distance = 0
second = 0
avg_speed = 0
while True:
d, t = yield avg_speed
distance += d
second += t
avg_speed = distance / second
avg = tractor()
print(avg.__next__()) # 0 摇车,人力启动柴油机
print(avg.send((10, 1))) # 10.0
print(avg.send((12, 0.5))) # 14.666666666666666
print(avg.send((10, 1))) # 12.8
生成器嵌套
def generator():
for i in range(4):
yield i
g = generator()
g1 = (i for i in g)
print(g1, type(g1))
g2 = (i for i in g1)
print(g2, type(g2))
# print(list(g)) # 若此处注释打开你猜会如何
print(list(g1)) # [0, 1, 2, 3]
print(list(g2)) # [] 因为执行的时候g1只是一个能产生[0, 1, 2, 3]的迭代器,并不同于值本身
def add(a, b):
return a + b
def nums():
for i in range(4):
yield i
g = nums()
for a in [1, 10]:
g = (add(a, b) for b in g) # 生成器表达式,不实际取元素的时候是不会列出元素的
print(list(g)) # [20, 21, 22, 23]
print(list((add(1, b) for b in nums()))) # [1, 2, 3, 4]
print(list((add(10, b) for b in nums()))) # [10, 11, 12, 13]
print(list((add(10, b) for b in (add(1, b) for b in nums())))) # [11, 12, 13, 14]
print(list((add(10, b) for b in (add(10, b) for b in nums())))) # [20, 21, 22, 23] 等价,a的值在list时才
迭代器并不会直接在内存中生成所有的数据,节省内存
print(range(3))
print(list(range(3)))
print(range(10000000000))
print(list(range(10000000000))) # MemoryError
函数剖析
def f(num):
print(num)
print(f.__code__.co_argcount)
print(f.__code__.co_varnames)
print(f.__defaults__)
print(f.__code__.co_code)
动态创建函数
from types import FunctionType
foo_code = compile('def foo(): return "bar"', "<string>", "exec")
foo_func = FunctionType(foo_code.co_consts[0], globals(), "foo")
print(foo_func())
内置函数
…