Python进阶笔记(一)
面向对象编程
在之前的Python类编程中,只知道类似的self调用类内方法,今天来学习一下类变量、类方法、静态方法。
类变量
来看一个类的例子:
1
2
3
4
5
6
  | 
class Student:
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
s1 = Student("Caicai", "male")
  | 
 
可以看到name和sex属性都代表了单个实例的信息,这时,假设我需要统计学生类的人数,也就是学生类实例的个数,我们就可以使用类变量的方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  | 
class Student:
    student_num = 0
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        Student.student_num += 1
s1 = Student("Caicai", "male")
print(f"Student.student_num: {Student.student_num}")
# Student.student_num: 0
  | 
 
在类下面添加的变量就是类变量,类变量的访问需要通过类直接进入。
假设我们将上述代码改成了如下形式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  | 
class Student:
    student_num = 0
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        # 执行该句时,python需要先取值再写入 
        self.student_num += 1 
s1 = Student("Caicai", "male")
print(f"Student.student_num: {Student.student_num}")
print(f"s1.student_num:{s1.student_num}")
# Student.student_num: 0
# s1.student_num:1
  | 
 
上述代码在执行self.student_num += 1时,python需要先取值再写入,而实例中并不存在student_num这个属性。因此在取值的时候,python实际上是在类里面获取了student_num的值,写入时给实例写入了这一属性,没有改变类内属性的值。因此我们需要统一通过类名进行访问类属性!!!
类方法
修正上述方法的表达后,我们增加一个添加学生数量的类方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  | 
class Student:
    student_num = 0
    # 构造方法
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        Student.student_num += 1
    # 类方法
    @classmethod
    def add_students(cls, add_num):
        cls.student_num += add_num
    # 类方法
    @classmethod
    def from_string(cls, info):
        name, sex = info.split(" ")
        return cls(name, sex) # 返回类方法
s1 = Student("Caicai", "male")
s2 = Student.from_string("Caicai male")
print(f"Student.student_num: {Student.student_num}")
# Student.student_num: 2
  | 
 
类方法需要使用装饰器@classmethod来装饰,类方法的第一个参数通常为类本身class的缩写cls,它可用于访问类变量。
上述的类通过from_string类方法解析不同的格式来返回构造函数,增加了不同的初始化方法。使用类方法来替代构造方法是一种很常见的方法。
静态方法
静态方法需要使用@staticmethod来装饰,静态方法不需要传入cls或者self,它同样不能访问类和实例里面的私有属性。静态方法的适用场景:该静态方法不需要类里面的内容,但在逻辑上这个静态方法需要在类的里面,同样也适用于一系列功能相关的函数封装在一起。
来看一个例子:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  | 
class Student:
    student_num = 0
    # 构造方法
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex
        Student.student_num += 1
    # 类方法
    @classmethod
    def add_students(cls, add_num):
        cls.student_num += add_num
    # 类方法
    @classmethod
    def from_string(cls, info):
        name, sex = info.split(" ")
        return cls(name, sex) # 返回类方法
    # 静态方法
    @staticmethod
    def name_len(name):
        return len(name)
s1 = Student("Caicai", "male")
s2 = Student.from_string("Caicai male")
print(f"s2.name:{s2.name}, s2.name.len:{Student.name_len(s2.name)}")
# s2.name:Caicai, s2.name.len:6
  | 
 
静态方法在外部必须使用类本身来访问,在类内则可以通过self来访问。
推导式
推导式是一种高效创建列表、字典、集合或者其他可迭代的对象的方式,极大了压缩了代码的长度。
列表
先看一个列表复制的例子:
1
2
3
4
5
6
  | 
nums = [0, 1, 2, 3, 4, 5]
my_list = []
for i in nums:
    my_list.append(nums[i])
print(my_list)
  | 
 
上述为普通的写法,如果使用推导式,它的代码将简化为:
1
2
  | 
my_list = [i for i in nums]
print(my_list)
  | 
 
再来看一个略微复杂的例子:
1
2
3
4
5
6
  | 
nums = [0, 1, 2, 3, 4, 5]
my_list = []
for i in nums:
    my_list.append(i**2)
print(my_list)
  | 
 
它的推导式写法可变为:
1
2
  | 
my_list = [i**2 for i in nums]
print(my_list)
  | 
 
在循环中加入条件判断
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  | 
# 1
nums = [0, 1, 2, 3, 4, 5]
my_list = []
for i in nums:
    if i % 2 == 0:
        my_list.append(i**2)
print(my_list)
# 2
nums = [0, 1, 2, 3, 4, 5]
my_list = [] 
for i in nums:
    if i % 2 == 0:
        my_list.append(i**2)
print(my_list)
  | 
 
注意只有if和有if-else的写法是不一样的
1
2
3
4
5
6
7
8
9
  | 
# 1
nums = [0, 1, 2, 3, 4, 5]
my_list = [i**2 for i in nums if i%2 == 0]
print(my_list)
# 2
nums = [0, 1, 2, 3, 4, 5]
my_list = [i**2 if i%2 == 0 else i**3 for i in nums]
print(my_list)
  | 
 
再加大一点难度,加入双层for循环:
1
2
3
4
5
6
7
8
  | 
letters = ["a", "b", "c"]
nums = [1, 2, 3]
my_list = []
for i in letters:
    for j in nums:
        my_list.append((i, j))
print(my_list)
  | 
 
推导式的写法为:
1
2
3
4
5
  | 
letters = ["a", "b", "c"]
nums = [1, 2, 3]
my_list = [(i, j) for i in letters for j in nums]
print(my_list)
  | 
 
字典
将列表中的元素一一配对:
1
2
3
4
5
6
7
  | 
letters = ["a", "b", "c"]
nums = [1, 2, 3]
my_dict = {}
for i, j in zip(letters, nums):
    my_dict[i] = j
print(my_dict)
  | 
 
推导式写法为:
1
2
  | 
# 注意切换为字典后写法的变化
my_dict = {i: j for i, j in zip(letters, nums)}
  | 
 
集合
集合的特点是无序,不重复。
举一个例子,使用频率没有前两个那么高:
1
2
3
4
5
  | 
l = [1,2,3,4,5,6,7,8,8]
my_set = set()
for i in l:
    my_set.add(i)
print(my_set)
  | 
 
1
2
3
4
  | 
l = [1,2,3,4,5,6,7,8,8]
my_set = set()
my_set = {i for i in l}
print(my_set)
  | 
 
总结
Python推导式非常适用于从零创建有序的一些迭代对象。但是推导式使用循环最多两层,再多不建议使用,因为比较容易造成代码可读性较差的问题,以及在代码调试上的一些难题。
生成器
生成器是一个在处理大量数据时较为有效的工具。它与列表是一种类似的工具,但两者的性能却完全不同。它们的主要区别:
Python中的生成器(Generator)和列表(List)是两种不同的数据结构,它们在内存使用、性能和使用场景上有显著的区别。以下是生成器和列表的主要区别:
- 
内存使用:
- 列表:列表在内存中存储所有的元素。如果列表很大,会占用大量的内存。
 
- 生成器:生成器是惰性计算的,它只在需要时生成下一个元素。这意味着生成器在内存中只存储生成下一个元素所需的状态,因此占用的内存非常少。
 
 
- 
性能:
- 列表:列表在创建时会立即计算并存储所有元素,因此访问列表中的元素非常快。
 
- 生成器:生成器在每次迭代时才计算下一个元素,因此生成器的创建和迭代速度可能比列表慢。
 
 
- 
迭代行为:
- 列表:列表可以多次迭代,因为所有元素都存储在内存中。
 
- 生成器:生成器只能迭代一次,因为元素是按需生成的,一旦生成并使用,生成器就无法再次生成相同的元素。
 
 
- 
语法:
- 列表:列表使用方括号 
[] 来定义,例如 [1, 2, 3, 4]。 
- 生成器:生成器使用圆括号 
() 来定义生成器表达式,或者使用 yield 关键字来定义生成器函数。例如:
1
2
3
4
5
6
7
  | 
# 生成器表达式
gen = (x for x in range(10))
# 生成器函数
def gen_func():
    for i in range(10):
        yield i
  | 
 
 
 
- 
使用场景:
- 列表:适用于需要多次访问、修改或查找元素的场景。
 
- 生成器:适用于需要处理大量数据或无限序列,且不需要多次访问所有元素的场景。
 
 
生成器适用于超大数据场景的读取上,还有大语言模型的流式输出也使用到了生成器的思想。
来看一个例子来学习生成器:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  | 
def square_numbers(nums):
    result=[]
    for i in nums:
        result.append(i * i)
    return result
def gen_numbers(nums):
    for i in nums:
        yield i * i
my_nums = square_numbers([1, 2, 3, 4, 5])
print(my_nums)
# [1, 4, 9, 16, 25]
my_gens = gen_numbers([1, 2, 3, 4, 5])
print("Running next function:")
print(next(my_gens))
print("Then running for loop:")
for gen in my_gens:
    print(gen)
print("Then running for loop:")
for gen in my_gens:
    print(gen)
# Running next function:
# 1
# Then running for loop:
# 4
# 9
# 16
# 25
# Then running for loop:
  | 
 
可以看到先使用next读取之后,for loop的读取便从4开始了,由此可见生成器生成的内容只允许读取一遍,所以再第二次遍历的时候已经变为了空值。
再举一个大语言模型的流式输出例子:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  | 
def stream_output(data, chunk_size=10):
    """
    生成器函数,用于逐个生成数据块
    :param data: 要处理的数据
    :param chunk_size: 每个数据块的大小
    """
    start = 0
    end = chunk_size
    while start < len(data):
        yield data[start:end]
        # 使用时间停顿来模拟大模型处理过程
        time.sleep(0.1)
        start = end
        end += chunk_size
# 示例数据
data = "This is a long piece of text that we want to stream out in chunks."
# 使用生成器进行流式输出
for chunk in stream_output(data):
    print(chunk)
  | 
 
匿名函数
匿名函数的应用场景:当我们在使用一个简单的,只会使用一次的函数,就可以使用匿名函数。匿名函数的关键字是lambda,来看一个使用的例子:
1
2
3
4
5
6
7
  | 
def add(a, b):
    return a + b
print(add(3, 4))
# 匿名函数写法
add = lambda a, b: a + b
print(add(3, 4))
  | 
 
匿名函数的构造:函数名 = lambda + 入参 + 返回值,入参如果没有可以省略。让我们来看一个更常见的写法:
1
2
3
  | 
my_list = [1, 2, 3, 4, 5]
new_list = list(map(lambda x: x**2, my_list))
print(new_list)
  | 
 
首先先解释一下map函数的作用,map函数接受一个函数和一个可迭代对象作为参数,对可迭代对象每个元素都应用这个函数,最后返回一个新的迭代器。
再看一个好用的例子,将字典中的value作为匿名函数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  | 
def user_logging(user):
    if user.level == 1:
        user.credits += 2
    elif user.level == 2:
        user.credits += 5
    elif user.level == 3:
        user.credits += 10
def user_logging_1(user):
    level_credit_map = {
        1: lambda x: x + 2,
        2: lambda x: x + 5,
        3: lambda x: x + 10
    }
    user.credits = level_credit_map[user.level](user.credits)
  | 
 
 
                    
                    
		            最后修改于 2024-06-23
                    
                    
                    
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。