Python快速入门

1 Python语言入门
1.1 Hello,world!
这是一个示例Python脚本.
# 定义了一个函数,名为print_hi# def <函数名>(<形参列表>)# <函数体>def print_hi(name): # 函数体 # 在这里,printf语句中,有一个模板字符串{<变量名>}, # printf会自动在该函数体中使用相应的变量替换这个模板 # 注意,在字符串前,有一个f,这代表后面这个字符串是一个模板字符串. print(f"Hello,{name}")
if __name__ == '__main__': print_hi('world')1.2 变量声明
在Python中没有真正的私有变量,若要使用私有变量,只能使用类似于约定的方式声明.
声明私有变量: _变量名. 以单下划线开头.
2 语法
2.1 数据类型
变量名使用全小写字母+下划线的方式命名
PS: Python不支持常量.
常用方法
address = id(<变量名>) # 获取变量的逻辑地址print(address)数字类型
intVar = 10 #intfloatVar = 3.14 #float
print(intVar + 1)print(floatVar / 3)精度丢失问题及其解决方案
from decimal import Decimal
z = 0.1 + 0.2print(z) # 期待0.3,实际0.30000000000000004,因为这里有精度丢失问题# 使用Decimal将0.1和0.2的字面量包裹起来.x = Decimal("0.1") # 传入的0.1是一个字符串的字面量y = Decimal("0.2") # 同上z = x + yprint(z)布尔类型(boolean)
# 在这里,有必要添加一个知识点,value若为以下内容,python在进行逻辑比较时,会认为其为False, 其它情况为True.# -None,"",0,[],{},falseboolVar = True# 推荐: 直接使用布尔值if boolVar: print("为真")
if not boolVar: print("为假")
# 也可以使用 == (更明确)if boolVar == True: print("为真")
# 可以使用,但有隐患,不推荐使用,具体原因见运算符章节if boolVar is True: print("为真")
# 这是个坑,bool只会判断是否为空串.虽然里面传入了False字面量,但这不是真正的False.boolVar = bool("False")print(boolVar)
emptyTuple = () # 创建空元组方式一emptyTuple = tuple() # 创建空元组方式二boolVar = bool(emptyTuple)print(boolVar) # False字符串(string)
字符串类型的声明方式主要有三种,单引号,双引号和三引号,三引号为三个单引号或三个双引号组成.
单引号与双引号声明的字符串,没有任何区别,仅仅只是定界符不同.
三引号声明的字符串,支持多行模式,即在代码中是什么样的,在程序运行时就是什么样的.
字符串是不可变的,尝试修改会报错,如果想修改字符串,会导致字符串变量的地址修改,即释放了旧字符串,新建了一个新字符串并由字符串变量指向它.
strVar1 = "hello"strVar2 = 'world'strVar3 = """多行字符串,也常用于函数和类的文档注释"""
print(f"{strVar1 + strVar2 + strVar3}")字符串切片
# 字符串切片(slice)# [<起始下标>:<终止下标>:<步长>] # 从起始下标开始,终于终止下标,但不包括终止下标.# 另外,如果起始下标为0,或终止下标为 len(list),那么可以省略str_var2 = str_var[1:2] # 取出下标从1开始,取到2之前,不包括2,也就是只有1# 实际上,字符切片完全可以套在列表上使用.# 当然起始下标和终止下标也可以为负数,一般不用.# 取负数时,从右边开始取,最右边元素下标为-1,第二个为-2,以此类推.# 有一个反转字符串的方法较为常用,如下,记住即可. [::-1],双冒号,后跟-1.str_var_reversed = str_var[::-1]
# 切片示例s = "abcdefghij"print(s[0:5]) # 'abcde' (索引0到4)print(s[2:8:2]) # 'ceg' (从索引2开始,到7结束,步长为2)
# 省略start,默认从0开始print(s[:5]) # 'abcde'
# 省略stop,默认到字符串末尾print(s[5:]) # 'fghij'
# 省略step,默认步长为1print(s[::]) # 完整取出
# 使用负数索引,从后往前数,很少使用# 当使用负数索引时,索引下标从-1开始,表示最后一个元素.print(s[-1]) # 'j' (最后一个字符)print(s[-5:]) # 'fghij' (倒数5个字符)print(s[::-1]) # 'jihgfedcba' (字符串反转,步长为-1),这个方法很常用字符串常用方法
# 获取字符串长度,字符长度,而非字节长度.即有几个字符.str_var = "Hello kulve"length = len(str_var)print(length)
# 注意:所有修改字符串的方法都会返回一个新的字符串,原字符串不变text = " Python is a programming language. Welcome to using Python. "print("--- 去除空白 ---")print(text.strip()) # 去除两端空白print(text.lstrip()) # 去除左侧空白print(text.rstrip()) # 去除右侧空白print(text.strip().strip(".")) # 允许给定一个字符,然后将其去掉.
text = "Python is a programming language. Welcome to using Python."
print("--- 大小写转换 ---")print(text.lower()) # 转换为小写print(text.upper()) # 转换为大写print(text.title()) # 每个单词首字母大写print(text.capitalize()) # 仅第一个字符大写
print("--- 查找 ---")# find() 找到返回索引,找不到返回 -1print(text.find("language")) # language在text中的索引位置print(text.find("CRAZYTHURSDAYVME50")) # -1# index(<值>) 类似find,但找不到会抛出ValueError异常print(text.index("Python")) # 0
print("--- 统计出现次数 ---")# count(<值>) 统计子串出现的次数print(text.count("Python")) # 2
print("--- 替换 ---")# replace(<被替换字符串>,<替换值>) 替换print(text.replace("Python", "Java"))# Java is a programming language. Welcome to using Java.
print("--- 分割 ---")words = text.split() # 默认按空白字符分割,返回列表print(words)# ['Python', 'is', 'a', 'programming', 'language.', 'Welcome', 'to', 'using', 'Python.']
csv_var = "apple,banana,cherry"fruits = csv_var.split(',') # 按指定分隔符分割print(fruits) # ['apple', 'banana', 'cherry']
print("--- 连接 ---")# join() 是split的逆操作,将列表元素连接成一个字符串new_text = '-'.join(words)print(new_text)# Python-is-a-programming-language.-Welcome-to-using-Python.
print("--- 开头和结尾匹配 ---")filename = "report.pdf"print(filename.startswith("report")) # Trueprint(filename.endswith(".pdf")) # True
print("--- 判断字符串内容 ---")print("12345".isdigit()) # True (是否全部由数字组成)print("abc123".isalpha()) # False (是否全部由字母组成)print("abc123".isalnum()) # True (是否全部由字母或数字组成)print(" ".isspace()) # True (是否全部由空白符组成)
# ======================# 4. 类型转换 (Type Casting)# ======================
# --- 字符串转其他类型 ---num_str = "123"float_str = "123.45"bool_str = "True"list_str = "[1, 2, 3]"
# 转为整数num_int = int(num_str) # 123
# 转为浮点数num_float = float(float_str) # 123.45
# 转为布尔值 (非空字符串转为True)bool_val1 = bool("any non-empty string") # Truebool_val2 = bool("") # False# 注意:这种方式将"True"转为True,"False"转为True,因为它们都是非空字符串。# 如果想根据字符串内容转为对应的布尔值,需要特殊处理或使用ast.literal_evalimport ast
bool_val3 = ast.literal_eval(bool_str) # True
# 将表示列表/字典的字符串转为对应对象 (安全方式)list_obj = ast.literal_eval(list_str) # [1, 2, 3]
# --- 其他类型转字符串 ---num = 42pi = 3.1415is_true = Truemy_list = [1, 2, 3]
# 使用 str() 函数str_num = str(num) # "42"str_pi = str(pi) # "3.1415"str_bool = str(is_true) # "True"str_list = str(my_list) # "[1, 2, 3]"
# 使用格式化方法 (更灵活,推荐使用f-string)name = "Alice"age = 30greeting = f"My name is {name} and I am {age} years old."print(greeting) # My name is Alice and I am 30 years old.
# 使用 % 格式化 (较旧的方式)greeting_old = "My name is %s and I am %d years old." % (name, age)print(greeting_old)
# 使用 str.format() (Python 2.6+)greeting_format = "My name is {} and I am {} years old.".format(name, age)print(greeting_format)类型强转
邪门方法/语法
greeting2 = "Hello"" World" # 结果: "Hello World" (仅限于字面量)
print(greeting2)
# 输出内容: Hello World# 这个语法仅限字面量拼接,两个字面量直接使用引号隔开,编译器会将这些字面量拼接成一个.列表(list)-[]
注意,列表不等于其它语言中的数组,但其使用方法相似.
不同点:
- 一个列表中支持多种不同的对象,如数字,字符串等.
- 自动扩容/缩容.
- 自动垃圾回收.
可以简单理解为Java中的object类型的ArrayList,同时带有自动绑定类型的功能.
# 列表(list)# Python没有传统数组,一般使用list
arr = [1, 2, 3, 4] # 创建一个列表print(arr) # 调用函数输出所有的字符
arr[0] = 100 # 将第一个元素设置为100print(arr)
arr.append(5) # 追加一个元素5print(arr)
arrPopVar = arr.pop() # 弹出最后一个元素.print(arr)print(arrPopVar)
# 解释下面这行: 这是Python中的列表推导式,用于快速创建列表# 语法: [<表达式> for <变量> in <可迭代对象>]# range(5): 生成0->4,差值为1的列表.# x * x: 表达式,也即将0->4计算为平方.
squares = [x * x for x in range(5)]print(squares)
# 等价写法squares2 = []for x in range(5): squares2.append(x * x)
# 与其它语言的数组的不同点# 1. 一个列表中支持多种不同类型的对象.# 可以理解为一个Object类型的数组,什么都能存.常用方法
list_var = [1, 2, 3, 4]
print("关于append的补充")list_var.append("#")
print("=" * 40)print("方法extend(<可迭代对象>),用于将给定的可迭代对象添加进列表中.")list_var.extend((21, 22, 23)) # 在最后追加一个元组print(f"list_var{list_var}")list_var.extend([11, 12, 13]) # 在最后追加一个列表print(f"list_var{list_var}")list_var.extend({31, 32, 33}) # 在最后追加一个集合print(f"list_var{list_var}")list_var.append([41, 42, 43]) # 使用append在最后追加一个列表print(f"list_var{list_var}")
print("=" * 40)print("方法insert(<下标>,<值>),用于将给定对象插入到指定下标前.")list_var.insert(0, [41, 42, 43])print(f"list_var{list_var}")
print("=" * 40)print("方法remove(<值>),从列表中删除指定值")list_var.remove(1) # 删除出现的指定元素,若元素不存在则会报错print(f"list_var{list_var}")
print("=" * 40)print("方法count(<值>),统计给定值在列表中的出现次数,可以查询列表.")# tips: 如果列表中包含列表,而列表中的列表中包含你要查询的单个元素,此时你去查询单个元素时,是查不到的.print(list_var.count([41, 42, 43]))
print("=" * 40)print("方法index(<值>[,<起始下标>]),查找指定值在列表中首次出现的下标")print(list_var.index(3, 2))
print("=" * 40)print("方法copy(),用于复制一个列表,首先先查看地址和复制后列表的地址")list_var = [1, 2, 3, 4, [5, 6]]print(f"id(list_var): {id(list_var)}")list_var_copy = list_var.copy() # 我把这个列表复制了一份
print(f"id(list_var_copy): {id(list_var_copy)}")list_var_copy[4][1] = 41 # 尝试修改复制后的列表中的嵌套列表print(f"list_var{list_var}") # 输出源列表,此时可以发现嵌套列表被修改了.print(f"list_var_copy: {list_var_copy}") # 输出现有列表
list_var = [1, 2, 3, 4, [5, 6]]list_var_copy[0] = 10 # 尝试修改复制后的列表list_var_copy[4] = [7, 8] # 尝试修改复制后的列表print(f"list_var{list_var}") # 输出源列表print(f"list_var_copy: {list_var_copy}") # 输出现有列表此时可以发现源列表并无影响.
print("=" * 40)print("方法sort([key=len][,reverse=True]),用于原地排序列表")list_var = [[1], [2], [1, 2], [0, "2", 3, 4]]# 注意,列表中的元素必须为相同类型才可以排序,否则报错.# 如果排序内容为列表,那么首位元素类型必须相同,否则报错.# 排序内容为列表时,会按首位元素排序.# 参数key=len: 按长度排序,如字符串按字符串长度,列表按列表长度排序.# 反转reverse=True: 降序排序.list_var.sort(key=len, reverse=True)print(f"list_var{list_var}") # 输出排序后列表
print("=" * 40)print("方法sortd(<原列表>,[key=len][,reverse=True]),用于非原地排序列表")list_var = [3, 2, 4, 1]sorted_list_var = sorted(list_var, reverse=True)print(f"list_var{list_var}") # 输出原列表print(f"sorted_list_var{sorted_list_var}") # 输出排序后列表
print("=" * 40)print("方法reverse(),用于原地反转列表")list_var = [1, 2, 3, 4]list_var.reverse()print(f"list_var{list_var}") # 输出排序后列表
print("=" * 40)print("方法reversed(),用于非原地反转列表")list_var = [1, 2, 3, 4]reversed_list_var = reversed(list_var)print(f"list_var{list_var}") # 输出原列表print(f"reversed_list_var{reversed_list_var}") # 输出排序后列表# 一些奇怪的语法集合(set)-{}
set是无序的,去重的.
def foreach_set(set): # 这里实际上就是在遍历列表. # for <变量> in <列表> for i in set: print(i, end="") print("")
# 集合(set)是去重的,无序的.setVar = {1, 2, 3, 3}foreach_set(setVar) # 在这里,可以发现刚刚添加的两个3,已经被去重了.
setVar.add(4)foreach_set(setVar) # 新增元素4
setVar.remove(2)foreach_set(setVar) # 移除元素2# 声明空集合,只有这一种方法.empty_set = set()集合数学运算
包括 并,差,交,对称差集
a = {1, 2, 3, 4, 5, 5}b = {5, 7}# 还有其它运算符# &: 交集# |: 并集# -: 差集# ^: 对称差集,即不同时存在于两个集合中的元素.相当于(a | b) - (a & b)
c = a ^ b
print(c)print((a | b) - (a & b))集合关系运算
包括子集,超集
# 定义几个集合set_x = {1, 2}set_y = {1, 2, 3, 4}
# --- 子集 (Subset) ---# X.issubset(<Y>) X是否都在Y中,即X是否为Y的子集.# X <= Y 运算符,同issubset.
# X.issuperset(<Y>) X是否包含Y的所有元素,即X是否为Y的超集# X >= Y 运算符,同issuperset.
# X < Y X是否都在Y中,且X不等于Y,即X是否为Y的真子集# X > Y X是否包含Y的所有元素,且X不等于Y,即Y是否为X的真子集
# 判断 set_x 是否是 set_y 的子集 (所有元素都在 set_y 中)print("set_x 是 set_y 的子集吗?", set_x.issubset(set_y)) # Trueprint("set_x <= set_y?", set_x <= set_y)
# --- 超集 (Superset) ---# 判断 set_y 是否是 set_x 的超集 (包含 set_x 的所有元素)print("set_y 是 set_x 的超集吗?", set_y.issuperset(set_x)) # True# 也可以使用 >= 运算符print("set_y >= set_x?", set_y >= set_x)
# --- 判断真子集/真超集 ---# 如果 A 是 B 的子集,且 A 不等于 B,那么 A 是 B 的真子集print("set_x 是 set_y 的真子集吗?", set_x < set_y) # True (因为 set_x != set_y)print("set_y 是 set_x 的真超集吗?", set_y > set_x) # True集合的不可变版本
frozenset 是 set 的不可变版本,一旦创建,内容就不能改变。 因为不可变,所以它可以作为字典的键,或者作为另一个集合的元素。
# 创建一个 frozensetfrozen_set = frozenset([1, 2, 3, 4])print("不可变集合 (frozenset):", frozen_set)
# 尝试修改会报错# frozen_set.add(5) # AttributeError: 'frozenset' object has no attribute 'add'
# 可以用 frozenset 作为字典的键dict_with_frozen_key = {frozenset({1, 2}): "value"}print("字典的键为frozenset:", dict_with_frozen_key)字典(dict)-{}
基本用法
字典与Java中的HashMap很像,用于存储键值对.
person = { "name": "Alice", "age": 20}print(person)
# 修改/添加值person["name"] = "bob"print(person)person["city"] = "New Zealand"print(person)高级用法
创建字典
# --- 1. 创建字典的多种方式 ---# 1) 使用花括号,如上面的例子.dict1 = { "key1": "value1", "key2": "value2"}
# 2) 使用dict()构造函数dict2 = dict( name="Charlie", age=25)print("使用dict()创建:", dict2)
# 3) 使用dict()传入 键值对 列表dict3 = dict([ ("fruit", "apple"), ("color", "red")])print("使用列表创建:", dict3)安全访问和修改字典
person = { "name": "Alice", "age": 20}
# --- 2. 安全地访问和修改字典 ---
# 1. get(<键>[, <默认值>]):# 安全获取值,如果键不存在,返回默认值,而不是抛出KeyError.# 若未在get方法中给定默认值,则返回Noneprint("获取'age'的值:", person.get("age")) # 20print("获取不存在的键的值:", person.get("notExistsKey", "Not Specified") ) # Not Specified 未指定
# 2. setdefault(key[, default]): 如果键存在,返回其值;如果不存在,插入该键并设置为默认值,然后返回默认值person.setdefault("email", "default@example.com")print("使用setdefault添加默认值后:")print(person)字典合并
person = { "name": "Alice", "age": 20}# --- 3. 字典合并 ---# 1. 使用 update() 方法,会修改原字典.(Python3.5引入)person.update({ "age": 21, "job": "Engineer"})print(f"使用update()方法合并后: {person}")
# 2. 使用 | 运算符不修改原字典,返回新字典)(Python3.9引入)new_info = { "country": "USA", "job": "Developer"}merged_dict = person | new_infoprint(f"使用 | 运算符合并后 (新字典): {merged_dict}")print(f"原字典person保持不变: {person}")字典推导式
# --- 4. 字典推导式 (Dictionary Comprehension) ---# 类似于列表推导式,用于简洁地创建新字典# 例如,创建一个包含数字及其平方的字典# {<表达式> for i in <可迭代对象>}squares = {x: x ** 2 for x in range(1, 6)}print("字典推导式(数字及其平方): ", squares)字典的视图/常用方法
如获取键列表,获取值列表
person = { "name": "Alice", "age": 20}# --- 5. 字典的视图 (Views) ---# keys(), values(), items() 返回的是视图对象,它们是动态的,会反映字典的最新状态keys_view = person.keys()print("字典的键视图:", keys_view)values_view = person.values()print("字典的值视图:", values_view)items_view = person.items()print("字典的键值对视图:", items_view)
# 尝试修改字典,视图会随之更新person["new_key"] = "new_value"print("修改字典后,键视图自动更新:", keys_view)枚举(enum)-class
# enum# enum是需要导入包的,导入包的方法为# from <包名> import <方法名>from enum import Enum
# enum适用于如状态,类型,选项等,而不使用魔法数字.# 声明枚举类型class Color(Enum): RED = 1 GREEN = 2 BLUE = 3
def a(self): print("asdf") return "asdf的返回值"
colorVar = Color.RED
print(colorVar) # Color.REDprint(colorVar.value) # 1print(colorVar.name) # REDprint(colorVar.a()) # RED
if colorVar == Color.RED: print("红色的")元组(tuple)-()
tuple是用圆括号括起来的一组数据,是不可变的.
一般用于固定数据的返回或在一个函数中同时返回多个值.
# 元组 tupletupleVar = (1, 2, 3)# 这里会提示 元组不支持项目赋值# tupleVar[0] = 2;
# 一般用于固定数据或在一个函数中同时返回多个值,这有点和Go语言类似def func(): return 1, 2x, y = func()emptyTuple = () # 创建空元组方式一emptyTuple = tuple() # 创建空元组方式二数组(array)-()
有,但不太推荐使用python提供的array包
日常用 list,搞数据/计算用 numpy,内置 array 基本可以忽略
# 实际上,Python中也有真正意义的数组# 内置array包from array import array# i: 代表int类型,这里声明了一个整数类型的数组.arr = array('i', [1, 2, 3, 4])
print(arr[1])
# 适用于科学计算的数组,支持矩阵运算,广播等.# 这个包需要额外安装import numpy as npa = np.array([1, 2, 3])
print(a[1])类型强制转换
movie = "叶问"number = [1, 2]
# 常用强制类型转换的通用语法# <类型>(<值>)print(movie + str(number))
# --- 字符串转其他类型 ---num_str = "123"float_str = "123.45"bool_str = "True"list_str = "[1, 2, 3]"
# 转为整数num_int = int(num_str) # 123
# 转为浮点数num_float = float(float_str) # 123.45
# 转为布尔值 (非空字符串转为True)bool_val1 = bool("False") # Truebool_val2 = bool("") # False# 注意这里有个坑,这种方式将"True"转为True,"False"转为True,因为它们都是非空字符串。# 如果想根据字符串内容转为对应的布尔值,需要特殊处理或使用ast.literal_eval
# --- 高级用法: 使用其它库转换字面量为相应对象 ---# 这个库,允许字面量结构的字符串,转换为python中的对象,同时只允许解析字面量表达式,不会执行代码,更安全.import ast# 用法: ast.literal_eval(<字面量>)# 返回值为相应的对象bool_val3 = ast.literal_eval(bool_str) # True
# 将表示列表/字典的字符串转为对应对象 (安全方式)list_obj = ast.literal_eval(list_str) # [1, 2, 3]
# --- 其他类型转字符串 ---num = 42pi = 3.1415is_true = Truemy_list = [1, 2, 3]
# 使用 str() 函数str_num = str(num) # "42"str_pi = str(pi) # "3.1415"str_bool = str(is_true) # "True"str_list = str(my_list) # "[1, 2, 3]"
# eval是一个非常不安全的方法,他允许通过字符串向程序中注释代码片段,并执行它,有严重的安全漏洞,推荐直接移除该库.exp = "print([i for i in range(100)])"# eval支持将一个字符串当成程序来执行.print(eval(exp))# 删除eval库del __builtins__.__dict__['eval'] # 在python标准库中,删除eval函数列表与元组转换时的拷贝
tuple_var = (1, 2, 3, [4, 5, 6])
list_var = list(tuple_var) # 将元组转换为列表
list_var[2] = 120 # 修改列表中的元素list_var[3][1] = 10 # 修改列表中的列表
print(tuple_var) # 在这里可以发现,元组中的那个列表,已经被修改了.print(list_var)2.2 运算符
算术运算符
a=10,b=20
| 运算符 | 描述 | 实例 |
|---|---|---|
| + | 加 - 两个对象相加 | a + b 输出结果 30 |
| - | 减 - 得到负数或是一个数减去另一个数 | a - b 输出结果 -10 |
| * | 乘 - 两个数相乘或是返回一个被重复若干次的字符串 | a * b 输出结果 200 |
| / | 除 - x除以y | b / a 输出结果 2 |
| % | 取模 - 返回除法的余数 | b % a 输出结果 0. 运算公式a % b -> a - b * floor(a / b) print(7 % 3) # 1 print(-7 % 3) # 2 (不是 -1) print(7 % -3) # -2 (不是 1) print(-7 % -3) # -1 |
| ** | 幂 - 返回x的y次幂 | a**b 为10的20次方, 输出结果 100000000000000000000 |
| // | 取整除 - 返回商的整数部分(向下取整) | 9//2 = 4 # 正数整除是向下取整,效果等同于截断.-9//2 = -5 # 负数整除,实际上不是截断,而是向下取整. |
比较运算符
a=10,b=20
| 运算符 | 描述 | 实例 |
|---|---|---|
| == | 等于 - 比较对象是否相等 | (a == b) 返回 False. |
| != | 不等于 - 比较两个对象是否不相等 | (a != b) 返回 True. |
| <> | 不等于 - 比较两个对象是否不相等.python3 已废弃. | (a <> b) 返回 True.这个运算符类似 != . |
| > | 大于 - 返回x是否大于y | (a > b) 返回 False. |
| < | 小于 - 返回x是否小于y.所有比较运算符返回1表示真,返回0表示假.这分别与特殊的变量 True 和 False 等价. | (a < b) 返回 True. |
| >= | 大于等于 - 返回x是否大于等于y. | (a >= b) 返回 False. |
| <= | 小于等于 - 返回x是否小于等于y. | (a <= b) 返回 True. |
链式比较
# Python中的键式比较# 在其它语言中,一般是左结合,也就是这么运算# True == False == False# -> (True == False) == False# -> False == False# -> True
# 但是,python并非如此,python的计算过程如下# True == False == False# -> True == False and False == False# -> False# 也就是说,中间的False被使用了两次,键式比较被拆分成了使用and连接的两个小等式.# 这导致结果与其它语言有些许不同.print(True == False == False)赋值运算符
a=10,b=20
| 运算符 | 描述 | 实例 |
|---|---|---|
| = | 简单的赋值运算符 | c = a + b 将 a + b 的运算结果赋值为 c |
| += | 加法赋值运算符 | c += a 等效于 c = c + a |
| -= | 减法赋值运算符 | c -= a 等效于 c = c - a |
| *= | 乘法赋值运算符 | c *= a 等效于 c = c * a |
| /= | 除法赋值运算符 | c /= a 等效于 c = c / a |
| %= | 取模赋值运算符 | c %= a 等效于 c = c % a |
| **= | 幂赋值运算符 | c **= a 等效于 c = c ** a |
| //= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
位运算符
a=60,b=13
代码见下
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1. | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
| ^ | 按位异或运算符:当两对应的二进位相异时,结果为1 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
| ~ | 按位取反运算符:将二进制表示中的每一位取反,0 变为 1,1 变为 0.~x 类似于 -x-1 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011 (以补码形式表示),在一个有符号二进制数的补码形式. |
| << | 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0. | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
| >> | 右移动运算符:把”>>“左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数 | a >> 2 输 |
a = 60 # 60 = 0011 1100b = 13 # 13 = 0000 1101c = 0
# andc = a & b # 12 = 0000 1100print("a & b 的值为:", c)# orc = a | b # 61 = 0011 1101print("a | b 的值为:", c)# xorc = a ^ b # 49 = 0011 0001print("a ^ b 的值为:", c)# notc = ~a # -61 = 1100 0011print("~a 的值为:", c)# 左位移,后面的数字代表移动几位c = a << 2 # 240 = 1111 0000print("a << 2 的值为:", c)# 右位移,后面的数字代表移动几位c = a >> 2 # 15 = 0000 1111print("a >> 2 的值为:", c)逻辑运算符
a=10,b=20
| 运算符 | 逻辑表达式 | 描述 | 实例 |
|---|---|---|---|
| and | x and y | 布尔”与” - 如果 x 为可以定义为False的值,x and y 返回 x,否则它返回 y 的计算值. | (a and b) 返回 20. |
| or | x or y | 布尔”或” - 如果 x 是可以定义为True的值,它返回 x 的计算值,否则它返回 y 的计算值. | (a or b) 返回 10. |
| not | not x | 布尔”非” - 如果 x 为 True,返回 False .如果 x 为 False,它返回 True. | not(a and b) 返回 False |
| 运算符 | 逻辑表达式 | 描述 | 实例 |
|---|---|---|---|
| and | x and y | 布尔”与” | 都为True返回True |
| or | x or y | 布尔”或” | 都为False返回False |
| not | not x | 布尔”非” | 取反 |
成员运算符
成员运算符用于判断字符,元素等是否在字符串,列表或元组中.
| 运算符 | 描述 | 实例 |
|---|---|---|
| in | 如果在指定的序列中找到值返回 True,否则返回 False。 | x 在 y 序列中 , 如果 x 在 y 序列中返回 True。 |
| not in | 如果在指定的序列中没有找到值返回 True,否则返回 False。 | x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。 |
a = 1b = 20
list_var = [1, 2, 3, 4, 5]
print(f"a in list:{a in list_var}") # 类型也要匹配.print(f"b not in list:{b not in list_var}")
list_var = ["apple","good","pear","orange"]print(f"good in list_var:{'good' in list_var}")
# in同时也适用于字符串.src_var = "Gooooood morning, Night City!"target_var = "Kulve"print(target_var in src_var)is身份运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| is | is 是判断两个标识符是不是引用自一个对象 | x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False |
| is not | is not 是判断两个标识符是不是引用自不同对象 | x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果 True,否则返回 False。 |
is与==的区别:
is是用于判断两个变量是否引用了同一块内存地址,而==是用来判断两个变量是否相等
a = 20b = 20# 解释: is是用于判断两个变量是否引用了同一块内存究竟# 而==是用来判断两个变量是否相等print(f"a is b:{a is b}")
b = 30
print(f"a is b:{a is b}")关于is在列表中的应用
a = [1, 2, 3, 4, 5]b = aprint(f"a is b:{a is b}") # True: b直接指向了a
b = [1, 2, 3, 4, 5]print(f"a is b:{a is b}") # False: 这个列表是一个新列表而非因为省内存指向了a
b = a[:]print(f"a is b:{a is b}") # False: a[:]代表了a列表的复制.运算符优先级
| 运算符 | 描述 |
|---|---|
| ** | 指数 (最高优先级) |
| ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) |
| * / % // | 乘,除,取模和取整除 |
| + - | 加法减法 |
| >> << | 右移,左移运算符 |
| & | 位 ‘AND’ |
| ^ | | 位运算符 |
| <= < > >= | 比较运算符 |
| <> == != | 等于运算符 |
| = %= /= //= -= += *= **= | 赋值运算符 |
| is is not | 身份运算符 |
| in not in | 成员运算符 |
| not and or | 逻辑运算符 |
2.3 函数
变量名和函数使用全小写字母+下划线的方式命名
标准函数调用
# 标准函数调用def describe_person(name, age): """描述一个人的基本信息""" description = f"{name} 今年 {age} 岁" return description
# 调用函数person_info = describe_person("张三", 25)print(person_info) # 输出: 张三 今年 25 岁带有默认形参的函数调用
形参可以指定默认值,指定默认值的参数在不传入的情况下,会自动使用指定的值.
默认形参只能放在最后,或者说,默认形参后,不能跟随非默认形参.
# 带有默认形参的函数调用# age带有默认参数0# 注意事项:# - 默认形参只能放在最后,或者说,默认形参后,不能跟随非默认形参.def describe_person(name, age=0): """描述一个人的基本信息""" description = f"{name} 今年 {age} 岁" return description
# 调用函数,此处未提供age形参.person_info = describe_person("张三", )print(person_info) # 输出: 张三 今年 25 岁多返回值的函数
一个函数可以返回多个值,可以使用接收参数列表接收,也可以使用单个参数接收,此时这个接收参数会变成一个元组.
# 多返回值的函数调用def describe_person(name, age=0): """描述一个人的基本信息""" description_form1 = f"{name} 今年 {age} 岁" description_form2 = f"今年 {age} 岁的 {name} " return description_form1, description_form2
# 调用函数,此处未提供age形参.person_info1, person_info2 = describe_person("张三")print(person_info1) # 输出: 张三 今年 0 岁print(person_info2) # 输出: 今年 0 岁的 张三
# 在这里,如果只给定一个接收参数,那么会传回一个元组.person_info = describe_person("张三")print(person_info)参数传递
Python默认为引用传递
严格来说,并不是引用传递. python传递的是对象的引用,可以简单理解为是赋值.
不可变对象如 int, float, str, tuple, bool, None也都是引用传递,不过因为对象不可变,看起来像值传递,如
# 修改不可变对象def change_number(num): num = 999 # 重新赋值,不会影响原变量,此时num是一个新变量而不是形参传入的地址. print(f"函数内: {num}")
n = 10print(f"调用前: {n}")change_number(n)print(f"调用后: {n}") # 输出: 10# main函数中传入的参数去修改不可变对象def change_name(person_name, new_name): """尝试修改人的名字""" print(f"函数内: {person_name is new_name}") # False person_name = new_name # 创建新字符串对象,此时person_name 指向了 new_name print(f"函数内: {person_name}") print(f"函数内: {person_name is new_name}") # True
name = "张三"print(f"调用前: {name}")change_name(name, "李四")print(f"调用后: {name}") # 张三修改可变对象,如list, dict, set
这些对象都形成了类似引用传递的效果,即形参变量在函数中修改后会影响到实参变量.
def add_student(students, name): """向学生列表添加新学生"""
students.append(name) # 修改原列表 print(f"函数内: {students}")
student_list = ["小明", "小红"]print(f"调用前: {student_list}") # ["小明", "小红"]add_student(student_list, "小刚")print(f"调用后: {student_list}") # ['小明', '小红', '小刚']def update_teacher_info(teacher, key, value): """更新教师信息""" teacher[key] = value # 修改原字典 print(f"函数内: {teacher}")
teacher_info = {"name": "王老师", "subject": "数学"}print(f"调用前: {teacher_info}") # {'name': '王老师', 'subject': '数学'}update_teacher_info(teacher_info, "school", "第一中学")print(f"调用后: {teacher_info}") # {'name': '王老师', 'subject': '数学', 'school': '第一中学'}可变参数
可变参数有两种:
- `*args`位置可变参数: 可以接收任意数量的**位置参数**,打包成一个元组.- `**kwargs`关键字可变参数: 接收任意数量的**关键字参数**,打包成字典.# *student位置参数# **scores关键字参数# def function(普通参数, 默认参数, *args, **kwargs):def school_report(school_name, year=2024, *students, **scores): print(f"学校: {school_name}, 年份: {year}") print(f"学生: {students}") print(f"成绩: {scores}")
# 位置参数要在命名参数前# 在此示例中没有命名参数school_report("第一中学", # school_name 2024, # year "小明", # *student "小红", # *student 数学=95, # **kwargs 英语=88 # **kwargs )# 输出:# 学校: 第一中学, 年份: 2024# 学生: ('小明', '小红')# 成绩: {'数学': 95, '英语': 88}命名参数
可以通过指定参数关键字的形式,调换参数的顺序
但要注意,位置参数要在最前面
def school_report(school_name, year=2024, ): print(f"学校: {school_name}, 年份: {year}")
# 位置参数要在命名参数前school_report("第一中学", # 位置参数 year=2024, # 命名参数 )# 输出:# 学校: 第一中学, 年份: 2024匿名函数
匿名函数是基于lambda函数实现的,其语法如下
lambda [arg1 [,arg2,.....argn]]:expression示例
# 匿名函数sum_lambda = lambda x, y: x + y
# 调用sum函数print(f"相加后的值为: {sum_lambda(10, 20)}") # 相加后的值为: 302.4 结构
分支
if分支语法如下
if <表达式>: <then 块>[else: <else 块>]多个if分支时如下
if <表达式1>: <then1 块>elif <表达式2>: <then2 块>...[else: <else 块>]Python 3.1.0版本中新增match
基本语法
match <表达式>: case <值>[if <守卫表达式>]: <case1> [case <值>[if <守卫表达式>]: <case2>] ... [case _: <else>]case中,可以放以下内容
- 字面量
- 序列
- 字典
一些复杂的用法中,可以出现
- or 或者条件: 允许多选一
- as为模式整体起个别名
- 守卫条件: 在模式匹配到之后进行一次if判断决定是否进入.
def process_data(data): match data: # 1. 字面量匹配:匹配具体的值 case 0: return "【字面量】是数字 0" case "hello": return "【字面量】是问候语" case None: return "【字面量】是空值"
# 2. 序列模式:匹配列表或元组的结构 case (x, y, z): return f"【序列】三维坐标 -> x:{x}, y:{y}, z:{z}" case [c, m, y, k]: return f"【序列】cmyk颜色 -> 青:{c}, 品红:{m}, 黄:{y}, 黑:{k}" case (a, b, c, d, *rest): return f"【序列】是 {a, b, c, d},剩余项有 {len(rest)} 个"
# 3. 字典模式:匹配字典中的键 case {"name": name, "role": "admin"}: return f"【字典】管理员: {name} 登录" case {"type": "error", "code": code} if code >= 500: return f"【字典+守卫】服务器错误 (代码: {code})"
# 4. OR 模式:匹配多种情况之一 case "yes" | "y" | True: return "【OR】确认操作" case "no" | "n" | False: return "【OR】取消操作"
# 5. AS 模式:给匹配到的整体起个别名 case (x, y) as point: return f"【AS】点坐标 {point} 解构为 x={x}, y={y}"
# 6. 守卫条件:在模式后加 if 判断 case n if isinstance(n, int) and n > 100: return f"【守卫】这是一个大于 100 的大整数: {n}" case n if isinstance(n, int) and n < 0: return f"【守卫】这是一个负数: {n}"
# 7. 通配符:匹配所有其他情况 case _: return "【默认】未知的数据类型"
# --- 测试运行 ---print(process_data(0)) # 字面量print(process_data((10, 20, 30, 40))) # 序列print(process_data({"name": "Bob", "role": "admin"})) # 字典1print(process_data({"type": "error", "code": 500})) # 字典2
print(process_data((1, 2)))
print(process_data("yes")) # OR 模式print(process_data(150)) # 守卫条件print(process_data("unknown")) # 默认情况2.5 类
类名使用大驼峰的方式命名
对应于类名的类文件,使用全小写字母+下划线的方式命名
基本组成
一个Python类由以下部分组成
- 类属性
- 构造方法
- 实例方法
- 类方法
- 静态方法
- 特殊方法
class Person: # 类属性(属于类本身) species = "Human"
# 构造方法(初始化实例) def __init__(self, name: str, age: int): self.name = name # 实例属性 self.age = age
# 实例方法(最常见) def introduce(self): return f"我叫{self.name},今年{self.age}岁"
# 类方法 @classmethod def change_species(cls, new_species: str): cls.species = new_species
# 静态方法 @staticmethod def is_adult(age: int) -> bool: return age >= 18
# 特殊(魔法)方法 def __str__(self): return f"{self.name}({self.age})"
def __eq__(self, other): return self.name == other.name and self.age == other.age基本用法
# 命名规范# 文件名使用 a_b的方式命名,类名使用大写驼峰命名,以规避导入时混淆的问题.class Person: # 这个方法,相当于Java中的构造函数,第一个参数self是不可省略的,另外,第二个参数对外暴露时是第一个参数. # 实际上,Python规定,在类中定义函数,第一个参数必须是self,是不可变,不可省略的. # 普通方法中,self一般为实例本身. def __init__(self, name): # 关于self.name,这实际上是一个实例属性,只有创建好实例后才能访问 self.name = name
# 声明了一个get方法,使用self来获取类中的成员变量. def get_name(self): return self.namefrom person import Person# 在这里,将alice这个字符串,传入到了person类中的__init__方法中.p = Person("alice")# 调用类的方法,打印字符.print(p.get_name())类方法
在java中,匿名对象是如下方法使用的
new person().getName(); // 用完就丢弃,没有被赋值给任何变量.
而Python中的匿名对象,实际上是一种类匿名对象,他只是声明了一个名为”匿名”的对象而不是真正的在内存方面都没有名字的对象.
创建实例(“类”匿名对象// 简单工厂方法)
类方法有一个cls特殊参数,代表了类本身.
类方法可以访问/修改类属性
子类调用时,会自动传入子类
一般用在工厂方法,替代构造函数上.
类方法需要使用@classmethod进行装饰.
示例1
# 命名规范# 文件名使用 a_b的方式命名,类名使用大写驼峰命名,以规避导入时混淆的问题.class Person: # 这个方法,相当于Java中的构造函数,第一个参数self是不可省略的,另外,第二个参数对外暴露时是第一个参数. # 实际上,Python规定,在类中定义函数,第一个参数必须是self,是不可变,不可省略的. # 普通方法中,self一般为实例本身. def __init__(self, name): # 关于self.name,这实际上是一个实例属性,只有创建好实例后才能访问 self.name = name
# 声明了一个get方法,使用self来获取类中的成员变量. def get_name(self): return self.name
# classmethod: 这是个装饰器,用于将普通方法转换为类方法. # 类方法的第一个参数是类本身(通常命名为cls而不是实例self) # cls实际上和self一样,是一个"系统"级别的占位符, # 当解释器看到这里有@classmethod时,就会把cls赋值为类本身(注意,不是实例本身) @classmethod def create_anonymous(cls): # 类方法:创建匿名对象 return cls("匿名")from person import Person
# 使用类方法创建实例# 注意,在这里传入的参数,被映射到了name属性上,而不是cls.p2 = Person.create_anonymous()print(p2.get_name()) # 匿名示例2
class Person: school = "第一中学"
def __init__(self, name: str): self.name = name # cls代表实例本身 @classmethod def change_school(cls, new_name: str): cls.school = new_namefrom demo5 import Person# 类方法可以访问类对象print(Person.school) # 实验中学
# 方式1:通过类调用Person.change_school("实验中学")print(Person.school) # 实验中学
# 方式2:通过实例调用p = Person("小明")p.change_school("外国语中学")print(p.school) # 外国语中学静态方法
静态方法没有特殊参数
不能访问类/实例属性
一般只作为工具函数辅助功能使用.
class Person: a1 = 10
def __init__(self, name: str): self.name = name
@staticmethod def is_adult(age: int) -> bool: #a2 = a1 # 这里的a1访问不到 return age >= 18
@classmethod def change_school(cls, new_school: str): cls.school = new_schoolfrom demo5 import Person
# 通过类调用静态方法(推荐)print(Person.is_adult(20)) # Trueprint(Person.is_adult(15)) # False
# 通过实例调用静态方法p = Person("小明") # 现在可以正常工作了print(p.is_adult(20)) # True关于类方法与静态方法的实际应用场景
使用类方法创建不同方式的实例化(工厂模式)
使用静态方法进行数据验证、格式转换等独立操作
类方法更适合需要访问类状态的操作
静态方法更适合与类相关但不依赖类状态的工具函数
类方法在继承时的多态
当子类继承父类的 类方法 时,该方法的第一个参数 cls 会指向子类而不是父类.如以下示例:
- 在People(父类)中声明了一个create类方法,其有cls参数- Student,Teacher(子类)继承了父类,但没有对create进行重写.- 所以,在子类使用create进行实例化时,因为类方法具有多态性,所以cls会指向子类而不是父类.class People: def __init__(self, name): self.name = name
# 在此处定义了一个类对象 @classmethod def create(cls, name): # 这里的 cls 具有多态性,谁调用就是谁 cls_type = cls.__name__ print(f"cls的类型为{cls_type}") return cls(name) # 关键点:调用的是 cls 的构造函数
class Student(People): def study(self): print(f"{self.name} 正在学习")
class Teacher(People): def teach(self): print(f"{self.name} 正在教学")from demo8 import Student, Teacher
# 1. 通过 Student 调用继承来的 create 方法# 通过继承过来的类方法,实例化Student# 在这里,类方法本身没有变化,但是传入的cls对象是有变化的,现在传入的是Student.
stu = Student.create("小明")# 输出: 正在通过 Student 类创建对象...# 实际执行: cls = Student, 所以 return Student("小明")
# 所以,在这里的study是可以正常调用的.也即调用的刚刚的实例的study方法.stu.study()# 这行代码是用于打印20个"-",形成分隔符,在Python中,对字符串相乘等于重复字符串相应次数.print("-" * 20)
# 2. 通过 Teacher 调用继承来的 create 方法tea = Teacher.create("王老师")# 输出: 正在通过 Teacher 类创建对象...# 实际执行: cls = Teacher, 所以 return Teacher("王老师")tea.teach()单继承与方法重写
什么是继承? 继承可以使子类获取父类的属性,方法. 父类的属性,方法通过super()进行调用.
什么是重写? 重写就是子类觉得父类提供的同定义方法不能满足自己的使用,于是自己实现了一个新的.
- 类继承的语法: class <子类名>(<父类名>)
- 方法重写的语法: 无需装饰器,直接与父类同名,同参数即可.
class Person: def __init__(self, name): self.name = name
def introduce(self): return f"我是 {self.name}"
# 继承: class <子类名>(<父类名>)class Student(Person): # Student 继承自 Person,新增 grade 属性 def __init__(self, name, grade): super().__init__(name) # 调用父类构造函数 self.grade = grade
# 无需装饰器,直接隐式重写父类方法了. def introduce(self): # 重写父类方法 return f"我是 {self.name},是一名学生,今年 {self.grade} 年级"
# 定义一个专属方法 def study(self): return f"{self.name} 正在学习中..."
class Teacher(Person): # Teacher 继承自 Person,新增 subject 属性 def __init__(self, name, subject): super().__init__(name) self.subject = subject
def introduce(self): return f"我是 {self.name},是一名老师,教授科目为 {self.subject}"
# 定义一个专属方法 def teach(self): return f"{self.name} 正在教 {self.subject}"from demo1 import Student, Teacher
if __name__ == "__main__": print("单继承与方法重写") s = Student("小明", 3) t = Teacher("王老师", "数学")
# 调用各自实例对象的方法 print(s.introduce()) print(t.introduce()) print(s.study()) print(t.teach())单继承与方法重写我是 小明,是一名学生,今年 3 年级我是 王老师,是一名老师,教授科目为 数学小明 正在学习中...王老师 正在教 数学多态
class Person: def introduce(self): return "我是一个普通人"# 继承自Personclass Student(Person): def introduce(self): return "我是一名学生"# 继承自Personclass Teacher(Person): def introduce(self): return "我是一名老师"from demo2 import Person, Student, Teacher
def make_introduction(people_object): # 同一个接口,传入不同对象,表现出不同行为 print(people_object.introduce())
if __name__ == "__main__": print("多态演示") peopleList = [Student(), Teacher(), Person()] for i in peopleList: make_introduction(i)多态演示我是一名学生我是一名老师我是一个普通人多继承与MRO
python与java不同的一点是,python是支持一个类继承多个类的,也即多继承.
所以,由此引出了一个问题,若一个子类的方法,在调用父类的方法是,要遵循什么样顺序来调用父类的方法,如果同名呢?
class Person: def __init__(self, name): # 3. 将传入的值小张赋值给Person的实例对象的实例属性name中. self.name = name # 其声明了一个专有方法. def introduce(self): return f"我是 {self.name},是人"
class Student(Person): def __init__(self, name, grade=1): # 2. 调用Person.__init__(),因为grade没有值,所以传入默认值1. Person.__init__(self, name) # 各自的专有属性 # 4. 因未传入gradle,所以默认为1. self.grade = grade # def introduce(self): # return f"我是 {self.name},是学生"
class Teacher(Person): def __init__(self, name, subject="未知"): Person.__init__(self, name) # 各自的专有属性 self.subject = subject
# def introduce(self): # return f"我是 {self.name},是老师"
# 助教类,多继承,同时继承自两个类Student,Teacherclass Assistant(Student, Teacher): # 计算过程见下文. # MRO(方法解析顺序)为 Assistant → Student → Teacher → Person → object def __init__(self, name, grade, subject): # 这里可以采用调试器来观察程序运行步骤 # 1. 这里是MRO的入口,详细解释: # super(): 返回了一个代理对象,代表MRO中的下一个类,按上面的注释来看,也就是 Student类的proxy. # 所以,这里实际上调用的是 Student.__init__(name) super().__init__(name) # 按MRO顺序调用
self.grade = grade self.subject = subject # def introduce(self): # return f"我是 {self.name},是助教"
# 针对MRO算法的解释 # C3算法: # 线性化公式: L[C] = C + merge(L[P1], L[P2], ..., L[Pn], [P1, P2, ..., Pn]) # 其中: # - L[C] 表示类C的线性化 # - P1,P2,...,Pn是类C的直接父类(按声明顺序排序) # - merge(): C3核心合并操作 # merge(): 对多个列表进行合并 # - Start: 取出所有列表的头部(第一个元素)组合成为<候选元素列表> # - Check: 检查取出的元素是否出现在其他任何列表的非头部(尾部) # - 不在任何列表的非头部(尾部) -> 选定此元素,从所有列表中移除并清空<候选元素列表> # - 在某个列表尾部 -> 跳过,看下一个候选元素 # - Jmp Start: 循环读取所有<候选元素列表>中的元素,直到成功取出一个元素或无法取出元素 # 本例是如何计算的? # 1. 画出类继承图 # object # │ # Person # ╱ ╲ # Student Teacher # ╲ ╱ # Assistant # 2. 线性化 # > 什么是线性化: 将这个树形结构拍扁
# 步骤1: 计算L[object] # L[object] = [object]
# 步骤2: 计算L[Person] # 参数列表: L[object] = [object], 直接父类列表 = [object] # L[Person] = Person + merge(L[object], [object]) # = Person + merge([object], [object]) # = [Person] + [object] # merge 结果为 [object] # = [Person, object]
# 步骤3: 计算L[Student] # 参数列表: L[Person] = [Person, object], 直接父类列表 = [Person] # L[Student] = Student + merge(L[Person], [Person]) # = Student + merge([Person, object], [Person]) # = [Student] + [Person, object] # merge 结果为 [Person, object] # = [Student, Person, object]
# 步骤4: 计算L[Teacher] # 参数列表: L[Person] = [Person, object], 直接父类列表 = [Person] # L[Teacher] = Teacher + merge(L[Person], [Person]) # = Teacher + merge([Person, object], [Person]) # = [Teacher] + [Person, object] # merge 结果为 [Person, object] # = [Teacher, Person, object]
# 步骤5: 计算L[Assistant] # 参数列表: # - L[Student] = [Student, Person, object] # - L[Teacher] = [Teacher, Person, object] # - 直接父类列表 = [Student, Teacher] # L[Assistant] = Assistant + merge(L[Student], L[Teacher], [Student, Teacher]) # = Assistant + merge([Student, Person, object], [Teacher, Person, object], [Student, Teacher]) # 开始逐步执行 merge([Student, Person, object], [Teacher, Person, object], [Student, Teacher]): # # 第1轮尝试: # - 候选 = [Student, Teacher] (取各列表头部) # - 尝试 Student -> 不在其他列表非头部 -> 成功移除 # - 结果累积: [Student] # - 剩余列表: merge([Person, object], [Teacher, Person, object], [Teacher]) # # 第2轮尝试: # - 候选 = [Person, Teacher] # - 尝试 Person -> 在 [Teacher, Person, object] 的非头部 -> 失败,跳过 # - 尝试 Teacher -> 不在其他列表非头部 -> 成功移除 # - 结果累积: [Student, Teacher] # - 剩余列表: merge([Person, object], [Person, object]) # # 第3轮尝试: # - 候选 = [Person] # - 尝试 Person -> 不在其他列表非头部 -> 成功移除 # - 结果累积: [Student, Teacher, Person] # - 剩余列表: merge([object], [object]) # # 第4轮尝试: # - 候选 = [object] # - 尝试 object -> 不在其他列表非头部 -> 成功移除 # - 结果累积: [Student, Teacher, Person, object] # - 剩余列表: merge() -> 列表全空,结束
# 将 merge 结果拼接回外层公式: # L[Assistant] = Assistant + [Student, Teacher, Person, object] # = [Assistant] + [Student, Teacher, Person, object] # = [Assistant, Student, Teacher, Person, object]
# 3. 得到最终结果 # - L[Assistant] = [Assistant, Student, Teacher, Person, object]from demo3 import Assistant
if __name__ == "__main__": print("多继承与 MRO") a = Assistant("小张", 4, "化学") print(a.introduce()) print(f"MRO 顺序: {[c.__name__ for c in Assistant.__mro__]}")抽象基类
什么是抽象基类?
抽象基类定义了一组方法,要求继承自该抽象基类的子类,必须实现该类的抽象方法.
抽象基类中,也可以同时存在以下内容:
- 普通方法
- 抽象方法
- 类方法
- 抽象类方法
- 静态方法
- 抽象静态方法
from abc import ABC, abstractmethod
# PythonABC: 定义了必须有什么class Person(ABC): # 使用此装饰器来表示该方法没有实现,是抽象方法,需要子类实现 @abstractmethod def get_role(self) -> str: # 返回人的角色 pass
@abstractmethod def introduce(self) -> str: # 自我介绍 pass
class Student(Person): def __init__(self, name: str, grade: str): self.name = name self.grade = grade # 如果子类不实现父类的抽象方法,那么IDE会进行提示 def get_role(self) -> str: return "学生"
def introduce(self) -> str: return f"我叫{self.name},是{self.grade}的学生."
class Teacher(Person): def __init__(self, name: str, subject: str): self.name = name self.subject = subject
def get_role(self) -> str: return "老师"
def introduce(self) -> str: return f"我是{self.name}老师,教{self.subject}."封装: 私有属性+@property与名称重整
私有属性: 对外暴露,但是访问比较困难,因为python没有传统意义上的私有属性.
@property: 用于声明只读属性
@<属性名>.setter: 用于声明对应的只读属性的setter方法
class Person: def __init__(self, name, age): self._name = name # 保护属性(约定式,对外其实可以访问,只是一种默认约定) self.____age = age # 私有属性(双下划线,会触发解释器的名称重整,这里会将变量名替换为 _Person__age) self.__id = None # 私有属性(同上,会触发名称重整)
# ===== @property 只读属性 ===== # @property 并非完全不可读,只是在对名暴露的变量中,不能直接修改,需要使用指定的setter修改. # 实际上是可以改的,因为_name对外是可以访问的,只是约定俗成的东西. @property def name(self): """获取姓名""" return self._name
# ===== @property.setter 可写属性 ===== @name.setter def name(self, value): """设置姓名(带验证)"""
# 在这里,有必要添加一个知识点,value若为以下内容,python在进行逻辑比较时,会认为其为false # -None,"",0,[],{},false # 那么,此处传入""将会使条件为真,从而触发异常.
if not value or not isinstance(value, str): # raise: 用于抛出异常,相当于java中的throw关键字. raise ValueError("姓名必须是非空字符串") # strip(): 用于去掉前后空白字符. self._name = value.strip()
@property def age(self): """获取年龄""" return self.____age
@age.setter def age(self, value): """设置年龄(带验证)""" if not isinstance(value, int) or value < 0: raise ValueError("年龄必须是正整数") self.____age = value
@property def id(self): """获取ID(只读)""" return self.__id
def set_id(self, id_value): """设置ID(受控方法)""" if self.__id is not None: raise ValueError("ID 已设置,不能修改") self.__id = id_valuefrom demo6 import Person
if __name__ == "__main__": print("=== 基本使用 ===") p = Person("张三", 25)
# 通过 property 访问 print(f"姓名: {p.name}") print(f"年龄: {p.age}")
# 通过 property 修改(触发验证) p.name = "李四" p.age = 30 print(f"修改后 - 姓名: {p.name}, 年龄: {p.age}")
print("\n=== 私有属性演示 ===") p.set_id("P001") print(f"ID: {p.id}") # 只读,无法修改
print("\n=== 名称重整演示 ===") print("对象属性字典:", p.__dict__) # 可以看到 __age 被重命名为 _Person__age
print("\n=== 验证机制演示 ===") try: p.age = -5 # 触发验证错误 except ValueError as e: print(f"设置年龄失败: {e}")
try: p.name = "" # 触发验证错误 except ValueError as e: print(f"设置姓名失败: {e}")鸭子类型
什么是鸭子类型?
鸭子类型就是,不关心你是什么类型,只关心你有没有对应的方法.
用鸭子来理解就是,我不关心你是不是鸭子,只要你会嘠嘠叫,那么你就是鸭子,甚至如果你是一只会嘎嘎叫的机器鸭子,你也是鸭子.
class Person: def __init__(self, name, age): self.name = name self.age = age # 每个类都有 speak方法 def speak(self): return f"{self.name}:大家好"
class Student(Person): def __init__(self, name, age, student_id, grade=None): super().__init__(name, age) self.student_id = student_id self.grade = grade
def speak(self): return f"{self.name}:老师好,我是学生" # 专有方法 def submit_homework(self): return f"{self.name} 提交了作业"
class Teacher(Person): def __init__(self, name, age, subject, salary=0): super().__init__(name, age) self.subject = subject self.salary = salary
def speak(self): return f"{self.name}:同学们好,我是老师" # 专有方法 def grade_homework(self): return f"{self.name} 正在批改作业"
class Parrot: def __init__(self, name): self.name = name
def speak(self): return f"{self.name}:呱呱!"from demo7 import Person, Student, Teacher, Parrot
def morning_greeting(obj): """晨间问候:只要是"能 speak 的对象"都能用""" print(obj.speak())
if __name__ == "__main__": print("=== 鸭子类型演示 ===")
# 按照每个类的构造函数参数传递 people = [ Person("路人甲", 20), # Person(name, age) Student("李四", 15, "S001"), # Student(name, age, student_id) Teacher("王老师", 35, "数学"), # Teacher(name, age, subject) Parrot("阿花") # Parrot(name) ]
# 对每个对象调用 早上好 # 实际上,这里不用people才好 for p in people: morning_greeting(p)魔术方法
魔术方法就是以双下划线开头与结尾的方法,如__init__,__str__,不需要显式调用,会由python解释器在一定情况下自动调用,如实例化,打印,比较,运算等,
另外,非常不推荐你自己声明一个魔术方法,这是由python官方声明的.
原因是,自己声明的魔术方法,有可能会在未来的某一个版本中,与python官方的定义冲突,导致程序出现异常.
但并不是说,你不能声明魔术方法,你仍然可以声明一个以双下划线开关与结尾的方法,并以普通方法的形式调用.
常见的魔术方法
| 魔术方法 | 触发场景 | 典型用途 |
|---|---|---|
__init__ | obj = Class() | 初始化对象属性,也即构造函数 |
__str__ | print(obj) | 定义对象的显示文本(给用户看) |
__repr__ | repr(obj)/交互环境 | 定义对象的详细文本(给开发者看) |
__eq__ | obj1 == obj2 | 相等 比较 |
__call__ | obj() | 进行方法调用. |
__len__ | len(obj) | 定义对象的长度 |
__getitem__ | obj[key] | 支持切片或索引访问 |
类型判断
class People: pass
class Student(People): pass
class Teacher(People): passfrom demo9 import People, Student, Teacher
teacher = Teacher()student = Student()
# isinstance(): 考虑继承,即如果teacher是继承自People的,那么也返回trueisinstanceVar = isinstance(teacher, People)print(f"isinstance:{isinstanceVar}")
# type(): 不考虑继承,即只考虑精确比较,只有两个实例完全由同一个类实例化时,才会返回truetypeVar1 = type(teacher) == PeopletypeVar2 = type(teacher) == Teacherprint(f"typeVar1:{typeVar1}")print(f"typeVar2:{typeVar2}")属性遮蔽
Python在访问对象的属性时,会遵循以下的顺序
实例的属性 -> 类的属性 -> 父类的属性 -> 父类的父类的属性 -> ... -> 没有找到,报错AttributeError.简单示例
class Demo: k = 100 # 类属性
demo_instance = Demo()
# 1. 直接访问 -> 读到的是类属性print(demo_instance.k) # 100
# 2. 赋值后 -> 实例属性遮蔽类属性demo_instance.k = 999print(demo_instance.k) # 999(实例属性)print(Demo.k) # 100(类属性未受影响)
# 3. 删除实例属性 -> 恢复对类属性的访问del demo_instance.kprint(demo_instance.k) # 100(又回到了类属性)包含有父类的示例
class Demo: k = 100 # 类属性
class ChildDemo(Demo): k = 200
# 声明子类实例child_demo = ChildDemo()print(child_demo.k) # 输出: 200
child_demo.k = 300 # 为实例添加了实例属性k(self.k)print(child_demo.k) # 输出: 300,访问的是实例属性,也就是chld_demo.k -> self.kprint(ChildDemo.k) # 输出: 200,访问的是子类属性,也就是ChildDemo.kprint(Demo.k) # 输出: 100,访问的是父类属性,也就是Demo.k
# 移除实例属性del child_demo.k # 移除了实例属性k(self.k)print(child_demo.k) # 输出: 200,访问的是子类属性,也就是ChildDemo.kdel ChildDemo.k # 移除了子类属性k(self.k)print(ChildDemo.k) # 输出: 100,访问的是父类属性,也就是Demo.kdel Demo.kprint(Demo.k) # 找不到对象,报错3 常用方法
3.1 print方法
基本语法
print("<要打印的内容>")扩展语法
print方法中,可以对要输出的内容进行格式化,主要有三种方式,最常用的是第一种: f”模板字符串”
可以运行下面的代码,来体验print方法的用途
def print_hi(name): # 函数体 # 在这里,printf语句中,有一个模板字符串{<变量名>}, # printf会自动在该函数体中使用相应的变量替换这个模板 # 注意,在字符串前,有一个f,这代表后面这个字符串是一个模板字符串. print(f"Hello,{name}")
# print的其它示例 # 1. --------打印多个对象-------- a, b = 10, 20 print(a, b, "#最标准的打印,没有任何特殊之处,不过要注意,在Python中,print是会自动换行的") # 2. --------打印多个对象时,指定分隔符-------- print(a, b, "#指定分隔符,使用sep='<分隔符>'指定使用哪些字符分隔多个对象", sep=" | ") # 3. --------不换行打印(指定结束符号)-------- print(a, b, "#不换行打印,使用end=''来指定结束符", end="") # 4. --------将输入打印到文件-------- # 1). 需要先打开文件,其语法为 with open("<文件路径>","<打开方式>") as <变量名> # 2). 之后使用文件时,在print中使用file来指定刚刚声明的指代文件的变量名. with open("M:\\output.txt", "w") as outputFile: print("Hello,world!", file=outputFile)
# 进阶内容: 格式化字符串的3种方式(非常重要) name, age = "张三", 17 pi = 3.1415926 # --------格式化方式一: f"模板字符串"-------- # 模板字符串: 使用{<变量名>}来使用变量替换其中的值,使用{<表达式>}来进行简单计算后再输出. # 注意事项: # - 变量不存在或表达式有误会报错 # - 如果想要输出{},则需要进行转义,转义方式为再加一对大括号{{}} # - 严禁使用该方式拼接SQL! # 1). 基本用法 print(f"Name:{name},Age:{age}") # 注意本行代码在上一次向屏幕输出的语句的末尾
# 2). 精度控制: {变量名: <格式字符串>},如下面这个示例就是保留两位小数. print(f"pi:{pi:.2f}")
# 3). 对齐/填充 # 解释一下05d和15d的区别 # 05d: 0代表使用0补位填充,5代表补足目标长度为5,d代表是整数 # 15d: 15代表补足目标长度为15,d代表是整数,因为前面没有0,所以不会使用0补足.
print(f"{123:05d}") # 长度为5,用0填充 print(f"{123:15d}") # 长度为15,用空格填充 # <15: 使用<代表在右侧填充空格填充,目标长度为15 print(f"{123:<15}") # 在右侧填充空格
# --------格式化方式二: str.format()方法-------- # str.format方法的使用说明 # 1). 基本用法 print("Name:{},Age:{}".format(name, age)) # 2). 指定位置(如将第一个参数放在第二个位置,第二个参数放在第一个位置) print("Age:{1}, Name:{0}".format(name, age)) # 3). 关键字参数(有点类似于命名参数) print("Name:{nameStr}, Age:{ageStr}".format(nameStr=name, ageStr=age)) # 4). 对齐/填充 print("保留两位小数: pi: {:.2f}".format(pi)) print("填充空格以对齐: pi: {:<10}".format(pi))
# --------格式化方式三: 经典方式,类C-------- # 注意语法是 print("<包含占位符的字符串>" % (<变量名列表>) print("Name:%s,Age:%d,pi:%.2f" % (name, age, pi)) # 常见占位符 %s: 字符串,%d: 整数, %f: 浮点数, %x: 十六进制 # 注意事项: # - 类型不匹配会报错 # - 参数数量不对会报错 # - 元组内元素支持表达式
if __name__ == '__main__': print_hi('world')支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





















