跳至正文
来两杯美式
返回

Python 知识系列(二):面向对象编程

By 来两杯美式
发布于更新于

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 的方式进行定义。正如上面例子中的 nameage

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++那样的严格的 privatepublic 关键字。但通过命名约定,可以实现访问限制的效果:

前缀含义说明
单下划线 _保护属性(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_templatellm


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
Python
第 2 / 8 篇
查看系列全部文章
  1. 01.Python 知识系列(一):极简核心语法
  2. 02.Python 知识系列(二):面向对象编程
  3. 03.Python 知识系列(三):从 0 开始理解函数
  4. 04.Python 知识系列(四):装饰器详解
  5. 05.Python 知识系列(五):模块和包
  6. 06.Python 知识系列(六):异步编程
  7. 07.Python 知识系列(七):虚拟环境与依赖管理
  8. 08.Python 知识系列(八):企业级开发场景最佳实践

上一篇
Python 知识系列(三):从 0 开始理解函数
下一篇
Python 知识系列(一):极简核心语法