《Python Cookbook》的作者David Beazley的课程PPT开源了,目标用户是希望从编写基础脚本过渡到编写更复杂程序的高级 Python 程序员,课程主题侧重于流行库和框架中使用的编程技术,主要目的是更好地理解 Python 语言本身,以便阅读他人的代码,并将新发现的知识应用到自己的项目中。内容组织的很棒,总共分为九个章节,我在阅读过程中顺便翻译整理下,用来查缺补漏了。翻译内容并非原版无对照翻译,有所增减,本篇是系列第三节。
感兴趣可前往原课件进行阅读👉 https://github.com/dabeaz-course/python-mastery/blob/main/PythonMastery.pdf
LLM应用全栈开发
面对对象
-
面向对象编程主要关注“行为”的建模。
-
“对象”由一些内部状态组成,但更重要的是,它具有使其执行各种操作的方法。这些方法赋予对象个性。
-
数据和行为绑定在一起。
# Data host = ('www.python.org', 80) # Behavior c = Connection('www.python.org',80) c.open() c.send(data) c.recv() c.close()
类
类声明
只是定义(它本身不执行任何操作),类包含由该类的所有实例共享的定义
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.health = 100
def move(self, dx, dy):
self.dx += dx
self.dy += dy
def damage(self, pts):
self.health -= pts
-
类变量,通常用于应用于所有实例的设置
class SomeClass: debug = False def __init__(self, x): self.x = x # 两条访问路径 >>> SomeClass.debug False >>> s = SomeClass(42) >>> s.debug False
类方法
对类本身进行操作的方法,在类上调用的,而不是在实例上调用
class SomeClass:
@classmethod
def yow(cls):
print('SomeClass.yow', cls)
>>> SomeClass.yow()
SomeClass.yow <class '__main__.SomeClass'>
>>>
类方法经常被用作替代初始化的工具,通过类方法可以定义不同的初始化方式来适应不同的需求
class MyClass:
def __init__(self, value):
self.value = value
@classmethod
def from_string(cls, string):
# 从字符串中提取值并创建实例
value = string.upper()
return cls(value)
# 使用默认的初始化器创建实例
my_object1 = MyClass("Hello")
# 使用类方法作为替代初始化器创建实例
my_object2 = MyClass.from_string("World")
print(my_object1.value) # 输出: Hello
print(my_object2.value) # 输出: WORLD
当涉及到继承和方法重写时,类方法可以提供一些特殊的功能和解决方案。下面是一些具体的说明:
-
方法重写(Method Overriding):在继承中,子类可以重写父类的方法以满足自己的需求。但是,在某些情况下,子类可能希望在重写方法时保留父类方法的一部分逻辑。这时,可以使用类方法来定义一个与父类方法同名的方法,并在其中使用
super()
函数来调用父类方法。这样,子类可以在不完全覆盖父类方法的情况下,修改或扩展父类方法的行为。class Parent: def greet(self): print("Hello, I am the parent") class Child(Parent): def greet(self): super().greet() # 调用父类的greet方法 print("And I am the child") child = Child() child.greet() # 输出: # Hello, I am the parent # And I am the child
-
继承链中的属性访问:当子类需要访问继承链中的某个属性时,可以使用类方法来获取该属性的值。类方法可以通过类名访问父类的属性,以便在子类中使用。
class Grandparent: family_name = "Smith" class Parent(Grandparent): pass class Child(Parent): @classmethod def get_family_name(cls): return cls.family_name print(Child.get_family_name()) # 输出: Smith
静态方法
定义为类的一部分的函数,但不对实例或类进行操作
-
用于各种方法的实用函数:这些实用函数可以在类的不同方法中共享,并提供通用的功能支持。
class MathUtils: @staticmethod def square(number): return number ** 2 @staticmethod def cube(number): return number ** 3 def calculate_area(self, length, width): # 使用静态方法进行数学计算 area = MathUtils.square(length) * MathUtils.square(width) return area def calculate_volume(self, length, width, height): # 使用静态方法进行数学计算 volume = MathUtils.square(length) * MathUtils.square(width) * MathUtils.cube(height) return volume
-
实例管理/跟踪:静态方法可以用于实例的管理和跟踪。
class Car: total_cars = 0 def __init__(self): Car.total_cars += 1 @staticmethod def get_total_cars(): return Car.total_cars # 创建多个汽车实例 car1 = Car() car2 = Car() car3 = Car() # 获取汽车实例的总数 total_cars = Car.get_total_cars() print(total_cars) # 输出: 3
-
完成、资源管理:在类的实例被销毁之前,可以使用静态方法来执行一些清理或释放资源的操作。
-
特定设计模式:某些设计模式可能需要使用静态方法来实现特定的行为。例如,单例模式中的静态方法可以用于获取类的唯一实例。(使用单例模式的好处是,它可以确保在整个应用程序中只有一个类的实例存在,比如数据库连接、日志记录器等场景)
class Singleton: instance = None @staticmethod def get_instance(): if not Singleton.instance: Singleton.instance = Singleton() return Singleton.instance # 获取单例实例 singleton = Singleton.get_instance()
-
将相关功能组合在一个类中的静态方法可以可以更容易地理解和维护代码。
实例
-
实例是在程序中操作的实际“对象”
-
Created by calling the class as a function,通过将类作为函数调用来创建对象的过。
在许多编程语言中,类可以被看作是一种用于创建对象的蓝图或模板。当我们想要创建一个类的实例时,我们可以将该类作为函数调用。这个调用过程会触发类的构造函数(Constructor),构造函数会执行一些初始化操作,并返回一个新的对象,这个对象就是该类的一个实例。
-
每个实例都有自己的本地数据,属性总数和类型没有限制
>>> a = Player(2, 3) >>> b = Player(10, 20) >>> a.x 2 >>> b.x 10
实例方法
-
应用于对象实例的函数
-
对象始终作为第一个参数传递
class Player: ... def move(self, dx, dy): self.x += dx self.y += dy # 该实例称为“self” >>> a.move(1, 2) move(self, 1, 2):
-
实例方法是普通的函数定义,它只是接收实例作为第一个参数(self)
-
想要对实例进行操作,必须显式引用它(例如 self)
操作实例
-
对实例有三种操作
obj.attr # 获取属性 getattr(obj, 'name') obj.attr = value # 设置属性 setattr(obj, 'name', value) del obj.attr # 删除属性 delattr(obj, 'name') hasattr(obj, 'name')# 测试属性是否存在
-
未通过函数调用运算符 () 调用的方法被称为"未绑定方法",只有通过实例调用方法时,方法才与实例绑定在一起。
>>> s = Stock('ACME', 50, 91.1) >>> s <Stock object at 0x590d0> >>> c = s.cost >>> c <bound method Stock.cost of <Stock object at 0x590d0>> >>> c() 4555.0 >>>
类的封装
类的主要作用之一是封装对象的数据和内部实现细节,通过定义"public"接口,类提供了一种规范和约定,使得外部代码可以使用特定的方法和属性来操作对象。这种封装性和抽象性有助于构建模块化、可重用和易于理解的代码。
私有属性
任何以 _
开头的属性名称都被视为“私有”,然而,这只是一种编程风格,仍然可以访问。
class Base:
def __init__(self, name):
self._name = name
>>> b = Base('Guido')
>>> b._name
'Guido'
>>> b._name = 'Dave'
>>>
变体:带有两个前导 _ 的属性名称,在子类中不可见
class Base:
def __init__(self, name):
self.__name = name
class Child(Base):
def spam(self):
print('Spam', self.__name) # AttributeError
# 修改技巧
>>> b = Base('Guido')
>>> b._Base__name
'Guido'
>>>
properties
在面向对象编程中,属性(properties)对于创建具有一致的编程接口的对象非常有用。当设计一个类时,可以使用属性来封装对象的状态和行为。属性的使用可以带来以下几个好处:
-
一致的接口:通过使用属性,可以为对象提供一致的编程接口,使得其他代码可以以相同的方式访问和修改属性值。这样可以提高代码的可读性和可维护性。
-
封装性:属性允许我们隐藏对象的内部状态,并通过公共的getter和setter方法来控制对属性的访问和修改。这样可以保护对象的内部状态,防止直接访问和修改,从而提高代码的安全性和稳定性。
-
验证和计算:属性的getter和setter方法可以用于验证和计算属性的值。例如,可以在设置属性值之前对其进行验证,确保它满足某些条件。或者我们可以在获取属性值时进行计算,返回一个经过处理的值。这样可以增加代码的灵活性和可扩展性。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value >= 0:
self._age = value
else:
raise ValueError("Age must be a positive number")
# 创建一个Person对象
person = Person("Alice", 25)
# 通过属性访问和修改对象的状态
print(person.name) # 输出: Alice
print(person.age) # 输出: 25
person.name = "Bob"
person.age = 30
print(person.name) # 输出: Bob
print(person.age) # 输出: 30
类的继承
继承通常用作代码定制/可扩展性功能
class Shape:
def calculate_area(self):
pass
def calculate_perimeter(self):
pass
class Triangle(Shape):
def calculate_area(self):
# 三角形的面积计算逻辑
pass
def calculate_perimeter(self):
# 三角形的周长计算逻辑
pass
Cooperative Inheritance(合作式继承)
一种继承机制,它允许子类在继承父类的同时与父类进行合作,共同完成某个任务。在这种继承机制下,子类可以调用父类的方法,并在此基础上添加自己的实现。
Multiple inheritance(多重继承)
一个子类可以继承多个父类,这些父类可以是不同的类,它们之间没有直接的继承关系。子类会继承每个父类的属性和方法,并可以在此基础上添加自己的实现。多重继承的优点是可以从多个父类中继承不同的特性和功能,提供了更大的灵活性和复用性。然而,多重继承也可能带来一些问题,例如命名冲突和继承的复杂性。在使用多重继承时,需要注意解决命名冲突的问题,并仔细设计类之间的继承关系,以避免继承的复杂性导致代码难以理解和维护。
Mixin Classes(混合类)
Mixin 类有时被用作向更基本的对象添加可选功能的一种方式,例如,添加了线程支持、持久化等。
特殊方法
类可以自定义其行为的几乎每个方面,是通过特殊方法(dunder methods)完成的。下面这些特殊方法是常见且常用的,它们允许我们自定义类的行为,使其更符合我们的需求
__init__
: 初始化方法,在创建对象时被调用,用于初始化对象的属性。__str__
: 字符串表示方法,返回对象的字符串表示。__repr__
: 用于生成对象的官方字符串表示,通常用于调试和日志记录。__iter__
和__next__
: 实现对象的迭代器协议,使对象可迭代。__call__
: 将对象作为函数调用。
元素访问
len(x) x.__len__()
x[a] x.__getitem__(a)
x[a] = v x.__setitem__(a,v)
del x[a] x.__delitem__(a)
a in x x.__contains__(a)
弱引用 Weak References
弱引用(Weak references)在存在对象之间的引用循环时有时会被使用,引用循环指的是多个对象之间相互引用,导致它们无法被垃圾回收器正确地回收。在这种情况下,使用弱引用可以解决内存泄漏的问题,由weakref库模块支持。
>>> import weakref
>>> f = Foo()
>>> fref = weakref.ref(f)
>>> fref
<weakref at 0x4203c0; to 'Foo' at 0x41dff0>
>>> g = fref()
>>> print(g)
<__main__.Foo object at 0x41dff0>
弱引用的使用场景包括:
- 图形(graphs)和树(trees):在图形和树等数据结构中,对象之间经常存在相互引用的情况。使用弱引用可以避免引用循环,确保对象在不再被使用时能够被垃圾回收。
- 观察者模式(observers):在观察者模式中,观察者对象通常会引用被观察的对象。如果观察者对象没有被正确地取消注册,就会导致引用循环。使用弱引用可以解决这个问题,确保观察者对象在不再需要时能够被垃圾回收。
- 缓存(caches):在缓存中,对象通常会被缓存起来以提高性能。如果缓存的对象没有被正确地清理,就可能导致内存泄漏。使用弱引用可以避免这种情况,当对象不再被其他地方引用时,它可以被垃圾回收。
上下文管理器
class Manager:
def __enter__(self):
print('Entering')
return self
def __exit__(self, ty, val, tb):
print('Leaving')
if type:
print('An exception occurred')
>>> m = Manager()
>>> with m:
... print('Hello World')
...
Entering
Hello World
Leaving
>>>
代码重用
接口
类通常充当一种设计规范或编程接口,此类不直接使用,但通常作为其他对象的基类包含在内
class IStream:
def read(self, maxbytes=None):
raise NotImplementedError()
def write(self, data):
raise NotImplementedError()
class UnixPipe(IStream):
def read(self, maxbytes=None):
...
def write(self, data):
...
抽象基类
将接口定义为抽象基类 (ABC),除非所有抽象方法都已完全实现,否则不允许实例化
from abc import ABC, abstractmethod
class IStream(ABC):
@abstractmethod
def read(self, maxbytes=None):
pass
@abstractmethod
def write(self, data):
pass
处理器类Handler Classes
有时候代码会实现一个通用的算法,但会将某些步骤的具体实现交给一个单独提供的处理器对象来处理。这种情况下可以使用"策略"设计模式,"策略"设计模式是一种行为型设计模式,它允许定义一系列算法,将每个算法封装在独立的策略类中,并使这些策略类可以互相替换。在这种模式下,可以在运行时选择不同的策略来完成特定的任务。通过将通用算法的某些步骤委托给这个处理器对象,我们可以实现算法的灵活性和可扩展性。
# 处理器接口或抽象类
class Handler:
def handle_data(self, data):
pass
# 处理器类A
class HandlerA(Handler):
def handle_data(self, data):
# 对类型A的数据进行处理
# ...
# 处理器类B
class HandlerB(Handler):
def handle_data(self, data):
# 对类型B的数据进行处理
# ...
接下来,我们可以在通用算法中使用这些处理器对象来完成特定步骤的处理。例如:
class DataProcessor:
def __init__(self, handler):
self.handler = handler
def process_data(self, data):
# 通用算法的其他步骤
# ...
# 调用处理器对象的方法来完成特定步骤的处理
self.handler.handle_data(data)
# 通用算法的其他步骤
# ...
# 创建处理器对象
handler_a = HandlerA()
handler_b = HandlerB()
# 使用处理器对象来处理数据
data_processor = DataProcessor(handler_a)
data_processor.process_data(data_a)
data_processor = DataProcessor(handler_b)
data_processor.process_data(data_b)
处理器类的使用在整个 Python 标准库中非常常见(可能是 Python 中使用的最流行的 OO 设计模式),处理程序与实现分离,允许在其他上下文中重用处理程序代码。
类作为模板
一个类可以实现通用算法,但将某些步骤委托给子类
反对者:模板模式通常过于复杂,考虑使用函数+回调方式替代
class CSVParser:
def parse(self, filename):
records = []
with open(filename) as f:
rows = csv.reader(f)
self.headers = next(rows)
for row in rows:
record = self.make_record(row)
records.append(record)
return records
def make_record(self, row):
raise RuntimeError('Must implement')
class DictCSVParser(CSVParser):
def make_record(self, row):
return dict(zip(self.headers, row))
parser = DictCSVParser()
portfolio = parser.parse('portfolio.csv')