目录
函数是组织好的,封装功能上相对独立而且会被反复使用的代码,用来实现单一,或相关联功能的代码段。
如果我们将重复代码封装成函数,那么将来通过调用函数就能实现对代码的复用!!!
函数分为内置函数(已经写好的,例如print()、len())、自定义函数。这篇主要介绍自定义函数基础知识。
简单认识一下函数:
"""
example01 - 双色球 - 6个红色球+1个蓝色球
红色球:1-33 ---> 6
蓝色球:1-16 ---> 1
实现机选n注的功能
random.randint(a, b) ---> [a, b]
random.randrange(a, b) ---> [a, b)
random.sample(population, k) ---> 无放回抽样 ---> list
random.choices(population, k) ---> 有放回抽样 ---> list
random.choice(population) ---> 随机抽出1个样本 ---> item
"""
import random
def generate():
"""生成一组随机双色球号码"""
red_balls = [i for i in range(1, 34)]
# random.sample(items, k) ---> []
selected_balls = random.sample(red_balls, k=6)
selected_balls.sort()
# random.choices(items, k) ---> []
# random.choice(items) ---> item
temp = random.randrange(1, 17)
selected_balls.append(temp)
return selected_balls
def display(balls):
"""显示一组双色球号码"""
# 输出红色球
for ball in balls[:-1]:
print(f'\033[31m{ball:0>2d}', end=' ')
# 输出蓝色球
print(f'\033[34m{balls[-1]:0>2d}')
# 上面输出对控制端做了特殊设置,改变了字体颜色
n = int(input('机选几注: '))
for _ in range(n):
display(generate())
一、定义一个函数
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号 : 起始,并且缩进。
return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None
注意:函数不一定有return,有没有取决于函数具体需不需要返回值。当没有return时,如果打印输出,函数也会默认返回None。
语法:
def 函数名(形参列表): 函数说明文档 函数体
说明:
def | 关键字;固定写法 |
函数名 | 由程序员自己命名 见名知义(看到函数名就大概知道这个函数的功能) 所有的字母都小写,单词和单词之间用下划线隔开 |
( ) | 固定写法 |
形参列表 | 以'变量名1, 变量名2, 变量名3,...'的形式存在,这里的每一个变量就是一个形参 将函数外部的数据传递到函数内部(用于提供通道的) 看实现函数的功能需不需要额外的数据,如果需要就提供形参,需要多少个数据就给多少个形参 |
函数说明文档 | 本质就是多行注释 |
函数体 | 结构上,是和def保持一个缩进的一条或者多条语句(至少一条) 逻辑上,函数体就是实现函数功能的代码 |
案例:
"""
example02 - 输入两个整数m和n(m >= n),计算组合数C(m, n)
函数:封装功能上相对独立而且会被反复使用的代码
如果我们将重复代码封装成函数,那么将来通过调用函数就能实现对代码的复用!!!
如果函数是现成的(Python标准库或三方库已经提供了该函数)就直接导入调用;
如果函数不是现成的,那么就需要我们自定义函数。
组合 - combination
排列 - permutation
y = f(x)
bug - 臭屁虫、蛾子 - 计算机系统软硬件的缺陷或故障
debug - 调试
"""
from math import comb, perm
# def fac(num):
# result = 1
# for i in range(2, num + 1):
# result *= i
# return result
#
#
# def comb(m, n):
# """组合数"""
# return fac(m) // fac(n) // fac(m - n)
#
#
# def perm(m, n):
# """排列数"""
# return fac(m) // fac(m - n)
m = int(input('m = '))
n = int(input('n = '))
print('排列数: ', perm(m, n))
print('组合数: ', comb(m, n))
# math库中就有已经写好的排列组合函数,可以直接调用
"""
example03 - 输入x和y,求它们的最大公约数和最小公倍数
最大公约数(greatest common divisor):x % z == 0 and y % z == 0 ---> 最大的z
最小公倍数(least common multiplier):z % x == 0 and z % y == 0 ---> 最小的z
辗转求余数(欧几里得算法)
15, 27 <----> 27, 15 <----> 15, 12 <----> 12, 3
x % y != 0
x, y = y, x % y
as - alias - 别名
模块名.函数名 ---> 完全限定名(qualified name)
"""
from math import gcd, lcm
# def gcd(x, y):
# # 最大公约数
# value_min = min(x, y)
# for i in range(value_min, 0, -1):
# if x % i == 0 and y % i == 0:
# return i
# def gcd(x, y):
# # 最大公约数-辗转相除法/欧几里得算法
# while x % y != 0:
# x, y = y, x % y
# return y
#
#
# def lcm(x, y):
# return x * y // gcd(x, y)
print(gcd(18, 6))
print(lcm(15, 27))
二、调用函数
语法:
函数名(实参列表)
说明:
函数名 | 必须是已经定义过的函数的函数名(需要哪个函数的功能就调用哪个函数,就写哪个函数的函数名) |
( ) | 固定写法 |
实参列表 | 实参就是从函数外部传递到函数内部的数据。 调用函数的时候提供的实参必须和定义函数的形参对应 |
函数调用的过程:
第一步:回到函数定义的位置
第二步:传参(用实参给形参赋值) - 传参的时候必须保证每个参数都有值
第三步:执行函数体
第四步:执行完函数体确定函数返回值
第五步:回到函数调用的位置接着往后执行
"""
example07 - 调用自定义函数
"""
import random
import matplotlib.pyplot as plt
from example05 import mean, median, mode, ptp, std, var
def describe(data: list, func_names: list):
"""输出描述性统计信息"""
for name in func_names:
print(f'{name}: ', end='')
# Python 3.10+构造多分支结构 - math-case
match name.lower():
case 'mean' | '均值': print(mean(data))
case 'median' | '中位数': print(median(data))
case 'mode' | '众数': print(mode(data))
case 'max' | '最大值': print(max(data))
case 'min' | '最小值': print(min(data))
case 'ptp' | '极差': print(ptp(data))
case 'pvar' | '总体方差': print(var(data, ddof=0))
case 'var' | '方差' | '样本方差': print(var(data, ddof=1))
case 'pstd' | '总体标准差': print(std(data, ddof=0))
case 'std' | '标准差' | '样本标准差': print(std(data, ddof=1))
case 'vc' | '变异系数': print(std(data) / mean(data))
case _: print('无法完成对应的数据处理')
# double underscore ---> dunder
if __name__ == '__main__':
# 产生9个1-99均匀分布的随机数
nums = [random.randint(1, 100) for _ in range(10)]
print(nums)
print(sorted(nums))
describe(nums, ['MEAN', 'MEDIAN', 'VAR', 'xyz'])
print('-' * 50)
# 产生5000个均值为171标准差为6.5正态分布的随机数
heights = [int(random.normalvariate(171, 6.5)) for _ in range(5000)]
print(heights)
describe(heights, ['均值', '众数', '最大值', '最小值', '极差', '标准差'])
# 调用三方库的函数绘制直方图
plt.hist(heights, bins=15)
plt.show()
三、函数的参数
1.形参和实参
对于函数来说,形式参数简称形参,是指在定义函数时,定义的一个变量名。
下面的代码中,x、y、z 就是形参。
#定义函数
def foo(x, y, z):
print("x=", x)
print("y=", y)
print("z=", z)
#调用函数
foo(1,3,5) #此处的1,3,5是实参
2. 参数的分类
根据实参提供方式的不同可以将实参分为位置参数和关键字参数两种
1)位置参数
调用函数的时候实参对应的数据直接写,让实参和形参从位置上一一对应
2)关键字参数
调用函数时以'形参名=数据'的方式来提供实参,实参和形参的对应关系由数据前面的形参名来决定
注意:调用同一个函数时位置参数和关键字参数可以一起使用,但是位置参数必须在关键字参数的前面
"""
example04 - 按要求编写函数实现相应的功能
高内聚,低耦合 - high cohesion low coupling
1. 编写一个函数判断输入的正整数是不是质数(只能被1和自身整除的数)
2. 开发一个生成指定长度的随机验证码的函数(要求:随机验证码由英文大小写字母和数字构成)
3. 编写一个函数输入三角形三条边的长度,计算三角形的面积(注意:三条边的长度不一定能构成三角形)
"""
import math
import random
import string
# import time
# from captcha import Captcha
def is_prime(num):
"""
判断质数
:param num: 待判断的正整数
:return: 质数返回True,否则返回False
"""
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
def random_code(*, length=4):
"""
随机验证码
:param length: 验证码长度
:return: 大小写英文字母和数字构成的字符串
"""
all_chars = string.ascii_letters + string.digits
return ''.join(random.choices(all_chars, k=length))
def is_triangle(a, b, c):
"""判断三条边能否构成三角形"""
return a + b > c and b + c > a and a + c > b
# 写在*后面的参数叫做(命名)关键字参数(keyword arguments),调用函数传入参数时必须带上参数名
# 写在/前面的参数叫做强制位置参数(positional-only arguments),调用函数传入参数时只能对号入座不能带参数名
# 既不在/前面也不在*后面的参数叫做位置参数,位置参数在传参时可以给参数名也可以对号入座,关键字参数必须放在位置参数之后
def area(a, b, c):
"""用海伦公式计算三角形面积"""
if is_triangle(a, b, c):
h = (a + b + c) / 2
return math.sqrt(h * (h - a) * (h - b) * (h - c))
# 如果三条边的长度无效通过引发异常的方式提示函数的调用者
raise ValueError('三条边长无法构成三角形')
print(area(3, 1, 5))
3.参数默认值
定义函数的时候可以'形参名=数据'的方式给参数赋默认值,有默认值的形参在调用函数的时候可以不用传参。
注意:如果一个函数有的参数有默认值有的参数没有默认值,没有默认值的参数必须在有默认值参数的前面。
4.参数类型说明
定义函数的时候指定参数类型
1)没有默认值的参数 - 在形参名后面加 ': 类型名'
2)给参数赋默认值,默认值对应的数据类型就是参数的类型
def func(x: str, y: int, z: list, t='', m: int = 10):
pass
5.不定长参数
一个不定长参数可以同时接收若干实参
1)带 * 的不定长参数
在形参前面加 *,那么这个形参就会变成一个元组,它接受到的所有的实参会变成这个元组中的元素。
给带*号的不定长参数传参,只能使用位置参数。
注意:如果带*的参数前后都有定长参数,*前面的定长参数只能用位置参数传参,*后面的定长参数只能用关键字参数传参 (记)
2)带**的不定长参数
在形参前面加 **,那么这个形参就会变成一个字典,它接收到的所有实参会变成这个字典中的键值对(实参必须是关键字参数)
四、函数的返回值
1.定义
返回值就是函数内部传递到函数外部的数据。(函数内部产生的数据如果不通过返回值传递到函数外部,函数外部无法使用这个数据)
初学者建议:只要实现函数的功能产生了新的数据,都应该将新的数据作为返回值返回
1) 如何将数据从函数内部传递到函数外部(怎么确定函数返回值)
语法:return 需要返回的数据
注意:如果执行函数体的时候没有遇到return,函数的返回值就是 None(即函数也可以没有返回值,也就是没有return语句)
2) 如何在函数外部使用函数的返回值
函数调用表达式的值就是函数的返回值。
函数调用表达式: 调用函数的语句
2.关键字return
return只能在函数体中使用,有两个作用:
1)确定函数返回值
2)提前结束函数(执行函数体的时候,如果遇到return,函数直接结束)
五、变量的作用域
变量的作用域指的是变量定义以后可以使用的范围,根据变量作用域的不同可以将变量分为全局变量和局部变量
1)全局变量
没有定义在函数或者类里面的变量就是全局变量,全局变量的作用域是从定义开始到程序结束。
2)局部变量
定义在函数中的变量就是局部变量,局部变量的作用域是从定义开始到函数结束。
3)变量存储的方式
全局变量默认保存在全局栈区间(全局栈区间在程序结束后才会自动释放),
局部变量保存在函数对应的临时栈区间中(每次调用函数的时候系统都会自动为这个函数创建一个临时栈区间,函数调用结束的时候这个栈区间会自动释放)
global关键字可以修改局部变量的保存方式,让局部变量保存到全局栈区间中。(global 变量名)
Python程序搜索一个标识符的时候会按照下面的顺序进行搜索:
局部作用域 ---> 嵌套作用域 ---> 全局作用域 ---> 内置作用域
local embedded global built-in
global - 说明变量来自于全局作用域或者把一个局部变量放到全局作用域
nonlocal - 说明变量来自于嵌套作用域,如果嵌套作用域没有这个变量直接报错
x = 10 # 全局
def foo():
x = 100 # 局部
def bar():
x = 1000 # 嵌套
print(x)
bar()
print(x)
# print(y)
foo()
print(x)
六、匿名函数
匿名函数的本质还是函数,除了定义方式和普通函数不同,其他的和普通函数一样。
语法:
函数名 = lambda 形参列表:返回值(或者说是表达式)
注意:只有一句代码就可以实现功能的函数可以使用匿名函数
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 )) # 30
print ("相加后的值为 : ", sum( 20, 20 )) # 40
七、实参高阶函数
1.定义
参数是函数的函数就是实参高阶函数,给实参高阶函数传参的时候可以使用普通函数的函数名也可以使用匿名函数(内置函数都是高等函数)
注意:函数作为函数的参数时,不要跟圆括号调用函数,只是把函数的名字传入即可
2.常见实参高阶函数
max、min、sored、列表.sort、map、reduce、filter
max、min、sorted、列表.sort
max(容器, *, key:函数)
说明:
max(容器, 函数) - 按照函数制定的规则比较容器中元素的大小来获取最大值
函数的要求:1)有且只有一个参数,这个参数代表前面容器中的每个元素
2)有一个返回值,返回值就是比较对象
nums = [90, 78, 67, -92, 56, -71]
# 案例1:求nums中元素的最大值
print(max(nums, key=lambda item: item))
# 案例2:求nums中绝对值最大的元素
print(max(nums, key=lambda item: abs(item)))
# 案例3:求students中分数最高的元素
students = [
{'name': '张三', 'score': 38, 'age': 18},
{'name': '小明', 'score': 99, 'age': 25},
{'name': '李四', 'score': 89, 'age': 19},
{'name': '老王', 'score': 70, 'age': 30},
{'name': '小花', 'score': 65, 'age': 26}
]
print(max(students, key=lambda item: item['score']))
# 练习1:求nums中个位数最小的元素
nums = [90, 78, 67, 92, 56, 71]
print(min(nums, key=lambda item: item % 10))
# 练习2:将names中的名字按照长度最小到大到排序
names = ['李小名', '张三', '古丽热巴', '小花', '王大富']
names.sort(key=lambda item: len(item))
print(names)
# 练习3:求students中年龄最大的学生
students = [
{'name': '张三', 'score': 38, 'age': 18},
{'name': '小明', 'score': 99, 'age': 25},
{'name': '李四', 'score': 89, 'age': 19},
{'name': '老王', 'score': 70, 'age': 30},
{'name': '小花', 'score': 65, 'age': 26}
]
print(max(students, key=lambda item: item['age']))
map(映射)
基于原容器中的元素创建一个新的容器
1)map(函数, 容器) - 根据函数制定的规则将容器中的元素变成一个新的容器
函数的要求:a.有且只有一个参数,代表容器中的每个元素
b.需要一个返回值,返回值就是新容器中的元素
2)map(函数, 容器1, 容器2)
函数的要求:a.有且只有两个参数,分别代码后面两个容器中的元素
b.需要一个返回值,返回值就是新容器中的元素
3)map(函数, 容器1, 容器2, 容器3, ....)
# 案例1:创建一个新的容器,容器中的元素是nums中的元素乘以10的结果
nums = [19, 45, 1, 90, 67]
# [190, 450, 10, 900, 670]
result = map(lambda item: item * 10, nums)
print(list(result))
# 案例2:将nums1和nums2中的元素相加(相同位置上的元素相加)
nums1 = [10, 20, 30, 40, 50]
nums2 = [2, 3, 5, 3, 2]
result = list(map(lambda item1, item2: item1 + item2, nums1, nums2))
print(result)
# 练习1:提取nums中所有元素的个位数
nums = [19, 108, 44, 37, 82, 91, 86]
# [9, 8, 4, 7, 2, 1, 6]
result = map(lambda item: item % 10, nums)
print(list(result))
# 练习2:将scores中分数转换成对应及格关系True或者False
scores = [90, 56, 89, 99, 45, 34, 87]
# [True, False, True, True, False, False, True]
result = map(lambda item: item >= 60, scores)
print(list(result))
# 练习3:将scores中的分为转换成对应的通过关系
scores = [90, 56, 89, 99, 45, 34, 87]
# ['通过', '不通过', '通过', ....]
result = map(lambda item: '不通过' if item < 60 else '通过', scores)
print(list(result))
# 练习4:将nums1中是元素的个位数作为新容器中元素的十位数,nums2中元素的十位数作为新容器中元素的个位数
nums1 = [89, 34, 89, 20, 192, 93]
nums2 = [102, 89, 37, 82, 26, 1293]
# [90, 48, 93, 8, 22, 39]
result = map(lambda i1, i2: i1 % 10 * 10 + i2 // 10 % 10, nums1, nums2)
print(list(result))
result = map(lambda i1, i2: int(str(i1)[-1] + str(i2)[-2]), nums1, nums2)
print(list(result))
filter(过滤)
获取容器中所有满足条件的元素(筛选)
filter(规则, 容器数据:list...)
规则是指传入函数名来当规则的,即不加括号(不调用),而用filter来调用
函数的要求:1)有且只有一个参数,代码容器中的每个元素
2)需要一个返回值,返回值对应的就是提取条件
# 案例1:提取nums中所有的偶数
nums = [19, 108, 44, 37, 82, 91, 86]
result = filter(lambda item: item % 2 == 0, nums)
print(list(result)) # [108, 44, 82, 86]
# 提取nums中所有大于50的元素
result = filter(lambda item: item > 50, nums)
print(list(result))
# 案例2:提起scores中所有不及格的分数
scores = [90, 56, 89, 99, 45, 34, 87]
result = filter(lambda item: item < 60, scores)
print(list(result))
# 案例3:提取所有不及格的学生
students = [
{'name': '张三', 'score': 38, 'age': 18},
{'name': '小明', 'score': 99, 'age': 25},
{'name': '李四', 'score': 89, 'age': 19},
{'name': '老王', 'score': 50, 'age': 30},
{'name': '小花', 'score': 65, 'age': 26}
]
result = filter(lambda stu: stu['score'] < 60, students)
print(list(result))
# 练习1:提取字符串中所有的中文字符
str1 = 'sjs健康os02-技术上ss=2e 块'
result = filter(lambda char: '\u4e00' <= char <= '\u9fa5', str1)
print(''.join(result))
reduce(规约)
将容器中的元素合并成一个值
reduce(函数, 容器, 初始值) - 将容器中的元素按照函数制定的规则合并一个数据
函数的要求:1)有且只有两个参数,第一个指向初始值,第二个指向容器中的每个元素
2) 需要一个返回值,就是合并规则
注意:reduce使用的时候需要从functools中导入
from functools import reduce
nums = [2, 3, 4, 5, 1]
# 2+3+4+5+1
result = reduce(lambda i, item: i + item, nums, 0)
print(result)
# 2*3*4*5*1
result = reduce(lambda i, item: i * item, nums, 1)
print(result)
# 23451
# '' + '2' + '3' + '4' + '5' + '1' -> '' + str(2) + str(3) + ...
result = reduce(lambda i, item: i + str(item), nums, '')
print(result)
# 练习1: 求nums中所有元素的个位数的和
scores = [90, 56, 89, 99, 45, 34, 87]
result = reduce(lambda i, item: i + item % 10, scores, 0)
print(result)
# 练习2:求students中所有学生的总成绩
students = [
{'name': '张三', 'score': 38, 'age': 18},
{'name': '小明', 'score': 99, 'age': 25},
{'name': '李四', 'score': 89, 'age': 19},
{'name': '老王', 'score': 50, 'age': 30},
{'name': '小花', 'score': 65, 'age': 26}
]
result = reduce(lambda i, stu: i + stu['score'], students, 0)
print(result)
3.高阶函数和lambda函数的应用
sorted(list, key, reverse) - key参数可以传入一个函数指定比较元素大小的规则
# 按照股票价格从高到低输出股票代码和股票价格
prices = {
'AAPL': 191.88,
'GOOG': 1186.96,
'IBM': 149.24,
'ORCL': 48.44,
'ACN': 166.89,
'FB': 208.09,
'SYMC': 21.29
}
# sorted_keys = sorted(prices, key=prices.get, reverse=True)
sorted_keys = sorted(prices, key=lambda x: prices[x], reverse=True)
for key in sorted_keys:
print(f'{key}: {prices[key]}')
# 按照字符串的长度从短到长对列表进行排序
words = ['apple', 'banana', 'zoo', 'no', 'watermelon', 'internationalization', 'over']
print(sorted(words, key=len, reverse=True))
八、模块
1.定义与应用
python中的一个.py文件就是一个模块。
应用前提:
被使用的模块的模块名必须符合变量名的要求;并且导入后才能使用
2.导入模块
1) import 模块名
导入指定模块,导入后可以通过'模块名.xxx'的方式去使用这个模块中的所有内容
2) from 模块名 import 内容1, 内容2, 内容3, ...
导入指定模块,导入后可以直接使用指定的内容
3) from 模块名 import *
导入指定模块,导入后可以直接使用模块中所有内容
4) import 模块名 as 新模块名
对导入的模块进行重命名
5) from 模块名 import 内容 as 新内容名
九、装饰器函数
装饰器:用一个函数去装饰另外一个函数并为其提供额外的能力(跟正常业务没有必然联系的功能)。
装饰器实现了设计模式中的代理模式。
装饰器函数的参数是被装饰的函数,装饰器函数的返回值是带有装饰功能的函数(不仅要执行原函数还要执行额外的操作)。
很显然,装饰器函数是高阶函数的用法。
语法:
def decorator_function(original_function):
def wrapper(*args, **kwargs):
# 这里是在调用原始函数前添加的新功能
before_call_code()
result = original_function(*args, **kwargs)
# 这里是在调用原始函数后添加的新功能
after_call_code()
return result
return wrapper
# 使用装饰器
@decorator_function
def target_function(arg1, arg2):
pass # 原始函数的实现
案例1:
from functools import wraps
# 装饰器函数
def titlize_string(func):
# *args可变参数,**kwargs可变关键字参数
# *args - 将传入的位置参数打包成一个元组
# **kwargs - 将传入的关键字参数打包成字典
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
# if type(result) == str:
if isinstance(result, str):
n = len(result)
result = '~' * n + '\n' + result.title() + '\n' + '~' * n
return result
return wrapper
@titlize_string
def f1():
return 'hello, world!'
# f1 = titlize_string(f1)
print(f1())
案例2:
import random
import time
from functools import wraps
def random_delay(lower, upper):
"""随机延迟指定范围的秒数"""
duration = random.random() * (upper - lower) + lower
time.sleep(duration)
def record_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'Time elapsed: {end - start:.3f}s')
return result
return wrapper
# download = record_time(download)
@record_time
def download(filename):
"""下载文件"""
print(f'开始下载"{filename}".')
random_delay(3, 8)
print(f'"{filename}"下载完成.')
# upload = record_time(upload)
@record_time
def upload(filename):
"""上传文件"""
print(f'开始上传"{filename}".')
random_delay(5, 10)
print(f'"{filename}"上传完成.')
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
# 取消装饰器(执行原函数 - 被装饰前的download函数)
download.__wrapped__('MySQL从删库到跑路.avi')
十、函数的递归调用
函数的递归(recursion)调用
1. 递归公式 - 第n次跟第n-1、n-2、……次之间的关系
2. 收敛条件 - 什么时候停止递归
from functools import lru_cache
def fac(num):
if num == 0:
return 1
return num * fac(num - 1)
# lru - least recently used - 缓存置换策略
# cache - 缓存
@lru_cache(maxsize=128)
def fib(n):
if n in (1, 2):
return 1
return fib(n - 1) + fib(n - 2)
def main():
for n in range(0, 10):
print(f'{n}! = {fac(n)}')
print('-' * 20)
for n in range(1, 121):
print(f'{n:0>3d}: {fib(n)}')
if __name__ == '__main__':
main()
案例:
"""
example15 - 一个小孩爬楼梯,一次可以爬一个、两个或三个台阶,
问:走完10个台阶一共有多少种走法?
"""
# from functools import lru_cache
# @lru_cache(maxsize=64)
# def climb(n):
# if n < 0:
# return 0
# elif n == 0:
# return 1
# return climb(n - 1) + climb(n - 2) + climb(n - 3)
# 调用函数会得到一个生成器对象
def climb(n):
a, b, c, d = 0, 1, 2, 4
for _ in range(n):
a, b, c, d = b, c, d, b + c + d
yield a
# # 生成器对象
# gen = climb(30)
# # 通过next函数从生成器中获取数据
# # print(next(gen))
# # 将生成器直接放到for-in循环中
# for value in gen:
# print(value)
#
# print('~' * 30)
#
# # 循环体一次都不会执行
# # 因为生成器的数据已经被取走无法再提供任何数据
# for value in gen:
# print(value)
# generator expression - 生成器表达式
x = (i ** 2 for i in range(1, 10))
print(x) # 生成器对象 - 惰性计算(拿一个算一个给一个)
print(type(x)) # generator
print(next(x))
print(next(x))
print(next(x))
print(next(x))
print('~' * 30)
for v in x:
print(v)
十一、面向对象编程入门
对象:接收消息的实体(一组数据和处理数据的方法构成的整体)- 具体
~ 一切皆为对象
~ 对象都有属性(数据)和行为(函数)
~ 对象一定属于某个类
类:创建对象的蓝图和模板(行为相同的对象可以归纳成一个类)- 抽象
~ 数据抽象 - 找到一类对象共同的属性(静态特征)
~ 行为抽象 - 找到一类对象共同的行为(动态特征)
流程:定义类 ----> 创建对象 ----> 给对象发消息
继承:从已有的类创建新类的过程,提供继承信息的叫做父类(超类),得到继承信息的叫做子类(派生类)is-a关系
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self, food_name):
print(f'{self.name}正在吃{food_name}')
def sleep(self):
print(f'{self.name}正在睡觉')
class Student(Person):
def study(self, course_name):
print(f'{self.name}正在学习{course_name}')
class Teacher(Person):
def __init__(self, name, age, title):
super().__init__(name, age)
self.title = title
def teach(self, course_name):
print(f'{self.name}{self.title}正在讲授{course_name}')
class Programmer(Person):
def __init__(self, name, age, level):
super().__init__(name, age)
self.level = level
# override - 方法重写 - 子类把父类已有的方法重新实现一遍
def sleep(self):
# super().sleep() # 调用父类的sleep方法
print(f'{self.name}每天只睡两个小时')
def write_code(self, language):
print(f'{self.name}正在用{language}写屎山代码')
class DataAnalyst(Person):
def __init__(self, name, age, year):
super().__init__(name, age)
self.year = year
def analyse_data(self):
print(f'{self.name}正在从数据中发掘商业价值')
# 2. 创建对象 - 构造器语法
stu1 = Student('骆昊', 44)
stu2 = Student('王大锤', 20)
teacher = Teacher('张三丰', 100, '教授')
# 3. 给对象发消息
stu1.eat('方便面')
stu2.study('Python程序设计')
stu2.sleep()
teacher.sleep()
teacher.teach('太极拳')
programmer = Programmer('狄仁杰', 38, 10)
programmer.sleep()
programmer.write_code('Java')
上面先定义了一个Person()的类,在Person类里面定义了相应的属性和方法,然后又通过继承Person类定义了Student(Person)、Teacher(Person)等类,在类里又有相应只属于自己类的方法,同时继承了Person的类又一定包含Person类里的方法和属性。
练习1:
定义一个时钟类,有时、分、秒三个属性,同时有显示、走针两种方法。
class Clock:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
def show(self, display_second=True):
result = f'{self.hour:0>2d}:{self.minute:0>2d}'
if display_second:
result += f':{self.second:0>2d}'
return result
def run(self):
self.second += 1
if self.second == 60:
self.second = 0
self.minute += 1
if self.minute == 60:
self.minute = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
clock = Clock(23, 58, 58)
while True:
# windows
# os.system('cls')
# macOS
os.system('clear')
print(clock.show())
clock.run()
time.sleep(1)
练习2:
"""
1. 定义一个类描述平面上的点,提供移动点和计算到另一个点的距离的方法
2. 定义一个类描述平面上的线段,提供计算线段长度和斜率的方法
思考:这个类之间又没有什么关系??? has-a关系 - 关联关系
"""
import math
class Point:
"""平面上的点"""
def __init__(self, x, y):
self.x = x
self.y = y
def move_to(self, x, y):
"""移动到指定的位置"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""移动指定的偏移量"""
self.x += dx
self.y += dy
def distance(self, other):
"""计算到另一个点的距离"""
return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
class Line:
"""平面上的线段"""
def __init__(self, start: Point, end: Point):
self.start = start
self.end = end
@property
def length(self):
"""长度"""
return self.start.distance(self.end)
@property
def slope(self):
"""斜率"""
return (self.end.y - self.start.y) / (self.end.x - self.start.x)
p1 = Point(2, 3)
p2 = Point(-5, 6)
print(p1.distance(p2))
p2.move_by(10, 1)
print(p1.distance(p2))
line = Line(p1, p2)
print(line.length)
print(line.slope)
面向对象编程应用:
"""
example20 - 面向对象编程案例:扑克游戏 - 牌、扑克、玩家
类:创建对象的蓝图和模板(对象的共性抽取出来)
~ 数据抽象 - 属性 - 静态特征
~ 行为抽象 - 方法 - 动态特征
对象:类实例化以后就是对象,对象是可以接收消息的实体
~ 一切皆为对象
~ 每个对象都是独一无二的
~ 对象都有属性和行为
~ 对象都属于某个类
三大支柱:
~ 封装(encapsulation):隐藏实现细节,暴露简单的调用接口
~ 继承(inheritance):从已有的类创建新类的过程,提供继承信息的叫父类,得到继承信息的叫子类
~ 多态(polymorphism):子类可以重写(override)父类的方法,不同的子类在接收到相同的消息时会表现出不同的行为
Spade / Heart / Club / Diamond
面试官:讲一讲Python中的迭代器和生成器,二者是什么关系?
参考答案:Python中的迭代器是实现了迭代器协议的对象,迭代器协议是两个魔术方法,一个叫__iter__,一个叫__next__。
如果一个类里面有__next__和__iter__两个魔术方法,那么该类的对象是迭代器对象。
生成器是迭代器语法简化升级之后的版本,生成器就是迭代器,只不过创建生成器的语法更加简单。
"""
import itertools
import random
class Card:
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
# less than
def __lt__(self, other):
if self.face == other.face:
return self.suite < other.suite
return self.face < other.face
# representation
def __repr__(self):
suites = ['♠️', '❤️', '♣️', '♦️']
faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
return f'{suites[self.suite]}{faces[self.face]}'
class Poker:
"""扑克"""
def __init__(self):
self.position = 0
self.cards = [Card(suite, face)
for suite, face in itertools.product(range(4), range(1, 14))]
def shuffle(self):
"""洗牌"""
self.position = 0
random.shuffle(self.cards)
# self.cards = random.sample(self.cards, k=len(self.cards))
def __iter__(self):
return self
def __next__(self):
if self.has_more():
card = self.cards[self.position]
self.position += 1
return card
raise StopIteration()
def has_more(self):
"""有没有牌可以发出"""
return self.position < len(self.cards)
class Player:
"""玩家"""
def __init__(self, nickname):
self.nickname = nickname
self.cards = []
def get_one(self, card):
"""摸牌"""
self.cards.append(card)
def arrange(self):
"""整理"""
self.cards.sort()
def main():
"""入口函数"""
poker = Poker()
poker.shuffle()
players = [Player('骆昊'), Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(3):
for player in players:
card = next(poker)
player.get_one(card)
for player in players:
player.arrange()
print(player.nickname, end=': ')
print(player.cards)
if __name__ == '__main__':
main()
综合案例
"""
example05 - 假设样本数据保存一个列表中,设计获取样本数据描述性统计信息的函数
经验:函数的设计一定要做到无副作用!!!
描述性统计信息 - 集中趋势、离散程度、分布规律
~ 集中趋势:均值、中位数、众数
~ 离散程度:极值、极差(全距)、方差、标准差、变异系数
~ 分布规律:直方图、偏态系数、峰度系数
1. 均值
2. 中位数
3. 极差
4. 样本方差
5. 样本标准差
"""
import math
import random
import statistics as stats
def mean(data: list):
"""算术平均"""
return sum(data) / len(data)
def median(data: list):
"""中位数"""
data, n = sorted(data), len(data)
if n % 2 == 0:
return mean(data[n // 2 - 1: n // 2 + 1])
return data[n // 2]
def mode(data: list):
"""众数"""
results = {}
for x_i in data:
results[x_i] = results.get(x_i, 0) + 1
return max(results, key=results.get)
def ptp(data: list):
"""极差(全距)"""
return max(data) - min(data)
# delta degree of freedom - 损失的自由度
def var(data: list, *, ddof=1) -> float:
"""
variance 方差
ddof = 1,求样本方差,函数默认为1
ddof = 0,求总体方差
ddof为关键字参数
"""
x_bar, n = mean(data), len(data)
return sum([(x_i - x_bar) ** 2 for x_i in data]) / (n - ddof)
def std(data: list, *, ddof=1) -> float:
"""标准差"""
return math.sqrt(var(data, ddof=ddof))
# __name__ 是模块的一个隐藏属性,代表这个模块的名字
# 如果你直接调用Python解释器运行某个模块,它的名字一定叫 __main__
# 如果你在其他模块导入这个模块,这个时候它的名字就是文件名(example05)
if __name__ == '__main__':
random.seed(1)
nums = [random.randint(1, 100) for _ in range(10)]
print(nums)
print(sorted(nums))
print('均值:', mean(nums))
print('均值:', stats.mean(nums))
print('中位数:', median(nums))
print('中位数:', stats.median(nums))
print('众数:', mode(nums))
print('众数:', stats.mode(nums))
print('极差:', ptp(nums))
print('样本方差:', var(nums))
print('样本方差:', stats.variance(nums))
print('样本方差:', var(nums, ddof=0))
print('样本方差:', stats.pvariance(nums))
print('样本标准差:', std(nums))
print('样本标准差:', stats.stdev(nums))
print('总体标准差:', std(nums, ddof=0))
print('总体标准差:', stats.pstdev(nums))
"""
example10 - 练习:设计一个函数,传入两组数据,计算两组数据的相关系数
"""
import math
import matplotlib.pyplot as plt
from example05 import mean
def corr(X, Y):
"""计算两组数据的皮尔逊相关系数"""
x_bar, y_bar = mean(X), mean(Y)
temp1 = sum([(x_i - x_bar) * (y_i - y_bar) for x_i, y_i in zip(X, Y)])
temp2 = math.sqrt(sum([(x_i - x_bar) ** 2 for x_i in X]))
temp3 = math.sqrt(sum([(y_i - y_bar) ** 2 for y_i in Y]))
return temp1 / (temp2 * temp3)
if __name__ == '__main__':
incomes = [25000, 15850, 15500, 20500, 22000, 20010, 26050, 12500, 18500, 27300,
13300, 8300, 23320, 5250, 5800, 9100, 4800, 16000, 28500, 32000,
31300, 10800, 6750, 6020, 15000, 30020, 3200, 17300, 8835, 3500]
payments = [2599, 1400, 1120, 2560, 1900, 1200, 2320, 800, 1650, 2200,
1200, 580, 1885, 600, 400, 800, 420, 1380, 1980, 3999,
3800, 725, 520, 420, 980, 4020, 350, 1500, 560, 500]
# Excel - CORREL(A1:A30, B1:B30)函数
print(corr(incomes, payments))
# 绘制散点图 - 定性分析两组数据的相关性
plt.scatter(incomes, payments)
plt.show()