函数概述

在Python中,函数是一段可重用的代码块,用于执行特定任务。

函数分类

在Python中,函数可以根据它们的定义和功能进行分类。以下是一些常见的函数分类:

  1. 内置函数(Built-in Functions):Python提供了许多内置函数,它们是Python解释器中默认可用的函数,可以直接使用,无需导入任何模块。例如,print()len()range()等都是内置函数。
  2. 用户自定义函数(User-defined Functions):这些函数是由程序员自己定义的函数。使用def关键字定义函数,并按照自己的需求编写函数体。用户自定义函数可以根据功能和用途进行分类。
  3. 参数传递方式:
    • 位置参数(Positional Arguments):按照函数定义时参数的顺序传递的参数。例如:def add(x, y)中的xy就是位置参数。
    • 关键字参数(Keyword Arguments):使用参数名和对应的值进行传递的参数。例如:print(end='\n')中的end就是关键字参数。
    • 默认参数(Default Arguments):在函数定义时为参数提供默认值的参数。调用函数时,如果没有为该参数提供值,则使用默认值。例如:def greet(name='Anonymous')中的name就是默认参数。
    • 可变长参数(Variable-length Arguments):函数可以接受可变数量的参数。有两种方式:
      • 位置可变参数(Arbitrary Arguments):使用*args表示,函数体内会将传递的参数作为元组进行处理。
      • 关键字可变参数(Arbitrary Keyword Arguments):使用**kwargs表示,函数体内会将传递的参数作为字典进行处理。
  4. 递归函数(Recursive Functions):递归函数是指在函数定义中调用自身的函数。递归函数通常用于解决可以被分解为更小的同一问题的问题。例如,计算阶乘的函数可以使用递归实现。
  5. 高阶函数(Higher-order Functions):在Python中,函数可以作为参数传递给其他函数,或者函数可以返回其他函数作为结果。这些函数称为高阶函数。例如,map()filter()reduce()等函数就是高阶函数。

这些是一些常见的函数分类,函数可以同时属于多个分类,具体取决于其定义和使用方式。

函数定义

def function_name(parameter1, parameter2, ...):
    函数体
    [return 返回值]
  • def关键字用于定义函数。
  • function_name是函数的名称,遵循命名规则。(不能为保留关键字、只能由数字、字母、下划线组成,且不能由数字开头)
  • parameter1, parameter2, ...是函数的参数列表,多个参数之间用逗号分隔。参数是可选的,可以为空。
    • 定义中的参数列表称为形式参数,只是一种符号表达式(标识符),简称**形参**
  • 函数体(Function Body):函数体是函数的实际执行部分,包含了一系列语句。函数体必须缩进,通常使用四个空格或一个制表符进行缩进。
  • 函数返回值(Return Value):函数可以返回一个值作为结果,使用return语句。如果函数没有返回语句,或者没有提供返回值,函数将默认返回None

形参(Parameters)是函数定义中的变量,用于接收调用函数时传递的实际参数(也称为实参)。形参的作用是在函数体内引用和处理传递给函数的数据。

形参的含义如下:

  1. 位置参数(Positional Arguments):位置参数是按照函数定义中的顺序进行匹配的参数。调用函数时,传递的实参将按照位置与形参进行对应。位置参数是最常见的参数类型。

示例:

def add(x, y):
    return x + y

result = add(3, 5)
print(result)  # 输出:8

在这个例子中,xy是函数add()的位置参数。当我们调用add(3, 5)时,x将接收值3,y将接收值5,然后函数将返回它们的和。

  1. 关键字参数(Keyword Arguments):关键字参数使用参数名和对应的值进行传递。在函数调用时,可以指定参数名并赋予相应的值,而不需要按照位置进行匹配。关键字参数提供了更好的可读性和灵活性。

示例:

def greet(name, message):
    print(f"Hello, {name}! {message}")

greet(name="Alice", message="How are you?")  # 输出:Hello, Alice! How are you?

在这个例子中,namemessage是函数greet()的关键字参数。我们在函数调用时明确指定了参数名和对应的值,使得代码更加清晰易读。

  1. 默认参数(Default Arguments):默认参数在函数定义时为参数提供默认值。如果调用函数时没有提供对应的实参,将使用默认值。默认参数使得函数更加灵活,可以根据需要选择是否提供实参。

示例:

def greet(name="Anonymous"):
    print(f"Hello, {name}!")

greet()          # 输出:Hello, Anonymous!
greet("Alice")   # 输出:Hello, Alice!

在这个例子中,name是函数greet()的默认参数,如果没有提供实参,默认值为"Anonymous"。当我们调用greet()时,将使用默认值打印问候语。

形参允许函数在被调用时接收外部数据并进行处理。通过位置参数、关键字参数和默认参数,可以根据具体需求灵活定义函数,并根据实际情况传递参数。

形参定义:

在Python中,函数的形参(形式参数)是在函数定义时用来接收参数值的占位符。当函数被调用时,实际的参数值会传递给形参,并在函数体内被使用。

Python中的形参定义可以包含以下几种方式:

位置参数

Positional Arguments,这是最常见的参数传递方式,形参按照定义的顺序一个接一个地接收实参的值。

例如:

def add(a, b):
    return a + b

result = add(2, 3)
print(result)  # 输出: 5

在上面的例子中,ab都是位置参数,分别接收传递给add()函数的第一个和第二个参数。

默认参数

Default Arguments,默认参数允许在函数定义时给形参指定默认值,这样在函数调用时如果没有传递对应的实参,就会使用默认值。

例如:

def greet(name, message="Hello"):
    print(message, name)

greet("Alice")  # 输出: Hello Alice
greet("Bob", "Hi")  # 输出: Hi Bob

在上面的例子中,message是一个默认参数,如果没有传递第二个参数,则默认使用"Hello"。

形参的缺省值是在函数定义时为形参指定的默认值。当函数被调用时,如果没有为该形参传递实际的参数值,就会使用缺省值作为参数的默认值。

形参的缺省值可以通过在函数定义时在形参后面使用等号(=)来指定。例如:

def greet(name, message="Hello"):
    print(message, name)

greet("Alice")  # 输出: Hello Alice
greet("Bob", "Hi")  # 输出: Hi Bob

在上面的例子中,message是一个形参,并且它的缺省值是"Hello"。当函数greet()被调用时,如果没有传递第二个参数,则会使用缺省值"Hello"作为参数的默认值。在第一个函数调用中,只传递了一个参数"Alice",因此message的值使用了缺省值。在第二个函数调用中,传递了两个参数"Bob""Hi",因此message的值被覆盖为"Hi"

需要注意以下几点关于形参缺省值的使用:

  • 形参缺省值只能在函数定义时指定,而不能在函数调用时指定。
  • 形参缺省值通常用于使函数在某些情况下更加灵活。如果大多数情况下都使用相同的参数值,可以使用缺省值来简化函数调用。
  • 如果函数的形参既有缺省值又没有缺省值的形参,那么缺省值形参必须放在非缺省值形参的后面。例如,def func(a, b=1, c) 是有效的,但 def func(a=1, b, c) 是无效的。

使用形参缺省值可以增加函数的可读性和灵活性,使函数在不同情况下具有不同的默认行为。

关键字参数

Keyword Arguments,关键字参数允许通过参数名来指定实参的值,而不需要按照形参的顺序传递。这种方式提高了函数调用的可读性和灵活性。例如:

def greet(name, message="Hello"):
    print(message, name)

greet(message="Hi", name="Alice")  # 输出: Hi Alice
greet(name="Bob")  # 输出: Hello Bob

在上面的例子中,通过指定参数名来传递实参,可以以任意顺序传递参数,并且可以只传递部分参数,而其他参数使用默认值。

可变长参数

Variable-length Arguments,有时候我们无法预先确定传递给函数的参数数量,这时可以使用可变长参数。Python提供了两种类型的可变长参数:可变位置参数和可变关键字参数。

可变位置参数

通过在形参前加上星号*,可以将多个实参作为元组传递给函数。例如:

def multiply(*numbers):
    result = 1
    for num in numbers:
        result *= num
    return result

result = multiply(2, 3, 4)
print(result)  # 输出: 24
  • 在上面的例子中,numbers是一个可变位置参数,它接收任意数量的实参,并将它们作为元组处理。
def fn1(*nums): # * 是可变位置形参,可以接受n个实参,多个实参被收集到一个元组对象中,元组不可变
    print(nums, type(nums))
    x = 0
    for y in nums:
        x += y
    return x

print(fn1(1, 2, 3)) # 按照位置传参

"""
(1, 2, 3) <class 'tuple'>
6
"""

可变关键字参数

通过在形参前加上两个星号**,可以将多个实参作为字典传递给函数。例如:

def print_info(**info):
    for key, value in info.items():
        print(key, ":", value)

print_info(name="Alice", age=25, city="London")
# 输出:
# name : Alice
# age : 25
# city : London

在上面的例子中,info是一个可变关键字参数,它接收任意数量的实参,并将它们作为字典处理。

这些是Python中函数形参定义的详解,你可以根据实际需求选择适合的参数传递方式。记住,形参的顺序是位置参数、默认参数、可变位置参数和可变关键字参数。

注意:可变关键字传参,只能接受关键字参数!

def showconfig(**kwargs): # 可变关键字参数收集关键字参数,收集成字典 kv,字典可变
    print(kwargs, type(kwargs))
    # 内部,你能传入变量名我们有要求,对kwargs处理,'username' in kwargs.key()

print(showconfig(host='127.0.0.1', password='123'))

"""
{'host': '127.0.0.1', 'password': '123'} <class 'dict'>
None
"""

*args 与 **kwargs

对于可变长参数,通常在Python中使用*args**kwargs来表示。

*args

*args用于传递不定数量的位置参数。它允许你将任意数量的参数作为元组传递给函数。在函数内部,你可以像操作元组一样使用args变量来访问这些参数。

下面是一个示例,展示了如何在函数中使用*args

def foo(*args):
    for arg in args:
        print(arg)

foo(1, 2, 3, 4, 5)

输出:

1
2
3
4
5

**kwargs

**kwargs用于传递不定数量的关键字参数。它允许你将任意数量的关键字参数作为字典传递给函数。在函数内部,你可以像操作字典一样使用kwargs变量来访问这些参数。

下面是一个示例,展示了如何在函数中使用**kwargs

def bar(**kwargs):
    for key, value in kwargs.items():
        print(key, value)

bar(name='Alice', age=25, city='New York')

输出:

name Alice
age 25
city New York

你还可以同时在函数中使用*args**kwargs来接收任意数量的位置参数和关键字参数。在函数定义中,*args必须在**kwargs之前。

keyword-only

“keyword-only"参数可以限制某些参数只能通过关键字方式传递,而不能通过位置传递。

“keyword-only"参数是一种使函数调用更清晰、更可读的技巧,同时也可以避免一些潜在的错误。它允许你明确指定某些参数只能通过关键字方式传递,提高了函数的灵活性和可维护性。

在函数定义中,使用星号(*)将"keyword-only"参数与其他参数分隔开。从这个星号位置开始,之后的参数将被视为"keyword-only"参数。

方法一

在参数的两边之间定义*,这样的话*后面的参数只能使用关键字传参,也就是"keyword-only"参数。在这种方式中,*之前的参数是位置参数,*之后的参数是"keyword-only"参数。

# a 和 b 是位置参数,* 后面的 c 和 d 是 keyword-only,只能通过关键字传参。
def my_function(a, b, *, c, d):
    print(f"a={a}, b={b}, c={c}, d={d}")


# 1 和 2 可以通过位置传参,c 和 d 只能通过关键字传参。
my_function(1, 2, c=3, d=4)
"""
a=1, b=2, c=3, d=4
"""


# * 后面的 c 和 d 只能使用关键字传参,否则会引发 TypeError
my_function(1, 2, 3, 4)
"""
TypeError: my_function() takes 2 positional arguments but 4 were given
"""

方法二

在参数的两边之间定义*args,这样的话,*args后面的参数只能使用关键字传参,并且args将收集额外的位置参数作为一个元组。

# a 和 b 是位置参数,*args 是一个包含额外位置参数的元组,c 和 d 是 keyword-only 参数,必须通过关键字方式传递。
def my_function(a, b, *args, c, d):
    print(f"a={a}, b={b}, args={args}, c={c}, d={d}")


# 1 和 2 可以通过关键字传参,3 和 4 通过位置传参的方式会被 *args 所接受成为元组,c 和 d 只能通过关键字传参。
my_function(1, 2, 3, 4, c=5, d=6)
"""
a=1, b=2, args=(3, 4), c=5, d=6
"""

# c 和 d 未被赋予关键字传参,因此会报错。
my_function(1, 2, 3, 4, 5, 6)
"""
TypeError: my_function() missing 2 required keyword-only arguments: 'c' and 'd'
"""
def fn(y, *args, x=5):
    print(y)
    print(args)
    print(x)


fn()
"""
TypeError: fn() missing 1 required positional argument: 'y'
"""

fn(5)
"""
5 
()
5
"""

fn(5, 6)
"""
5 
(6,)
5
"""

fn(x=6)
"""
TypeError: fn() missing 1 required positional argument: 'y'
"""

fn(1, 2, 3, x=10)
"""
1
(2, 3)
10
"""

fn(x=17, 2, 3, x=10)
"""
SyntaxError: positional argument follows keyword argument
"""

fn(1, 2, y=3, x=10)
"""
TypeError: fn() got multiple values for argument 'y'
"""

fn(y=20, x=30)
"""
20
()
30
"""

方法三

同时定义***,在***之间的参数为"keyword-only"参数。

*args也可以放在最前面,表示后面的参数全部为"keyword-only"参数。

# a 和 b 为位置参数,*args 为可变位置参数,c 和 d 为关键字参数,**kwargs 为可变关键字参数。
def my_function(a, b, *args, c, d, **kwargs):
    print(f"a={a}, b={b}, args={args}, c={c}, d={d}, kwargs={kwargs}")

    
# 1 和 2 为位置参数,传递给 a 和 b;
# 3 和 4 为可变位置参数,传递给 *args 成为元组;
# c 和 d 只能接受关键字传参;
# x 和 y 为可变关键字参数,传递给 **args 成为字典。
my_function(1, 2, 3, 4, c=5, d=6, x=7, y=8)
"""
a=1, b=2, args=(3, 4), c=5, d=6, kwargs={'x': 7, 'y': 8}
"""


# 这种方式会报错,c 和 d 没有被通过关键字的方式传参。
my_function(1, 2, 3, 4, 5, 6, x=7, y=8)
"""
TypeError: my_function() missing 2 required keyword-only arguments: 'c' and 'd'
"""

方法四

*放在最前面定义,在这种情况下,后续的参数将被视为"keyword-only"参数。

# * 后面的所有参数只能接受关键字传参。
def my_function(*, a, b, c, d):
    print(f"a={a}, b={b}, c={c}, d={d}")


# 正确传参方式,全部为关键字传参。
my_function(a=1, b=2, c=3, d=4)
"""
a=1, b=2, c=3, d=4
"""


# 错误传参方式,不能为位置传参啦~
my_function(1, 2, 3, d=4)
"""
TypeError: my_function() takes 0 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
"""
def fn(*, x=5):
    print(x)

fn()
"""
5
"""

fn(5)
"""
TypeError: fn() takes 0 positional arguments but 1 was given
"""

fn(x=6)
"""
6
"""

fn(1, 2, 3, x=10)
"""
TypeError: fn() takes 0 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
"""

fn(y=100)
"""
TypeError: fn() got an unexpected keyword argument 'y'
"""

方法五

*args放在最前面定义,在这种情况下,后续的参数将被视为"keyword-only"参数。

# *args 为可变位置参数,可以接受多个位置参数,最终成为元组;
# a、b、c、d 只能接受关键字传参。
def my_function(*args, a, b, c, d):
    print(f"args={args}, a={a}, b={b}, c={c}, d={d}")


# 正确传参方式,1、2、3、4 都会传递给 *args,最终成为元组;
# a、b、c、d 只能为关键字传参。
my_function(1, 2, 3, 4, a=5, b=6, c=7, d=8)
"""
args=(1, 2, 3, 4), a=5, b=6, c=7, d=8
"""

# 错误传参方式,因为 a、b、c、d 没有通过位置参数的方式传参。
my_function(1, 2, 3, 4, 5, 6, 7, 8)
"""
TypeError: my_function() missing 4 required keyword-only arguments: 'a', 'b', 'c', and 'd'
"""
def fn(*args, x=5):
    print(args)
    print(x)

fn()
"""
()
5
"""

fn(5)
"""
(5,)
5
"""

fn(x=6)
"""
()
6
"""

fn(1, 2, 3, x=10)
"""
(1, 2, 3)
10
"""

fn(y=100)
"""
TypeError: fn() got an unexpected keyword argument 'y'
"""

positional-only

仅位置传参,在/前的内容只能通过位置传参,这是 Python3.8版本出现的新特性。

在Python中,positional-only(位置参数)语法允许您定义只能通过位置传递而不能作为关键字参数传递的函数参数。这个功能是通过PEP 570在Python 3.8中引入的。

要在函数中定义位置参数,您需要在函数签名中的参数名之前使用/符号。在/之前的所有参数只能按它们的位置传递,而在/之后的参数既可以按位置传递,也可以作为关键字参数传递。

下面是定义带有位置参数的函数的通用语法:

def 函数名(参数1, 参数2, /, 位置参数1, 位置参数2, ..., 位置或关键字参数):
    # 函数体
    pass

让我们通过一个示例来解释这个语法:

def 计算幂(base, exponent, /, factor=1):
    return (base ** exponent) * factor

在这个示例中,baseexponent是位置参数,而factor可以按位置传递,也可以作为关键字参数传递。

使用示例:

# 仅使用位置参数调用函数
结果1 = 计算幂(2, 3)  # base=2, exponent=3, factor=1(默认值)
结果2 = 计算幂(2, 3, 10)  # base=2, exponent=3, factor=10

# 同时使用关键字参数和位置参数传递'factor'
结果3 = 计算幂(2, 3, factor=5)  # base=2, exponent=3, factor=5

如果尝试将baseexponent作为关键字参数传递,将引发SyntaxError错误:

# 这将引发SyntaxError错误
结果4 = 计算幂(base=2, exponent=3, factor=5)

引入位置参数的主要动机是改进标准库中某些函数的参数语义,使函数调用更易读,同时还允许库维护者更改内部参数名称而不会破坏向后兼容性。

注意:截至我在2021年9月的最后更新时,Python 3.8是最新的稳定版本,并且该功能是可用的。但请查阅Python文档,了解更近期版本中可能出现的任何更改或更新。

方法一

# / 前面的 a 和 b 只能通过位置传参。
def fn(a, b, /):
    print(a, b)

  
# 1 和 2 可以通过位置传参的方式,传递给 a 和 b。
fn(1, 2)
"""
1 2
"""


# b 不能通过关键字传参。
fn(1, b=2)
"""
TypeError: fn() got some positional-only arguments passed as keyword arguments: 'b'
"""

方法二

# / 前面的 a 和 b 只能通过位置传参。
def fn(a, /, b):
    print(a, b)

# 1 和 2 可以通过位置传参的方式,传递给 a 和 b。
fn(1, 2)
"""
1 2
"""

# b 可以通过关键字传参,因为它在 / 的后面。
fn(1, b=2)
"""
1 2
"""

# 错误的传参方式,因为 a 在 / 前面,因此 a 只能通过位置传参。
fn(a=1, b=2)
"""
TypeError: fn() got some positional-only arguments passed as keyword arguments: 'a'
"""

# 错误的传参方式,因为关键字传参只能位于位置传参的后面。
fn(b=2, 1)
"""
SyntaxError: positional argument follows keyword argument
"""

参数注解

在Python中,函数参数注解是一种用于提供形式参数和返回值类型的元数据的方法。这些注解并不会强制执行类型检查,它们仅仅是提供了对函数参数和返回值预期类型的描述。这样做的目的是为了增加代码的可读性和可维护性,以及方便开发工具和IDE提供更好的代码提示和类型推断。

函数参数注解使用冒号(:)后紧跟参数名的方式添加,如下所示:

def func(arg1: type1, arg2: type2, ...) -> return_type:
    # 函数体
    pass

其中:

  • arg1, arg2, 等等是函数的形式参数(输入参数)。
  • type1, type2, 等等是参数的预期类型。
  • return_type是函数的返回类型。

例如,下面是一个简单的函数,使用参数注解来声明参数和返回类型:

def add_numbers(a: int, b: int) -> int:
    return a + b

在这个例子中,我们指定了参数ab的类型为整数int,并且指定了函数返回类型为整数int。请注意,这只是注解,并不会强制要求ab必须是整数类型,或者函数必须返回整数类型。实际的类型检查需要借助其他工具或第三方库来实现。

可以通过typing模块中的类型来注解参数。在上述示例中,我们使用了int作为类型注解,但还有其他类型注解,例如:strfloatlistdict等等。

需要注意的是,Python解释器在运行时不会对参数注解进行处理。这些注解主要用于提供代码文档和类型提示。如果需要进行严格的类型检查,可以使用mypy等第三方工具来辅助实现。

注意事项

参数顺序

参数列表的一般顺序是:

  1. positional-only
  2. 普通参数
  3. 缺省参数
  4. 可变位置参数
  5. keyword-only(可带缺省值)
  6. 可变关键字参数

PS:

  • 函数中混合使用参数时,代码应该易读易懂

当在函数中混合使用参数时,需要注意参数的顺序。在Python中,参数的顺序如下:

  1. 位置参数(Positional Arguments):位置参数是按照定义函数时参数的顺序来传递的。在调用函数时,需要按照定义函数时参数的顺序来依次传递参数值。
  2. 默认参数(Default Arguments):默认参数是在定义函数时为参数指定的默认值。在调用函数时,如果没有为默认参数提供值,则使用默认值。默认参数应该在位置参数之后定义,以确保参数传递的准确性。
  3. 可变数量的位置参数(Variable-Length Positional Arguments):使用星号(*)可以定义一个元组形式的可变数量的位置参数。这意味着函数可以接受任意数量的位置参数。可变数量的位置参数应该出现在位置参数和关键字参数之后。
  4. 关键字参数(Keyword Arguments):关键字参数是通过指定参数名进行传递的。在函数调用时,可以使用参数名来显式地传递参数值。关键字参数可以按任意顺序传递,并且可以与位置参数一起使用。
  5. 可变数量的关键字参数(Variable-Length Keyword Arguments):使用两个星号(**)可以定义一个字典形式的可变数量的关键字参数。这允许函数接受任意数量的关键字参数。可变数量的关键字参数应该出现在所有参数的最后。

下面是一个简单的示例函数,展示了混合参数的顺序:

def example_func(positional_arg1, positional_arg2, default_arg1='default', *args, keyword_arg1, keyword_arg2='default', **kwargs):
    # 函数体
    pass

在这个例子中,参数的顺序是:

  1. 位置参数:positional_arg1positional_arg2
  2. 默认参数:default_arg1
  3. 可变数量的位置参数:args
  4. 关键字参数:keyword_arg1keyword_arg2
  5. 可变数量的关键字参数:kwargs

请注意,实际使用时,参数的顺序应根据具体的需求进行调整,并根据函数的目的和使用方式选择适当的参数类型和顺序。

def showconfig(username, password, *args): pass

def showconfig(username, password, **kwargs): pass

def showconfig(username, *args,  **kwargs): pass

def showconfig(username, **kwargs, *args): pass # 这种定义方式不行

def showconfig(*args,  **kwargs): pass # 特殊写法,也有可能会用到

**kwargs

  • 这种定义方式是错误的,可以记为**kwargs是最后一站。
def fn(**kwargs, x, y):
    pass
"""
SyntaxError: invalid syntax
"""

函数调用

  • 函数定义,只是声明了一个函数,它不能被执行,需要调用执行。
  • 调用的方式,就是函数名后加上小括号,如有必要在括号内填写上参数。
  • 调用时写的参数是实际参数,是实实在在传入的值,简称**实参**。
  • 函数在定义时要定义好形式参数,调用时也提供足够的实际参数,一般来说,形参和实参个数要一致(可变参数除外)。

实参传参方式

在Python中,函数引用指的是在函数调用时传递给函数的实际参数(也称为实参)。实参是函数调用中传递给函数的具体值或对象,它们用于函数执行期间的计算和处理。

实参的传参方式如下:

位置参数

和形参依次对应

Positional Arguments,位置参数是按照函数定义中的顺序进行匹配的参数。当调用函数时,实参按照位置与形参进行对应。位置参数是最常见的实参类型。

示例:

def add(x, y):
    return x + y

result = add(3, 5)
print(result)  # 输出:8

在这个例子中,调用add(3, 5)时,3和5是实参。它们按照位置与函数定义中的参数x和y进行对应,函数执行时将使用这些实参进行计算。

关键字参数

按照参数名称对应,与顺序无关

Keyword Arguments,关键字参数使用参数名和对应的值进行传递。在函数调用时,可以指定参数名并赋予相应的值,而不需要按照位置进行匹配。关键字参数提供了更好的可读性和灵活性。

示例:

def greet(name, message):
    print(f"Hello, {name}! {message}")

greet(name="Alice", message="How are you?")  # 输出:Hello, Alice! How are you?

在这个例子中,调用greet(name="Alice", message="How are you?")时,“Alice"和"How are you?“是实参。通过指定参数名和对应的值,可以清晰地传递实参给函数。

在函数调用过程中,实参的值将被传递给函数的形参,在函数体内可以使用这些实参进行计算、处理或返回结果。实参提供了向函数传递数据的机制,使得函数可以接受外部数据并进行操作。

使用实参,可以根据函数定义的形参需求,将具体的值或对象传递给函数,从而实现函数的功能。实参可以是常量、变量、表达式等任何合法的Python表达式,以满足函数对数据的要求。

位置和关键字传参混用

在Python中,你可以在函数调用中同时使用位置参数和关键字参数。这意味着你可以按照参数的顺序传递一部分参数,并为剩下的参数使用关键字来指定。

下面是一个示例,演示了如何在Python中混用位置参数和关键字参数:

def example_func(a, b, c):
    print("a =", a)
    print("b =", b)
    print("c =", c)

# 使用位置参数调用函数
example_func(1, 2, 3)
# 输出:
# a = 1
# b = 2
# c = 3

# 使用关键字参数调用函数
example_func(c=3, a=1, b=2)
# 输出:
# a = 1
# b = 2
# c = 3

# 混合使用位置参数和关键字参数
example_func(1, c=3, b=2)
# 输出:
# a = 1
# b = 2
# c = 3

在上面的示例中,example_func接受三个参数abc。你可以通过位置参数或关键字参数来调用函数。在混合使用位置参数和关键字参数的情况下,位置参数需要按照函数定义中的顺序传递,而关键字参数则可以任意顺序指定。

位置参数和关键字参数混用时,位置参数必须在关键字参数之前,否则会出现语法错误。例如,example_func(1, c=3, 2)是不合法的,因为位置参数2出现在关键字参数之后。

参数解构

  • 参数解构,是在给函数提供实参的时候(调用函数时),可以在可迭代对象前使用 * 或者 ** 来进行结构的解构,提取出其中所有元素作为函数的实参
  • 使用 * 解构成位置传参
  • 使用 ** 解构成关键字传参
  • 提取出来的元素数目要和参数的要求匹配(否则将属于传实参有误)

在Python中,函数参数解构是一种方便的技术,它允许你在函数调用时将一个序列(例如列表或元组)或映射(例如字典)解构为单独的参数。这样做可以使代码更简洁、易读,并且减少手动解包序列或映射的工作量。

在函数定义和调用时,Python支持两种形式的参数解构:位置解构和关键字解构。

位置解构

Positional Unpacking, 位置解构允许你将一个序列解构为独立的参数,并将它们传递给函数。

示例:使用位置解构传递列表元素给函数参数

def my_function(a, b, c):
    print(a, b, c)

my_list = [1, 2, 3]
my_function(*my_list)  # 输出:1 2 3

关键字解构

Keyword Unpacking,关键字解构允许你将一个映射(通常是字典)解构为独立的关键字参数,并将它们传递给函数。

示例:使用关键字解构传递字典的键值对给函数参数

def my_function(a, b, c):
    print(a, b, c)

my_dict = {'a': 1, 'b': 2, 'c': 3}
my_function(**my_dict)  # 输出:1 2 3

注意事项

  • 位置解构和关键字解构可以同时使用。

  • 当使用关键字解构时,映射的键必须与函数的参数名相匹配,否则会引发TypeError

  • 位置解构和关键字解构在函数定义和调用时都可以使用。

  • 仅位置传参 positional-only 时,不要使用关键字解构

    • def fn(a, b, /):
          print(a, b)
      
      fn(*range(4, 6))
      """
      4 5
      """
      
      fn(*{'a':1, 'b':2})
      """
      a b
      """
      
      # 错误传参方式,因为解构后相当于a=1, b=2,而a和b只能接受位置传参。
      fn(**{'a':1, 'b':2})
      """
      TypeError: fn() got some positional-only arguments passed as keyword arguments: 'a, b'
      """

范例

位置解构

def add(x, y):
    print(x, y)
    return x + y
add(4, 5)
"""
4 5
"""
  • 正确传参方式,4 传 x,5 传 y。
t = 4, 5

print(t)
"""
(4, 5)
"""

add(t)
"""
TypeError: add() missing 1 required positional argument: 'y'
"""
  • 错误传参方式,因为这种方式给 t 赋值的话,里面的参数会被归纳为元组,而这个元组只给 x 赋值了,y 并没有被赋值,因此会报错。
def add(x, y):
    print(x, y)
    return x + y


t = 4, 5

print(t)
"""
(4, 5)
"""

a, b = t # 先解构

add(a, b) # 再传参
"""
4 5
"""
  • 正确传参方式,先解构,再传参。
  • 较为麻烦
def add(x, y):
    print(x, y)
    return x + y


t = 4, 5

print(t)
"""
(4, 5)
"""

add(t[0], t[1]) # 取元组下标
"""
4 5
"""
  • 正确传参方式,取元组下标。
  • 较为麻烦
def add(x, y):
    print(x, y)
    return x + y


t = 4, 5

print(t)
"""
(4, 5)
"""

add(*t) # 参数解构
"""
4 5
"""
  • 正确传参方式,参数解构。
  • 较为简单

其它位置解构方式:

def add(x, y):
    print(x, y)
    return x + y

add(*(4, 5))
"""
4 5
"""

add(*'ab')
"""
a b
"""

add(*b'xy')
"""
120 121
"""

# 顺序可能不确定?
add(*{10, 11})
"""
10 11
"""

关键字解构

def add(x, y):
    print(x, y)
    return x + y

add(*{'a':100, 'b':200})
"""
a b
"""

# 错误写法,关键字解构后a和b会成为关键字传参(a=100, b=200),而add函数只接受x和y
add(**{'a':100, 'b':200})
"""
TypeError: add() got an unexpected keyword argument 'a'
"""

# 正确写法
add(**{'x':100, 'y':200})
"""
100 200
"""

多种解构方式

def fn(*nums):
    x = 0
    for y in nums:
        x += y
    return x

print(fn(1, 2, 3))
"""
6
"""

print(fn(*range(5), 100, *[20]))
"""
130
"""

函数返回值 return

return 是Python中用于函数返回值的关键字。它用于在函数执行完毕后将结果返回给函数的调用者。可以根据需要返回不同类型的值,如整数、浮点数、字符串、列表等(像 range(5) 这种惰性对象等除外)。

return 关键字在函数中起着重要的作用,它使函数能够将结果传递给调用者,并控制函数的执行流程。

默认返回值

如果未定义return ,则返回值为None

def foo():
    pass
    # 相当于显示定义此行
    #return None

print(foo())
"""
None
"""

单个返回值

def add(x, y):
    return x + y

result = add(3, 5)
print(result)  # 输出:8

在这个例子中,函数 add(x, y) 接收两个参数 xy,并通过 return 返回它们的和。调用 add(3, 5) 后,返回值 8 被赋给变量 result,然后被打印出来。

def fn(a, b, c):
    return [a, b, c]

print(sum(fn(*range(3)))) # sum([0, 1, 2])
"""
3
"""

print(max(fn(*range(3)))) # max([0, 1, 2])
"""
2
"""

print(min(fn(*range(3)))) # min([0, 1, 2])
"""
0
"""

多个返回值

return 关键字还可以用于返回多个值,这些值将以元组、列表或其他数据类型的形式返回。

归纳为元组:

def calculate(x, y):
    return x + y, x - y, x * y

result = calculate(3, 2)
print(result)  # 输出:(5, 1, 6)
  • 在这个例子中,函数 calculate(x, y) 接收两个参数 xy。使用 return 返回了三个值,它们分别是 x + yx - yx * y。调用 calculate(3, 2) 后,这三个值被作为一个元组返回,然后被打印出来。

归纳为元组:

def fn(a, b, c):
    return a+b, b+c

print(fn(*range(3)))
"""
(1, 3)
"""

将列表和元组,归纳为同一个元组:

def fn(a, b, c):
    return [a, b], (b, c)

print(fn(*range(3)))
"""
([0, 1], (1, 2)) # 
"""

归纳为列表:

def fn(a, b, c):
    return [a, b, c]

print(fn(*range(3)))
"""
[0, 1, 2]
"""

终止函数执行

return 关键字还可以用于提前终止函数的执行。当 return 语句被执行时,函数会立即停止执行,并返回指定的值(如果有)。

示例:

def greet(name):
    if name == "":
        return "Name cannot be empty"
    return f"Hello, {name}!"

result1 = greet("Alice")
result2 = greet("")
print(result1)  # 输出:Hello, Alice!
print(result2)  # 输出:Name cannot be empty

在这个例子中,函数 greet(name) 接收一个参数 name。如果 name 为空字符串,函数会执行 return "Name cannot be empty",终止函数的执行,并返回指定的字符串。否则,函数会执行 return f"Hello, {name}!",返回带有问候语的字符串。

函数变量作用域

在Python中,变量的作用域由其在代码中被赋值的位置所决定。Python中有四种作用域:

局部作用域

  • Local scope,指的是在函数内部定义的变量,只能在函数内部访问。这些变量在函数执行时创建,在函数执行完毕后销毁。
def my_function():
    x = 10  # 局部变量
    print(x)

my_function()
print(x)  # 这里将会引发 NameError,NameError: name 'x' is not defined

封闭作用域

  • Enclosing scope,对于嵌套函数(函数内部包含函数),内部函数可以访问外部函数中的变量,但不能修改它们。
def outer_function():
    x = 10  # 封闭作用域
    def inner_function():
        print(x)  # 可以访问封闭作用域中的变量
    inner_function()

outer_function() # 10

全局作用域

  • Global scope,在模块级别定义的变量,即在函数之外定义的变量。这些变量在整个模块中都是可见的。
x = 10  # 全局变量

def my_function():
    print(x)  # 可以访问全局变量

my_function() # 10
print(x) # 10

内置作用域

  • Built-in scope,这是Python解释器内部定义的变量,例如 len()print()。这些变量在整个代码中都可用。
print(len([1, 2, 3]))  # 内置函数 len() 在内置作用域中

搜索顺序

变量的搜索顺序是从内部向外部逐级搜索,直到找到变量或搜索完所有作用域。如果在局部作用域中找到了变量,就不会在更外层的作用域继续搜索。如果在所有作用域都没有找到变量,就会引发NameError异常。

例如,考虑以下代码示例:

x = 10  # 全局作用域

def outer():
    y = 20  # 嵌套作用域
    
    def inner():
        z = 30  # 局部作用域
        print(x, y, z)
    
    inner()

outer()

在这个例子中,inner()函数可以访问xy,但是outer()函数不能直接访问z

示例二

这两段代码之间的主要区别在于变量x的作用域和赋值操作。让我们逐步分析这两个代码段,以便理解它们的不同行为。

运行正常

x = 300

def foo():
    y = x + 1
    print(y)

foo()
'''
301
'''
  • 在这个代码段中,变量x在函数foo内部被引用,但是它并没有在函数内部被重新赋值。因此,Python 会在函数内部查找变量x,并在全局作用域中找到它。这是因为函数内部没有对x进行赋值,所以 Python 将x视为全局变量,可以在函数内部访问。代码运行时,函数foo调用后,它会输出301,因为 y = x + 1 此时x等于300。

运行不正常

x = 300

def foo():
    y = x + 1
    print(y)
    x = 500

foo()
'''
UnboundLocalError: local variable 'x' referenced before assignment
'''
  • 在这个代码段中,函数foo内部尝试将变量x重新赋值为500。这会导致 Python 将x视为一个局部变量,因为在函数内部有一个对x的赋值操作。当在y = x + 1处尝试访问x时,Python 认为x是一个局部变量,而此时局部变量x尚未被赋值,因此会触发 UnboundLocalError 异常

总结起来,两段代码的不同之处在于第一个代码段中的x被视为全局变量,而第二个代码段中的x被视为局部变量,因为在函数内部对它进行了重新赋值。

示例三

x = 300

def foo():
    x += 1
    print(x)

foo()
'''
UnboundLocalError: local variable 'x' referenced before assignment
'''

这个代码段产生了UnboundLocalError错误,是因为在函数foo内部使用了一个赋值操作x += 1,而在函数内部没有显式声明x为全局变量。

Python 在函数内部进行赋值操作时,会默认将被赋值的变量视为局部变量。但在这个函数中,你没有在函数内部使用global关键字来指示x是一个全局变量。因此,Python 尝试在局部作用域内找到变量x,但在赋值操作之前并没有在局部作用域中找到该变量,从而导致了UnboundLocalError错误。

要修复这个问题,你可以在函数内部使用global关键字,明确告诉 Python x 是一个全局变量:

x = 300

def foo():
    global x  # 声明 x 为全局变量
    x += 1
    print(x)

foo()
'''
301
'''

print(x) # 同时x这个全局变量的值也会被修改
'''
301
'''

这样,Python 就会知道你想要在全局作用域中操作变量x,并且代码将会输出301

报错是因为等式先做右边

是的,您说得对。在这种情况下,错误是因为等式右边的操作先执行,而此时Python会尝试在函数内部查找变量x,并且找到了x,但是在等式右边的操作执行之前,Python 将x视为局部变量。这就导致了错误。

让我详细解释一下:

在函数内部的这一行代码:x += 1,看起来像是在对变量x进行自增操作。然而,在Python中,对于赋值操作,等式右边的表达式会在赋值操作之前进行求值。这就意味着,Python 在执行 x += 1 之前,会尝试在函数的局部作用域中找到变量x

由于在函数内部没有对x进行声明,Python 就会将x视为局部变量。但是,在等式右边的操作中,Python 尝试获取局部变量x的值,而在赋值操作之前,局部变量x还没有被赋值,因此就产生了UnboundLocalError错误。

为了解决这个问题,您可以在函数内部使用global关键字,明确告诉Python您要在全局作用域中操作变量x。这将使Python在函数内部查找变量x时知道它是一个全局变量,而不是局部变量。

示例四

x = 300

def foo():
    y = x + 1
    print(y)

foo()
'''
301
'''

示例五

def foo():
    global x
    x += 1
    print(x)

foo()
'''
NameError: name 'x' is not defined
'''

这段代码报错是因为在函数 foo() 内部使用了 global x 声明来引用一个全局变量 x,但是在函数内部并没有在全局作用域中定义变量 x。在Python中,如果要在函数内部修改一个全局变量的值,必须在函数执行之前或者函数内部的某个地方对该全局变量进行定义。

要修正这个问题,你需要在函数 foo() 内部之前或函数内部的某个地方定义变量 x,例如:

x = 0  # 在全局作用域定义变量 x

def foo():
    global x
    x += 1
    print(x)

foo()

这样就能避免报错,并且函数会正确地输出 1

示例六

def foo():
    global x
    x = 700
    x += 1
    print(x)

foo()
'''
701
'''

print(x)
'''
701
'''
  1. 在函数 foo() 内部,你使用了 global x 来声明 x 是一个全局变量,然后将 x 的值设置为 700
  2. 接着,你将 x 的值增加了 1,使其变为 701
  3. 在函数内部使用 print(x) 打印了更新后的全局变量 x,输出为 701

之后,在代码的顶层(全局作用域)使用 print(x) 打印全局变量 x,仍然输出为 701,因为在函数内部使用了 global x 声明,使得在函数内对 x 的修改影响了全局作用域中的变量。

global

当在函数内部需要修改全局变量时,可以使用 global 关键字进行声明,以确保变量被视为全局变量而非局部变量。

注意事项

  • 定义函数时,不用 global ,学习它就是为了深入理解变量作用域。另外,函数的目的就是为了封装,尽量与外界隔离。
  • 如果函数需要使用外部全局变量,请尽量使用函数形参的方式进行定义,并在调用时传实参解决。
  • 修改全局变量是不被推荐的做法,因为它会引入难以追踪和理解的副作用。更好的做法是将函数设计成接受参数并返回结果,而不是直接修改全局状态。

范例

  • 未声明前
x = 10  # 全局变量 x

def modify_global():
    x += 5   # 修改全局变量 x 的值
    print("Inside function:", x)

modify_global()
print("Outside function:", x)
'''
UnboundLocalError: local variable 'x' referenced before assignment(局部变量在赋值之前被引用)
'''
  • 使用 global 关键字进行声明:
x = 10  # 全局变量 x

def modify_global():
    global x  # 声明 x 为全局变量
    x += 5   # 修改全局变量 x 的值
    print("Inside function:", x)

modify_global()
print("Outside function:", x)

输出:

Inside function: 15
Outside function: 15

在上面的示例中,函数 modify_global() 内部使用了 global x 声明,因此可以在函数内部修改全局变量 x 的值,同时函数外部的 print("Outside function:", x) 也会打印修改后的值。

nonlocal

nonlocal关键字可以修改上一级嵌套函数中的变量,但对全局作用域中的变量无效

注意事项

  • nonlocal 只能用于嵌套函数中,无法在全局作用域内使用。
  • 只能修改嵌套函数直接外部作用域的变量,不能跨越多个层级。

范例

def outer_function():
    x = 10

    def inner_function():
        nonlocal x
        x = 20

    inner_function()
    print("Value of x in outer_function:", x)

outer_function()

在这个例子中,inner_function 内部使用了 nonlocal x 来指示要修改外部的 x 变量,而不是在内部创建一个新的 x 变量。结果会输出 Value of x in outer_function: 20,因为 inner_function 修改了外部 x 变量的值。

函数闭包

函数闭包是计算机编程中的一个重要概念,它涉及函数和其环境的交互。为了更好地理解函数闭包,让我们逐步详解它:

函数和作用域

在程序中,函数不仅仅是一段可执行的代码,它还拥有一个作用域(scope),即定义了函数中变量的可见性和生命周期的区域。每当你调用一个函数时,都会创建一个新的作用域,其中包含函数内部声明的变量和参数。

闭包的定义

闭包就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,例如JavaScript

  • 自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量

闭包是指一个函数及其在定义该函数时创建的作用域。换句话说,闭包是一个函数和它捕获的所有变量的集合体。这意味着闭包不仅包括函数本身的代码,还包括在函数外部定义但在函数内部可访问的变量。

创建闭包

闭包的一个常见例子是在一个函数内部定义另一个函数,并在内部函数中引用外部函数的变量。这将使内部函数捕获外部函数的作用域,从而创建一个闭包。

以下是一个Python示例:

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
result = closure(5)  # 这里的 result 将会是 15

在这个例子中,outer_function 返回了一个闭包 inner_function。闭包中的 inner_function 可以访问并使用 outer_function 中传递的参数 x,即使 outer_function 已经执行完毕,x 的值仍然被保留在闭包中。

闭包的用途

闭包具有一些实际的用途,包括:

  1. 保护数据: 通过将数据封装在闭包内部,可以限制对数据的直接访问,从而实现一定程度的数据保护。

  2. 封装: 闭包允许你封装代码片段,将一些功能单元打包成可重用的组件。

  3. 回调: 闭包可以用作回调函数,将某些代码逻辑传递给其他函数,在特定事件发生时执行。

  4. 延迟执行: 闭包允许你将一些代码逻辑延迟到稍后执行,可以用于实现惰性求值等。

注意事项

尽管闭包非常有用,但在使用它们时需要注意一些事项:

  1. 内存管理: 由于闭包会捕获外部作用域的变量,可能导致内存泄漏。当不再需要闭包时,确保适时释放闭包,以避免占用过多内存。

  2. 性能考虑: 闭包相对于简单的函数调用可能会带来一些性能开销,因为它涉及更多的上下文切换和变量捕获。

  3. 可读性和维护性: 过度使用闭包可能会使代码变得难以理解和维护。在选择使用闭包时,要权衡其带来的好处和复杂性。

总之,闭包是一种强大的工具,可以在编程中提供灵活性和功能。理解闭包的工作原理并谨慎使用它们可以帮助你编写更高质量的代码。

闭包示例-1

def counter():
    c = [0]

    def inc():
        c[0] += 1
        return c[0]
    return inc

这段代码定义了一个函数 counter,它返回另一个函数 inc,并且这个内部函数 inc 是一个闭包。让我详细解释这段代码的每个部分。

  1. counter() 函数:这是一个外部函数,当调用它时,它会返回一个内部函数 inc,这个内部函数是闭包的一部分。外部函数 counter 中有一个局部变量 c,它是一个包含单个元素(整数)的列表。这里使用列表是为了让内部函数能够修改闭包之外的变量。

  2. inc() 函数:这是内部函数,定义在外部函数 counter 内部。它是一个闭包,因为它引用了外部函数 counter 中的变量 c。这意味着,即使 counter() 被调用后退出,c 的值也会在闭包中保持,并且可以被内部函数 inc 访问和修改。

    • c[0] += 1:这行代码会递增列表 c 中的第一个元素,也就是实际的计数器值。由于列表是可变对象,内部函数 inc 可以修改它。
      • c[0] += 1 这行代码的返回值是递增后的计数器值,也就是 c[0] 的新值。
      • 在这段代码中,c 是一个列表,c[0] 表示列表中的第一个元素,也就是计数器的值。+= 运算符用于递增 c[0] 的值,然后将递增后的值赋回给 c[0]。所以,c[0] += 1 的结果就是递增后的计数器值。
      • 举个例子,如果一开始计数器的值是 0,那么执行 c[0] += 1 后,计数器的值将变成 1。每次执行这行代码,计数器的值都会递增。
    • return c[0]:内部函数返回当前计数器的值。
      • return c[0] 这行代码的返回值是当前计数器的值,也就是 c[0] 的值。
      • 在这段代码中,c 是一个列表,c[0] 表示列表中的第一个元素,即计数器的值。return c[0] 表示从函数中返回 c[0] 的值作为函数的返回值。因此,每次调用内部函数 inc 并执行 return c[0] 后,你会得到当前计数器的值作为函数的结果。
      • 举个例子,如果计数器的值经过一些递增操作变成了 5,那么执行 return c[0] 将会返回 5。这也是为什么每次调用 counter_function() 后,你会在输出中看到递增的计数器值。
  3. return inc:外部函数 counter 返回了内部函数 inc 的引用。由于 inc 是一个闭包,它可以在外部函数调用后继续使用,并且可以继续修改计数器的值。

使用示例:

counter_function = counter()  # 调用外部函数,得到闭包函数的引用

print(counter_function())      # 输出:1

print(counter_function())      # 输出:2

print(counter_function())      # 输出:3

在每次调用 counter_function() 时,实际上都在执行内部函数 inc,这会递增计数器的值并返回它。闭包在这里的作用是将 c 保留在内部函数中,从而实现了持续的计数器状态。

闭包示例-2

这段代码和之前提供的代码非常相似,但有一个小的变化。在这个新的版本中,计数器 c 被声明为外部函数 counter 中的一个普通变量而不是列表。同时,内部函数 inc 中使用了 nonlocal c 来告诉Python,c 是一个外层函数的变量,而不是在 inc 函数内重新创建的局部变量。

下面逐步解释这段代码的每个部分:

def counter():
    c = 0

    def inc():
        nonlocal c
        c += 1
        return c
    return inc
  1. counter() 函数:这是外部函数,当调用它时,它会返回内部函数 inc 的引用。外部函数中的局部变量 c 被初始化为 0。

  2. inc() 函数:这是内部函数,定义在外部函数 counter 内部。它是一个闭包,因为它引用了外部函数 counter 中的变量 c。在这个版本中,c 被声明为 nonlocal,表示 c 是一个外层函数的变量。这允许内部函数修改外部函数的变量。

    • nonlocal c:这行代码告诉Python,c 不是内部函数 inc 的局部变量,而是外部函数 counter 的变量。
    • c += 1:这行代码递增外部函数的变量 c
    • return c:内部函数返回当前计数器的值。
  3. return inc:外部函数 counter 返回了内部函数 inc 的引用。

使用示例:

counter_function = counter()  # 调用外部函数,得到闭包函数的引用
print(counter_function())      # 输出:1
print(counter_function())      # 输出:2
print(counter_function())      # 输出:3

每次调用 counter_function() 都会递增计数器的值,并返回递增后的值。这个版本的代码通过 nonlocal 关键字来直接修改外部函数的变量,而不需要使用列表来捕获变量。

函数注释

在Python中,您可以使用函数注释来为函数的形参提供注释。函数注释是一种在函数定义中包含参数和返回值类型的方式。它不会影响函数的实际行为,但可以提供有用的信息以帮助其他人了解函数应该如何使用。

以下是一个简单的示例,展示了如何在Python函数中使用注释:

def greet(name: str, age: int) -> str:
    """
    This function greets a person with the given name and age.
    
    Args:
        name (str): The name of the person to greet.
        age (int): The age of the person.
        
    Returns:
        str: A greeting message including the name and age.
    """
    return f"Hello, {name}! You are {age} years old."

# Calling the function
result = greet("Alice", 30)
print(result)

在上面的示例中,name: strage: int 是函数参数的注释,指示 name 应该是一个字符串,age 应该是一个整数。而 -> str 则指示函数返回一个字符串。注释应该位于函数定义的冒号后面

函数装饰器

函数装饰器是Python中一种强大的工具,它允许你在不修改原始函数代码的情况下,对函数的行为进行扩展或修改。

装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数作为输出。这个新的函数通常用于包装原始函数,添加额外的功能。

装饰器的灵活性使得它们在很多场景下都非常有用,比如日志记录、性能分析、权限检查等。Python标准库和许多第三方库中都使用了装饰器来实现这些功能。

示例代码1

当你阅读以下代码时,请记住装饰器是一种函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常用于包装原始函数,以添加额外的功能。

def decorator(func):
    def wrapper():
        print("Before calling the function")
        func()
        print("After calling the function")
    return wrapper

@decorator
def say_hello():
    print("Hello!")

say_hello()
  1. decorator 函数是一个装饰器函数,它接受一个函数作为参数。
  2. 在装饰器函数内部,定义了一个内部函数 wrapper
  3. wrapper 函数用来包装原始函数(在这个例子中是 say_hello 函数)。
  4. wrapper 函数内部,你可以添加任何你想要的额外功能。在这个例子中,我们在调用原始函数之前和之后打印了一些内容。
  5. 装饰器函数返回内部函数 wrapper 的引用。
  6. 使用 @decorator 语法,我们将装饰器应用到 say_hello 函数上。这等效于 say_hello = decorator(say_hello)
  7. 当我们调用 say_hello() 时,实际上调用了 wrapper 函数,而不是原始的 say_hello 函数。
  8. wrapper 函数中的 func() 调用了原始的 say_hello 函数。
  9. 运行结果会打印出 “Before calling the function”、“Hello!” 和 “After calling the function”。

总的来说,这段代码演示了装饰器如何在不修改原始函数代码的情况下,扩展原始函数的行为。

示例代码2

当然,下面是另一个示例,演示如何使用装饰器来记录函数的运行时间:

import time

def calculate_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to run.")
        return result
    return wrapper

@calculate_time
def calculate_sum(n):
    total = 0
    for i in range(1, n+1):
        total += i
    return total

print(calculate_sum(1000000))

这段代码有以下几个关键点:

  1. calculate_time 是一个装饰器函数,它接受一个函数作为参数。
  2. 在装饰器函数内部,定义了一个内部函数 wrapper,它接受任意数量的位置参数 *args 和关键字参数 **kwargs
  3. wrapper 函数内部,我们记录了函数开始执行的时间(start_time)和函数执行结束的时间(end_time)。
  4. 调用原始函数 func(*args, **kwargs) 来执行原始函数,并获取其返回值。
  5. 计算函数执行的时间,并打印出来。
  6. 返回原始函数的返回值。
  7. 使用 @calculate_time 语法,将装饰器应用到 calculate_sum 函数上。
  8. 调用 calculate_sum(1000000),输出函数运行的时间以及函数的返回值。

这个示例展示了如何使用装饰器来记录函数的运行时间,而无需修改原始函数的代码。这在性能分析和优化中非常有用。

范例

demo-1

demo-1

def add(x, y):
    print(x, y)
    return x + y

a=1
b=2

add(a, b)
'''
1 2
'''

在Demo-1中,函数add(x, y)在执行时打印了形参xy的值,并返回它们的和。当调用add(a, b)时,实参ab的值被传递给函数的形参xy,并打印了它们的值,即输出了1 2。函数执行完成后,没有使用print()函数打印函数的返回值,因此返回值3没有被显示出来。

demo-2

def add(x, y):
    print(x, y)
    return x + y

a=1
b=2

print(add(a, b))
'''
1 2
3
'''

在Demo-2中,函数add(x, y)在执行时同样打印了形参xy的值,并返回它们的和。但是,不同的是在函数调用时使用了print()函数将函数的返回值打印出来,即print(add(a, b))。所以,在调用add(a, b)后,首先输出了1 2,然后又使用print()函数打印了函数的返回值3

因此,Demo-1和Demo-2的输出结果不同。在Demo-1中,只有形参的值被打印,没有打印函数的返回值。而在Demo-2中,不仅形参的值被打印,还打印了函数的返回值。

demo-3

def add(x, y):
    print(x, y)
    return x + y

out = add(1,2)

print(out)
'''
1 2
3
'''

demo-2

def add(x, y):
    print(x, y)
    return x + y

print(add(1,2), add('ab', 'cd'), add([1], [2]), add((1,2), ([10],)))

'''
1 2
ab cd
[1] [2]
(1, 2) ([10],)
3 abcd [1, 2] (1, 2, [10])
'''

demo-3

  • 输出结果为函数标识符???
def add(x, y):
    print(x, y)
    return x + y

print(add)

'''
<function add at 0x0000024138F69EA0>
'''
def add(x, y):
    print(x, y)
    return x + y

print(add)
print(id(add), hex(id(add)))

'''
<function add at 0x00000199E7CB9EA0>
1760530505376 0x199e7cb9ea0
'''

登录函数

  • 定义一个登录函数
  • 注意:例如 port 如果未对形参定义缺省值,则只能往前放
def login(host='localhost', port=3306, user='root', password='123'):
    print('mysql://{2}:{3}@{0}:{1}'.format(host, port, user, password))

login() # mysql://root:123@localhost:3306
login('127.0.0.1') # mysql://root:123@127.0.0.1:3306
login('127.0.0.1', 3307, 'azheng', '456') # mysql://azheng:456@127.0.0.1:3307
login(user='james') # mysql://james:123@localhost:3306

定义配置的多种方式

  • 有限个参数,没有缺省值是一种提醒,表示必须认真填写实参,2种传参方式都可以。
def showconfig(host, port, user='root', password='123'): pass
  • 有限个参数,因为都有默认值,所以好用,2种传参方式都可以。
def showconfig(host='localhost', port=3306, user='root', password='123'): pass
  • 无限个参数(一般属于参数太多,但有限个),因为 **kwargs): 可以实现无限个关键字参数的输入,但可变关键字参数内部都会对参数名有要求
def showconfig(host, username, password, port=3306, **kwargs):
    print(host, port, username, password, kwargs)

showconfig('127.0.0.1', database='test', username='azheng', port=3307, password='123')

"""
127.0.0.1 3307 azheng 123 {'database': 'test'}
"""
# 属于 **kwargs 可变关键字参数的,会最后处理。

demo-4

def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)
fn()

"""
TypeError: fn() missing 2 required positional arguments: 'x' and 'y'
"""
  • 报错,因为 x 和 y 这两个位置参数未被传实参
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2)

"""
1 2 6 () {}
"""
  • 正确,1 和 2 分别传递给 x 和 y,z 使用缺省值 。
  • 可变位置参数未被传参,因此输出为空元组。
  • 可变关键字参数未被传参,因此输出为空字典。
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(range(5))

"""
TypeError: fn() missing 1 required positional argument: 'y'
"""
  • 报错,因为range(5)会被视为一个对象,因此 y 就相当于未被赋值。
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2, 3, 4)

"""
1 2 3 (4,) {}
"""
  • 4 是位置实参,因此会传递给 *args
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2, 3, a=123)

"""
1 2 3 () {'a': 123}
"""
  • a=123 是关键字实参,因此会传递给 **kwargs
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2, z=10, a=123, b='abc')

"""
1 2 10 () {'a': 123, 'b': 'abc'}
"""
  • ~
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2, 3, 4, a=123, b='abc', x=321)

"""
TypeError: fn() got multiple values for argument 'x'
"""
  • 这种写法是错误的,因为x的赋值重复了
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(1, 2, 3, 4, a=123, b='abc', c=321)

"""
1 2 3 (4,) {'a': 123, 'b': 'abc', 'c': 321}
"""
  • ~
def fn(x, y, z=6, *args, **kwargs):
    print(x, y, z, args, kwargs)

fn(y=1, a=2, x=3)

"""
3 1 6 () {'a': 2}
"""
  • ~

复杂情况

# / 前面的参数 a, b 为 positional-only 仅位置传参;
# x, y, z=6 为普通参数,既可以接受关键字传参,也可以接受位置传参。且 z 还定义了缺省值;
# *args 为可变位置参数,可以接受多个位置参数,并将其收纳到元组当中,未接受即为空元祖;
# *args 后面的 m=4, n 为 keyword-only 仅关键字传参,且 m 还定义了缺省值;
# **kwargs 为可变关键字参数,可以接受多个关键字参数,并将其收纳到字典当中,未接受即为空字典;
# PS:python中形参就这么多类型。
def fn(a, b, /, x, y, z=6, *args, m=4, n, **kwargs):
    print(a, b)
    print(x, y, z)
    print(args)
    print(m, n)
    print(**kwargs)

fn()

"""
TypeError: fn() missing 4 required positional arguments: 'a', 'b', 'x', and 'y'
"""
  • 错误传参方式;
  • 因为 a、b、x、y、n 都没有被传参,且还没有缺省值。
def fn(a, b, /, x, y, z=6, *args, m=4, n, **kwargs):
    print(a, b)
    print(x, y, z)
    print(args)
    print(m, n)
    print(**kwargs)

fn(1, 2, 3, 4, 5, 6)

"""
TypeError: fn() missing 1 required keyword-only argument: 'n'
"""
  • 错误传参方式;
  • 1, 2 位置传参给 a, b;
  • 3, 4, 5 位置传参给 x, y, z,并且 z 的缺省值会由 6 替换成 5;
  • 6 位置传参给 *args,成为元组;
  • m 和 n 都为 keyword-only 关键字传参,m 有缺省值,但 n 没有缺省值,因此会报错。
def fn(a, b, /, x, y, z=6, *args, m=4, n, **kwargs):
    print(a, b)
    print(x, y, z)
    print(args)
    print(m, n)
    print(**kwargs)

fn(1, 2, 3, 4, 5, 6, n=10)
"""
1 2
3 4 5
(6,)
4 10
"""
  • 正确的传参方式;
  • n 关键字传参才对~!
def fn(a, b, /, x, y, z=6, *args, m=4, n, **kwargs):
    print(a, b)
    print(x, y, z)
    print(args)
    print(m, n)
    print(kwargs)

fn(1, 2, n=20, x=5, y=6, t=10)

"""
1 2
5 6 6
()
4 20
{'t': 10}
"""
  • 正确的传参方式;
  • 1 2 给 a b;
  • 5 6 给 x y,z 使用缺省值;
  • *args 未被传可变位置参数,因此为空元组;
  • m 为缺省值,n 被 keyword-only 传参;
  • **kwargs 接受 t=10,进而成为元组。
def fn(a, b, /, x, y, z=6, *args, m=4, n, **kwargs):
    print(a, b)
    print(x, y, z)
    print(args)
    print(m, n)
    print(kwargs)

fn(1, 2, 3, t=100, m=200, n=300, y=400)

"""
1 2
3 400 6
()
200 300
{'t': 100}
"""
  • 正确传参方式;
  • 1, 2 传递给 a, b,因为 a, b 为仅位置传参 positional-only;
  • 3 和 y=400 分别传递给 x 和 y,因为 x 和 y 并不是 positional-only,因此使用位置或关键字的方式传参都可以;
  • m=200, n=300 传递给 m=4, n,m 的缺省值会被替换为200;
  • *args 未被传参,因此成为空元组,t=100 传递给 **kwargs 成为字典中的一员。

connect()

def connect(host='localhost', user='admin', password='123', port='3306', **kwargs):
    print(host, port)
    print(user, password)
    print(kwargs)


connect()
"""
localhost 3306
admin 123
{}
"""

connect(db='log')
"""
localhost 3306
admin 123
{'db': 'log'}
"""

connect(debug=True)
"""
localhost 3306
admin 123
{'debug': True}
"""

定义参数时,应注意以下几点:

  • 最常用的参数为普通参数,可不提供缺省值(即必须由用户提供);
  • 注意这些参数的顺序,最常用的先定义;
  • 将必须使用名称才能使用的参数,定义为 keyword-only 参数,即必须使用关键字传参;
  • 如果函数有很多参数,无法逐一定义,可使用可变参数。如果需要知道这些参数的意义,则使用可变关键字参数收集。

函数练习题

题目一

编写一个函数,能够接受至少两个参数,返回最小值和最大值

初步实现

  • 使用内建函数 max()mix() 来实现

  • (x, y, *args) 这种形参定义可以直观的看出至少接受2个实参,使用位置或关键字传参都可以。

  • 缺点:

    • 遍历了两次,解决方案:一次遍历
    • *args 解构了两次,解决方案:一次解构或不解构
def fn1(x, y, *args):
    return min(x, y, *args), max(x, y, *args)


print(fn1(1, 3, 5, 7, 9))
'''
(1, 9)
'''
def fn1(x, y, *args):
    print("最小的数为:", min(x, y, *args))
    print("最大的数为:", max(x, y, *args))


fn1(1, 3, 5, 7, 9)
'''
最小的数为: 1
最大的数为: 9
'''

fn1(6, 8, *range(66)) # range 需解构
'''
最小的数为: 0
最大的数为: 65
'''

一次遍历

  • 在数据为乱序时,性能最差,不推荐!
def fn2(x, y, *args):
    ms, *_, mx = sorted((x, y, *args))
    print("最小的数为:", ms)
    print("最大的数为:", mx)


fn2(1, 3, 5, 7, 9)
"""
最小的数为: 1
最大的数为: 9
"""

fn2(6, 8, *range(66)) # range 需解构
'''
最小的数为: 0
最大的数为: 65
'''

这段代码定义了一个函数fn2,该函数接受两个必选参数 xy,以及可变长度的额外参数 *args。这样的函数可以接受任意数量的额外参数,并将它们作为一个元组传递给args

接下来,函数中使用了sorted()函数来对传入的参数进行排序,包括xy以及args中的所有元素。排序后的结果保存在一个新的列表中。

接着,通过使用解构赋值(destructuring assignment),将排序后的列表中的最小值赋值给变量ms,将最大值赋值给变量mx,而中间的部分则使用*_来表示不需要的部分。这样做的目的是为了只保留最小值和最大值,而不需要显式地访问中间的部分。

  • * _:这是一个星号加下划线的模式,它表示一个临时的占位符变量,用于收集排序后列表中最小值和最大值之间的所有元素。通常,_在 Python 中表示一个不需要使用的临时变量。

最后,使用print()函数分别输出最小值和最大值。

*ms, _, mx 是什么意思?

让我们来看一个例子:

data = [3, 9, 1, 7, 5]
ms, *_, mx = sorted(data)
print("最小的数为:", ms)
print("最大的数为:", mx)

输出:

最小的数为: 1
最大的数为: 9

在这个例子中,ms的值为1,mx的值为9。* _ 捕获了中间的元素 [3, 5, 7],但我们并没有显式地使用这些值,而是用*来忽略它们。

*x, y, args 为什么外面还要加个()括号?

在这段代码中,(x, y, *args) 中的括号是用于创建一个元组(tuple)。在函数参数列表中,这样的括号通常用于将多个参数组合成一个元组,以便在函数体内作为一个整体来处理。

函数定义中的参数 (x, y, *args) 表示:

  • x:第一个参数,是一个必选参数。
  • y:第二个参数,也是一个必选参数。
  • *args:可变长度参数,它是一个元组,用于接收任意数量的额外参数。

将它们用括号括起来是为了明确指示它们是一个元组,而不是单独的参数。这样做还可以增强代码的可读性,让读者更容易理解函数接受的参数组合。

如果不加括号,即 x, y, *args,则它们会被解释为独立的参数,并且语法是无效的,会导致语法错误。

例如,下面是一个示例函数定义:

def my_function(x, y, *args):
    # 函数体
    pass

在调用这个函数时,可以传入两个必选参数和任意数量的额外参数:

my_function(1, 2)
my_function(1, 2, 3, 4, 5)

在第二个调用中,额外的参数 3, 4, 5 会被打包成一个元组传递给 args,而函数体内部就可以通过 args 来访问这些额外的参数。

不解构

  • 推荐指数两颗星
def fn3(x, y, *args):
    ms, mx = min(args), max(args)
    print("最小的数为:", min(x, y, ms))
    print("最大的数为:", max(x, y, mx))


fn3(1, 3, 5, 7, 9)
'''
最小的数为: 1
最大的数为: 9
'''

fn3(6, 8, *range(66)) # range 需解构
'''
最小的数为: 0
最大的数为: 65
'''

一次性遍历,不依赖内建函数

  • 推荐指数三颗星!!!
def fn4(x, y, *args):
    min_, max_ = (y, x) if x > y else (x, y)
    for x in args:
        if x > max_:
            max_ = x
        elif x < min_:
            min_ = x
    print("最小的数为:", min_)
    print("最大的数为:", max_)


fn4(1, 3, 5, 7, 9)
'''
最小的数为: 1
最大的数为: 9
'''


fn4(6, 8, *range(66)) # range 需解构
'''
最小的数为: 0
最大的数为: 65
'''

AI

当编写一个函数,接受至少两个参数,并返回这些参数的最小值和最大值时,我们可以使用Python编程语言来实现。下面是实现这个功能的函数以及相应的解释:

def min_max(*args):
    """
    接受至少两个参数,并返回它们的最小值和最大值。

    参数:
    *args:可变参数列表,表示接受任意数量的参数。

    返回值:
    tuple:一个包含最小值和最大值的元组。

    示例:
    >>> min_max(3, 7, 1, 9, 5)
    (1, 9)
    >>> min_max(10, 20, 5)
    (5, 20)
    """

    # 检查是否至少有两个参数
    if len(args) < 2:
        raise ValueError("至少需要提供两个参数")

    # 初始化最小值和最大值为第一个参数
    min_value = max_value = args[0]

    # 遍历所有参数,找到最小值和最大值
    for arg in args[1:]:
        if arg < min_value:
            min_value = arg
        if arg > max_value:
            max_value = arg

    # 返回最小值和最大值组成的元组
    return min_value, max_value

# 测试函数
if __name__ == "__main__":
    # 测试用例1
    nums1 = (3, 7, 1, 9, 5)
    result1 = min_max(*nums1)
    print(f"测试用例1:{nums1},最小值:{result1[0]},最大值:{result1[1]}")

    # 测试用例2
    nums2 = (10, 20, 5)
    result2 = min_max(*nums2)
    print(f"测试用例2:{nums2},最小值:{result2[0]},最大值:{result2[1]}")

解释:

  1. 首先,我们定义了一个名为min_max的函数,这个函数接受任意数量的参数,因为我们使用了*args作为函数的参数列表,这意味着它可以接受零个或多个位置参数,并将这些参数存储在名为args的元组中。
  2. 我们使用一个条件判断来确保至少有两个参数传递给函数,如果参数个数小于2,我们会抛出一个ValueError异常,提示用户至少需要提供两个参数。
  3. 接下来,我们初始化两个变量min_valuemax_value,并将它们都设置为第一个参数的值。这是一个很好的起点,因为在开始时,第一个参数同时也是最小值和最大值。
  4. 然后,我们使用一个for循环遍历剩余的参数(从第二个参数开始),并在循环中对每个参数进行比较。如果当前参数小于min_value,我们将更新min_value为当前参数的值,从而找到更小的值。同样,如果当前参数大于max_value,我们将更新max_value为当前参数的值,从而找到更大的值。
  5. 最后,我们返回一个包含最小值和最大值的元组,以完成函数的任务。
  6. if __name__ == "__main__":代码块中,我们提供了两个测试用例来验证函数的正确性。我们调用min_max函数并传入不同的参数组合,然后打印出最小值和最大值,以确保函数的输出正确。

题目二

完成一个函数,可以接受输入的多个数(命令行输入,数字间隔可以使用空格或逗号),每一次都能返回到目前为止的最大值、最小值。

模拟输入

input('>>').replace(',', ' ').split()

这段Python代码是一个交互式的程序,用于从用户输入中接收一串带有逗号分隔的文本,然后将逗号替换为空格,并将结果分割成单词列表。

让我一步步解释这段代码:

  1. input('>>'):这部分代码用于在终端或命令行中显示>>作为提示,并等待用户输入。用户输入的内容会被作为字符串返回。

  2. .replace(',', ' '):在用户输入的字符串中,这一部分使用replace()函数,将所有的逗号替换为空格。这样做是为了将可能存在的逗号分隔的项目分隔开。

  3. .split():之后,使用split()函数,将经过替换的字符串按照空格进行分割,生成一个单词列表。默认情况下,split()会根据空格、制表符、换行符等分割字符串。

综合起来,这段代码的目的是,用户输入一个带有逗号分隔的文本,然后将逗号替换为空格,并将文本中的单词提取出来,存储在一个列表中。例如,如果用户输入apple,banana,orange,代码将会输出['apple', 'banana', 'orange']

点号 .

在这个上下文中,点号 . 被用作属性访问符。它用于访问对象(比如字符串、列表、函数等)的属性或方法。

在你提到的代码中,.replace().split() 都是字符串对象的方法。这些方法允许你在字符串上执行特定的操作,比如替换子字符串、分割字符串等。通过在字符串对象后面使用点号和方法名,你可以调用这些方法来处理字符串。

例如,input('>>').replace(',', ' ') 中的 input('>>') 返回一个字符串对象(用户输入的内容),然后在这个字符串对象上调用 .replace(',', ' ') 方法,将其中的逗号替换为空格。

类似地,.split() 方法也是在字符串对象上调用的,用于将字符串分割成子字符串列表。

所以,点号 . 在这里充当了调用方法或访问属性的作用。

点号 . 在Python中的作用与Unix/Linux命令行中的管道 | 有一些相似之处,但也有一些区别。

点号 . 和管道 | 都用于将操作应用于数据流中的内容,但它们的实现和用法有所不同:

  1. 点号 .

    • 在Python中,点号 . 用于访问对象的属性或方法。这是一种面向对象的方式,通过在对象上调用方法或访问属性来进行操作。
    • 通常用于对象的方法调用,如字符串的 .replace().split(),列表的 .append() 等。
  2. 管道 |

    • 在Unix/Linux命令行中,管道 | 用于将一个命令的输出作为另一个命令的输入,从而将多个命令连接起来形成一个数据处理流水线。
    • 通常用于命令行的数据处理,如将一个命令的输出传递给另一个命令进行进一步处理。

虽然点号 . 和管道 | 在概念上都是将操作应用于数据流,但点号更强调面向对象的方法调用和属性访问,而管道更强调命令行中的数据处理流程。

所以,虽然它们有相似之处,但点号和管道在不同的上下文中有不同的用途和含义。

方法一

def max_min():
    # 初始化最大值和最小值为None
    mx = ms = None

    # 开始一个无限循环,直到用户输入空行或'quit'时退出循环
    while True:
        # 从用户输入中获取字符串并去除首尾的空格
        x = input('>>> ').strip()
        # 如果用户输入了空行或'quit',则退出循环
        if x == '' or x == 'quit':
            break

        # 将输入的字符串按逗号或空格分割,并将分割后的字符串转换为整数列表
        nums = [int(y) for y in x.replace(',', ' ').split()]
        # 如果nums列表非空
        if nums:
            # 如果最大值和最小值都是None,则将它们初始化为nums列表的第一个元素
            if mx is None:
                mx = ms = nums[0]
            # 找出当前输入的数字列表中的最小值和最大值
            a, b = min(nums), max(nums)
            # 如果最小值a小于当前已记录的最小值ms,则更新ms为a
            if a < ms:
                ms = a
            # 如果最大值b大于当前已记录的最大值mx,则更新mx为b
            if b > mx:
                mx = b

            # 打印当前已记录的最小值ms和最大值mx
            print(ms, mx)



max_min()
"""
>>> 18 19
18 19
>>> -1
-1 19
>>> 33
-1 33
>>> -20
-20 33
>>> -31 ,  100
-31 100
>>> 1,1,1,1,,1,1,1,
-31 100
"""

总结来说,这段代码定义了一个函数max_min,用于在用户连续输入数字时,计算并输出已经输入数字序列中的最小值和最大值。代码使用了mxms两个变量分别记录已经输入的最大值和最小值,随着新的输入逐渐更新这两个变量。循环会持续进行,直到用户输入空行或’quit’,退出整个循环。

mx = ms = None 的意义是什么?

mx = ms = None 这行代码的意义是将两个变量 mxms 同时初始化为 None。这种写法在Python中是一种简洁的赋值方式,它将相同的值同时赋给多个变量。

具体来说,这行代码做了以下事情:

  • 将变量 mxms 都赋值为 None,表示在开始时还没有记录任何最大值和最小值。

这样,当第一次输入数字时,程序会通过检查 mxms 是否为 None 来判断是否需要初始化它们。如果它们都是 None,就会将它们设置为第一个输入的数字,然后随着后续输入的数字逐渐更新这两个变量。这样可以确保在程序运行过程中正确跟踪已输入数字的最大值和最小值。

如果未定义 mx = ms = None 会怎样?

如果未定义 mx = ms = None,那么在代码中使用 mxms 之前会导致一个 NameError,因为这两个变量没有被初始化,而在Python中必须先对变量进行初始化(赋予一个值)才能使用它们。

在这段代码中,mxms 都被用来记录最大值和最小值。如果不进行初始化,那么第一次尝试更新 mxms 的值时,Python会引发 NameError,因为这些变量在之前没有被定义。

因此,初始化 mxms 是确保代码能够正常运行并记录最大值和最小值的重要步骤。通过 mx = ms = None 的方式,可以在一开始就将它们初始化为 None,从而避免了未初始化变量的错误。

AI

我们可以编写一个函数,接受命令行输入的多个数,并在每次输入后返回到目前为止的最大值和最小值。下面是实现这个功能的代码及详细解释:

def find_min_max():
    """
    接受命令行输入的多个数,每次输入后返回到目前为止的最大值和最小值。

    返回值:
    None

    示例:
    输入:3 7 1 9 5
    输出:当前最小值:3,当前最大值:3
          当前最小值:3,当前最大值:7
          当前最小值:1,当前最大值:7
          当前最小值:1,当前最大值:9
          当前最小值:1,当前最大值:9
          当前最小值:1,当前最大值:9
    """

    # 初始化最小值和最大值为None
    min_value = max_value = None

    while True:
        try:
            # 从命令行读取输入的多个数,使用空格或逗号进行分隔
            input_str = input("请输入多个数(以空格或逗号分隔):")

            # 将输入的字符串分割成数字列表
            nums = [float(num) for num in input_str.replace(',', ' ').split()]

            # 检查是否至少有一个数
            if not nums:
                print("请至少输入一个数")
                continue

            # 更新最小值和最大值
            if min_value is None or min(nums) < min_value:
                min_value = min(nums)
            if max_value is None or max(nums) > max_value:
                max_value = max(nums)

            # 输出当前最小值和最大值
            print(f"当前最小值:{min_value},当前最大值:{max_value}")

        except ValueError:
            print("无效的输入,请重新输入数字")

if __name__ == "__main__":
    find_min_max()

解释:

  1. 我们定义了一个名为find_min_max的函数,该函数用于接受命令行输入的多个数,并在每次输入后返回到目前为止的最大值和最小值。
  2. 在函数中,我们使用while True循环,表示会不断接受输入,直到用户主动终止程序。
  3. 在每次循环开始时,我们初始化min_valuemax_valueNone,表示还没有接受任何输入,因此最小值和最大值都还未被设置。
  4. 然后,我们使用input()函数从命令行读取用户输入的字符串。用户可以在一行中输入多个数,使用空格或逗号进行分隔。
  5. 接下来,我们将输入的字符串中的逗号替换为空格,并使用split()方法分割字符串,得到一个包含数值的列表nums
  6. 我们检查nums列表是否为空,如果为空,说明用户没有输入有效的数值,此时我们打印提示信息并让用户重新输入。
  7. 如果nums列表不为空,我们使用内置的min()max()函数找到当前输入中的最小值和最大值,并将它们与之前的最小值和最大值进行比较,从而更新min_valuemax_value的值。
  8. 最后,我们输出当前的最小值和最大值,显示给用户,完成本次输入的处理。
  9. 程序会继续循环,等待下一次用户输入,直到用户主动终止程序(通常通过按下Ctrl+C键来中断循环)。

请注意,此代码假设用户输入的是有效的数字。为了提高代码的健壮性,可以进一步添加对用户输入的数值的有效性检查,例如处理非数字输入或处理可能引发异常的情况。

匿名函数 Lambda

Lambda 函数是 Python 中的一种匿名函数,它允许你在需要函数的地方定义简单的函数,而不必使用 def 关键字来定义一个正式的函数。

Lambda 函数通常用于函数式编程,可以作为参数传递给高阶函数(接受其他函数作为参数的函数)。

Lambda 函数通常与内置函数 map()filter()reduce() 等一起使用,这些函数接受函数作为参数,Lambda 函数便可在这些函数中起到作用。

语法:

lambda arguments: expression
  • arguments 是函数的参数,可以是任意数量的参数;
  • expression 是函数的返回值。
  • Lambda 函数只能包含单个表达式,并且该表达式的结果会成为该函数的返回值。

注意事项:

  • 需要注意的是,虽然 Lambda 函数可以让代码更简洁,但过度使用 Lambda 函数可能会导致代码难以理解。因此,在编写代码时,应该根据实际情况谨慎选择是否使用 Lambda 函数。

示例,使用 Lambda 函数计算两个数的和:

add = lambda x, y: x + y
print(add(3, 5))  # 输出: 8

示例,与 map() 一起使用,对列表中的每个元素进行平方:

map() 是一个内置函数,用于将一个函数应用于一个可迭代对象(如列表)的每个元素,并返回一个包含结果的迭代器。因为 map() 接受一个函数作为参数,所以它经常与 Lambda 函数一起使用。

下面是一个使用 map() 和 Lambda 函数的示例:

# 定义一个列表
numbers = [1, 2, 3, 4, 5]

# 使用 map() 将 Lambda 函数应用于列表中的每个元素,求每个元素的平方
squared_numbers = list(map(lambda x: x**2, numbers))

print("原始列表:", numbers)
print("平方后的列表:", squared_numbers)

在这个示例中,map() 函数接受两个参数:一个 Lambda 函数和一个列表 numbers

Lambda 函数是 lambda x: x**2,它接受一个参数 x,并返回 x 的平方。

map() 函数将这个 Lambda 函数应用于列表 numbers 中的每个元素,最终生成一个包含平方值的迭代器,然后通过 list() 函数将其转换为列表。最后打印了原始列表和平方后的列表。

示例,与 filter() 一起使用:

filter() 是另一个内置函数,它用于过滤可迭代对象(如列表)中的元素,根据指定的条件返回一个包含满足条件的元素的迭代器。与 map() 一样,filter() 也经常与 Lambda 函数一起使用。

下面是一个使用 filter() 和 Lambda 函数的示例:

# 定义一个列表
numbers = [1, 2, 3, 4, 5]

# 使用 filter() 将 Lambda 函数应用于列表中的每个元素,筛选出偶数
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))

print("原始列表:", numbers)
print("偶数列表:", even_numbers)

在这个示例中,filter() 函数接受两个参数:一个 Lambda 函数和一个列表 numbers。Lambda 函数是 lambda x: x % 2 == 0,它接受一个参数 x,并返回一个布尔值,指示 x 是否为偶数。filter() 函数将这个 Lambda 函数应用于列表 numbers 中的每个元素,只保留返回值为 True 的元素,最终生成一个包含偶数的迭代器,然后通过 list() 函数将其转换为列表。最后打印了原始列表和偶数列表。

这个示例演示了如何使用 filter() 函数和 Lambda 函数一次性对列表中的元素进行过滤,只保留满足特定条件的元素,从而实现筛选的功能。

示例,与 reduce() 一起使用:

reduce() 函数是 Python 中 functools 模块中的一个函数,用于对可迭代对象中的元素进行累积操作,它接受一个函数作为参数,这个函数必须接受两个参数,并返回一个值。reduce() 函数会将这个函数应用于可迭代对象中的前两个元素,然后将结果与下一个元素继续应用该函数,直到处理完整个序列。因为 reduce() 函数接受一个函数作为参数,所以它也常与 Lambda 函数一起使用。

下面是一个使用 reduce() 和 Lambda 函数的示例:

from functools import reduce

# 定义一个列表
numbers = [1, 2, 3, 4, 5]

# 使用 reduce() 将 Lambda 函数应用于列表中的元素,求列表中所有元素的和
sum_of_numbers = reduce(lambda x, y: x + y, numbers)

print("原始列表:", numbers)
print("列表中所有元素的和:", sum_of_numbers)

在这个示例中,reduce() 函数接受两个参数:一个 Lambda 函数和一个列表 numbers。Lambda 函数是 lambda x, y: x + y,它接受两个参数 xy,并返回它们的和。reduce() 函数将这个 Lambda 函数应用于列表 numbers 中的所有元素,逐步累积求和,最终得到列表中所有元素的和。最后打印了原始列表和列表中所有元素的和。

这个示例演示了如何使用 reduce() 函数和 Lambda 函数一次性对列表中的所有元素进行累积操作,从而实现求和的功能。

示例,接受其他函数作为参数的函数:

当我们说一个函数可以接受其他函数作为参数时,意味着该函数能够接受另一个函数作为输入,并在其内部对这个输入的函数进行操作或者调用。这样的函数通常称为高阶函数。

下面是一个简单的示例,演示了一个接受其他函数作为参数的函数:

# 定义一个高阶函数,接受一个函数和一个列表作为参数,
# 并将该函数应用于列表中的每个元素,返回处理后的结果列表
def apply_function(func, lst):
    result = []
    for item in lst:
        result.append(func(item))
    return result

# 定义一个函数,用于求一个数的平方
def square(x):
    return x ** 2

# 定义一个函数,用于求一个数的立方
def cube(x):
    return x ** 3

# 定义一个函数,用于求一个数的平方根
import math
def square_root(x):
    return math.sqrt(x)

# 创建一个列表
numbers = [1, 2, 3, 4, 5]

# 使用高阶函数 apply_function,将不同的函数应用于列表中的元素
squared_numbers = apply_function(square, numbers)
cubed_numbers = apply_function(cube, numbers)
square_root_numbers = apply_function(square_root, numbers)

print("原始列表:", numbers)
print("平方后的列表:", squared_numbers)
print("立方后的列表:", cubed_numbers)
print("平方根后的列表:", square_root_numbers)

在上面的示例中,apply_function() 函数接受两个参数:一个函数(如 squarecubesquare_root)和一个列表 numbers。然后,它将这个函数应用于列表中的每个元素,并返回处理后的结果列表。

这就是一个函数接受其他函数作为参数的例子,通过这种方式,我们可以更加灵活地组织代码,并实现各种功能。