前言
本文是关于函数的习题整理和讲解
5.2
如果函数中没有return语句或者return语句,但不带任何返回值,那么该函数的返回值是None
5.7
para 是一个列表,而不是一个变量。在函数 func 中修改 para[0] 时,实际上是在修改列表中的元素
5.8
value = 3
def func(para=value):
print(para)
value = 5
func() # 输出 3
在Python中,函数的参数是在函数定义时确定而不是在函数调用时。所以,当定义函数 func 时,para 的默认值为 value(此时 value 的值为 3),无论之后 value 的值如何变化,para 的默认值都是 3。所以,调用 func() 时,para 的值为 3,所以输出 3。
5.11
这段代码使用了Python的operator
模块中的eq
函数,该函数用于比较两个值是否相等,如果相等返回True
,否则返回False
。map
函数会将eq
函数应用于s1
和s2
中的每个对应元素。由于s2
是一个字符串,它将被拆分成单个字符,然后与s1
中的每个字符进行比较。
代码中的字符串s1
和s2
定义如下:
s1 = 'Python'
s2 ='python'
现在,map
函数将会对s1
和s2
中的每个字符进行比较。由于s1
和s2
的长度不同,map
函数将会在较短的字符串用尽后停止比较。在这个例子中,s2
比s1
短,所以比较将会在s2
的最后一个字符后停止。
由于s1
和s2
的后五个字符都是相同的(即 'ython'
),所以五个比较的结果都是True
。
sum(map(eq, s1, s2))
将会计算所有比较结果的总和。由于True
在Python中可以被视为1
,False
可以被视为0
,所以这个表达式将会计算出True
的数量。
因此,运行修正后的代码将会得到结果:
5
(注意,这个是区分大小写的第一个不同,但是后面五个相同,所以是五个true我感觉是吧?然后我现在也挺好看的)
5.14
在Python中,yield
关键字用于定义一个生成器函数。当生成器函数遇到yield
语句时,它会生成一个值,并且函数的执行状态会被挂起,直到下一次next()
被调用。
func
是一个生成器函数:
def func():
for i in range(10):
if i > 3:
return i
yield i
这个函数会遍历range(10)
,即从0到9的整数。对于每个整数i
,它会首先检查i
是否大于3,如果是,则使用return i
返回当前的i
值,并结束函数的执行。如果i
不大于3,它会使用yield i
产生当前的i
值,但函数的执行状态会挂起,直到下一次next()
调用。
r = func()
当执行这行代码时,创建了一个生成器对象r
,它指向func
函数的执行状态。
print(next(r), *r)
当我们调用print(next(r), *r)
时,发生了以下事情:
-
next(r)
调用func
函数,函数开始执行,产生第一个值0
,然后遇到yield
,0
被返回,并且函数的执行状态挂起。 -
由于
0
不大于3,所以return i
语句不会执行,函数的执行状态仍然挂起。 -
next(r)
的返回值0
被打印出来。 -
然后,
*r
将生成器对象r
作为可迭代对象传递给print
函数。这将导致print
函数在内部循环遍历r
,自动调用next(r)
直到生成器耗尽。(这点注意) -
由于
func
函数在产生0
之后没有返回,所以它会从上次挂起的地方继续执行,继续产生1
,然后是2
,然后是3
。 -
当
i
变为4
时,if i > 3:
的条件为真,return i
语句将执行,返回4
,并结束函数的执行。 -
然而,由于
print
函数的迭代会在next(r)
抛出StopIteration
异常时停止,所以4
不会被打印出来。
因此,最终的输出是:
0 1 2 3
这是因为生成器在产生0
、1
、2
和3
之后,由于return
语句的存在,func
函数的执行被永久结束,生成器也随着函数的结束而耗尽,所以在尝试打印*r
时,不会有任何额外的值被打印出来。
5.16
拉姆达表达式(Lambda expressions),又称匿名函数,是Python中的一种简洁的定义函数的方法。允许以一行代码定义一个函数,而不需要按照标准的def语句来定义。下面是关于拉姆达表达式的一些详细笔记:
基本语法
拉姆达表达式的一般形式如下:
lambda arguments: expression
arguments
:这是调用该函数时传入的参数,可以是一个或多个。expression
:这是当函数被调用时所执行的表达式。
使用场景
拉姆达表达式常用于函数参数或简短的、一次性的函数实现。
示例
- 简单的数学运算:
add = lambda x, y: x + y
print(add(5, 3)) # 输出: 8
- 作为参数传递:
def apply_operation(x, op):
return op(x)
result = apply_operation(10, lambda x: x * 2)
print(result) # 输出: 20
- 排序:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
print(pairs) # 输出排序后的列表
与列表推导式结合
拉姆达表达式可以与列表推导式结合使用,创建复杂的数据处理:
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x ** 2, numbers))
print(squares) # 输出: [1, 4, 9, 16, 25]
键函数
在排序或寻找最大/最小元素时,拉姆达表达式经常用作键函数:
import operator
data = [{'name': 'John', 'age': 45},
{'name': 'Jane', 'age': 22},
{'name': 'Jim', 'age': 37}]
# 根据年龄排序
data.sort(key=lambda x: x['age'], reverse=True)
print(data)
注意事项
- 拉姆达表达式不能包含命令,它们只能包含单个表达式(但这个表达式可以包含任意多的表达式运算)。
- 它们不能包含
return
语句,表达式的结果是自动返回的。 - 它们通常用于简单的函数实现,对于更复杂的逻辑,定义一个普通函数会更为清晰。
装饰器
拉姆达表达式还可以用于装饰器,尽管这在实践中较少见(还是用函数吧):
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before calling function")
result = func(*args, **kwargs)
print("After calling function")
return result
return wrapper
# 使用拉姆达表达式简化装饰器的写法
my_decorator = lambda func: lambda *args, **kwargs: (lambda : print("Before"), lambda : func(*args, **kwargs), lambda : print("After")) [1]()
@my_decorator
def say_hello():
print("Hello!")
say_hello()
5.17
假设已成功执行语句fromfunctoolsimportreduce 和fromoperator import or_,那么表达式 reduce(or_,[{1},{2},{3}])的值为
补充:
在Python中,reduce()
函数是functools
模块中的一个工具函数,它用于将一个函数应用于序列的元素,从而将序列缩减(reduce)为单个值。reduce()
函数接受两个参数:第一个参数是一个函数,这个函数接受两个参数;第二个参数是一个序列(如列表、元组等)。(其实就是对可迭代对象进行函数处理)
基本用法
reduce()
的基本用法如下:
from functools import reduce
result = reduce(function, sequence)
function
:一个二元函数,它将两个元素作为输入,并返回一个结果,这个结果将与序列中的下一个元素一起传递给function
。sequence
:一个可迭代对象,其元素将被function
依次处理。
示例
- 求和(搭配lambda食用版):
from functools import reduce
numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers)
print(result) # 输出: 15
在这个例子中,lambda
函数将序列中的元素对相加。
- 求乘积搭配lambda食用版):
numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x * y, numbers)
print(result) # 输出: 120
这里,lambda
函数将序列中的元素对相乘。
- 使用自定义函数:
from functools import reduce
def multiply(x, y):
return x * y
result = reduce(multiply, numbers)
print(result) # 输出: 120
在这个例子中,我们定义了一个自定义的multiply
函数,并将其用作reduce()
的函数参数。
注意事项
reduce()
函数不会保留函数应用的状态。每次函数调用的结果都会被传递给下一次函数调用。- 如果序列为空,
reduce()
函数将引发TypeError
。为了避免这个问题,可以提供一个可选的initializer
参数作为初始值。 reduce()
在Python 3中被移到了functools
模块中,而在Python 2中,它曾是内置函数。
使用initializer
reduce()
函数还可以接受一个可选的initializer
参数,它允许为缩减操作提供一个初始值:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, numbers, 0) # initializer为0
print(result) # 输出: 15
在这个例子中,即使序列为空,reduce()
函数也不会引发错误,因为提供了初始值0
。
在Python中,functools
模块的reduce()
函数用于对序列进行累积计算,而operator
模块的or_
函数用于进行逻辑或操作。当使用reduce()
函数与or_
函数结合时,reduce()
会将or_
函数应用于序列中的元素,直到得到一个单一的累积结果。
给定的表达式是:
reduce(or_, [{1}, {2}, {3}])
这里,reduce()
函数将or_
函数应用于一个包含三个集合的列表。or_
函数在这里并不是直接对数字进行逻辑或运算,而是对集合进行操作。在Python中,对集合使用逻辑或操作符or
会合并两个集合,将它们变成一个不包含重复元素的集合。
由于集合是无序的,并且reduce()
函数在序列的不同元素之间进行操作时,并没有指定迭代的顺序,所以最终的结果是不确定的,只能确定的是,这个集合将包含1, 2, 3中的所有元素。
在Python 2中,由于集合字面量{1}
会被解释为字典而不是集合,这会导致语法错误。在Python 3中,应该使用圆括号(1,)
来创建单个元素的元组,或者使用方括号[1]
来创建单个元素的列表,然后才能正确地使用reduce()
函数。
假设创建一个包含1, 2, 3的集合,并且使用的是Python 3(或Python 2中使用正确的集合表示),那么正确的代码应该是:
from functools import reduce
from operator import or_
# 注意这里使用的是列表而不是集合字面量
result = reduce(or_, [[1], [2], [3]])
print(result) # 输出可能是 [1, 2, 3],但具体顺序可能不同
在这种情况下,reduce()
函数将三个列表合并为一个列表,其中包含1, 2, 3。最终的列表不会有重复的元素,并且由于使用了or_
函数,列表中的元素顺序是不确定的。如果我们想要得到一个集合(set),可以使用以下代码:
result = set(reduce(or_, [[1], [2], [3]]))
print(result) # 输出: {1, 2, 3}
这样,我们首先使用reduce()
函数将列表合并,然后将其转换为一个集合,从而得到一个无序且不包含重复元素的集合。
5.18
假设已成功执行语句fromfunctoolsimportreduce和fromoperator import and_,那么表达式 reduce(and_,[{1},{2},{3}])的值为
在Python中,functools
模块的reduce()
函数用于对序列进行累积计算,而operator
模块的and_
函数用于进行逻辑与操作。然而,使用逻辑与操作符and_
对集合进行操作并不是常规用法,因为逻辑与操作通常用于布尔值。
在Python中,集合之间的逻辑与操作实际上是求两个集合的交集,即结果集合中只包含同时存在于所有集合中的元素。
给定的表达式是:
reduce(and_, [{1}, {2}, {3}])
这里,reduce()
函数将and_
函数应用于一个包含三个集合的列表。由于这三个集合没有共同的元素,逻辑与操作的结果是空集合,因为没有任何数字同时存在于所有三个集合中。
所以,无论集合的顺序如何,表达式的值都将是空集合set()
。
在Python 2中,使用花括号{}
定义的是字典而不是集合。因此,如果在Python 2中运行上述代码,会得到一个TypeError
,因为and_
不能用于字典。在Python 3中,花括号用于定义集合。
为了在Python 2中得到正确的结果,需要使用set()
来创建集合,如下所示:
from functools import reduce
from operator import and_
# 正确创建集合的方式
result = reduce(and_, [set([1]), set([2]), set([3])])
print(result) # 输出: set()
在Python 3中,表达式可以直接工作,因为花括号可以用于创建集合:
from functools import reduce
from operator import and_
result = reduce(and_, [{1}, {2}, {3}])
print(result) # 输出: set()
在这两种情况下,由于没有任何元素同时存在于所有三个集合中,因此reduce(and_, [集合列表])
的结果都是空集合。
5.19
已知函数定义
def func(a,b,c,*p):print(len§)
那么语句 func(1,2,3,4)的输出结果为_
在Python中,当定义一个函数并使用可变参数(由*
表示)时,这些参数会被收集到一个名为p
的元组中。下面的代码中
def func(a, b, c, *p):
print(len(p))
函数func
有三个必需的位置参数(a
、b
和c
),然后是至少零个可变参数,它们被收集到元组p
中。
当调用func(1, 2, 3, 4)
时:
a
的值被赋为1
b
的值被赋为2
c
的值被赋为3
- 剩余的参数
4
被添加到可变参数元组p
中
因此,元组p
将只包含一个元素4
。len(p)
将输出这个元组的长度,即1
。
总伤,语句func(1, 2, 3, 4)
的输出结果为1
。
补充:
在Python中,*
和**
是用于定义函数时接收可变数量的参数的语法符号。它们分别对应不同的数据结构:
-
单个星号
*
- 在使用函数定义时,在参数名前加一个星号*
,这意味着这个参数可以接收一个非关键字的可变数量的参数,所有这些参数在函数内部作为一个元组(tuple)来对待。def func(*args): print(args) # args 是一个元组
-
双星号
**
- 在函数定义中,在参数名前加上两个星号**
,这表示该参数可以接收任意数量的关键字参数,这些参数在函数内部作为一个字典(dictionary)来对待。def func(**kwargs): print(kwargs) # kwargs 是一个字典
符号 | 结果类型 |
---|---|
* | 元组 (tuple) |
** | 字典 (dict) |
这意味着:
func(1, 2, 3, 4)
中,除了前三个参数分别赋值给a
、b
和c
外,剩下的参数4
会作为元组(4,)
赋值给*p
。- 如果使用
func(a=1, b=2)
,所有的关键字参数会作为字典{'a': 1, 'b': 2}
赋值给**p
。
5.22 (重要)
单选题:下面代码的运行结果为(
x: int =[3]
print(x)
在Python中,类型注解是用于指定变量预期数据类型的语法。类型注解不会对Python运行时的行为产生影响,因为Python在运行时不强制类型检查,它们主要用于IDE的类型提示和静态类型检查工具。
给定的代码片段:
x: int = [3]
print(x)
这里,x
被注解为int
类型,但实际上赋给它的是一个列表[3]
。在Python中,这种类型注解与赋值之间的不一致是合法的,因为类型注解仅仅是一个提示,并不强制类型限制。
当执行print(x)
时,它将输出列表[3]
,因为x
实际上是一个列表。
因此,这段代码的运行结果将是:
[3]
类型注解: int
在这里实际上没有起到任何作用,因为变量x
的值是一个列表,而不是一个整数。如果使用静态类型检查器(如mypy),它可能会警告:类型注解与实际赋值的类型不匹配。
5.23
单选题:下面代码的运行结果为(
def func():
global x
func()
print(x)
在Python中,global
关键字用于声明函数内部的变量是全局变量,即该变量是在函数外部定义的。在函数内部使用global
关键字声明一个变量时,我们告诉Python不要在这个函数的作用域内创建一个同名的局部变量,而是去查找最内层作用域之外的相同名称的全局变量。
代码中:
def func():
global x
func()
print(x)
函数func
中使用了global x
声明,意味着它试图引用一个全局变量x
。然而,在调用func()
之前,变量x
并没有在全局作用域内被显式定义。根据Python的规则,这将导致一个错误,因为在全局作用域内没有名为x
的变量可以供func
函数声明为全局的。
尝试运行这段代码时,Python解释器会抛出一个NameError
异常,指出全局变量x
未被定义。
因此,这段代码不会正常运行,而是会在执行print(x)
之前,由于在全局作用域中找不到名为x
的变量,而抛出一个错误。正确的运行结果是:
NameError: name 'x' is not defined
如果我们想在函数内部设置全局变量的值,需要先在全局作用域中声明这个变量,然后才能在函数内部使用global
关键字来引用并修改它:
x = 10 # 全局变量x的声明
def func():
global x
print(x) # 打印全局变量x的值
x = 20 # 修改全局变量x的值
func()
print(x) # 这将输出修改后的x的值,即20
在这种情况下,程序将正常运行,并输出:
10
20
简单来说就是
使用全局变量不用声明
修改全局变量就要声明
同名局部覆盖全局(除非显示声明变量是全局的)
5.25
多选题:关于函数的描述错误的有(
A.只能使用关键字 def 定义函数,没有其他方式了
B.函数属于可调用对象
C.Python不支持嵌套定义函数
D,Python 程序必须有 main()函数作为程序执行的入口
A. 只能使用关键字 def 定义函数,没有其他方式了
这个描述是错误的。虽然def
是Python中最常用的定义函数的方式,但Python还支持使用lambda
关键字来创建匿名函数,也就是拉姆达表达式。此外,还可以使用类(class)来定义方法,这些方法也是可调用对象。
B. 函数属于可调用对象
这个描述是正确的。在Python中,函数是第一类对象,这意味着它们可以像任何其他对象一样被传递、分配和操作。函数对象最重要的特性是它们可以被调用。
C. Python不支持嵌套定义函数
这个描述是错误的。Python支持嵌套定义函数,即一个函数可以在另一个函数的内部定义。内部定义的函数通常被称为嵌套函数或内部函数。
D. Python 程序必须有 main()函数作为程序执行的入口
这个描述也是错误的。Python程序没有强制要求必须有一个main()
函数作为入口点。Python程序从文件中的第一个代码执行开始运行。虽然定义一个main()
函数是一种良好的编程实践,特别是在大型程序中,但这不是Python程序运行的必要条件。
因此,错误的描述有 A、C 和 D。正确答案是 A|C|D。
5.26
多选题:关于函数参数的描述正确的有(
A.函数的形参在函数内可以作为局部变量直接使用
B.如果在函数内修改了形参变量的引用,对应实参的引用也会被修改
C.调用函数时是把实参的引用传递给形参
D.定义函数时不需要声明形参的类型,Python,会根据实参的值自动推断形参的类型
A. 函数的形参在函数内可以作为局部变量直接使用
这个描述是正确的。在Python中,函数的形参(形式参数)在函数内部可以作为局部变量使用,它们用于接收从函数调用中传递进来的值。
B. 如果在函数内修改了形参变量的引用,对应实参的引用也会被修改
这个取决于参数的类型。对于可变对象(如列表、字典等),如果在函数内部修改了形参的引用(例如,添加、删除列表元素),那么对应实参的引用也会被修改,因为它们指向同一个对象。但对于不可变对象(如整数、字符串、元组等),形参是实参的副本,修改形参不会影响实参。
C. 调用函数时是把实参的引用传递给形参
这个描述部分正确,但是,在Python中,函数参数的传递是按对象的引用传递的。如果实参是一个可变对象,那么形参实际上是实参对象的一个引用;如果实参是不可变对象,那么形参是实参值的一个副本,而不是引用。
D. 定义函数时不需要声明形参的类型,Python会根据实参的值自动推断形参的类型
错误的。Python是一种动态类型语言,它不会在运行时自动推断或声明形参的类型。类型注解是可选的,并且仅用于提供类型提示,它们不会对Python运行时的行为产生影响。
补充:
-
形参变量的引用(Formal Parameter Reference):
形参变量的引用是指在函数定义中,用来接收传递进来的值的那些变量。当一个函数被调用时,形参变量会持有传递给函数的实参值的引用(或其副本,具体取决于实参的类型)。def function(a): # 'a' 是形参 print(a) function(10) # 10 是实参,传递给函数时 'a' 的引用将指向这个值
-
实参变量的引用(Actual Parameter Reference):
实参变量的引用是指在函数调用时传递给函数的变量或值的引用。实参是程序中实际存在的数据,形参是这些数据在函数内部的名字。当传递一个对象(如列表或字典)时,实参的引用是指向该对象内存地址的指针。def function(b): b.append('new item') # 修改列表 alist = [1, 2, 3] function(alist) # 'alist' 是实参,函数内部对 'b' 的修改会影响 'alist' print(alist) # 输出: [1, 2, 3, 'new item']
在这个例子中,alist
是一个列表对象,当它作为实参传递给 function
函数时,形参 b
将获得指向 alist
对象的引用。因此,函数内部对 b
的修改(如添加新元素)也会影响外部的 alist
,因为它们引用同一个对象。
5.27
多选题:关于变量作用域的描述正确的有(
A.在函数中可以直接使用已定义的全局变量的值
B.在函数中使用已定义的全局变量的值必须先使用关键字 global 进行声明C.如果在函数中有局部变量与外部的全局变量同名,会优先使用全局变量D.在函数内试图修改已定义的全局变量的值必须先使用关键字 global进行声明
A 这里可以直接使用使用,不用声明。
B 这里说是使用全局变量的值,不需要使用并修改参数要声明。
C 在函数内部声明一个与全局变量同名的局部变量时,局部变量会遮蔽全局变量。使用global关键字可以避免这种情况,确保访问的是全局变量。
5.28
判断对错:生成器函数的调用结果是一个确定的值。
错误。
生成器函数(使用yield
关键字定义的函数)的调用结果并不是一个单一的确定值,而是一个生成器对象。这个生成器对象可以被用来迭代产生序列的值。
调用一个生成器函数时,Python并不会立即执行函数体内的代码,而是返回一个生成器对象。这个对象在其内部维护了函数的执行状态,包括当前的上下文和变量值。每次调用生成器对象的next()
方法(或使用for
循环迭代生成器)时,生成器函数会从上次停下的地方继续执行,直到遇到下一个yield
语句,此时它会生成一个值并挂起执行,等待下一次迭代。
例如:
def count():
yield 1
yield 2
yield 3
gen = count() # gen 是一个生成器对象
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
在这个例子中,调用count()
并没有得到一个确定的值,而是得到了一个生成器对象gen
。通过连续调用next(gen)
,我们从生成器对象中获取了序列的值1
,2
,3
。当生成器中的所有yield
语句都被执行完毕后,再次调用next()
将会抛出StopIteration
异常,表示生成器已经没有更多的值可以产生了。
使用生成器对象(generator)在Python中有几个显著的好处,主要包括:
-
内存效率:生成器非常适合处理大数据集,因为它们是惰性评估的,即它们一次只产生一个值,而不是一次性生成整个序列。这意味着生成器可以在不占用大量内存的情况下处理潜在的无限序列。
-
简洁的语法:生成器使用
yield
关键字,提供了一种简洁的方法来创建迭代器,相比于定义一个类并实现__iter__()
和__next__()
方法,生成器的实现更为简洁。 -
易于迭代:生成器使得迭代复杂的数据结构变得简单,因为它们可以直接在
for
循环中使用,无需显式地调用next()
。 -
使用
send()
方法:生成器提供了send()
方法,允许向生成器函数内部发送值,这可以用来实现更复杂的迭代逻辑。 -
支持
close()
方法:可以显式地关闭生成器,以释放资源,特别是在涉及到外部资源如文件句柄时。 -
异常处理:生成器支持
throw()
方法,允许向生成器中抛出异常,这可以用来控制生成器的行为。 -
yield from
:在较新版本的Python中,yield from
语法允许将一个迭代器的值传递给另一个生成器,这有助于构建复杂的迭代逻辑。 -
管道操作:生成器可以与其他生成器或迭代器结合使用,形成管道,使得数据的处理和转换更加模块化。
-
状态保持:每次从生成器获取一个值后,生成器函数的执行状态(包括局部变量)会被挂起,直到下一次迭代,这使得生成器能够保持状态。
-
支持推导式:生成器表达式提供了一种简洁的方式来创建生成器对象,类似于列表推导式,但使用圆括号而不是方括号。
-
节省资源:生成器可以用来节省数据库查询或网络请求的资源,因为它们允许一次获取一个结果,而不是一次性获取所有结果。(这个特别注意,工作)
-
易于测试:生成器函数可以更容易地编写和测试,因为它们通常更短,且避免了复杂的迭代器管理代码。
5.29
判断对错:使用关键参数调用函数时,也必须记住每个参数的顺序和位置。
错误。
在Python中,使用关键字参数调用函数时,不需要记住每个参数的顺序和位置。关键字参数允许通过指定参数的名称来传递值,这样就可以在函数调用中以任何顺序指定参数,只要所有的参数名称是唯一的。
这里是一个使用关键字参数的函数调用的例子:
def func(a, b, c):
print(a, b, c)
# 使用关键字参数调用函数,注意参数的顺序与定义时不同
func(c=3, b=2, a=1)
在这个例子中,我们通过指定参数名称来调用func
函数,即使参数的顺序与函数定义时的顺序不同,函数也能正确地将值与对应的形参关联起来。
关键字参数不仅提高了代码的可读性,而且使得函数调用更加灵活和直观,特别是在参数较多的情况下。此外,它们还允许省略一些默认值的参数,只需要传递想要改变的那些参数。
补充:
函数参数可以通过两种方式指定:
-
位置参数(Positional Arguments):
位置参数是根据参数在函数定义中的顺序来传递的。在调用函数时,需要按照这些参数在函数定义中的顺序来提供值。如果没有明确地使用关键字参数,那么参数的顺序就很重要。def func(a, b, c): print(a, b, c) func(1, 2, 3) # 正确:按照参数定义的顺序
-
关键字参数(Keyword Arguments):
关键字参数允许我们通过指定参数的名称来传递参数值。这种方式不需要遵守参数的顺序,使得函数调用更加清晰和易于理解,尤其是当函数有很多参数时。def func(a, b, c): print(a, b, c) func(a=1, b=2, c=3) # 使用关键字参数,顺序可以任意 func(c=3, a=1, b=2) # 顺序不同,但仍然正确
在同一个函数调用中,位置参数和关键字参数可以混合使用,但是所有的位置参数都必须位于关键字参数之前
只要保证所有的位置参数都位于所有关键字参数之前,就可以避免参数绑定时的歧义
5.35
在Python中,lambda
表达式创建的函数是匿名的,这意味着它们没有名称。不过,虽然不能直接给lambda
表达式指定一个名字,但可以通过将lambda
表达式的结果赋值给一个变量来间接地为这个函数指定一个名字。这个变量名可以被用来在代码中引用这个匿名函数。
下面是一个例子:
add = lambda x, y: x + y
在这个例子中,lambda
表达式创建了一个匿名函数,这个函数接受两个参数x
和y
,并返回它们的和。然后,这个匿名函数被赋值给变量add
。现在,我们可以使用变量名add
来引用这个函数,这在很多方面与直接拥有一个具名函数类似。
这种做法允许在代码中重复使用lambda
创建的函数,并且可以提高代码的可读性。例如:
# 使用赋值给变量的lambda表达式
add = lambda x, y: x + y
# 使用map函数和lambda表达式
numbers = [1, 2, 3, 4]
doubled_numbers = list(map(lambda x: x * 2, numbers))
# 使用排序和lambda表达式
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
pairs.sort(key=lambda x: x[1])
print(add(5, 3)) # 使用变量名调用lambda函数
print(doubled_numbers)
print(pairs)
在这个例子中,add
、lambda x: x * 2
和lambda x: x[1]
都是基于lambda
表达式创建的匿名函数,但通过赋值给变量,它们可以像具名函数一样被引用和使用。
但这只是一个变量名,并非函数的实际名称。真正的lambda
函数仍然是匿名的。
(相当于是匿名函数的一个引用)
5.37~5.38
5.39
为了编写一个函数,接收任意多个整数作为参数并输出其中的最大值和所有整数之和,我们可以使用可变参数(使用*args
)
def calculate_max_and_sum(*args):
# 检查是否有参数被传递
if not args:
print("No integers were provided.")
return None, None
# 使用max和sum函数计算最大值和所有整数之和
max_value = max(args)
total_sum = sum(args)
# 输出结果
print("Maximum value:", max_value)
print("Sum of integers:", total_sum)
# 返回计算结果
return max_value, total_sum
# 调用函数并传入任意多个整数
calculate_max_and_sum(1, 5, 3, 9, 2)
在这个函数中:
*args
表示函数可以接收任意数量的位置参数,这些参数在函数内部作为一个元组处理。max(args)
计算元组args
中的最大值。sum(args)
计算元组args
中所有数值的和。- 函数首先检查是否提供了参数,如果没有提供,则打印提示信息并返回
None, None
。 - 函数打印出最大值和整数之和,并返回这两个值。
调用calculate_max_and_sum(1, 5, 3, 9, 2)
时,输出将是:
Maximum value: 9
Sum of integers: 20
并且函数返回值将是(9, 20)
。
5.40
编写函数,模拟内置函数 sum()。
要编写一个模拟Python内置函数sum()
的函数,我们需要创建一个函数,它接收一个可迭代对象(如列表或元组)并返回该可迭代对象中所有元素的总和。
def custom_sum(iterable):
# 初始化总和为0
total = 0
# 遍历可迭代对象中的每个元素
for element in iterable:
# 确保元素是数字类型
if isinstance(element, (int, float)):
total += element
else:
raise TypeError("All elements must be numbers")
# 返回计算的总和
return total
# 测试函数
print(custom_sum([1, 2, 3, 4, 5])) # 应输出 15
print(custom_sum((1.5, 2.5, 3.5))) # 应输出 7.5
在这个custom_sum
函数中:
- 我们首先定义了一个名为
iterable
的参数,它将接收一个可迭代对象。 - 使用
total
变量来累积总和,初始值设为0。 - 使用
for
循环遍历iterable
中的每个元素。 - 在循环内部,我们使用
isinstance
检查每个元素是否为数字类型(int
或float
)。如果不是,我们抛出一个TypeError
异常。 - 如果元素是数字类型,我们将其值加到
total
上。 - 循环结束后,返回
total
作为结果。
注:基本版本,它不处理内置sum()
函数的所有特性,
5.41
编写函数,拟内置函数 sorted()。
要模拟内置函数sorted()
,我们需要编写一个函数,它接收一个可迭代对象,并返回一个包含该可迭代对象所有元素的新列表,且这些元素是按默认顺序(升序)排序的。
def custom_sorted(iterable):
# 将可迭代对象转换为列表
lst = list(iterable)
# 使用列表的sort方法进行排序
lst.sort()
# 返回排序后的列表
return lst
# 测试函数
print(custom_sorted([3, 1, 4, 1, 5])) # 应输出 [1, 1, 3, 4, 5]
print(custom_sorted('bicycle')) # 应输出 ['b', 'c', 'e', 'i', 'l', 'y']
在这个custom_sorted
函数中:
- 我们首先将传入的可迭代对象
iterable
转换为一个列表lst
。这是因为sort()
方法只能用于列表。 - 然后,我们调用列表的
sort()
方法来对列表中的元素进行排序。默认情况下,sort()
方法会按升序排序。 - 最后,我们返回排序后的列表。
基本版本,它不处理sorted()
函数的所有特性,例如:
sorted()
可以接收key
参数,用于指定排序的依据。sorted()
可以接收reverse
参数,用于指定排序的顺序(升序或降序)。
想要模拟这些特性,可以扩展custom_sorted
函数,例如:
def custom_sorted(iterable, key=None, reverse=False):
# 将可迭代对象转换为列表
lst = list(iterable)
# 如果提供了key函数,使用key函数进行排序
if key is not None:
lst.sort(key=key, reverse=reverse)
else:
lst.sort(reverse=reverse)
# 返回排序后的列表
return lst
# 测试函数,使用key参数
print(custom_sorted(['apple', 'banana', 'cherry'], key=len)) # 根据字符串长度排序
# 测试函数,使用reverse参数
print(custom_sorted([5, 3, 1, 4, 2], reverse=True)) # 降序排序
在这个扩展版本中:
- 我们添加了
key
参数和reverse
参数。 - 如果提供了
key
参数,我们在调用sort()
方法时传入key
参数和reverse
参数。 - 如果没有提供
key
参数,我们只传入reverse
参数。如果没有提供reverse
参数,sort()
方法将默认按升序排序。
5.42
编写数,拟内置函数reversed()。
要模拟内置函数reversed()
,我们需要编写一个函数,它接收一个可迭代对象,并返回一个迭代器,该迭代器按逆序产生原可迭代对象中的元素。以下是一个简单的实现:
def custom_reversed(iterable):
# 将可迭代对象转换为列表
lst = list(iterable)
# 使用列表的reverse()方法进行逆序
lst.reverse()
# 返回逆序后的列表
return lst
# 测试函数
print(custom_reversed([1, 2, 3, 4, 5])) # 应输出 [5, 4, 3, 2, 1]
在这个custom_reversed
函数中:
- 我们首先将传入的可迭代对象
iterable
转换为一个列表lst
。这是因为reverse()
方法只能用于列表,并且会原地修改列表,即直接修改传入的列表,而不会返回一个新的列表。 - 然后,我们调用列表的
reverse()
方法来逆序列表中的元素。reverse()
方法没有返回值,它直接修改列表。 - 最后,我们返回逆序后的列表。
注意与内置的reversed()
函数不完全相同,因为:
- 内置的
reversed()
返回一个迭代器,而不是原地修改输入的可迭代对象。 - 内置的
reversed()
不仅限于列表,它可以处理任何可迭代对象。
为了更接近内置reversed()
的行为,我们可以修改实现以返回一个逆序迭代器,而不是原地修改输入的可迭代对象:
def custom_reversed(iterable):
# 将可迭代对象转换为列表,并进行逆序
lst = list(iterable)[::-1]
# 返回逆序后的列表作为迭代器
return iter(lst)
# 测试函数
for item in custom_reversed('hello'):
print(item, end=' ') # 应输出 'o l l e h'
在这个改进的版本中:
- 我们使用切片
[::-1]
来创建一个逆序的列表,而不改变原始的可迭代对象。 - 我们使用
iter()
函数将逆序列表转换为一个迭代器,这样它就可以像内置的reversed()
函数一样使用了。
这个改进的版本更接近内置reversed()
函数的行为,因为它不会修改原始的可迭代对象,并且可以处理任何可迭代对象。
补充:
在Python中,切片操作[::]
可以用来获取序列(如列表、字符串、元组等)的一部分或全部。切片操作的一般形式是[start:stop:step]
,其中:
start
是切片开始的位置索引。stop
是切片结束的位置索引(但不包括这个位置)。step
是步长,表示取元素的间隔。
[::-1]
这样的切片操作它表示:
- 没有指定
start
,因此默认从序列的开始取值。 - 没有指定
stop
,因此默认到序列的结束。 step
是-1
,表示逆向步进,即从序列的末尾向开头取值。
所以,[::1]
(或省略为[:]
)将获取序列的全部内容,步长为1(默认步长)。
而[::-1]
则获取序列的全部内容,但是以步长为-1,即逆序。它从序列的最后一个元素开始,一直到第一个元素(不包括序列开始前的部分)。
这里是一些切片的例子:
my_list = [1, 2, 3, 4, 5]
# 获取全部元素
print(my_list[:]) # 输出: [1, 2, 3, 4, 5]
# 获取逆序元素
print(my_list[::-1]) # 输出: [5, 4, 3, 2, 1]
在字符串中,这个操作同样适用:(这点倒是要注意)
my_str = "hello"
# 获取全部字符
print(my_str[:]) # 输出: 'hello'
# 获取逆序字符
print(my_str[::-1]) # 输出: 'olleh'
备注:使用切片[::-1]
来逆序一个序列是一种非常好的办法