1. Python类的定义与实例化
1.1 类的定义
在Python中,我们使用 class 关键字来定义一个类。类名通常采用驼峰命名法(CamelCase)。
一个最简单的类定义如下:
class Dog:
pass
这个 Dog 类目前是空的,pass 关键字表示一个空的代码块。
1.2 类的实例化
定义了类之后,我们就可以根据这个”蓝图”来创建具体的实例,也就是对象。这个过程称为实例化。
实例化一个类的语法就像调用一个函数:
# 实例化Dog类,创建一个名为my_dog的对象
my_dog = Dog()
# 你可以创建任意多个实例
another_dog = Dog()
print(my_dog)
print(another_dog)
输出结果会显示这两个对象是 Dog 类的不同实例,并且存储在不同的内存地址。
2. 类的初始化方法 (init)
为了让每个实例在创建时就拥有自己独特的属性(比如每只狗有不同的名字和年龄),我们需要使用一个特殊的方法:__init__。这个方法在创建新实例时会被自动调用。
__init__ 方法的第一个参数是 self,它代表实例本身。通过 self,我们可以为实例绑定属性。
class Dog:
def __init__(self, name, age):
# 将传入的参数赋值给实例的属性
self.name = name
self.age = age
print(f"一只叫做 {self.name} 的狗狗诞生了!")
# 实例化时,在类名后的括号里传入__init__方法需要的参数(除了self)
my_dog = Dog("旺财", 3)
another_dog = Dog("小黑", 5)
# 访问实例的属性
print(f"{my_dog.name} 今年 {my_dog.age} 岁了。")
print(f"{another_dog.name} 今年 {another_dog.age} 岁了。")
输出:
一只叫做 旺财 的狗狗诞生了!
一只叫做 小黑 的狗狗诞生了!
旺财 今年 3 岁了。
小黑 今年 5 岁了。
3. 类的属性
类的属性分为两种:实例属性 和 类属性。
3.1 实例属性(Instance Attributes)
实例属性是每个实例独有的数据。它们通常在 __init__ 方法中通过 self.attribute_name 的方式进行定义。正如上面例子中的 name 和 age。
3.2 类属性(Class Attributes)
类属性是该类的所有实例共享的属性。它直接在类中定义,而不是在任何方法中。
class Dog:
# 类属性,所有Dog实例共享
species = "犬科"
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
my_dog = Dog("旺财", 3)
another_dog = Dog("小黑", 5)
# 通过实例或类名都可以访问类属性
print(f"{my_dog.name} 属于 {my_dog.species}。")
print(f"{another_dog.name} 也属于 {another_dog.species}。")
print(f"Dog类属于 {Dog.species}。")
# 修改类属性会影响所有实例
Dog.species = "Canidae"
print(f"修改后,{my_dog.name} 属于 {my_dog.species}。")
输出:
旺财 属于 犬科。
小黑 也属于 犬科。
Dog类属于 犬科。
修改后,旺财 属于 Canidae。
注意:如果通过实例修改类属性
my_dog.species = "New Species",实际上是为该实例创建了一个新的同名实例属性,它会覆盖掉类属性的访问,但不会影响类本身和其他实例。
4. 类的方法
方法是定义在类中的函数,用于描述对象的行为。
4.1 实例方法(Instance Methods)
最常见的方法类型,它的第一个参数总是 self,代表调用该方法的实例。实例方法可以访问和修改实例属性和类属性。
class Dog:
species = "犬科"
def __init__(self, name, age):
self.name = name
self.age = age
# 这是一个实例方法
def bark(self):
return f"{self.name} 正在汪汪叫!"
# 另一个实例方法
def celebrate_birthday(self):
self.age += 1
return f"祝 {self.name} {self.age} 岁生日快乐!"
my_dog = Dog("旺财", 3)
print(my_dog.bark())
print(my_dog.celebrate_birthday())
print(f"{my_dog.name} 现在 {my_dog.age} 岁了。")
4.2 类方法(Class Methods)
类方法使用 @classmethod 装饰器来标识。它的第一个参数通常是 cls,代表类本身。类方法不能访问实例属性,但可以访问和修改类属性。
类方法常用于创建工厂函数,即根据传入的参数以不同的方式创建实例。
class Dog:
species = "犬科"
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, birth_year):
from datetime import date
current_year = date.today().year
age = current_year - birth_year
# cls(name, age) 等同于 Dog(name, age)
return cls(name, age)
def display_info(self):
return f"{self.name}, {self.age}岁, 属于{self.species}"
# 普通方式创建实例
dog1 = Dog("旺财", 3)
# 使用类方法创建实例
dog2 = Dog.from_birth_year("小黑", 2022)
print(dog1.display_info())
print(dog2.display_info())
4.3 静态方法(Static Methods)
静态方法使用 @staticmethod 装饰器。它既不接收 self 也不接收 cls 作为第一个参数。它就像一个普通的函数,只是被组织在了类的命名空间下。静态方法不能访问类属性和实例属性。
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_adult(age):
return age >= 18
print(MathUtils.add(5, 3))
print(f"一个15岁的人是成年人吗? {'是' if MathUtils.is_adult(15) else '否'}")
5. 属性的访问限制
在Python中,没有像Java或C++那样的严格的 private 或 public 关键字。但通过命名约定,可以实现访问限制的效果:
| 前缀 | 含义 | 说明 |
|---|---|---|
单下划线 _ | 保护属性(protected) | 比如 self._name。这是一种约定,告诉其他程序员这个属性不应该在类的外部直接访问。但这仅仅是约定,Python解释器并不会阻止你访问。 |
双下划线 __ | 私有属性(private) | 比如 self.__name。这会触发Python的名字改写(Name Mangling)机制。Python会将其解释为 _ClassName__attributeName。这使得从外部直接访问变得困难。 |
class Person:
def __init__(self, name, age, secret_id):
self.name = "公开姓名:" + name # 公开属性
self._age = age # 保护属性
self.__secret_id = secret_id # 私有属性
def display_info(self):
print(self.name)
print(f"年龄(保护): {self._age}")
print(f"ID(私有): {self.__secret_id}")
p = Person("张三", 30, "12345")
# 访问公开属性
print(p.name)
# 可以访问保护属性,但不推荐
print(p._age)
p.display_info()
# 尝试直接访问私有属性会导致 AttributeError
try:
print(p.__secret_id)
except AttributeError as e:
print(e)
# 通过名字改写后的名称可以访问(但不应该这样做)
print(p._Person__secret_id)
6. 类的继承与多态
6.1 继承(Inheritance)
继承允许我们创建一个新类(子类或派生类),它会继承另一个类(父类或基类)的属性和方法。这有助于代码重用和构建逻辑层次结构。
# 父类
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("子类必须实现这个方法")
# 子类,继承自Animal
class Dog(Animal):
def speak(self):
return f"{self.name} 说:汪汪!"
# 另一个子类
class Cat(Animal):
def speak(self):
return f"{self.name} 说:喵喵!"
d = Dog("旺财")
c = Cat("咪咪")
print(d.speak())
print(c.speak())
6.2 super() 函数
在子类中,如果你想调用父类中被覆盖的方法,可以使用 super() 函数。这在 __init__ 方法中尤其常见。
class Vehicle:
def __init__(self, brand):
self.brand = brand
print("Vehicle的__init__被调用")
class Car(Vehicle):
def __init__(self, brand, model):
# 调用父类的__init__方法来初始化brand属性
super().__init__(brand)
self.model = model
print("Car的__init__被调用")
my_car = Car("特斯拉", "Model Y")
print(f"我的车是 {my_car.brand} {my_car.model}")
6.3 多态(Polymorphism)
多态意味着”多种形态”。在编程中,它指的是不同类的对象可以对相同的方法调用做出不同的响应。上面的 speak 方法就是一个例子:Dog 对象和 Cat 对象的 speak 方法行为不同。
多态让我们的代码更具通用性。我们可以编写一个函数,它接收一个 Animal 类型的对象,而无需关心它到底是 Dog 还是 Cat。
def make_animal_speak(animal):
print(animal.speak())
# d 和 c 都是Animal的子类实例
make_animal_speak(d)
make_animal_speak(c)
7. 类中的默认方法(魔术方法)
Python类中有一系列以双下划线开头和结尾的特殊方法,它们被称为”魔术方法”(Magic Methods)或”双下划线方法”(Dunder Methods)。这些方法在你使用特定的Python语法时会被自动调用。
对象表示
| 方法 | 说明 |
|---|---|
__str__(self) | 返回对象的”非正式”或”用户友好”的字符串表示。使用 print() 或 str() 时调用。 |
__repr__(self) | 返回对象的”正式”或”官方”的字符串表示。在解释器中直接输入对象名或使用 repr() 时调用。 |
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# 用户友好的表示
return f"坐标点({self.x}, {self.y})"
def __repr__(self):
# 开发者友好的表示,可以用于重新创建对象
return f"Point(x={self.x}, y={self.y})"
p = Point(10, 20)
# print() 调用 __str__
print(p) # 输出: 坐标点(10, 20)
# repr() 调用 __repr__
print(repr(p)) # 输出: Point(x=10, y=20)
# 直接在解释器中输入对象名也会调用 __repr__
# >>> p
# Point(x=10, y=20)
比较操作符
通过实现这些方法,你可以自定义对象之间如何进行比较(例如 ==, <, <=, >, >=, !=)。
| 方法 | 操作符 |
|---|---|
__eq__(self, other) | == |
__ne__(self, other) | != |
__lt__(self, other) | < |
__le__(self, other) | <= |
__gt__(self, other) | > |
__ge__(self, other) | >= |
class Score:
def __init__(self, value):
self.value = value
def __eq__(self, other):
# 判断两个Score对象是否相等,基于它们的value属性
if isinstance(other, Score):
return self.value == other.value
return NotImplemented # 表示不处理非Score类型的比较
def __lt__(self, other):
# 判断一个Score对象是否小于另一个Score对象
if isinstance(other, Score):
return self.value < other.value
return NotImplemented
def __str__(self):
return f"分数: {self.value}"
s1 = Score(90)
s2 = Score(90)
s3 = Score(85)
print(f"{s1} == {s2} ? {s1 == s2}") # 调用 __eq__
print(f"{s1} < {s3} ? {s1 < s3}") # 调用 __lt__
print(f"{s1} > {s3} ? {s1 > s3}") # Python会尝试反向调用 __lt__ 或 __gt__
print(f"{s1} != {s3} ? {s1 != s3}") # Python会尝试调用 __eq__ 的反面
算数操作符
你可以让你的对象支持像数字一样的加减乘除等运算。
| 方法 | 操作符 |
|---|---|
__add__(self, other) | + |
__sub__(self, other) | - |
__mul__(self, other) | * |
__truediv__(self, other) | / |
__floordiv__(self, other) | // |
__mod__(self, other) | % |
__pow__(self, other) | ** |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# 定义两个Vector相加的行为:对应分量相加
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
# 定义两个Vector相减的行为
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
# 定义Vector与标量(数字)相乘的行为
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
# 定义当标量在左侧时 (例如 2 * vector) 的行为
return self.__mul__(scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v_sum = v1 + v2 # 调用 __add__
v_diff = v1 - v2 # 调用 __sub__
v_scaled = v1 * 3 # 调用 __mul__
v_scaled_rev = 3 * v1 # 调用 __rmul__
print(f"{v1} + {v2} = {v_sum}")
print(f"{v1} - {v2} = {v_diff}")
print(f"{v1} * 3 = {v_scaled}")
print(f"3 * {v1} = {v_scaled_rev}")
输出:
Vector(1, 2) + Vector(3, 4) = Vector(4, 6)
Vector(1, 2) - Vector(3, 4) = Vector(-2, -2)
Vector(1, 2) * 3 = Vector(3, 6)
3 * Vector(1, 2) = Vector(3, 6)
容器类型操作
这些方法让你的对象可以像列表、字典等容器一样被操作。
| 方法 | 说明 |
|---|---|
__len__(self) | 定义 len() 函数的行为 |
__getitem__(self, key) | 定义通过索引或键访问元素 obj[key] 的行为 |
__setitem__(self, key, value) | 定义通过索引或键设置元素 obj[key] = value 的行为 |
__delitem__(self, key) | 定义通过 del obj[key] 删除元素的行为 |
__contains__(self, item) | 定义 in 操作符 item in obj 的行为 |
__iter__(self) | 返回一个迭代器,用于支持 for ... in ... 循环 |
class MyList:
def __init__(self, *args):
self._data = list(args)
def __len__(self):
return len(self._data)
def __getitem__(self, index):
return self._data[index]
def __setitem__(self, index, value):
self._data[index] = value
def __delitem__(self, index):
del self._data[index]
def __contains__(self, item):
return item in self._data
def __iter__(self):
# 返回一个迭代器,这里直接返回底层列表的迭代器
return iter(self._data)
def __str__(self):
return f"MyList({self._data})"
ml = MyList(10, 20, 30, 40)
print(f"长度: {len(ml)}") # 调用 __len__
print(f"第一个元素: {ml[0]}") # 调用 __getitem__
ml[1] = 25 # 调用 __setitem__
print(f"修改后: {ml}")
print(f"是否包含 30? {30 in ml}") # 调用 __contains__
del ml[0] # 调用 __delitem__
print(f"删除后: {ml}")
print("遍历元素:")
for item in ml: # 调用 __iter__
print(item)
输出:
长度: 4
第一个元素: 10
修改后: MyList([10, 25, 30, 40])
是否包含 30? True
删除后: MyList([25, 30, 40])
遍历元素:
25
30
40
扩展:LangChain 的 LCEL
LangChain 的 LCEL(LangChain Expression Language)大量使用了 Python 的魔术方法(Dunder Methods)来实现其核心功能,尤其是管道操作符 | 的链式调用。
这是 LCEL 能够提供如此简洁、直观的链式编程体验的关键。
例如,我们想基于 LangChain 构建一个 Agent,伪代码可以是这样:
agent = prompt_template | llm | tools | output_parser
在 LCEL 中,每个这些组件都被设计成一个 Runnable 对象(或其子类)。Runnable 是 LCEL 的核心抽象,它定义了 invoke(), stream(), batch() 等方法,并且最重要的是,它实现了 __or__ 魔术方法,改变了 | 运算符的行为。
当你写 prompt_template | llm 时,Python 解释器会调用 prompt_template 对象的 __or__ 方法,并将 llm 作为参数 other 传入。__or__ 方法的定义为 __or__(self, other)。
这个 __or__ 方法会返回一个新的 Runnable 对象,通常是一个 RunnableSequence,它知道如何依次执行 prompt_template 和 llm。