1.函数和模块
输入M和N计算C(M,N)
m = int(input('m = '))
n = int(input('n = '))
#计算m的阶乘
fm = 1
for num in range(1,m+1):
fm *= num
#计算n的阶乘
fn = 1
for num in range(1,n+1):
fn *= num
#计算m-n的阶乘
fk = 1
for num in range(1,m-n+1)
fk *= num
print(fm // fn // fk)
代码有很多种坏味道,重复是最坏的一种
用函数进行重构
def fac(num):
result = 1
for n in range(1,num+1)
result *= n
return result
m = int(input('m = '))
n = int(input('n = '))
print(fac(m) // fac(n) // fac(m-n))
函数参数
-参数的默认值
def add(a=0, b=0, c=0):
"""三个数相加求和"""
return a + b + c
# 调用add函数,没有传入参数,那么a、b、c都使用默认值0
print(add()) # 0
# 调用add函数,传入一个参数,那么该参数赋值给变量a, 变量b和c使用默认值0
print(add(1)) # 1
# 调用add函数,传入两个参数,1和2分别赋值给变量a和b,变量c使用默认值0
print(add(1, 2)) # 3
# 调用add函数,传入三个参数,分别赋值给a、b、c三个变量
print(add(1, 2, 3)) # 6
# 传递参数时可以不按照设定的顺序进行传递,但是要用“参数名=参数值”的形式
print(add(c=50, a=100, b=200)) # 350
-可变参数
# 用星号表达式来表示args可以接收0个或任意多个参数
def add(*args):
total = 0
# 可变参数可以放在for循环中取出每个参数的值
for val in args:
if type(val) in (int, float):
total += val
return total
# 在调用add函数时可以传入0个或任意多个参数
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))
用模块管理函数
def foo():
print('hello, world!')
def foo():
print('goodbye, world!')
foo() # 大家猜猜调用foo函数会输出什么
当然上面的这种情况我们很容易就能避免,但是如果项目是团队协作多人开发的时候,团队中可能有多个程序员都定义了名为foo
的函数,这种情况下怎么解决命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import
关键字导入指定的模块再使用完全限定名的调用方式就可以区分到底要使用的是哪个模块中的foo
函数,代码如下所示。
module1.py
def foo():
print('hello, world!')
module2.py
def foo():
print('goodbye, world!')
test.py
import module1
import module2
# 用“模块名.函数名”的方式(完全限定名)调用函数,
module1.foo() # hello, world!
module2.foo() # goodbye, world!
在导入模块时,还可以使用as
关键字对模块进行别名,这样我们可以使用更为简短的完全限定名。
test.py
import module1 as m1
import module2 as m2
m1.foo() # hello, world!
m2.foo() # goodbye, world!
上面的代码我们导入了定义函数的模块,我们也可以使用from...import...
语法从模块中直接导入需要使用的函数,代码如下所示。
test.py
from module1 import foo
foo() # hello, world!
from module2 import foo
foo() # goodbye, world!
如果我们如果从两个不同的模块中导入了同名的函数,后导入的函数会覆盖掉先前的导入
标准库中的模块和函数
Python标准库中提供了大量的模块和函数来简化我们的开发工作,我们之前用过的random
模块就为我们提供了生成随机数和进行随机抽样的函数;而time
模块则提供了和时间操作相关的函数;上面求阶乘的函数在Python标准库中的math
模块中已经有了,实际开发中并不需要我们自己编写,而math
模块中还包括了计算正弦、余弦、指数、对数等一系列的数学函数。随着我们进一步的学习Python编程知识,我们还会用到更多的模块和函数。
Python标准库中还有一类函数是不需要import
就能够直接使用的,我们将其称之为内置函数,这些内置函数都是很有用也是最常用的,下面的表格列出了一部分的内置函数。
函数 | 说明 |
---|---|
abs | 返回一个数的绝对值,例如:abs(-1.3) 会返回1.3 。 |
bin | 把一个整数转换成以'0b' 开头的二进制字符串,例如:bin(123) 会返回'0b1111011' 。 |
chr | 将Unicode编码转换成对应的字符,例如:chr(8364) 会返回'€' 。 |
hex | 将一个整数转换成以'0x' 开头的十六进制字符串,例如:hex(123) 会返回'0x7b' 。 |
input | 从输入中读取一行,返回读到的字符串。 |
len | 获取字符串、列表等的长度。 |
max | 返回多个参数或一个可迭代对象中的最大值,例如:max(12, 95, 37) 会返回95 。 |
min | 返回多个参数或一个可迭代对象中的最小值,例如:min(12, 95, 37) 会返回12 。 |
oct | 把一个整数转换成以'0o' 开头的八进制字符串,例如:oct(123) 会返回'0o173' 。 |
open | 打开一个文件并返回文件对象。 |
ord | 将字符转换成对应的Unicode编码,例如:ord('€') 会返回8364 。 |
pow | 求幂运算,例如:pow(2, 3) 会返回8 ;pow(2, 0.5) 会返回1.4142135623730951 。 |
print | 打印输出。 |
range | 构造一个范围序列,例如:range(100) 会产生0 到99 的整数序列。 |
round | 按照指定的精度对数值进行四舍五入,例如:round(1.23456, 4) 会返回1.2346 。 |
sum | 对一个序列中的项从左到右进行求和运算,例如:sum(range(1, 101)) 会返回5050 。 |
type | 返回对象的类型,例如:type(10) 会返回int ;而type('hello') 会返回str 。 |
2.函数的应用
设计一个生成验证码的函数
import random
import string
ALL_CHARS = string.digits + string.ascii_letters
def generate_code(code_len=4):
"""生成指定长度的验证码
:param code_len: 验证码的长度(默认4个字符)
:return: 由大小写英文字母和数字构成的随机验证码字符串
"""
return ''.join(random.choices(ALL_CHARS, k=code_len))
设计一个函数返回给定文件的后缀名
def get_suffix(filename, ignore_dot=True):
"""获取文件名的后缀名
:param filename: 文件名
:param ignore_dot: 是否忽略后缀名前面的点
:return: 文件的后缀名
"""
# 从字符串中逆向查找.出现的位置
pos = filename.rfind('.')
# 通过切片操作从文件名中取出后缀名
if pos <= 0:
return ''
return filename[pos + 1:] if ignore_dot else filename[pos:]
3.写一个判断给定的正整数是不是质数的函数
def is_prime(num: int) -> bool:
"""判断一个正整数是不是质数
:param num: 正整数
:return: 如果是质数返回True,否则返回False
"""
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return num != 1
4.写出计算两个正整数最大公约数和最小公倍数的函数
def gcd(x: int, y: int) -> int:
"""求最大公约数"""
while y % x != 0:
x, y = y % x, x
return x
def lcm(x: int, y: int) -> int:
"""求最小公倍数"""
return x * y // gcd(x, y)
5.写出计算一组样本数据描述性统计信息的函数
import math
def ptp(data):
"""求极差(全距)"""
return max(data) - min(data)
def average(data):
"""求均值"""
return sum(data) / len(data)
def variance(data):
"""求方差"""
x_bar = average(data)
temp = [(num - x_bar) ** 2 for num in data]
return sum(temp) / (len(temp) - 1)
def standard_deviation(data):
"""求标准差"""
return math.sqrt(variance(data))
def median(data):
"""找中位数"""
temp, size = sorted(data), len(data)
if size % 2 != 0:
return temp[size // 2]
else:
return average(temp[size // 2 - 1:size // 2 + 1])
3.函数进阶
关键字参数
def is_triangle(*, a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
return a + b > c and b + c > a and a + c > b
# TypeError: is_triangle() takes 0 positional arguments but 3 were given
# print(is_triangle(3, 4, 5))
# 传参时必须使用“参数名=参数值”的方式,位置不重要
print(is_triangle(a=3, b=4, c=5))
print(is_triangle(c=5, b=4, a=3))
同时使用可变参数和关键字参数
def calc(*args, **kwargs):
result = 0
for arg in args:
if type(arg) in (int, float):
result += arg
for value in kwargs.values():
if type(value) in (int, float):
result += value
return result
print(calc()) # 0
print(calc(1, 2, 3)) # 6
print(calc(a=1, b=2, c=3)) # 6
print(calc(1, 2, c=3, d=4)) # 10
高阶函数
函数本身可以作为函数的参数或者返回值
lambda函数
没有名字,一行代码,匿名函数
4. 函数的高级应用
装饰器 和 函数的递归调用
装饰器
用一个函数装饰另外一个函数或类并为其提供额外功能
import random
import time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
start = time.time()
download('MySQL从删库到跑路.avi')
end = time.time()
print(f'花费时间: {end - start:.3f}秒')
start = time.time()
upload('Python从入门到住院.pdf')
end = time.time()
print(f'花费时间: {end - start:.3f}秒')
有编程经验的人都知道,重复的代码是万恶之源,那么有没有办法在不写重复代码的前提下,用一种简单优雅的方式记录下函数的执行时间呢?在Python中,装饰器就是解决这类问题的最佳选择。我们可以把记录函数执行时间的功能封装到一个装饰器中,在有需要的地方直接使用这个装饰器就可以了,代码如下所示。
饰器函数有两种方式
1)第一种方式就是直接调用装饰器函数,传入被装饰的函数并获得返回值,我们可以用这个返回值直接覆盖原来的函数,那么在调用时就已经获得了装饰器提供的额外的功能(记录执行时间),大家可以试试下面的代码就明白了。
import time
# 定义装饰器函数,它的参数是被装饰的函数或类
def record_time(func):
# 定义一个带装饰功能(记录被装饰函数的执行时间)的函数
# 因为不知道被装饰的函数有怎样的参数所以使用*args和**kwargs接收所有参数
# 在Python中函数可以嵌套的定义(函数中可以再定义函数)
def wrapper(*args, **kwargs):
# 在执行被装饰的函数之前记录开始时间
start = time.time()
# 执行被装饰的函数并获取返回值
result = func(*args, **kwargs)
# 在执行被装饰的函数之后记录结束时间
end = time.time()
# 计算和显示被装饰函数的执行时间
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
# 返回被装饰函数的返回值(装饰器通常不会改变被装饰函数的执行结果)
return result
# 返回带装饰功能的wrapper函数
return wrapper
download = record_time(download)
upload = record_time(upload)
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
2)第二种方法
使用装饰器很有更为便捷的语法糖(编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强,我们将其称之为“语法糖”或“糖衣语法”),可以用@装饰器函数将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同,下面是完整的代码。
import random
import time
def record_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
return result
return wrapper
@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')
@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
使用wrapper函数
import random
import time
from functools import wraps
def record_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
return result
return wrapper
@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')
@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
# 取消装饰器
download.__wrapped__('MySQL必知必会.pdf')
upload = upload.__wrapped__
upload('Python从新手到大师.pdf')
递归调用
def fac(num):
if num in (0, 1):
return 1
return num * fac(num - 1)
通常,内存中的栈空间很小,因此递归调用的次数如果太多,会导致栈溢出(stack overflow),所以递归调用一定要确保能够快速收敛。我们可以尝试执行fac(5000)
,看看是不是会提示RecursionError
错误,错误消息为:maximum recursion depth exceeded in comparison
(超出最大递归深度),其实就是发生了栈溢出。
装饰器是Python中的特色语法,可以通过装饰器来增强现有的函数,这是一种非常有用的编程技巧。一些复杂的问题用函数递归调用的方式写起来真的很简单,但是函数的递归调用一定要注意收敛条件和递归公式,找到递归公式才有机会使用递归调用,而收敛条件确定了递归什么时候停下来。函数调用通过内存中的栈空间来保存现场和恢复现场,栈空间通常都很小,所以递归如果不能迅速收敛,很可能会引发栈溢出错误,从而导致程序的崩溃。