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 #int
floatVar = 3.14 #float
print(intVar + 1)
print(floatVar / 3)精度丢失问题及其解决方案
from decimal import Decimal
z = 0.1 + 0.2
print(z) # 期待0.3,实际0.30000000000000004,因为这里有精度丢失问题
# 使用Decimal将0.1和0.2的字面量包裹起来.
x = Decimal("0.1") # 传入的0.1是一个字符串的字面量
y = Decimal("0.2") # 同上
z = x + y
print(z)布尔类型(boolean)
# 在这里,有必要添加一个知识点,value若为以下内容,python在进行逻辑比较时,会认为其为False, 其它情况为True.
# -None,"",0,[],{},false
boolVar = 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,默认步长为1
print(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() 找到返回索引,找不到返回 -1
print(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")) # True
print(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") # True
bool_val2 = bool("") # False
# 注意:这种方式将"True"转为True,"False"转为True,因为它们都是非空字符串。
# 如果想根据字符串内容转为对应的布尔值,需要特殊处理或使用ast.literal_eval
import ast
bool_val3 = ast.literal_eval(bool_str) # True
# 将表示列表/字典的字符串转为对应对象 (安全方式)
list_obj = ast.literal_eval(list_str) # [1, 2, 3]
# --- 其他类型转字符串 ---
num = 42
pi = 3.1415
is_true = True
my_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 = 30
greeting = 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 # 将第一个元素设置为100
print(arr)
arr.append(5) # 追加一个元素5
print(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()集合数学运算
包括 并,差,交,对称差集
# main.py
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)) # True
print("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 的不可变版本,一旦创建,内容就不能改变。 因为不可变,所以它可以作为字典的键,或者作为另一个集合的元素。
# 创建一个 frozenset
frozen_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方法中给定默认值,则返回None
print("获取'age'的值:", person.get("age")) # 20
print("获取不存在的键的值:",
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_info
print(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.RED
print(colorVar.value) # 1
print(colorVar.name) # RED
print(colorVar.a()) # RED
if colorVar == Color.RED:
print("红色的")元组(tuple)-()
tuple是用圆括号括起来的一组数据,是不可变的.
一般用于固定数据的返回或在一个函数中同时返回多个值.
# 元组 tuple
tupleVar = (1, 2, 3)
# 这里会提示 元组不支持项目赋值
# tupleVar[0] = 2;
# 一般用于固定数据或在一个函数中同时返回多个值,这有点和Go语言类似
def func():
return 1, 2
x, 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 np
a = 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") # True
bool_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 = 42
pi = 3.1415
is_true = True
my_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. |
链式比较
# main.py
# 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 输 |
# main.py
a = 60 # 60 = 0011 1100
b = 13 # 13 = 0000 1101
c = 0
# and
c = a & b # 12 = 0000 1100
print("a & b 的值为:", c)
# or
c = a | b # 61 = 0011 1101
print("a | b 的值为:", c)
# xor
c = a ^ b # 49 = 0011 0001
print("a ^ b 的值为:", c)
# not
c = ~a # -61 = 1100 0011
print("~a 的值为:", c)
# 左位移,后面的数字代表移动几位
c = a << 2 # 240 = 1111 0000
print("a << 2 的值为:", c)
# 右位移,后面的数字代表移动几位
c = a >> 2 # 15 = 0000 1111
print("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。 |
# main.py
a = 1
b = 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是用于判断两个变量是否引用了同一块内存地址,而==是用来判断两个变量是否相等
# main.py
a = 20
b = 20
# 解释: is是用于判断两个变量是否引用了同一块内存究竟
# 而==是用来判断两个变量是否相等
print(f"a is b:{a is b}")
b = 30
print(f"a is b:{a is b}")关于is在列表中的应用
# main.py
a = [1, 2, 3, 4, 5]
b = a
print(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 函数
变量名和函数使用全小写字母+下划线的方式命名
标准函数调用
# main.py
# 标准函数调用
def describe_person(name, age):
"""描述一个人的基本信息"""
description = f"{name} 今年 {age} 岁"
return description
# 调用函数
person_info = describe_person("张三", 25)
print(person_info) # 输出: 张三 今年 25 岁带有默认形参的函数调用
形参可以指定默认值,指定默认值的参数在不传入的情况下,会自动使用指定的值.
默认形参只能放在最后,或者说,默认形参后,不能跟随非默认形参.
# main.py
# 带有默认形参的函数调用
# age带有默认参数0
# 注意事项:
# - 默认形参只能放在最后,或者说,默认形参后,不能跟随非默认形参.
def describe_person(name, age=0):
"""描述一个人的基本信息"""
description = f"{name} 今年 {age} 岁"
return description
# 调用函数,此处未提供age形参.
person_info = describe_person("张三", )
print(person_info) # 输出: 张三 今年 25 岁多返回值的函数
一个函数可以返回多个值,可以使用接收参数列表接收,也可以使用单个参数接收,此时这个接收参数会变成一个元组.
# main.py
# 多返回值的函数调用
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也都是引用传递,不过因为对象不可变,看起来像值传递,如
# main.py
# 修改不可变对象
def change_number(num):
num = 999 # 重新赋值,不会影响原变量,此时num是一个新变量而不是形参传入的地址.
print(f"函数内: {num}")
n = 10
print(f"调用前: {n}")
change_number(n)
print(f"调用后: {n}") # 输出: 10# main.py
# 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
这些对象都形成了类似引用传递的效果,即形参变量在函数中修改后会影响到实参变量.
# main.py
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}") # ['小明', '小红', '小刚']# main.py
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`关键字可变参数: 接收任意数量的**关键字参数**,打包成字典.
# main.py
# *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}命名参数
可以通过指定参数关键字的形式,调换参数的顺序
但要注意,位置参数要在最前面
# main.py
def school_report(school_name, year=2024, ):
print(f"学校: {school_name}, 年份: {year}")
# 位置参数要在命名参数前
school_report("第一中学", # 位置参数
year=2024, # 命名参数
)
# 输出:
# 学校: 第一中学, 年份: 2024匿名函数
匿名函数是基于lambda函数实现的,其语法如下
lambda [arg1 [,arg2,.....argn]]:expression示例
# main.py
# 匿名函数
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判断决定是否进入.
# main.py
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"})) # 字典1
print(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基本用法
# person.py
# 命名规范
# 文件名使用 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# main.py
from person import Person
# 在这里,将alice这个字符串,传入到了person类中的__init__方法中.
p = Person("alice")
# 调用类的方法,打印字符.
print(p.get_name())类方法
在java中,匿名对象是如下方法使用的
new person().getName(); // 用完就丢弃,没有被赋值给任何变量.
而Python中的匿名对象,实际上是一种类匿名对象,他只是声明了一个名为"匿名"的对象而不是真正的在内存方面都没有名字的对象.
创建实例(“类"匿名对象// 简单工厂方法)
类方法有一个cls特殊参数,代表了类本身.
类方法可以访问/修改类属性
子类调用时,会自动传入子类
一般用在工厂方法,替代构造函数上.
类方法需要使用@classmethod进行装饰.
示例1
# person.py
# 命名规范
# 文件名使用 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("匿名")# main.py
from person import Person
# 使用类方法创建实例
# 注意,在这里传入的参数,被映射到了name属性上,而不是cls.
p2 = Person.create_anonymous()
print(p2.get_name()) # 匿名示例2
# demo5.py
class Person:
school = "第一中学"
def __init__(self, name: str):
self.name = name
# cls代表实例本身
@classmethod
def change_school(cls, new_name: str):
cls.school = new_name# main.py
from demo5 import Person
# 类方法可以访问类对象
print(Person.school) # 实验中学
# 方式1:通过类调用
Person.change_school("实验中学")
print(Person.school) # 实验中学
# 方式2:通过实例调用
p = Person("小明")
p.change_school("外国语中学")
print(p.school) # 外国语中学静态方法
静态方法没有特殊参数
不能访问类/实例属性
一般只作为工具函数辅助功能使用.
# demo5.py
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_school# main.py
from demo5 import Person
# 通过类调用静态方法(推荐)
print(Person.is_adult(20)) # True
print(Person.is_adult(15)) # False
# 通过实例调用静态方法
p = Person("小明") # 现在可以正常工作了
print(p.is_adult(20)) # True关于类方法与静态方法的实际应用场景
使用类方法创建不同方式的实例化(工厂模式)
使用静态方法进行数据验证、格式转换等独立操作
类方法更适合需要访问类状态的操作
静态方法更适合与类相关但不依赖类状态的工具函数
类方法在继承时的多态
当子类继承父类的 类方法 时,该方法的第一个参数 cls 会指向子类而不是父类.如以下示例:
- 在People(父类)中声明了一个create类方法,其有cls参数
- Student,Teacher(子类)继承了父类,但没有对create进行重写.
- 所以,在子类使用create进行实例化时,因为类方法具有多态性,所以cls会指向子类而不是父类.
# demo8.py
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} 正在教学")# main.py
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 <子类名>(<父类名>)
- 方法重写的语法: 无需装饰器,直接与父类同名,同参数即可.
# demo1.py
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}"# main.py
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 年级
我是 王老师,是一名老师,教授科目为 数学
小明 正在学习中...
王老师 正在教 数学多态
# demo2.py
class Person:
def introduce(self):
return "我是一个普通人"
# 继承自Person
class Student(Person):
def introduce(self):
return "我是一名学生"
# 继承自Person
class Teacher(Person):
def introduce(self):
return "我是一名老师"# main.py
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是支持一个类继承多个类的,也即多继承.
所以,由此引出了一个问题,若一个子类的方法,在调用父类的方法是,要遵循什么样顺序来调用父类的方法,如果同名呢?
# demo3.py
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,Teacher
class 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]# main.py
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__]}")抽象基类
什么是抽象基类?
抽象基类定义了一组方法,要求继承自该抽象基类的子类,必须实现该类的抽象方法.
抽象基类中,也可以同时存在以下内容:
- 普通方法
- 抽象方法
- 类方法
- 抽象类方法
- 静态方法
- 抽象静态方法
# demo4.py
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方法
# demo6.py
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_value# main.py
from 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}")鸭子类型
什么是鸭子类型?
鸭子类型就是,不关心你是什么类型,只关心你有没有对应的方法.
用鸭子来理解就是,我不关心你是不是鸭子,只要你会嘠嘠叫,那么你就是鸭子,甚至如果你是一只会嘎嘎叫的机器鸭子,你也是鸭子.
# demo7.py
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}:呱呱!"# main.py
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] | 支持切片或索引访问 |
类型判断
# demo9.py
class People:
pass
class Student(People):
pass
class Teacher(People):
pass# main.py
from demo9 import People, Student, Teacher
teacher = Teacher()
student = Student()
# isinstance(): 考虑继承,即如果teacher是继承自People的,那么也返回true
isinstanceVar = isinstance(teacher, People)
print(f"isinstance:{isinstanceVar}")
# type(): 不考虑继承,即只考虑精确比较,只有两个实例完全由同一个类实例化时,才会返回true
typeVar1 = type(teacher) == People
typeVar2 = type(teacher) == Teacher
print(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 = 999
print(demo_instance.k) # 999(实例属性)
print(Demo.k) # 100(类属性未受影响)
# 3. 删除实例属性 -> 恢复对类属性的访问
del demo_instance.k
print(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.k
print(ChildDemo.k) # 输出: 200,访问的是子类属性,也就是ChildDemo.k
print(Demo.k) # 输出: 100,访问的是父类属性,也就是Demo.k
# 移除实例属性
del child_demo.k # 移除了实例属性k(self.k)
print(child_demo.k) # 输出: 200,访问的是子类属性,也就是ChildDemo.k
del ChildDemo.k # 移除了子类属性k(self.k)
print(ChildDemo.k) # 输出: 100,访问的是父类属性,也就是Demo.k
del Demo.k
print(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')- 本文链接: https://go.kulve.tech/posts/python%E8%AF%AD%E8%A8%80%E5%85%A5%E9%97%A8/
- 版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。
