11.函数

予早 2024-10-05 10:20:02
Categories: Tags:

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)

注:

  1. 整体上,参数传入时必然以位置参数或关键字参数的形式传入,通常联合使用*args**kwargs接受任意参数

    def func_name(*args, **kwargs):
        print(args, kwargs)
    
    func_name(1, b=2, c=3, d=4, f=5)
    
  2. 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. 是否需要参数
  2. 是否需要包装

三种成分:

  1. 参数函数,可选
  2. 装饰器函数,必选
  3. 包装函数,可选

1.无需参数,也无需包装

def decorator(f):
    do_something(f)
    return f


@decorator
def my_func():
    pass

使用案例

  1. 为函数打自定义标记,用于某些后续流程

    def testcase(func)
        setattr(func, 'test', True)
        return func
    
  2. 将函数注册到某个地方,后续可以集中处理,例如注册中心

    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

三种成分中参数函数和包装函数存在的问题

  1. 参数函数在使用默认参数时需要加空括号问题
  2. 包装函数丢失原本函数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__或内置函数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

可迭代与迭代器

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

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())

内置函数