# 面向对象

2023年8月10日23分钟阅读

- FormalSciences/ComputerScience
- ProgrammingLanguage/Python
- Python/Advanced

## 生命周期

    graph TD
    A[类定义阶段: class MyClass] --> B[类对象创建]
    B --> C1[类属性访问: MyClass.class_attr]
    B --> C[实例化阶段: __new__ method]
    C --> D[初始化阶段: __init__ method]
    D --> E1[属性的设置与获取: __setattr__, __getattr__]
    D --> E2[对象的表示: __str__, __repr__]
    D --> E3[方法调用]
    D --> E4[上下文管理: __enter__, __exit__]
    D --> E5[对象比较: __eq__, __lt__]
    D --> E6[对象的计算: __add__, __mul__]
    E1 --> F
    E2 --> F
    E3 --> F
    E4 --> F
    E5 --> F
    E6 --> F
    F[对象销毁: del obj or GC] --> G[__del__ method]
    G --> H[垃圾回收: Cleanup by Python GC]

1. 类定义阶段：在这个阶段定义类的方法和属性。
2. 类对象创建：当 Python 读取到类定义时，它会创建一个类对象。
3. 类属性访问：在实例化之前，可以访问类的静态和类属性。
4. 实例化阶段：使用类名创建对象时，首先调用 `__new__` 方法来分配内存。
5. 初始化阶段：紧随其后，`__init__` 方法会被调用，对新对象进行初始化。
6. 属性的设置与获取：在对象的生命周期中，你可以使用 `__getattr__`, `__setattr__`, `__getattribute__` 等特殊方法来控制属性的访问。
7. 对象的表示：当尝试显示对象时（例如，通过 print），`__str__` 和 `__repr__` 方法可以被调用。
8. 方法调用：可以调用对象的方法。
9. 上下文管理：使用 `with` 语句时，对象的 `__enter__` 和 `__exit__` 方法会被调用。
10. 对象比较：使用比较操作符时（如==或>），`__eq__`, `__lt__`, `__le__` 等特殊方法会被调用。
11. 对象的计算：如加法或乘法等运算符，会调用 `__add__`, `__mul__` 等特殊方法。
12. 对象销毁：当对象的引用计数减少到零或者被明确销毁（使用 `del` 语句）时，`__del__` 方法会被调用。
13. 垃圾回收：Python 的垃圾回收器会识别循环引用，并在适当的时候销毁相关对象。

## 特殊方法

Python 中的类包含了许多特殊方法，它们经常被称为魔术方法（magic methods）或者双下方法（dunder methods），因为它们的名字都是以双下划线开始和结束的。这些特殊方法为 Python 的对象提供了许多内置的功能，如算术运算、迭代和字符串表示。

魔术方法是 Python 类的特殊方法，它们定义了许多 Python 中基本的操作。例如，当你为一个对象定义了 `__add__` 方法时，这个对象就可以使用 `+` 运算符。这些方法的名称都是以两个下划线开始和结束，这也是为什么它们被称为双下方法的原因。

### 基础方法

#### `__init__`

`__init__` 方法是类的构造函数，当我们创建类的实例时，`__init__` 方法会被自动调用。我们可以在 `__init__` 方法中初始化实例的属性。

    class MyClass:
        def __init__(self, value):
            self.value = value  # 初始化实例属性

    # 创建实例
    mc = MyClass(10)
    print(mc.value)  # 输出：10

#### `__del__`

`__del__` 方法是类的析构函数，当一个实例被销毁时（例如被垃圾回收器回收时），`__del__` 方法会被自动调用。注意，我们通常不需要在 `__del__` 方法中做清理工作，Python 的垃圾回收器会自动清理对象的资源。

    class MyClass:
        def __del__(self):
            print("Instance is being destroyed.")

    mc = MyClass()  # 创建实例
    del mc  # 销毁实例

#### `__str__`

`__str__` 方法返回一个表示该对象的字符串，这个字符串通常用于给用户看。当我们调用 `str()` 或 `print()` 时，会使用 `__str__` 的返回值。

    class MyClass:
        def __str__(self):
            return "This is a MyClass instance."

    mc = MyClass()  # 创建实例
    print(mc)  # 输出：This is a MyClass instance.

注意，`__repr__` 和 `__str__` 的区别在于，`__repr__` 更侧重于开发，而 `__str__` 更侧重于用户。

#### `__repr__`

`__repr__` 方法返回一个表示该对象的官方字符串，这个字符串通常可以被 `eval()` 执行来重新得到这个对象。如果我们没有定义 `__str__` 方法，那么在调用 `str()` 或 `print()` 时也会使用 `__repr__` 的返回值。

    class MyClass:
        def __repr__(self):
            return "MyClass()"

    mc = MyClass()  # 创建实例
    print(mc)  # 输出：MyClass()

#### `__format__`

`__format__` 方法定义了当我们调用 `format()` 或使用格式化字符串（f-string）时的行为。`format_spec` 是一个格式说明符，它是在格式化字符串中 `:` 后面的部分。

    class MyClass:
        def __format__(self, format_spec):
            if format_spec == 'fancy':
                return 'This is a fancy MyClass instance.'
            return 'This is a MyClass instance.'

    mc = MyClass()  # 创建实例
    print(f"{mc:fancy}")  # 输出：This is a fancy MyClass instance.

### 数学运算

比较运算的魔术方法允许类的实例之间进行比较。例如，`__eq__` 定义了对象的等于操作。这些方法的使用可以使您的类实例支持标准的比较操作符，如 `==`, `!=`, `+`, `-`, `%`, 和 `@`。类似地，可以为其他数学运算符定义其他魔术方法。

    class Book:
        def __init__(self, title, author):
            self.title = title
            self.author = author

        def __eq__(self, other):
            if isinstance(other, Book):
                return self.title == other.title and self.author == other.author
            return False

    # 比较两本书是否具有相同的标题和作者
    book1 = Book('1984', 'George Orwell')
    book2 = Book('1984', 'George Orwell')
    book3 = Book('Brave New World', 'Aldous Huxley')

    print(book1 == book2)  # 输出：True
    print(book1 == book3)  # 输出：False

    class Vector:
        def __init__(self, x, y):
            self.x = x
            self.y = y

        def __add__(self, other):
            if isinstance(other, Vector):
                return Vector(self.x + other.x, self.y + other.y)
            return NotImplemented

    # 使用 + 操作符组合两个向量
    v1 = Vector(1, 2)
    v2 = Vector(2, 3)
    v3 = v1 + v2

    print(v3.x, v3.y)  # 输出：3 5

运算符 | 对应的魔术方法 | 描述
--- | --- | ---
`==` | `__eq__` | 等于
`!=` | `__ne__` | 不等于
`<` | `__lt__` | 小于
`<=` | `__le__` | 小于或等于
`>` | `__gt__` | 大于
`>=` | `__ge__` | 大于或等于
`+` | `__add__` | 加法
`-` | `__sub__` | 减法
`*` | `__mul__` | 乘法
`/` | `__truediv__` | 真除
`//` | `__floordiv__` | 整除
`%` | `__mod__` | 取模
`**` | `__pow__` | 乘方
`@` | `__matmul__` | Python 3.5+ 矩阵乘法

### 容器方法

#### `__len__`

`__len__` 方法定义了 `len()` 的返回值。它应该返回一个整数，表示对象包含的元素的个数。

    class MyClass:
        def __init__(self):
            self.data = [1, 2, 3]

        def __len__(self):
            return len(self.data)

    mc = MyClass()  # 创建实例
    print(len(mc))  # 输出：3

#### `__getitem__`

`__getitem__` 方法定义了使用索引访问元素的行为。`key` 是索引。

    class MyClass:
        def __getitem__(self, key):
            return f"You are trying to access key {key}."

    mc = MyClass()  # 创建实例
    print(mc[10])  # 输出：You are trying to access key 10.

#### `__setitem__`

`__setitem__` 方法定义了使用索引设置元素的行为。`key` 是索引，`value` 是我们试图设置的值。

    class MyClass:
        def __setitem__(self, key, value):
            print(f"Set key {key} to {value}.")

    mc = MyClass()  # 创建实例
    mc[10] = "value"  # 输出：Set key 10 to value.

#### `__delitem__`

`__delitem__` 方法定义了使用索引删除元素的行为。`key` 是索引。

    class MyClass:
        def __delitem__(self, key):
            print(f"Deleted key {key}.")

    mc = MyClass()  # 创建实例
    del mc[10]  # 输出：Deleted key 10.

#### `__iter__`、`__next__`

迭代器协议的魔术方法允许对象支持迭代，这意味着您可以在对象上使用 `for` 循环。为了使一个对象可迭代，您需要定义 `__iter__` 和 `__next__` 两个魔术方法。

- `__iter__` 返回对象本身，它应该返回一个实现了 `__next__` 的迭代器对象。

- `__next__` 方法返回序列中的下一个值。如果所有项都返回了，那么它应该引发一个 `StopIteration` 异常来通知迭代的完成。

    class Counter:
        def __init__(self, start, end):
            self.current = start
            self.end = end

        def __iter__(self):
            return self

        def __next__(self):
            if self.current < self.end:
                value = self.current
                self.current += 1
                return value
            else:
                raise StopIteration

    # 使用Counter迭代器
    counter = Counter(1, 4)

    for number in counter:
        print(number)
    # 输出：
    # 1
    # 2
    # 3

在上述 `Counter` 类中，我们定义了一个简单的迭代器，它从 `start` 开始，每次迭代增加 1，直到 `end` 为止。`for` 循环通过调用 `__iter__` 来获取迭代器对象，并在每次迭代中调用 `__next__`，直到捕获 `StopIteration` 异常为止。

### 属性访问

#### `__dir__`

`__dir__` 方法返回类中定义的属性、方法等的列表。它对内置的 `dir()` 函数的行为进行重载。

    class MyClass:
        def __dir__(self):
            return ["custom_attr1", "custom_attr2"]

    mc = MyClass()
    print(dir(mc))  # 输出：['custom_attr1', 'custom_attr2']

#### `__getattr__`

当尝试访问一个不存在的属性时，`__getattr__` 方法会被调用。`name` 是试图访问的属性名称。

    class MyClass:
        def __getattr__(self, name):
            return f"{name} does not exist."

    mc = MyClass()  # 创建实例
    print(mc.unknown_attr)  # 输出：unknown_attr does not exist.

值得注意的是，`__getattr__` 只有在所请求的属性不存在时才会被调用。

#### `__setattr__`

每当尝试设置一个属性值时，`__setattr__` 都会被调用，不论该属性是否存在。`name` 是试图设置的属性的名称，而 `value` 是试图赋给该属性的值。

    class MyClass:
        def __setattr__(self, name, value):
            self.__dict__[name] = value  # 防止无限递归, 需要在__dict__中设置属性
            print(f"Set {name} to {value}.")

    mc = MyClass()  # 创建实例
    mc.attr = 10  # 输出：Set attr to 10.

#### `__delattr__`

当试图删除一个属性时，`__delattr__` 方法会被调用。`name` 是试图删除的属性的名称。

    class MyClass:
        attr = 10

        def __delattr__(self, name):
            del self.__dict__[name]  # 防止无限递归, 需要在__dict__中删除属性
            print(f"Deleted {name}.")

    mc = MyClass()  # 创建实例
    del mc.attr  # 输出：Deleted attr.

#### `__getattribute__`

每次尝试访问一个属性时，`__getattribute__` 方法都会被调用，无论该属性是否存在。

    class MyClass:
        def __getattribute__(self, name):
            return f"You are trying to access {name}."

    mc = MyClass()  # 创建实例
    print(mc.attr)  # 输出：You are trying to access attr.

重要的是，如果 `__getattribute__` 被定义，那么 `__getattr__` 不会被调用，因为 `__getattribute__` 的优先级更高。*

### 上下文管理

#### `__enter__`、`__exit__`

当使用 `with` 语句进入上下文管理时，`__enter__` 方法会被调用。它应该返回上下文管理器对象本身或其他与上下文相关的对象。

当 `with` 语句块结束时，`__exit__` 方法会被调用。它接收三个参数：`exc_type`、`exc_val` 和 `exc_tb`，分别代表异常类型、异常值和异常回溯。如果 `with` 语句块中没有发生异常，这三个参数的值都为 `None`。

    class ContextManager:
        def __enter__(self):
            print("Entering the context")
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is None:
                print("Exiting the context without any exception.")
            else:
                print(f"Exiting the context with exception: {exc_val}")

        def say_hello(self):
            print("Hello from inside the context!")

    with ContextManager() as cm:
        cm.say_hello()

    # 输出：
    # Entering the context
    # Hello from inside the context!
    # Exiting the context without any exception.

如果在 `with` 语句块中引发了异常，`__exit__` 方法可以选择处理这个异常（例如记录日志）并返回 `True` 来阻止异常向外传播，或者返回 `False`（或 `None`）让异常继续向外传播。

上下文管理器是一种非常强大的工具，特别是当涉及到需要设置和清理资源的任务时，例如文件 I/O、网络连接或数据库连接。

### 描述符

描述符是实现了描述符协议的对象。描述符协议由 `__get__`、`__set__` 和 `__delete__` 方法组成。描述符用于创建那些需要特殊行为的对象属性，例如类型检查或只读属性。

#### `__get__`

`__get__` 方法定义了在获取属性时应执行的行为。当试图获取属性值时，这个方法会被调用。

    class Descriptor:
        def __get__(self, instance, owner):
            print(f"Getting value from {instance.__class__.__name__}")

    class MyClass:
        attr = Descriptor()

    mc = MyClass()
    mc.attr
    # 输出：
    # Getting value from MyClass

#### `__set__`

`__set__` 方法定义了在设置属性值时应执行的行为。当试图给属性赋值时，这个方法会被调用。

    class Descriptor:
        def __set__(self, instance, value):
            print(f"Setting value {value} to {instance.__class__.__name__}")

    class MyClass:
        attr = Descriptor()

    mc = MyClass()
    mc.attr = 10
    # 输出：
    # Setting value 10 to MyClass

#### `__delete__`

`__delete__` 方法定义了当删除属性时应执行的行为。当试图删除属性时，这个方法会被调用。

    class Descriptor:
        def __delete__(self, instance):
            print(f"Deleting value from {instance.__class__.__name__}")

    class MyClass:
        attr = Descriptor()

    del mc.attr
    # 输出：
    # Deleting value from MyClass

### 使对象可调用

**在 Python 中，函数是一类对象，可以调用它们。**但是，您知道您也可以使自己的对象表现得像函数一样吗？通过定义 `__call__` 魔术方法，您可以使类的实例表现得像函数，从而允许对它们进行调用。

#### `__call__`

当实例作为函数被“调用”时，`__call__` 方法就会被执行。这提供了一种优雅的方式来使用对象，同时保持其对象性质。通过这种方式，您的对象不仅可以表示数据，还可以表现得像函数，这增加了编码的灵活性和创造性。

    class Greeter:
        def __init__(self, greeting="Hello"):
            self.greeting = greeting

        def __call__(self, name):
            return f"{self.greeting}, {name}!"

    # 创建一个Greeter实例
    hello_greeter = Greeter("Hello")
    print(hello_greeter("John"))  # 输出：Hello, John!

    bonjour_greeter = Greeter("Bonjour")
    print(bonjour_greeter("Pierre"))  # 输出：Bonjour, Pierre!

### 值比较

#### `__hash__`

`__hash__` 方法返回对象的哈希值。哈希值通常用于字典的键值和其他需要快速查找的数据结构中。如果一个对象是可变的，通常最好不要实现此方法。如果对象定义了 `__eq__` 方法并且是不可变的，则通常也应定义此方法。

    class MyClass:
        def __init__(self, value):
            self.value = value

        def __eq__(self, other):
            if isinstance(other, MyClass):
                return self.value == other.value
            return NotImplemented

        def __hash__(self):
            return hash(self.value)

    obj1 = MyClass(5)
    obj2 = MyClass(5)
    my_dict = {obj1: "a"}
    print(my_dict[obj2])  # 输出：a

#### `__bool__`

`__bool__` 方法用于实现 `bool()` 内置函数的调用。当我们调用 `bool()` 函数或使用对象在条件语句（例如 `if obj:`）中作为条件时，会调用此方法。如果 `__bool__` 没有被定义，`__len__` 会被调用（如果已定义）。如果两者都未定义，所有实例都默认为 `True`。

    class MyClass:
        def __init__(self, value):
            self.value = value

        def __bool__(self):
            return bool(self.value)

    obj1 = MyClass(0)
    obj2 = MyClass(5)
    print(bool(obj1))  # 输出：False
    print(bool(obj2))  # 输出：True

### 类型转换

#### `__int__`

`__int__` 方法允许将一个对象转换为整数。当使用 `int()` 内置函数时，如果对象实现了此方法，会被调用。

    class MyClass:
        def __int__(self):
            return 42

    obj = MyClass()
    print(int(obj))  # 输出：42

#### `__float__`

`__float__` 方法允许将一个对象转换为浮点数。当使用 `float()` 内置函数时，如果对象实现了此方法，会被调用。

    class MyClass:
        def __float__(self):
            return 42.0

    obj = MyClass()
    print(float(obj))  # 输出：42.0

#### `__complex__`

`__complex__` 方法允许将一个对象转换为复数。当使用 `complex()` 内置函数时，如果对象实现了此方法，会被调用。

    class MyClass:
        def __complex__(self):
            return 3 + 4j

    obj = MyClass()
    print(complex(obj))  # 输出：(3+4j)

#### `__bytes__`

`__bytes__` 方法定义了当我们调用 `bytes()` 时的行为。它应该返回一个字节串。

    class MyClass:
        def __bytes__(self):
            return b'MyClass instance'

    mc = MyClass()  # 创建实例
    print(bytes(mc))  # 输出：b'MyClass instance'

## 类和静态方法

在 Python 中，类是一个创建对象的蓝图。对象则是基于类定义的实例。默认情况下，在类内部定义的方法是实例方法。

### 实例方法

实例方法的第一个参数是 `self`，代表类的实例对象。它可以访问和修改与实例相关的属性和方法。实例方法只能由其实例对象调用。

    class Example:
        def instance_method(self):
            return "This is an instance method", self

    e = Example()
    print(e.instance_method())
    # 输出 ('This is an instance method', <__main__.Example object at 0x7f49af98f610>)

### 类方法

类方法使用 `@classmethod` 装饰器定义。其第一个参数是 `cls`，代表类本身。类方法既可以由类直接调用，也可以被其实例调用。

    class Example:
        class_var = "Class Variable"

        @classmethod
        def class_method(cls):
            return "This is a class method accessing:", cls.class_var

    print(Example.class_method())
    # 输出 ('This is a class method accessing:', 'Class Variable')

### 静态方法

静态方法使用 `@staticmethod` 装饰器定义。它不需要传递 `self` 或 `cls` 参数。静态方法不能访问或修改类或实例的属性和方法。它仅仅与它所在的类相关，但不需要访问类的特性。

    class Example:
        @staticmethod
        def static_method():
            return "This is a static method"

    print(Example.static_method())
    # 输出 This is a static method

### 类方法 Vs 静态方法

类型 | 用途 | 优点 | 缺点
--- | --- | --- | ---
类方法 | 访问/修改类属性，方法继承于子类 | 可访问/修改类属性，适用于继承 | 不能访问实例特有的属性
静态方法 | 不需访问实例/类数据的操作，与类相关但不需访问类或实例 | 无需实例化，代码组织清晰 | 不能访问类和实例的属性或方法

在面向对象编程中，合理地使用实例方法、类方法和静态方法可以使代码更有组织性，更易于维护。

## 属性

### `@property` 装饰器

在 Python 中，`@property` 装饰器使我们能够将类中的方法用作属性，从而实现对属性的控制。它可以用于确保属性的读取和设置遵循某种特定的逻辑。

    class Example:
        def __init__(self, number):
            self._number = number

        @property
        def number(self):
            return self._number

    e = Example(5)
    print(e.number)
    # 输出 5

### 使用 Setter 和 Getter 方法

Setter 和 Getter 在 Python 中用于控制属性的访问和赋值。

#### `@<property_name>.setter`

在定义了属性的 getter 方法后，我们可以使用 `@<property_name>.setter` 装饰器定义相应的 setter 方法，以控制该属性的赋值逻辑。

    class Example:
        def __init__(self, number):
            self._number = number

        @property
        def number(self):
            return self._number

        @number.setter
        def number(self, value):
            self._number = value

    e = Example(5)
    e.number = 10
    print(e.number)
    # 输出 10

#### `@<property_name>.deleter`

除了设置和获取属性，我们还可以定义如何删除属性。

    class Example:
        def __init__(self, number):
            self._number = number

        @property
        def number(self):
            return self._number

        @number.deleter
        def number(self):
            print("Deleting number")
            del self._number

    e = Example(5)
    del e.number

### 属性保护

在 Python 中，我们通常使用下划线来表示属性应该是私有的或受保护的。

- 受保护的属性: 通常使用单下划线前缀 `_` 来定义，例如 `_name`。这只是一个约定，表示这个属性是为类内部使用的，但外部仍然可以访问。
- 私有属性: 使用双下划线前缀 `__` 来定义，例如 `__name`。Python 会对其进行名称修饰，使得在类的外部更难直接访问。

    class Example:
        def __init__(self):
            self.__private_attr = "I am private"
            self._protected_attr = "I am protected"

        def reveal(self):
            print(self.__private_attr)

    e = Example()
    print(e._protected_attr)
    # print(e.__private_attr)  # 将会抛出错误异常
    e.reveal()  # 正常工作

#### `__slots__` 限制动态属性的添加

为了提高性能和内存效率，Python 允许在类定义中使用 `__slots__` 来限制可以添加到对象的属性。这通常在需要创建大量对象时很有用。

    class Example:
        __slots__ = ['name', 'age']

    e = Example()
    e.name = "ChatGPT"
    # e.salary = 10000  # 将会抛出错误异常