学习Python第三周总结
一、函数和模块
1.1 函数的定义
Python中的函数的自变量称为函数的参数,而因变量称为函数的返回值
在Python中可以使用def
关键字来定义函数,命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,就是我们刚才说到的函数的自变量,而函数执行完成后我们会通过return
关键字来返回函数的执行结果,就是我们刚才说的函数的因变量。一个函数要执行的代码块(要做的事情)也是通过缩进的方式来表示的,跟之前分支和循环结构的代码块是一样的。
1.2函数的参数
如果函数中没有return
语句,那么函数默认返回代表空值的None
。另外,在定义函数时,函数也可以没有自变量,但是函数名后面的圆括号是必须有的。
-
位置参数——在没有特殊处理的情况下,函数的参数都是位置参数
-
关键字参数——我们在设计函数时,如果既不知道调用者会传入的参数个数,也不知道调用者会不会指定参数名,那么同时使用可变参数和关键字参数。关键字参数会将传入的带参数名的参数组装成一个字典,参数名就是字典中键值对的键,而参数值就是字典中键值对的值
重点提醒:关键字参数一定要在位置参数的后面。
代码例子如下:
# *args--->可变参数--->可以接收零个或任意多个位置参数--->将位置参数打包成元组
# **kwargs--->可以接收零个或任意多个关键字参数--->将所有的关键字参数打包成一个字典
def add(*args, **kwargs):
print(args, type(args))
# print(kwargs, type(kwargs))
total = 0
for arg in args:
if type(arg) in (int, float):
total += arg
for value in kwargs.values():
if type(value) in (int, float):
total += value
return total
print(add(1, 2, 4, a=3))
def mul(*args, **kwargs):
# print(args, type(args))
# print(kwargs, type(kwargs))
total = 1
for arg in args:
if type(arg) in (int, float):
total *= arg
for value in kwargs.values():
if type(value) in (int, float):
total *= value
return total
print(mul(1, 2, 4, a=3))
函数的例题一:
玩家摇两颗骰子,如果第一次摇出了7点或11点,玩家胜;如果摇出了2点、3点、12点,庄家胜;
如果摇出了其他的点数,游戏继续,玩家重新摇色子;如果玩家摇出了第一次摇的点数,玩家胜;
如果玩家摇出了7点,庄家胜;如果玩家摇出其他点数,游戏继续,玩家重新摇色子,直到分出胜负。
游戏开始之前,玩家有1000元的初始资金,玩家可以下注,赢了获得下注的金额,输了就扣除下注的金额,
游戏结束的条件是玩家把钱输光。
import random
def roll_dice(num):
"""
摇骰子
:param num: 骰子的数量
:return: 摇出的点数
"""
total = 0
for _ in range(num):
total += random.randrange(1, 7)
return total
def win():
global x
print('玩家胜')
x += z
def lose():
global x
print('庄家胜')
x -= z
x = 1000
while x > 0:
print(f'玩家总资产为{x}元')
z = 0
while z <= 0 or z > x:
z = int(input('请下注'))
m = roll_dice(3)
print(f'玩家摇出了{m}点')
if m in (7, 11):
win()
elif m in (2, 3, 12):
lose()
else:
while True:
n = roll_dice(3)
print(f'玩家摇出了{n}点')
if n == m:
win()
break
elif n == 7:
lose()
break
print('玩家已破产,游戏结束')
例题二:
# 求阶乘
def fac(num):
"""求阶乘"""
result = 1
for i in range(2, num + 1):
result *= i
return result
m = int(input('m = '))
n = int(input('n = '))
print(fac(m) // fac(n) // fac(m - n))
1.3标准库中的模块和函数
Python标准库中提供了大量的模块和函数来简化我们的开发工作,random
模块可以提供生成随机数和进行随机抽样的函数;而time
模块则可以提供和时间操作相关的函数。在Python标准库中的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 。 |
二、高阶函数
Python中的函数是一等函数,但是函数本身也可以作为函数的参数或返回值,而且还可以赋值给变量。这就是所谓的高阶函数。通常使用高阶函数可以实现对原有函数的解藕合操作。
Lambda函数——没有名字而且一句话就能写完的函数,唯一的表达式就是函数的返回值。也被称为匿名函数
例如:
# fn ---> 一个实现二元运算的函数(可以做任意的二元运算)
def calc(*args, op, init_value=0, **kwargs):
total = init_value
for arg in args:
if type(arg) in (int, float):
total = op(total, arg)
for value in kwargs.values():
if type(value) in (int, float):
total = op(total, value)
return total
# def add(x, y):
# return x + y
#
#
# def mul(x, y):
# return x * y
# print(calc(11, 22, 33, 44, op=add))
print(calc(11, 22, 33, 44, op=lambda x, y: x + y))
# print(calc(11, 22, 33, 44, op=mul, init_value=1))
print(calc(11, 22, 33, 44, init_value=1, op=lambda x, y: x * y))
fn = lambda x, y: x - y
print(calc(11, 22, 33, 44, init_value=100, op=fn))
三、递归调用
函数如果直接或者间接的调用了自身,那么这种调用就被称为递归调用。
递归函数的两个要点:
- 递归公式(第n次跟第n-1次的关系)
- 收敛条件(什么时候停止递归调用)
# 不管函数是调用别的函数,还是调用自身,一定要做到快速收敛。
# 在比较有限的调用次数内能够结束,而不是无限制的调用函数。
# 如果一个函数(通常指递归调用的函数)不能够快速收敛,那么就很有可能产生下面的错误
# RecursionError: maximum recursion depth exceeded
# 最终导致程序的崩溃。
def fac(num: int) -> int:
"""求阶乘(递归写法)"""
if num == 0:
return 1
return num * fac(num - 1)
if __name__ == '__main__':
# return 5 * fac(4)
# return 4 * fac(3)
# return 3 * fac(2)
# return 2 * fac(1)
# return 1 * fac(0)
# return 1
print(fac(5))
def fib(n):
if n in (1, 2):
return 1
return fib(n - 1) + fib(n - 2)
if __name__ == '__main__':
for i in range(1, 21):
print(i, fib(i))
例题一:编写实现对列表元素进行冒泡排序的函数
def bubble_sort(items, ascending=True, gt=lambda x, y: x > y):
"""
冒泡排序
:param items: 待排序的列表
:param ascending:
:param gt:
:return:
"""
items = items[:]
for i in range(1, len(items)):
swapped = False
for j in range(0, len(items) - i):
if gt(items[j], items[j + 1]):
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if not swapped:
break
if not ascending:
items = items[::-1]
return items
if __name__ == '__main__':
nums = [35, 96, 12, 7, 20, 8, 15]
print(bubble_sort(nums, ascending=False))
print(nums)
例题二:编写实现查找列表元素的函数:
def seq_search(items: list, key) -> int:
"""
顺序查找
:param items: 待查找的元素
:param key: 要找的元素
:return: 找到了返回元素的索引,找不到返回-1
"""
for index, item in enumerate(items):
if item == key:
return index
return -1
def bin_search(items: list, key) -> int:
"""
二分查找
:param items:待查找的元素(元素有序)
:param key: 要找的元素
:return: 找到了返回元素的索引,找不到返回-1
"""
start, end = 0, len(items) - 1
while start <= end:
mid = (start + end) // 2
if key > items[mid]:
start = mid + 1
elif key < items[mid]:
end = mid - 1
else:
return mid
return -1
if __name__ == '__main__':
nums1 = [5, 4, 7, 20, 8, 15]
print(seq_search(nums1, 20))
print('_' * 20)
nums2 = [4, 5, 7, 8, 15, 20]
print(bin_search(nums2, 15))
print(bin_search(nums2, 45))
四、面向对象编程
1、定义
面向对象编程是一种编程范式(程序设计的方法论)。
如果要用一句话来概括面向对象编程,我认为下面的说法是相当精准的。
面向对象编程:把一组数据和处理数据的方法组成对象,把行为相同的对象归纳为类,通过封装隐藏对象的内部细节,通过继承实现类的特化和泛化,通过多态实现基于对象类型的动态分派。
对象:对象是可以接收消息的实体,面向对象编程就行通过给对象发消息达到解决问题的目标。对象 = 数据 + 函数(方法),即对象将数据和操作数据的函数从逻辑上变成了一个整体。
对象有以下四个特征:
- 一切皆为对象
- 对象都有属性和行为
- 每个对象都是独一无二的
- 对象一定属于某个类
类:将有共同特征(静态特征和动态特征)的对象的共同特征抽取出来之后得到的一个抽象概念。简单的说,类是对象的蓝图(模板),有了类才能够创建出这种类型的对象。
2、创建和使用对象
1、定义类:类的命名使用驼峰命名法(每个单词首字母大写)
数据抽象:找到和对象相关的静态特征(属性)
行为抽象:找到和对象相关的动态特征(方法)
2、创建对象
3、给对象发消息
例如:
# 第一步:定义类
class Student:
"""学生"""
# 数据抽象(属性)
def __init__(self, name, age):
self.name = name
self.age = age
# 行为抽象(方法)
def eat(self, name):
"""吃饭"""
print(f'{self.name}正在吃{name}')
def study(self, course_name):
"""
学习
:param course_name: 课程名字
"""
print(f'{self.name}正在学习{course_name}')
def play(self, game_name):
"""
玩耍
:param game_name: 游戏名字
"""
print(f'{self.name}正在玩{game_name}')
# 第二步:创建对象--->构造器语法--->类名(..., ...)
Stu1 = Student('黄小宇', 18)
Stu2 = Student('周大大', 21)
# Student.study(stu1, 'Python程序设计')
# 第三步:给对象发消息(调用对象的方法)
Stu1.eat('螺蛳粉')
Stu2.study('Python程序设计')
3、魔术方法
-
魔术方法(魔法方法)—> 有特殊用途和意义的方法
- init —> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
- str —> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
-
repr —> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
—> representation - lt —> 在使用 < 运算符比较两个对象大小时会自动调用
如果一个变量的取值只有有限个选项,可以考虑使用枚举类型。
Python中没有定义枚举类型的语法,但是可以通过继承Enum类来实现枚举类型。
结论1:枚举类型是定义符号常量的最佳选择!!!
结论2:符号常量(有意义的名字)总是优于字面常量!!
4、继承和多态
继承:对已有的类进行扩展创建出新的类,这个过程就叫继承。
提供继承信息的类叫做父类(超类、基类),得到继承信息的类称为子类(派生类)。
继承是实现代码复用的一种手段,但是千万不要滥用继承。
继承是一种is-a关系。
a student is a person.
a teacher is a person.
a programmer is a person.
子类直接从父类继承公共的属性和行为,再添加自己特有的属性和行为,
所以子类一定是比父类更强大的,任何时候都可以用子类对象去替代父类对象。
Python中的继承允许多重继承,一个类可以有一个或多个父类。
如果不是必须使用多重继承的场景下,请尽量使用单一继承。
5、两个类之间有哪些关系
~ is-a关系:继承—>从一个类派生出另一个类
a student is a person
~ has-a关系:关联—>把一个类的对象作为另外一个类的对象的属性
a person has an identity card
——(普通)关联
-
—— 强关联:整体和部分的关联,聚合和合成
-
use-a关系:依赖—>一个类的对象作为另外一个类的方法的参数或返回值
a person use a vehicle(交通工具)
6、面向对象编程的四大支柱
1、抽象(abstraction):提取共性(定义类就是一个抽象过程,需要做数据抽象和行为抽象)
2、封装(encapsulation):把数据和操作数据的函数从逻辑上组成一个整体(对象)
—>隐藏实现细节,暴露简单的调用接口
3、继承(inheritance):扩展已有的类创建新类,实现对已有类的代码复用
4、多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。
—>方法重写(override):子类对父类已有的方法,重新给出自己的实现版本
在重写方法的过程中,不同的子类可以对父类的同一个方法给出不同的实现版本,
那么该方法在运行时就会表现出多态性
7 经典例题
例题一:
-
现在有三类员工:
- 部门经理:固定月薪,15000
- 程序员:计时结算月薪,
-
销售员:底薪 + 提成,底薪1800,销售额 %5提成
写一个面向对象的编程实现工资的计算
from abc import abstractmethod
class Employee:
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
pass
class Manager(Employee):
def get_salary(self):
return 15000
class Programmer(Employee):
def __init__(self, name):
super().__init__(name)
self.working_hour = 0
def get_salary(self):
return 200 * self.working_hour
class Salesman(Employee):
def __init__(self, name):
super().__init__(name)
self.sales = 0
def get_salary(self):
return 1800 + 0.05 * self.sales
def main():
emps = [Manager('刘备'), Programmer('诸葛亮'), Salesman('关羽')]
for emp in emps:
if type(emp) == Programmer:
emp.working_hour = int(input(f'请输入{emp.name}本月工作时长'))
elif type(emp) == Salesman:
emp.sales = int(input(f'请输入{emp.name}本月销售额'))
print(f'{emp.name}本月工资:{emp.get_salary()}元')
if __name__ == '__main__':
main()
例题二:
# 创建一个时钟对象(可以显示时/分/秒),让它运转起来
import time
class Clock:
# 数据抽象
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.min = minute
self.sec = second
def show(self):
"""显示时间"""
return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
# 行为抽象
def run(self):
"""走字"""
self.sec += 1
if self.sec == 60:
self.sec = 0
self.min += 1
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
if __name__ == '__main__':
clock = Clock()
while True:
print(clock.show())
time.sleep(1)
clock.run()
五、总结
Python中的函数可以使用可变参数*args
和关键字参数**kwargs
来接收任意数量的参数,而且传入参数时可以带上参数名也可以没有参数名,可变参数会被处理成一个元组,而关键字参数会被处理成一个字典。Python中的函数也是对象,所以函数可以作为函数的参数和返回值,也就是说,在Python中我们可以使用高阶函数。如果我们要定义的函数非常简单,只有一行代码且不需要名字,可以将函数写成Lambda函数(匿名函数)的形式。
面向对象编程是一种非常流行的编程范式,除此之外还有指令式编程、函数式编程等编程范式。由于现实世界是由对象构成的,而对象是可以接收消息的实体,所以面向对象编程更符合人类正常的思维习惯。类是抽象的,对象是具体的,有了类就能创建对象,有了对象就可以接收消息,这就是面向对象编程的基础。定义类的过程是一个抽象的过程,找到对象公共的属性属于数据抽象,找到对象公共的方法属于行为抽象。
这周的函数和面向对象编程是重点,也是难点。需要反复的去巩固练习,需要用时间去练习,以此来让我们能够熟练的掌握。