Bootstrap

python函数的使用方式_day18

函数作用

  1. 函数可以赋值给变量
variable = lambda x: x ** 2
  1. 函数可以作为函数的参数
def func(func1):
	pass
  1. 函数可以作为函数的返回值
def time(func):
    """一个计算函数执行时间的装饰器"""
    def wrapper(*args, **kwargs):
        start_time = time.time()  # 记录开始时间
        result = func(*args, **kwargs)  # 调用原始函数
        end_time = time.time()  # 记录结束时间
        print(f"{func.__name__} 执行时间: {end_time - start_time} 秒")
        return result
    return wrapper

高阶函数的用法(filter、map以及它们的替代品)

items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10))))
items2 = [x ** 2 for x in range(1, 10) if x % 2]

传参

位置传参

置参数是最基本的参数类型,调用函数时,实参会按照函数定义时参数的顺序传递给函数。在函数定义中,位置参数的位置是固定的,调用时必须按照相同的顺序传递相应数量的实参。

def greet(name, greeting):  
    print(f"{greeting}, {name}!")  
  
greet("Alice", "Hello")  # 正确调用  
# greet("Hello", "Alice")  # 错误调用,因为参数的顺序不对

可变传参

可变参数允许你向函数传递任意数量的位置参数(即非关键字参数)。在函数定义中,可变参数通过在参数名前加上星号(*)来定义。在函数内部,这个参数会被视为一个元组。

def greet(*names):  
    for name in names:  
        print(f"Hello, {name}!")  
  
greet("Alice", "Bob", "Charlie")

关键字参数

关键字参数允许你通过参数名来传递参数,这使得函数调用更加清晰,并且参数顺序不重要。在函数定义中,关键字参数没有特殊的语法,但在调用时,你可以通过参数名=值的形式来传递参数。

def greet(greeting, name):  
    print(f"{greeting}, {name}!")  
  
greet(name="Alice", greeting="Hello")  # 使用关键字参数

命名关键字参数

命名关键字参数是Python 3新增的一种参数类型,它要求调用函数时必须以关键字参数的形式来传递这些参数。在函数定义中,命名关键字参数通过在星号和参数名之间加上星号来定义。这表示在这个参数之后的所有参数(包括位置参数和关键字参数)都必须被显式地以关键字参数的形式传递。

def greet(*, greeting, name):  
    print(f"{greeting}, {name}!")  
  
greet(greeting="Hello", name="Alice")  # 正确调用  
# greet("Hello", "Alice")  # 错误调用,因为greeting和name必须是关键字参数

参数的元信息(代码可读性问题)

在编程中,参数的元信息(metadata about parameters)是指关于函数或方法参数的额外信息,这些信息通常不是直接用于程序执行的逻辑,而是用于提高代码的可读性、可维护性和可文档性。这些元信息可能包括参数的名称、类型、描述、默认值等。在Python中,虽然语言本身不直接支持在函数定义中嵌入所有类型的元信息,但可以通过一些方法和技术来间接地提供这些信息。

文档字符串(Docstrings)

Python的文档字符串(通常简称为docstrings)是定义在函数、类或模块等最开始的字符串字面量,用于描述这些对象的用途、参数、返回值等。通过在函数定义下方添加三引号(‘’’ 或 “”")包围的字符串,可以提供关于函数参数的详细信息。

def greet(name, greeting="Hello"):  
    """  
    Greet a person.  
  
    Args:  
        name (str): The name of the person to greet.  
        greeting (str, optional): The greeting to use. Defaults to "Hello".  
  
    Returns:  
        None  
    """  
    print(f"{greeting}, {name}!")

在这个例子中,Args 部分描述了参数的名称、类型和描述。虽然这不是Python语法的一部分,但它是Python社区广泛接受的惯例,并且大多数IDE和文档工具都能识别并展示这些信息。

类型注解(Type Hints)

从Python 3.5开始,Python支持函数参数和返回值的类型注解。虽然这些注解主要用于类型检查(如使用mypy等工具),但它们也提高了代码的可读性,因为它们明确指出了参数的预期类型。

def greet(name: str, greeting: str = "Hello") -> None:  
    print(f"{greeting}, {name}!")

注释

虽然不如文档字符串和类型注解正式,但在代码中使用注释来提供关于参数的额外信息也是一种常见做法。然而,这种方法应该谨慎使用,因为它可能会使代码变得混乱,特别是当注释过时或不一致时。

匿名函数和内联函数的用法

匿名函数(Lambda函数)

Python的lambda表达式允许你快速定义单行的小函数。这些函数没有名称,但可以作为参数传递给高阶函数,或者赋值给变量以便后续使用。它们通常用于定义简单的、一次性的、不需要显式命名的函数对象。

# 定义一个lambda表达式,计算并返回两个数的和  
add = lambda x, y: x + y  
print(add(5, 3))  # 输出: 8  
  
# 将lambda函数作为参数传递给高阶函数(如filter, map, sorted的key参数等)  
numbers = [1, 2, 3, 4, 5]  
squared = list(map(lambda x: x**2, numbers))  
print(squared)  # 输出: [1, 4, 9, 16, 25]  
  
# 使用lambda表达式作为sorted函数的key参数  
sorted_numbers = sorted(numbers, key=lambda x: -x)  
print(sorted_numbers)  # 输出: [5, 4, 3, 2, 1]

内联函数

虽然Python没有内联函数的概念,但你可以通过其他方式模拟类似的效果,比如使用立即执行的函数表达式(不过Python不直接支持这种语法,这是JavaScript等语言的特性)。但在Python中,你可以通过定义一个普通函数并在需要时立即调用它来达到类似的效果,尽管这并不会像C++中的内联函数那样在编译时进行优化。

然而,对于追求极致性能的场景,Python并不是最佳选择,因为它的解释执行和动态类型系统意味着它无法像编译型语言那样进行深度优化。

闭包和作用域问题

作用域(Scope)

作用域是指程序中变量可以被访问的区域。在Python中,主要有两种类型的作用域:全局作用域(Global Scope)和局部作用域(Local Scope)。从Python 3开始,通过nonlocal关键字,还引入了另一种特殊的局部作用域,即嵌套函数中的外部函数作用域。但严格来说,这并不是一个新的作用域类型,而是对局部作用域的一种特殊访问方式。

  • 全局作用域:全局作用域是程序的最外层作用域,即所有在函数外部定义的变量都拥有全局作用域。全局作用域中的变量可以在程序的任何地方被访问(除了被局部变量遮蔽的情况下)。
  • 局部作用域:局部作用域是在函数内部定义的变量的作用域。这些变量只能在函数内部被访问和修改。如果函数内部定义了与外部同名的变量,则内部变量会遮蔽外部的全局变量。

-Python搜索变量的LEGB顺序(Local >>> Embedded >>> Global >>> Built-in)

  • global和nonlocal关键字的作用

  • global:声明或定义全局变量(要么直接使用现有的全局作用域的变量,要么定义一个变量放到全局作用域)。

  • nonlocal:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量,否则报错)。

闭包(Closure)

闭包是一个能够记住并访问其外部(词法)作用域中变量的函数。即使该函数在其外部作用域之外执行,它仍然能够访问那些变量。闭包是由函数以及创建该函数的词法环境组合而成的。

在Python中,当你定义一个嵌套函数(内部函数),并且这个嵌套函数引用了外部函数(外部作用域)中的变量时,就创建了一个闭包。这个闭包在外部函数执行完毕后仍然能够访问和修改那些外部变量,即使外部函数的作用域已经结束。

def outer_function(text):  
    def inner_function():  
        # 访问外部函数的变量  
        print(text)  
    return inner_function  
  
# 创建闭包  
my_closure = outer_function("Hello, World!")  
  
# 在外部作用域之外调用闭包  
my_closure()  # 输出: Hello, World!

在这个例子中,inner_function是一个闭包,因为它引用了outer_function中的变量text。即使outer_function已经执行完毕,my_closure(即inner_function的引用)仍然能够访问text变量。

装饰器

自定义函数的装饰器

from time import time

def record_time(func):
    """自定义装饰函数的装饰器"""
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time()
        result = func(*args, **kwargs)
        print(f'{func.__name__}: {time() - start}秒')
        return result
        
    return wrapper

可以参数化的装饰器

def log_decorator(prefix):  
    def decorator(func):  
        def wrapper(*args, **kwargs):  
            print(f"{prefix} - Function {func.__name__} is being called")  
            result = func(*args, **kwargs)  
            print(f"{prefix} - Function {func.__name__} has been called")  
            return result  
        return wrapper  
    return decorator  
  
# 使用装饰器  
@log_decorator("INFO")  
def say_hello(name):  
    print(f"Hello, {name}!")  
  
say_hello("Alice")

在上面的示例中,log_decorator是一个参数化装饰器,它接收一个prefix参数,并返回一个装饰器decorator。decorator函数再接收一个函数func作为参数,并返回一个新的函数wrapper。当调用say_hello函数时,实际上执行的是wrapper函数,它会在调用say_hello前后打印带有前缀的日志。

参数化装饰器在Python编程中有广泛的应用场景,包括但不限于:

  • 权限验证:根据传入的角色或权限参数,对函数执行权限进行验证。
  • 缓存:根据传入的缓存策略参数,对函数的返回值进行缓存。
  • 日志记录:如上述示例所示,根据传入的日志级别或前缀参数,定制日志记录的行为。
  • 性能测试:根据传入的性能指标参数,测量函数的执行时间或资源消耗。

定义装饰器类

from functools import wraps
from time import time


class Record():
    """通过定义类的方式定义装饰器"""

    def __init__(self, output):
        self.output = output

    def __call__(self, func):

        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time()
            result = func(*args, **kwargs)
            self.output(func.__name__, time() - start)
            return result

        return wrapper

说明:由于对带装饰功能的函数添加了@wraps装饰器,可以通过func.__wrapped__方式获得被装饰之前的函数或类来取消装饰器的作用。

用装饰器来实现单例模式

from functools import wraps


def singleton(cls):
    """装饰类的装饰器"""
    instances = {}

    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return wrapper


@singleton
class President:
    """总统(单例类)"""
    pass

提示:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?

线程安全的单例装饰器

from functools import wraps
from threading import RLock


def singleton(cls):
    """线程安全的单例装饰器"""
    instances = {}
    locker = RLock()

    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in instances:
            with locker:
                if cls not in instances:
                    instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return wrapper

提示:上面的代码用到了with上下文语法来进行锁操作,因为锁对象本身就是上下文管理器对象(支持__enter__和__exit__魔术方法)。在wrapper函数中,我们先做了一次不带锁的检查,然后再做带锁的检查,这样做比直接加锁检查性能要更好,如果对象已经创建就没有必须再去加锁而是直接返回该对象就可以了。

;