python面向对象
顺序与运算
内置数据类型
类型 | 描述 | 举例 |
---|---|---|
int | 整数 | 123 |
float | 浮点数 | 1.23, 1.24e5 |
str | 字符串 | “python”, ‘acwing’ |
bool | 布尔值 | True, False |
list | 列表 | [1, 2, 3] |
tuple | 元组 | (1, 2, 3) |
set | 集合 | {1, 2, 3} |
dict | 字典 | {1: “python”, “acwing”: 2, 3: 4.0} |
注意int 支持高精度
运算符
类别 | 运算符 | 描述 | 示例 | 结果 |
---|---|---|---|---|
算术运算符 | + |
加法 | 5 + 3 |
8 |
- |
减法 | 10 - 2 |
8 |
|
* |
乘法 | 3 * 4 |
12 |
|
/ |
除法 | 10 / 3 |
3.333... |
|
% |
取模 | 10 % 3 |
1 |
|
** |
幂运算 | 2 ** 3 |
8 |
|
// |
整除 | 10 // 3 |
3 |
|
比较运算符 | == |
等于 | 5 == 5 |
True |
!= |
不等于 | 5 != 3 |
True |
|
> |
大于 | 5 > 3 |
True |
|
< |
小于 | 5 < 3 |
False |
|
>= |
大于等于 | 5 >= 5 |
True |
|
<= |
小于等于 | 5 <= 3 |
False |
|
逻辑运算符 | and |
逻辑与 | True and False |
False |
or |
逻辑或 | True or False |
True |
|
not |
逻辑非 | not True |
False |
|
赋值运算符 | = |
赋值 | x = 5 |
x = 5 |
+= |
加法赋值 | x += 2 (等价于x = x + 2 ) |
x = 7 |
|
-= |
减法赋值 | x -= 3 |
x = 4 |
|
*= |
乘法赋值 | x *= 2 |
x = 8 |
|
/= |
除法赋值 | x /= 4 |
x = 2.0 |
|
//= |
整除赋值 | x //= 3 |
x = 2 |
|
%= |
取模赋值 | x %= 3 |
x = 1 |
|
**= |
幂赋值 | x **= 2 |
x = 4 |
|
位运算符 | & |
按位与 | 0b1010 & 0b1100 |
0b1000 (8) |
\| |
按位或 | 0b1010 \| 0b1100 |
0b1110 (14) |
|
^ |
按位异或 | 0b1010 ^ 0b1100 |
0b0110 (6) |
|
~ |
按位取反 | ~0b1010 |
-0b1011 (-11) |
|
<< |
左移 | 0b1010 << 2 |
0b101000 (40) |
|
>> |
右移 | 0b1010 >> 2 |
0b10 (2) |
|
成员运算符 | in |
检查是否包含 | 3 in [1, 2, 3] |
True |
not in |
检查是否不包含 | 4 not in {1, 2, 3} |
True |
|
身份运算符 | is |
判断是否为同一对象 | a = [1]; b = a; a is b |
True |
is not |
判断是否不为同一对象 | a = [1]; b = [1]; a is not b |
True |
类型转换
浮点数和整数之间的隐式类型转换是合法的,整数会转换为浮点数。但在绝大部分情况下,Python 不能进行隐式数据类型转换。
1 |
|
也可以运用int()、float()、str()等函数强制转换类型。例如:
1 |
|
输入输出
input()
函数
input()
函数用于从标准输入读取用户输入的数据,它会暂停程序的执行,等待用户输入内容,直到用户按下回车键。函数返回一个字符串类型的值,包含用户输入的所有内容。
1 |
|
在上述代码中,input()
函数会先显示提示信息
"请输入你的名字:"
,然后等待用户输入。用户输入完成并按下回车键后,输入的内容会被赋值给变量
name
,最后通过 print()
函数输出问候语。
2. 输入数据类型处理
由于 input()
函数返回的是字符串类型,如果需要使用其他数据类型,需要进行显式类型转换。
转换为整数
1 |
|
注意:如果用户输入的内容不能转换为整数,会抛出
ValueError
异常。
转换为浮点数
1 |
|
3. 处理多个输入
可以通过字符串的分割方法处理多个输入,例如输入多个用空格分隔的数字。
1 |
|
print()
函数
print()
函数用于将对象的字符串表示形式输出到标准输出(通常是控制台)。可以接受一个或多个参数,参数之间用逗号分隔,输出时会用空格分隔各个参数。
f-string 格式化(Python 3.6+)
f-string 是一种简洁、直观的字符串格式化方法,在字符串前加上
f
或 F
,并在字符串中使用 {}
包含变量名。
1 |
|
还可以在 {}
中使用表达式:
1 |
|
默认回车结尾,如果想不以换行结尾,可以用如下表达式
1 |
|
判断与逻辑
- if和else后需要加上冒号:
- if语句的代码块需要缩进统一长度,规范写法是缩进4个空格。
- 注意:else if要写成elif
- pass 语句不执行任何动作。
if语句内部的变量,可以在语句外访问。例如:
1 |
|
在 Python 中,变量的作用域是基于代码块的,但是 Python 并没有像 C++ 那样严格的块级作用域。在 C++ 里,if、for、while 等语句块内部定义的变量,其作用域仅限于该语句块;而在 Python 中,函数、类和模块会创建新的作用域,if、for、while 等语句块不会创建新的作用域。
条件表达式
- 与 and
- 或 or
- 非 not 三目运算
1 |
|
Python中交换两个变量,可以用:a, b = b, a。
Python中的比较运算符支持链式操作,这一点跟C++和Java等语言不同。例如,给三个数排序的代码可以这么写:
1
2
3
4
5a, b, c = map(int, input().split())
x, y, z = a, b, c
if a >= b >= c:
print(c, b, a)
循环语句
while循环
while是每次判断,如果条件成立,则执行循环体中的语句,否则停止。 - while后需要加上冒号: - while语句的代码块需要缩进统一长度,规范写法是缩进4个空格。
1 |
|
for循环
遍历 range
range()函数可以生成等差数列,可以接收1个、2个或者3个整数参数:
1.接收1个整数参数时:range(x) 会按顺序返回0, 1, 2, 3, …x − 1这个数列。
2.接收2个整数参数时:range(x,y) 会按顺序返回x, x + 1, x + 2, …, y − 1这个数列。
3.接收3个整数参数时:range(x,y,z) 分为两种情况:
z>0 时,按顺序返回x, x + z, x + 2z, x + 3z, …这个数列中小于y的所有数。
z<0 时,按顺序返回x, x + z, x + 2z, x + 3z, …这个数列中大于y的所有数。
1 |
|
设计哲学
思想
1. 基于迭代器模式(与 C++ 的索引循环不同)
直接遍历可迭代对象:无需手动管理索引,直接遍历列表、字典、字符串等。
1
2
3
4
5
6
7
8
9
10
11
12# 遍历列表
for num in [1, 2, 3]:
print(num) # 输出 1, 2, 3
# 遍历字符串
for char in "hello":
print(char) # 输出 h, e, l, l, o
# 遍历字典键
d = {'a': 1, 'b': 2}
for key in d:
print(key) # 输出 'a', 'b'对比 C++:
C++ 的for
循环通常基于索引(如for(int i=0; i<n; i++)
),而 Python 的for
更接近 C++11 的范围循环(for(auto& x : container)
)。
2. 支持任何可迭代对象
- 可迭代对象:列表、元组、字典、集合、字符串、文件对象、生成器等。
1
2
3
4
5
6
7
8
9# 遍历文件行
with open("data.txt") as f:
for line in f:
print(line.strip())
# 遍历生成器
squares = (x**2 for x in range(5))
for num in squares:
print(num) # 输出 0, 1, 4, 9, 16
3. 自动处理迭代细节
- 无需手动控制索引或迭代器失效:Python
的迭代器自动管理迭代状态。
1
2
3
4# 无需担心索引越界
lst = [10, 20, 30]
for item in lst:
print(item) # 安全遍历
二、技巧
1. 使用
enumerate
同时获取索引和值
场景:需要遍历元素并获取索引(类似 C++ 的
for(int i=0; ...)
)。1
2
3
4
5
6
7fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"Index {index}: {fruit}")
# 输出:
# Index 0: apple
# Index 1: banana
# Index 2: cherry指定起始索引:
1
2for idx, val in enumerate(fruits, start=1):
print(f"#{idx}: {val}")
2. 使用 zip
并行遍历多个序列
场景:同时遍历多个列表/元组(类似 C++ 的多个迭代器)。
1
2
3
4
5
6
7
8names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# 输出:
# Alice is 25
# Bob is 30
# Charlie is 35处理不等长序列:默认以最短序列为准,可用
itertools.zip_longest
填充。
3. 列表推导式(List Comprehension)
场景:简化循环创建列表的操作。
1
2
3
4
5
6
7# 传统写法
squares = []
for x in range(5):
squares.append(x**2)
# 列表推导式
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]嵌套循环:
1
2pairs = [(x, y) for x in [1,2] for y in [3,4]]
# [(1,3), (1,4), (2,3), (2,4)]
4. 生成器表达式(Generator Expression)
场景:处理大数据时节省内存(惰性计算)。
1
2
3
4
5
6# 生成器表达式(不立即生成全部数据)
gen = (x**2 for x in range(1000000))
for num in gen:
if num > 100:
break
print(num)
5. 遍历字典的键、值或键值对
直接遍历键:
1
2
3d = {'a': 1, 'b': 2}
for key in d:
print(key) # 输出 'a', 'b'遍历键值对:
1
2for key, value in d.items():
print(f"{key}: {value}")
三、陷阱
不要在循环中修改正在迭代的列表
错误示例:
1
2
3
4
5lst = [1, 2, 3, 4]
for x in lst:
if x % 2 == 0:
lst.remove(x) # 导致跳过元素
# 结果可能为 [1, 3]正确做法:遍历副本或使用列表推导式。
1
2
3
4
5
6
7# 遍历副本
for x in lst.copy():
if x % 2 == 0:
lst.remove(x)
# 使用列表推导式
lst = [x for x in lst if x % 2 != 0]
四、与 C++ 的对比总结
特性 | Python for 循环 |
C++ for 循环 |
---|---|---|
迭代对象 | 任何可迭代对象(无需索引) | 通常基于索引或迭代器 |
内存效率 | 生成器支持惰性计算 | 需手动优化(如迭代器或范围循环) |
代码简洁性 | 高度简洁(如列表推导式) | 需要更多样板代码 |
作用域 | 循环变量在外部作用域可见 | 循环变量通常限定在循环块内 |
修改集合 | 需谨慎操作(建议遍历副本) | 可能引发迭代器失效(如vector ) |
列表
以下是关于 Python 列表的详细讲解,结合与 C++ 的对比,帮助你快速掌握其核心特性和使用技巧:
一、Python 列表的核心特性
1. 动态可变序列
- 动态扩容:列表长度可动态调整(无需预先声明大小),类似
C++ 的
std::vector
,但更灵活。 - 异构元素:允许存储不同类型的数据(如
[1, "hello", 3.14]
),而 C++ 容器要求元素类型一致。 - 有序性:元素按插入顺序存储,支持索引访问。
2. 底层实现
- 基于动态数组:Python 列表的底层是动态数组(类似 C++
的
vector
),通过连续内存存储元素。 - 扩容机制:当空间不足时,按一定策略(如容量翻倍)重新分配内存,保证均摊时间复杂度为 O(1)。
二、列表基本操作
1. 创建列表
1 |
|
做题常用:开一个n*m的列表 1
res = [[0 for j in range(m)] for i in range(n)]
2. 访问元素
- 正向索引:从
0
开始。 - 反向索引:从
-1
开始(倒数第一个元素)。
1 |
|
3. 切片操作
- 语法:
list[start:end:step]
。 - 返回新列表:原列表不受影响。
1 |
|
4. 修改元素
- 直接赋值:通过索引或切片。
1 |
|
三、常用方法
1. 增删元素
方法 | 功能 | 示例 |
---|---|---|
append(x) |
尾部添加单个元素 | lst.append(4) → [1,2,3,4] |
extend(iterable) |
尾部扩展多个元素(合并列表) | lst.extend([4,5]) → [1,2,3,4,5] |
insert(i, x) |
在索引 i 处插入元素 x |
lst.insert(1, 99) → [1,99,2,3] |
remove(x) |
删除第一个值为 x 的元素(无则报错) |
lst.remove(2) → [1,3] |
pop([i]) |
删除并返回索引 i 处的元素(默认尾) |
lst.pop(0) → 1 ,列表变为 [2,3] |
clear() |
清空列表 | lst.clear() → [] |
2. 查找与统计
方法 | 功能 | 示例 |
---|---|---|
index(x) |
返回第一个值为 x 的元素的索引 |
lst.index(2) → 1 |
count(x) |
统计值为 x 的元素个数 |
lst.count(2) → 1 |
in 操作符 |
检查元素是否存在 | 3 in lst → True |
3. 排序与反转
方法 | 功能 | 示例 |
---|---|---|
sort(key=None, reverse=False) |
原地排序(修改原列表) | lst.sort() → [1,2,3] |
sorted(lst) |
返回新排序列表(原列表不变) | sorted([3,1,2]) → [1,2,3] |
reverse() |
原地反转列表顺序 | lst.reverse() → [3,2,1] |
四、高级技巧
1. 列表推导式(List Comprehension)
简化循环创建列表:
1
2
3
4
5# 生成平方列表
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
# 条件过滤
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]嵌套循环:
1
pairs = [(i, j) for i in [1,2] for j in [3,4]] # [(1,3), (1,4), (2,3), (2,4)]
2. 浅拷贝与深拷贝
浅拷贝:仅复制引用(共享子对象)。
1
2
3
4lst1 = [1, [2, 3]]
lst2 = lst1.copy() # 浅拷贝
lst2[1][0] = 99
print(lst1) # [1, [99, 3]](共享内部列表)深拷贝:完全独立复制所有层级。
1
2
3
4import copy
lst3 = copy.deepcopy(lst1) # 深拷贝
lst3[1][0] = 100
print(lst1) # [1, [99, 3]](原列表不受影响)
五、性能分析(对比 C++)
操作 | Python 列表时间复杂度 | C++ std::vector 时间复杂度 |
说明 |
---|---|---|---|
访问元素 | O(1) | O(1) | 均基于索引直接访问 |
尾部追加元素 | O(1)(均摊) | O(1)(均摊) | 动态扩容策略类似 |
头部插入元素 | O(n) | O(n) | 需要移动所有元素 |
查找元素 | O(n) | O(n) | 线性遍历 |
切片操作 | O(k)(k 为切片长度) | 无直接支持 | C++ 需手动实现 |
字符串
函数
函数基础
基本形式
1 |
|
当函数定义完但还不想实现时,可以用pass占位符,来避免出现语法错误。
1 |
|
return语句终止当前正在执行的函数,返回到调用该函数的地方,并返回结果。
1 |
|
参数
在 Python
中,函数参数初始化包含多个方面的知识,下面将从默认参数、位置参数、关键字参数、可变参数(*args
和 **kwargs
)以及参数解包等几个重要角度详细展开讲解。
1. 位置参数
按照参数定义的顺序依次提供对应的值。
1 |
|
2. 默认参数(初始化)
默认参数允许在定义函数时为参数指定默认值。如果调用函数时没有为该参数提供值,就会使用默认值。
这里的 greeting
就是一个默认参数,默认值为
"Hello"
。当调用 greet
函数时只传入一个参数,greeting
就使用默认值;若传入两个参数,则使用传入的值。
注意事项:默认参数必须放在位置参数之后,这与C++相同
1 |
|
3. 关键字参数:参数名1=参数值1,参数名2=参数值2
关键字参数允许在调用函数时通过参数名来指定参数的值,这样做的好处是可以不按照参数定义的顺序传递参数。
1 |
|
4. 可变参数
*args
*args
用于接收任意数量的位置参数,它会将这些参数打包成一个元组。
1 |
|
在这个例子中,*args
接收了
1
、2
、3
、4
这几个参数,并将它们打包成一个元组 (1, 2, 3, 4)
。
**kwargs
**kwargs
用于接收任意数量的关键字参数,它会将这些参数打包成一个字典。
1 |
|
这里的 **kwargs
接收了
name
、age
、city
这几个关键字参数,并将它们打包成一个字典
{'name': 'Alice', 'age': 30, 'city': 'Los Angeles'}
。
5. 参数解包
在调用函数时,可以使用 *
和 **
对可迭代对象和字典进行解包。
可以用*
将列表解包为若干个位置参数
可以用**
将字典解包为若干个关键字参数(参数名1=参数值1,参数名2=参数值2)
1 |
|
*numbers
将列表 numbers
解包成位置参数,**person
将字典 person
解包成关键字参数。
高级性质
1.参数在函数定义时初始化,而非在函数调用时初始化
在这个例子中,lst在def函数定义时就被初始化为[],指向了内存中的一个具体实例
下面每一次调用函数都是在对这个实例不断的增删改查
1 |
|
在这个例子中,lst在函数调用过程中采被赋予了确定的值,这个lst是函数调用过程中创建的值,生命周期随着函数消失而消失
1 |
|
2.可变对象和不可变对象的参数传递机制不同
不可变对象(如数值、字符串):类似C++的值传递,函数内修改不影响外部变量。
1 |
|
可变对象(如列表、NumPy数组):类似C++的引用传递,函数内修改会影响外部,但重新赋值引用(不影响外部)
1 |
|
3.lambda表达式
lambda
表达式是 Python
中一种创建匿名函数的方式。它允许快速定义简单的、一次性使用的函数。
lambda
表达式的基本语法如下:
1 |
|
lambda
:关键字,用于声明这是一个lambda
表达式。参数列表
:和普通函数的参数列表类似,可以有零个或多个参数,多个参数之间用逗号分隔。表达式
:函数的返回值,lambda
表达式只能包含一个表达式,不能包含复杂的语句块。
例如,定义一个简单的 lambda
函数来实现两个数相加:
1 |
|
这里 lambda x, y: x + y
定义了一个匿名函数add,它接受两个参数 x
和
y
,并返回它们的和。
在一些内置函数(如
sorted()
、map()
、filter()
等)中,经常需要传入一个函数作为参数来定义排序规则或对元素进行处理。
lambda
表达式可以很方便地创建这种一次性使用的函数。以sort函数为例子
1
2
3
4
5
6
7
8
9pairs = [[1, "one"], [2, "two"], [3, "three"], [4, "four"]]
def compare(pair):
return pair[1]
pairs.sort(key=compare) # 每个元素使用第二个变量比较大小
print(pairs) # 输出:[[4, 'four'], [1, 'one'], [3, 'three'], [2, 'two']]
等价于下面的写法:
1 |
|
其他数据结构
1.元组
元组跟列表类似,只是不支持动态添加、删除元素,以及不能修改元素。
1.1 元组的初始化
元组需要用小括号括起来,中间的元素用逗号隔开。 注意,如果初始化只包含一个元素的元组,需要在该元素后添加逗号。
1 |
|
1.3 元组的比较运算
元组和列表均支持比较运算符:==、!=、>、<、>=、<=等,按字典序进行比较。
1.4 元组的其他操作
元组的下标访问元素、循环遍历、切片、加法和乘法运算等操作,都与列表相同。
2. 集合
集合是Python中最常用的数据结构之一,用来存储不同元素。 注意,集合中的元素是无序的。
2.1 集合的初始化
创建集合用花括号或set()函数。注意: - 创建空集合只能用set(),不能用{},因为{}创建的是空字典
集合常见的初始化方式: 1
2
3
4
5
6
7
8
9
10
11
12
13
14basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} # 会自动去除重复元素
print(basket) # 重复的元素已经去除了
s = set() # 初始化一个空列表
print(s)
a = [1, 2, 1, 3, 1]
b = set(a) # 将列表转化成集合,一般是为了去重。
c = list(b) # 将集合转化回列表
print(b, c)
x = "abracadabra"
a = set(x) # 将字符串中的每个字符存到集合中
print(a) # {'r', 'd', 'a', 'c', 'b'}
2.2 集合的常用操作
假设a表示一个集合。
- len(a) 返回集合中包含的元素数量。
- a.add(x) 在集合中添加一个元素。
- a.remove(x) 删除集合中的x,如果x不存在,则报异常。
- a.discard(x) 删除集合中的x,如果x不存在,则不进行任何操作。
- x in a 判断x是否在a中。
- x not in a 判断x是否不在a中。
1
2
3
4
5
6
7
8
9
10
11
12
13a = {1, 2, 3}
print(len(a)) # 输出3
a.add(4)
print(a) # 输出 {1, 2, 3, 4},注意集合中的元素是无序的。
a.remove(2)
print(a) # 输出 {1, 3, 4}
a.remove(5) # 因为5不存在,所以会报异常
a.discard(5) # 因为5不存在,所以不进行任何操作
print(a) # {1, 3, 4}
2.3 使用for循环遍历集合
类似于列表,集合也可以用for … in …的形式遍历。例如:
1 |
|
3. 字典
字典是Python中最常用的数据结构之一,用来存储映射关系。
key:value
字典中的元素是无序的。
字典是以key进行索引的,可以将每个key映射到某个value。
key必须是不可变类型,常用可以作为key的类型有数字和字符串。
value可以是任意类型。
3.1 字典的初始化
创建字典用花括号或dict()函数。 1
2
3
4
5
6
7
8
9
10
11tel = {'jack': 4098, 'sape': 4139} # 创建一个字典
print(tel) # 输出 {'jack': 4098, 'sape': 4139}
###########################################################
a = dict() # 创建一个空字典
a[123] = "abc" # 在字典中插入一个key-value对
a[456] = "def" # 在字典中插入一个key-value对
print(a) # 输出 {123: 'abc', 456: 'def'}
b = list(a) # 将字典的key转化成列表
print(b) # 输出[123, 456]
3.2 字典的常用操作
假设a表示一个字典。
1 |
|
例如:
1 |
|
3.3 使用for循环遍历字典
类似于列表,字典也可以用for … in …的形式遍历。例如:
1 |
|
面向对象
1. 类的定义
在 Python 中,使用 class
关键字来定义一个类,类名通常采用大写字母开头的驼峰命名法。
1 |
|
2. 类的属性和方法
类是一种重要的编程结构,它能够将数据(属性)和操作数据的代码(方法)封装在一起。类中的属性和方法大致分为三类,下面一一介绍
2.1 实例属性与实例方法
实例属性和实例方法是最普通的属性和方法。每个实例都的独立的属性即为实例属性,实例方法用于对这些实例属性进行操作。
构造函数(特殊的实例方法)
构造函数在创建类的实例时会自动被调用,其主要功能是对实例的属性进行初始化。在
Python 中,构造函数的名称固定为 __init__
。
第一个传入的参数必须是self
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Dog:
def __init__(self, name, age):
# self 代表类的实例本身
self.name = name
self.age = age
def bark(self):
print(f"{self.name} says woof!")
# 创建 Dog 类的实例
dog1 = Dog("Buddy", 3)
print(dog1.name) # 输出 "Buddy"
print(dog1.age) # 输出 3
dog1.bark() # 输出 "Buddy says woof!"
__init__
方法:这是一个特殊的实例方法。当我们使用Dog("Buddy", 3)
创建Dog
类的实例时,Python 会自动调用__init__
方法,并把新创建的实例对象传递给self
参数,同时将"Buddy"
和3
分别传递给name
和age
参数。通过self.name = name
和self.age = age
,为实例对象设置了name
和age
这两个实例属性。bark
方法:这是一个普通的实例方法。在方法内部,通过self.name
可以访问实例的name
属性,并打印出相应的信息。实例方法只能通过类的实例来调用,若使用类名直接调用实例方法,会因缺少实例对象作为第一个参数而报错。例如,若执行Dog.bark()
会产生错误。
2.2 类属性与类方法
类属性和类方法是与类本身相关联的。与不同的实例无关。类属性被所有类的实例共享,类方法则用于访问和操作这些类属性。
类方法既可以通过类调用,也可以通过实例调用
注意以下三点 - 类方法必须对类属性操作 -
类方法传入的第一个参数必须是cls
-
类方法之前使用@classmethod
装饰器
1 |
|
species
:这是一个类属性,它存储了狗的物种信息。无论创建多少个Dog
类的实例,它们访问的species
属性都是同一个值。get_species
方法:这是一个类方法,使用@classmethod
装饰器进行定义。类方法的第一个参数通常命名为cls
,它代表类本身。通过cls.species
,我们可以访问类的species
属性。类方法的调用方式较为灵活,既可以通过类名直接调用,也可以通过类的实例调用,Python 会自动将类对象传递给cls
参数。
2.3 静态方法
静态方法与类和实例都没有直接的关联,它不访问或修改类和实例的属性。静态方法通常用于执行一些通用的、与类或实例状态无关的操作。
1 |
|
info
方法:这是一个静态方法,使用@staticmethod
装饰器进行定义。静态方法没有特殊的第一个参数,它就像一个普通的函数,不依赖于类或实例的状态。静态方法的调用方式和类方法一样灵活,既可以通过类名Dog
调用,也可以通过类的实例dog1
调用。
总结表格
方法类型 | 定义方式 | 第一个参数 | 调用方式 | 用途 |
---|---|---|---|---|
实例方法 | 普通定义,无特殊装饰器 | self (代表实例对象) |
只能通过实例调用 | 操作实例的属性,实现与特定实例相关的行为 |
类方法 | 使用 @classmethod 装饰器 |
cls (代表类本身) |
可通过类名或实例调用 | 访问和修改类属性,执行与类相关的操作 |
静态方法 | 使用 @staticmethod 装饰器 |
无特殊第一个参数 | 可通过类名或实例调用 | 执行通用的、与类或实例状态无关的操作 |
2.4 私有属性和私有方法
在 Python 中,通过在属性名或方法名前加上双下划线 __
来定义私有属性和私有方法。私有属性和私有方法不能在类的外部直接访问,只能在类的内部使用。
私有实例属性和私有实例方法 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Dog:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有实例属性
def __display_age(self): # 私有实例方法
print(f"{self.name} is {self.__age} years old.")
def show_age_info(self):
self.__display_age()
# 创建 Dog 类的实例
dog1 = Dog("Buddy", 3)
# 以下代码会报错,不能直接访问私有实例属性和方法
# print(dog1.__age)
# dog1.__display_age()
dog1.show_age_info() # 可以通过公共实例方法间接访问私有实例方法
- __age:这是一个私有实例属性,每个实例都有自己独立的该属性副本。它只能在类的内部通过其他实例方法访问。
- __display_age:这是一个私有实例方法,只能在类的内部被调用,比如通过公共实例方法 show_age_info 间接调用。
3. 继承
继承是面向对象编程的一个重要特性,它允许你创建一个新的类(子类),继承另一个类(父类)的属性和方法。 子类可以重写父类的方法,也可以添加新的属性和方法。
1 |
|
Animal
类:这是一个父类,定义了__init__
方法和speak
方法。Dog
类:这是一个子类,继承了Animal
类的属性和方法,并重写了speak
方法。
4. 多态
多态是指不同的对象可以对同一个消息做出不同的响应。在 Python 中,多态通常通过继承和方法重写来实现。
1 |
|
make_animal_speak
函数:它接受一个Animal
类的对象作为参数,并调用该对象的speak
方法。由于Dog
和Cat
类都重写了speak
方法,所以可以对同一个消息(调用speak
方法)做出不同的响应。
5. 封装
封装是指将数据和操作数据的方法隐藏在类的内部,只对外提供必要的接口。在
Python
中,通过使用私有属性和私有方法来实现封装。私有属性和私有方法以双下划线
__
开头。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Deposited {amount}. New balance: {self.__balance}")
else:
print("Invalid deposit amount.")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"Withdrew {amount}. New balance: {self.__balance}")
else:
print("Invalid withdrawal amount.")
# 创建 BankAccount 类的实例
account = BankAccount(1000)
account.deposit(500) # 输出 "Deposited 500. New balance: 1500"
account.withdraw(200) # 输出 "Withdrew 200. New balance: 1300"__balance
:这是一个私有属性,只能通过类的方法来访问和修改。
- deposit
和 withdraw
方法:这是对外提供的接口,用于存款和取款操作。
包
一、基础概念
在 Python 里,模块是组织代码的一种方式,一个 .py
文件就是一个模块。例如,abc.py
就是名为 abc
的模块,xyz.py
就是名为 xyz
的模块。模块能把相关的代码组合在一起,方便复用和管理。
1.1 模块的导入
在 Python 中,使用 import
语句来导入模块。下面是几种常见的导入方式:
1.1.1 直接导入整个模块
1 |
|
这里通过 import math
导入了 Python 标准库中的
math
模块,之后就可以使用 math
模块里的函数,像 sqrt()
函数用于计算平方根。
1.1.2 导入模块中的特定函数或变量
1 |
|
使用 from math import sqrt
语句,只导入了
math
模块中的 sqrt
函数,这样在代码里就可以直接使用 sqrt
函数,而不需要加上
math.
前缀。
1.1.3 导入模块并起别名
1 |
|
通过 import math as m
为 math
模块起了别名
m
,之后就可以使用 m
来调用 math
模块中的函数和变量。
二、包的使用
当模块数量增多,为避免模块名冲突,可使用包来组织模块。包是包含多个模块的目录,每个包目录下必须有一个
__init__.py
文件(Python 3.3
之后,该文件不是严格必需的,但为了兼容性和清晰性,建议保留),这个文件可以为空,也可以包含
Python 代码。
2.1 包的目录结构示例
1 |
|
在这个结构中,mycompany
是顶层包名,只要这个顶层包名不与其他包冲突,abc.py
和
xyz.py
的模块名就分别变为 mycompany.abc
和
mycompany.xyz
。
2.2 多级包结构示例
1 |
|
在这个多级包结构中,www.py
的模块名是
mycompany.web.www
,两个 utils.py
文件的模块名分别是 mycompany.utils
和
mycompany.web.utils
。
2.3 包中模块的导入
2.3.1 导入包中的模块
1 |
|
2.3.2 从包中导入特定模块
1 |
|
2.3.3 从包的子模块中导入特定函数
1 |
|
三、模块的搜索路径
当使用 import
语句导入模块时,Python
会按照以下顺序在指定的路径中搜索模块: 1.
当前目录:也就是运行 Python 脚本的目录。 2.
环境变量 PYTHONPATH
中指定的目录:可以通过设置 PYTHONPATH
环境变量来添加额外的模块搜索路径。 3. Python
安装的默认库目录:包含 Python 标准库和第三方库。
可以使用 sys.path
查看 Python 的模块搜索路径:
1
2
3import sys
print(sys.path)
四、__name__
属性
每个模块都有一个 __name__
属性,它在不同情况下有不同的值: -
当模块作为脚本直接运行时,__name__
的值为
'__main__'
。 -
当模块被其他模块导入时,__name__
的值为模块名(文件名)
利用 __name__
属性,可以在模块中编写测试代码,这些代码只有在模块作为脚本直接运行时才会执行:
1
2
3
4
5
6
7# test_module.py
def add(a, b):
return a + b
if __name__ == '__main__':
result = add(3, 5)
print(f"测试结果: {result}")if __name__ == '__main__':
块中的代码只有在直接运行 test_module.py
时才会执行,当该模块被其他模块导入时,这部分代码不会执行。
文件结构:以机器学习为例
对于机器学习科研项目,一个良好的文件夹结构能够帮助更高效地管理代码、数据、模型和实验结果。以下是一个典型的机器学习科研项目文件夹结构及其各部分的详细解释:
1 |
|
各部分详细解释
1. data/
raw/
: 用于存放从数据源获取的原始数据,例如 CSV 文件、图像文件、文本文件等。这些数据未经任何处理,保持原始状态,方便追溯和重复实验。processed/
: 存放经过预处理后的数据,如归一化、编码、划分训练集和测试集后的数据。这些数据可以以常见的数据格式保存,如 NumPy 数组(.npy
)或 Pandas DataFrame(.csv
)。
2. models/
architectures/
: 包含各种模型的定义代码。例如,在深度学习项目中,可以定义不同结构的神经网络模型;在传统机器学习项目中,可以定义决策树、支持向量机等模型。saved_models/
: 保存训练好的模型参数。根据使用的框架不同,模型可以保存为不同的格式,如 PyTorch 的.pth
文件、Scikit-learn 的.pkl
文件。
3. notebooks/
- 存放 Jupyter Notebook 文件/markdown文件,用于进行数据探索性分析(EDA)、模型实验和可视化。这些 Notebook 可以记录你的思考过程、实验结果和分析结论,方便后续查看和分享。
4. scripts/
(脚本目录)
data_preprocessing.py
: 包含数据预处理的脚本,如数据清洗、特征工程、数据划分等操作。model_training.py
: 用于训练模型的脚本,可以包含模型的初始化、训练循环、优化器的设置等。model_evaluation.py
: 对训练好的模型进行评估的脚本,计算各种评估指标,如准确率、召回率、F1 值等。
5. results/
figures/
: 保存实验过程中生成的可视化图表,如准确率曲线、损失曲线、混淆矩阵图等。这些图表可以直观地展示模型的性能和训练过程。metrics/
: 存放模型评估的指标结果,如分类报告、混淆矩阵等。可以以文本文件或 CSV 文件的形式保存,方便后续分析和比较。
6. configs/
- 存放实验的配置文件,通常使用 YAML 或 JSON 格式。配置文件可以包含模型的超参数、数据路径、训练设置等信息,方便不同实验之间的参数调整和管理。
7. utils/
general_utils.py
: 包含一些通用的工具函数,如文件操作、日志记录、随机种子设置等。visualization_utils.py
: 专门用于可视化的工具函数,如绘制图表、展示图像等。
8. tests/
- 包含对代码各部分进行单元测试的脚本,确保代码的正确性和稳定性。例如,对数据预处理函数、模型架构进行测试。
9. README.md
- 项目的说明文档,介绍项目的背景、目的、使用方法、依赖环境等信息,方便其他人员快速了解和使用你的项目。
10. requirements.txt
- 列出项目所依赖的第三方库及其版本号,方便其他人在新环境中安装相同的依赖,确保项目的可复现性。
通过这样的文件夹结构,你可以将不同功能的代码和数据进行清晰的划分,提高项目的可维护性和可扩展性,更高效地进行机器学习科研工作。