Python语言入门

Python语言入门

1 Python语言入门

1.1 Hello,world!

这是一个示例Python脚本.

python
# 定义了一个函数,名为print_hi
# def <函数名>(<形参列表>)
# <函数体>
def print_hi(name):
    # 函数体
    # 在这里,printf语句中,有一个模板字符串{<变量名>},
    #   printf会自动在该函数体中使用相应的变量替换这个模板
    # 注意,在字符串前,有一个f,这代表后面这个字符串是一个模板字符串.
    print(f"Hello,{name}")

if __name__ == '__main__':
    print_hi('world')

1.2 变量声明

在Python中没有真正的私有变量,若要使用私有变量,只能使用类似于约定的方式声明.

声明私有变量: _变量名. 以单下划线开头.

python

2 语法

2.1 数据类型

变量名使用全小写字母+下划线的方式命名

PS: Python不支持常量.

常用方法

python
address = id(<变量名>)	# 获取变量的逻辑地址
print(address)

数字类型

python
intVar = 10 #int
floatVar = 3.14 #float

print(intVar + 1)
print(floatVar / 3)

精度丢失问题及其解决方案

python
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
python
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)

字符串类型的声明方式主要有三种,单引号,双引号和三引号,三引号为三个单引号或三个双引号组成.

单引号与双引号声明的字符串,没有任何区别,仅仅只是定界符不同.

三引号声明的字符串,支持多行模式,即在代码中是什么样的,在程序运行时就是什么样的.

字符串是不可变的,尝试修改会报错,如果想修改字符串,会导致字符串变量的地址修改,即释放了旧字符串,新建了一个新字符串并由字符串变量指向它.

python
strVar1 = "hello"
strVar2 = 'world'
strVar3 = """多行
字符串,也常用于函数和类的文档注释
"""

print(f"{strVar1 + strVar2 + strVar3}")

字符串切片

python
# 字符串切片(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),这个方法很常用

字符串常用方法

python
# 获取字符串长度,字符长度,而非字节长度.即有几个字符.
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)

类型强转

python

邪门方法/语法

python
greeting2 = "Hello"" World"  # 结果: "Hello World" (仅限于字面量)

print(greeting2)

# 输出内容: Hello World
# 这个语法仅限字面量拼接,两个字面量直接使用引号隔开,编译器会将这些字面量拼接成一个.

列表(list)-[]

注意,列表不等于其它语言中的数组,但其使用方法相似.

不同点:

  • 一个列表中支持多种不同的对象,如数字,字符串等.
  • 自动扩容/缩容.
  • 自动垃圾回收.

可以简单理解为Java中的object类型的ArrayList,同时带有自动绑定类型的功能.

python
# 列表(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类型的数组,什么都能存.

常用方法

python
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}")  # 输出排序后列表
python
# 一些奇怪的语法

集合(set)-

set是无序的,去重的.

python
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
python
# 声明空集合,只有这一种方法.
empty_set = set()

集合数学运算

包括 并,差,交,对称差集

python
# 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))

集合关系运算

包括子集,超集

python

# 定义几个集合
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 的不可变版本,一旦创建,内容就不能改变。 因为不可变,所以它可以作为字典的键,或者作为另一个集合的元素。

python
# 创建一个 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很像,用于存储键值对.

python
person = {
    "name": "Alice",
    "age": 20
}
print(person)

# 修改/添加值
person["name"] = "bob"
print(person)
person["city"] = "New Zealand"
print(person)

高级用法

创建字典

python
# --- 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)

安全访问和修改字典

python
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)

字典合并

python
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}")

字典推导式

python
# --- 4. 字典推导式 (Dictionary Comprehension) ---
# 类似于列表推导式,用于简洁地创建新字典
# 例如,创建一个包含数字及其平方的字典
# {<表达式> for i in <可迭代对象>}
squares = {x: x ** 2 for x in range(1, 6)}
print("字典推导式(数字及其平方): ", squares)

字典的视图/常用方法

如获取键列表,获取值列表

python
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

python
# 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是用圆括号括起来的一组数据,是不可变的.

一般用于固定数据的返回或在一个函数中同时返回多个值.

python
# 元组 tuple
tupleVar = (1, 2, 3)
# 这里会提示 元组不支持项目赋值
# tupleVar[0] = 2;

# 一般用于固定数据或在一个函数中同时返回多个值,这有点和Go语言类似
def func():
    return 1, 2
x, y = func()
python
emptyTuple = ()  # 创建空元组方式一
emptyTuple = tuple()    # 创建空元组方式二

数组(array)-()

有,但不太推荐使用python提供的array包

日常用 list,搞数据/计算用 numpy,内置 array 基本可以忽略

text
# 实际上,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])

类型强制转换

python
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))
python
# 删除eval库
del __builtins__.__dict__['eval']  # 在python标准库中,删除eval函数

列表与元组转换时的拷贝

python
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除以yb / 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
# 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 输
python
# 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

运算符逻辑表达式描述实例
andx and y布尔"与”
- 如果 x 为可以定义为False的值,x and y 返回 x,否则它返回 y 的计算值.
(a and b) 返回 20.
orx or y布尔"或"
- 如果 x 是可以定义为True的值,它返回 x 的计算值,否则它返回 y 的计算值.
(a or b) 返回 10.
notnot x布尔"非"
- 如果 x 为 True,返回 False .如果 x 为 False,它返回 True.
not(a and b) 返回 False
运算符逻辑表达式描述实例
andx and y布尔"与"都为True返回True
orx or y布尔"或"都为False返回False
notnot x布尔"非"取反

成员运算符

成员运算符用于判断字符,元素等是否在字符串,列表或元组中.

运算符描述实例
in如果在指定的序列中找到值返回 True,否则返回 False。x 在 y 序列中 , 如果 x 在 y 序列中返回 True。
not in如果在指定的序列中没有找到值返回 True,否则返回 False。x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。
python
# 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身份运算符

运算符描述实例
isis 是判断两个标识符是不是引用自一个对象x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False
is notis not 是判断两个标识符是不是引用自不同对象x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果 True,否则返回 False。

is与==的区别:

is是用于判断两个变量是否引用了同一块内存地址,而==是用来判断两个变量是否相等

python
# 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在列表中的应用

python
# 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 函数

变量名和函数使用全小写字母+下划线的方式命名

标准函数调用

python
# main.py

# 标准函数调用
def describe_person(name, age):
    """描述一个人的基本信息"""
    description = f"{name} 今年 {age} 岁"
    return description


# 调用函数
person_info = describe_person("张三", 25)
print(person_info)  # 输出: 张三 今年 25 岁

带有默认形参的函数调用

形参可以指定默认值,指定默认值的参数在不传入的情况下,会自动使用指定的值.

默认形参只能放在最后,或者说,默认形参后,不能跟随非默认形参.

python
# 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 岁

多返回值的函数

一个函数可以返回多个值,可以使用接收参数列表接收,也可以使用单个参数接收,此时这个接收参数会变成一个元组.

python
# 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也都是引用传递,不过因为对象不可变,看起来像值传递,如

python
# main.py

# 修改不可变对象
def change_number(num):
    num = 999  # 重新赋值,不会影响原变量,此时num是一个新变量而不是形参传入的地址.
    print(f"函数内: {num}")


n = 10
print(f"调用前: {n}")
change_number(n)
print(f"调用后: {n}")  # 输出: 10
python
# 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

这些对象都形成了类似引用传递的效果,即形参变量在函数中修改后会影响到实参变量.

python
# 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}")  # ['小明', '小红', '小刚']
python
# 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`关键字可变参数: 接收任意数量的**关键字参数**,打包成字典.
python
# 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}

命名参数

可以通过指定参数关键字的形式,调换参数的顺序

但要注意,位置参数要在最前面

python
# main.py

def school_report(school_name, year=2024, ):
    print(f"学校: {school_name}, 年份: {year}")


# 位置参数要在命名参数前
school_report("第一中学",  # 位置参数
              year=2024,  # 命名参数
              )
# 输出:
# 学校: 第一中学, 年份: 2024

匿名函数

匿名函数是基于lambda函数实现的,其语法如下

python
lambda [arg1 [,arg2,.....argn]]:expression

示例

python
# main.py

# 匿名函数
sum_lambda = lambda x, y: x + y

# 调用sum函数
print(f"相加后的值为: {sum_lambda(10, 20)}")  # 相加后的值为: 30

2.4 结构

分支

if分支语法如下

python
if <表达式>
    <then >
[else
    <else >]

多个if分支时如下

python
if <表达式1>
    <then1 >
elif <表达式2>
	<then2 >
...
[else:
    <else >]

Python 3.1.0版本中新增match

基本语法

python
match <表达式>:
    case <>[if <守卫表达式>]:
        <case1>
    [case <>[if <守卫表达式>]:
        <case2>]
    ...
    [case _:
        <else>]

case中,可以放以下内容

  • 字面量
  • 序列
  • 字典

一些复杂的用法中,可以出现

  • or 或者条件: 允许多选一
  • as为模式整体起个别名
  • 守卫条件: 在模式匹配到之后进行一次if判断决定是否进入.
python
# 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类由以下部分组成

  • 类属性
  • 构造方法
  • 实例方法
  • 类方法
  • 静态方法
  • 特殊方法
sql
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

基本用法

python
# 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
python
# main.py
from person import Person
# 在这里,将alice这个字符串,传入到了person类中的__init__方法中.
p = Person("alice")
# 调用类的方法,打印字符.
print(p.get_name())

类方法

在java中,匿名对象是如下方法使用的

new person().getName(); // 用完就丢弃,没有被赋值给任何变量.

而Python中的匿名对象,实际上是一种类匿名对象,他只是声明了一个名为"匿名"的对象而不是真正的在内存方面都没有名字的对象.

创建实例(“类"匿名对象// 简单工厂方法)

类方法有一个cls特殊参数,代表了类本身.

类方法可以访问/修改类属性

子类调用时,会自动传入子类

一般用在工厂方法,替代构造函数上.

类方法需要使用@classmethod进行装饰.

示例1

python
# 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("匿名")
python
# main.py
from person import Person

# 使用类方法创建实例
# 注意,在这里传入的参数,被映射到了name属性上,而不是cls.
p2 = Person.create_anonymous()
print(p2.get_name())  # 匿名

示例2

python
# 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
python
# 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)  # 外国语中学

静态方法

静态方法没有特殊参数

不能访问类/实例属性

一般只作为工具函数辅助功能使用.

python
# 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
python
# 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会指向子类而不是父类.
python
# 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} 正在教学")
python
# 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 <子类名>(<父类名>)
  • 方法重写的语法: 无需装饰器,直接与父类同名,同参数即可.
python
# 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}"
python
# 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())
powershell
单继承与方法重写
我是 小明,是一名学生,今年 3 年级
我是 王老师,是一名老师,教授科目为 数学
小明 正在学习中...
王老师 正在教 数学

多态

python
# demo2.py
class Person:
    def introduce(self):
        return "我是一个普通人"
# 继承自Person
class Student(Person):
    def introduce(self):
        return "我是一名学生"
# 继承自Person
class Teacher(Person):
    def introduce(self):
        return "我是一名老师"
python
# 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)
powershell
多态演示
我是一名学生
我是一名老师
我是一个普通人

多继承与MRO

python与java不同的一点是,python是支持一个类继承多个类的,也即多继承.

所以,由此引出了一个问题,若一个子类的方法,在调用父类的方法是,要遵循什么样顺序来调用父类的方法,如果同名呢?

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]
python
# 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__]}")

抽象基类

什么是抽象基类?

抽象基类定义了一组方法,要求继承自该抽象基类的子类,必须实现该类的抽象方法.

抽象基类中,也可以同时存在以下内容:

  • 普通方法
  • 抽象方法
  • 类方法
  • 抽象类方法
  • 静态方法
  • 抽象静态方法
python
# 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方法

python
# 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
python
# 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}")

鸭子类型

什么是鸭子类型?

鸭子类型就是,不关心你是什么类型,只关心你有没有对应的方法.

用鸭子来理解就是,我不关心你是不是鸭子,只要你会嘠嘠叫,那么你就是鸭子,甚至如果你是一只会嘎嘎叫的机器鸭子,你也是鸭子.

python
# 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}:呱呱!"
python
# 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]支持切片或索引访问

类型判断

python
# demo9.py

class People:
    pass

class Student(People):
    pass

class Teacher(People):
    pass
python
# 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在访问对象的属性时,会遵循以下的顺序

txt
实例的属性 -> 类的属性 -> 父类的属性 -> 父类的父类的属性 -> ... -> 没有找到,报错AttributeError.

简单示例

python
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(又回到了类属性)

包含有父类的示例

python
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方法

基本语法

python
print("<要打印的内容>")

扩展语法

print方法中,可以对要输出的内容进行格式化,主要有三种方式,最常用的是第一种: f"模板字符串”

可以运行下面的代码,来体验print方法的用途

python
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')