跳转至

面向对象

面向对象:把公共数据封装到对象,类(文件、redis信息等)函数封装到一个类,将函数归类和划分。

实例化:通过类创建一个对象的过程。

📌 多态

多态指同一种接口可以有多种不同的实现方式。

鸭子模型是一种动态类型检查的概念,是多态的一种常见实现方式。不限制传参类型,只要求这个对象有函数所期望的方法或属性。

class Duck:
    def quack(self):
        print("Quack!")


class Turkey:
    def quack(self):
        print("Gobble gobble!")


def make_quack(animal):
    animal.quack()


# 创建对象
duck = Duck()
turkey = Turkey()

# 调用函数
make_quack(duck)  # 输出 "Quack!"
make_quack(turkey)  # 输出 "Gobble gobble!"


class Dog:
    def bark(self):
        print("Woof woof!")


dog = Dog()
make_quack(dog)  # 将会抛出 AttributeError: 'Dog' object has no attribute 'quack'

📌 继承

子类继承父类的属性和方法,子类可以重写父类的方法。

class Base:
    def __init__(self):
        pass

    def func(self):
        # 通过在父类抛NotImplementedError异常约束子类。
        # 面向对象的约束:通过继承,在父类中的方法抛异常,实现对子类的控制。
        raise NotImplementedError("Subclass must implement this abstract method")


class Foo(Base):
    # self优先从子类中查找,子类优先于父类
    def __init__(self):
        super().__init__()  # 如果不显式调用,父类的构造方法不会被执行。

🚁 抽象类

无法被实例化,用于设计者表达自己的设计意图,并且要求后续的开发者按照设计来泛化实现。

继承抽象类的子类可实例化,必须实现抽象类中的抽象方法。

python中通过@abstreactmethod装饰器实现抽象方法

📌 特殊成员方法/魔术方法

子类应尽量避免在继承内置类型的同时重写其魔术方法。

🚁 call

当一个对象被当作函数调用时,即在对象后面加上括号(),便会寻找并调用该对象的__call__方法。

在unittest中,测试类TestCase即通过该方式调用run方法。

🚁 new

__new__方法在创建对象时调用,返回一个对象。

# 限制某个类只能创建一个实例(单例模式)
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

🚁 str

print(myclass())
调用__str__方法,返回对象的字符串表示形式。

🚁 dict

myclass().__dict__
对象的变量转为字典

🚁 doc

描述信息,当定义类、方法,第一个语句是字符串时,就会被自动赋值给__doc__属性。

🚁 class

__class__返回类本身

__class__.__name__返回类名

🚁 getitem

__getitem__用于实现对象对索引或键的访问操作,该类的实例可以使用obj[key]的形式来访问数据。

class MyList:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, index):
        return self.items[index]

my_list = MyList([10, 20, 30, 40])
print(my_list[1])  # 输出: 20
print(my_list[1:3])  # 输出: [20, 30]
class Person:
    def __init__(self):
        self.info = {'name': '张三', 'age': 18}

    def __getitem__(self, key):
        return self.info[key]

tmp = Person()
print(tmp['name'])
print(tmp['age'])

📌 变量

Python中,以单下划线开头的变量作为保护变量,表明不希望用户直接访属性的约定。
但实际通过instance._variable的方式可以访问,但不会被from module import *导入。

而双下划线开头的变量表示私有变量,目的是为了防止子类中同名变量或方法的冲突。
但实际通过instance._ClassName__variable的方式可以访问。

📌 编程规范

🚁 类定义时方法的顺序建议

  1. 类变量
  2. __new__
  3. __init__
  4. __post_init__,实例化后立即调用,可以用于执行任何必要的后处理或验证
  5. 其他魔术方法,如__len__,__bool__等,具有固定返回类型的,重写时必须返回该类型
  6. @property,类属性的封装,允许使用点语法访问,并可通过@<attribute>.setter、@<attribute>.deleter设置或删除属性
  7. @staticmethod,静态方法,不需要访问实例也不需要访问类属性或方法时;可通过类名直接调方法,不推荐
  8. @classmethod,类方法,不需要访问实例,但需要访问类属性或方法时;可通过类名直接调方法,不推荐
  9. 普通方法
  10. 保护或私有方法

🚁 具名函数

当函数参数数量较多(一般指多于5个),且参数间有一定相关性时,建议通过类/namedtuple(具名元组类)/@dataclass等具名形式进行封装。

from collections import namedtuple

# 定义一个具名元组类
Person = namedtuple('Person', ['name', 'age', 'email'])

person1 = Person(name="Alice", age=30, email="alice@example.com")
# person1.age = 14  # 报错:AttributeError: can't set attribute
person2 = Person(name="Bob", age=25, email="bob@example.com")

# 访问具名元组的属性
print(person1.name)  # 输出: Alice
print(person2.age)   # 输出: 25

# 输出整个具名元组
print(person1)  # 输出: Person(name='Alice', age=30, email='alice@example.com')
print(person2)  # 输出: Person(name='Bob', age=25, email='bob@example.com')
from dataclasses import dataclass

@dataclass
# 默认生成的类是可变的(除非装饰器传参frozen=True)
class Person:
    name: str
    age: int
    email: str = "example@example.com"  # 可选参数,默认值为 example@example.com


# dataclass自动为类生成了 __init__ 方法
person1 = Person("Alice", 30)
person1.age = 19
person2 = Person("Bob", 25, "bob@example.com")

# dataclass自动为类生成了 __repr__ 方法
print(person1)  # 输出: Person(name='Alice', age=19, email='example@example.com')
print(person2)  # 输出: Person(name='Bob', age=25, email='bob@example.com')

"""
什么时候不宜使用:
1. 需要继承时
2. 复杂的业务逻辑
3. 性能敏感
4. 高度定制化
"""

Q: Python类的主要特性有什么?

OOP 核心特性 + Python 特有机制

主要特性包括:封装、继承、多态、抽象;同时具备动态语言特性,如类也是对象、支持元类、多继承和 MRO(super())、魔术方法/运算符重载、property/描述符、类方法/静态方法、实例属性与类属性、运行时动态修改类,以及鸭子类型。

  1. 封装

    • 把属性和方法包装在类里。
    • Python 没有真正私有:_x 是约定保护,__x 会触发名称改写为 _类名__x。
    • 可用 property、描述符控制属性访问。
  2. 继承

    • 支持单继承和多继承。
    • 子类可复用、扩展、重写父类方法。
    • 用 super() 按 MRO 调用父类方法。
    • 多继承方法查找顺序由 C3 算法 MRO 决定:类.__mro__。
  3. 多态

    • 同一接口,不同实现;子类重写父类方法。
    • Python 更强调 鸭子类型:不关心对象是什么类型,只关心它有没有对应方法/行为。
  4. 抽象

    • 通过 abc.ABC、@abstractmethod 定义抽象基类,约束子类实现。
    • 也可用 typing.Protocol 做结构化子类型。
  5. 类也是对象

    • Python 中一切皆对象,类本身也是对象。
    • 类通常由 type 创建;元类 metaclass 可控制类的创建过程。
    • Python 3 所有类默认继承 object,都是新式类。
  6. 类属性与实例属性

    • 类属性属于类,所有实例共享。
    • 实例属性属于具体实例,通常写在 __init__ 里。
    • 查找顺序大致:实例 __dict__ → 类 __dict__ → 父类 MRO。
  7. 方法类型

    • 实例方法:第一个参数 self。
    • 类方法:@classmethod,第一个参数 cls。
    • 静态方法:@staticmethod,无默认绑定参数。
    • 属性方法:@property,把方法当属性访问。
  8. 魔术方法 / 协议

    • __init__:初始化实例。
    • __new__:创建实例。
    • __str__ / __repr__:字符串表示。
    • __eq__ / __hash__:相等与哈希。
    • __getitem__ / __iter__ / __next__:容器、迭代协议。
    • __enter__ / __exit__:上下文管理器。
    • __call__:让实例可调用。

通过这些实现运算符重载和内置协议。

  1. 动态性

    • 运行时可以给类或实例动态添加、修改、删除属性/方法。
    • 可以猴子补丁。
    • 可以用 type() 动态创建类。
    • __slots__ 可限制实例属性、节省内存。
  2. 属性管理

    • property:getter/setter/deleter。
    • 描述符:实现 __get__、__set__、__delete__,是 property、方法绑定等的基础。
    • __getattr__、__getattribute__ 可拦截属性访问。
  3. 构造与析构

    • __new__ 负责创建,__init__ 负责初始化。
    • __del__ 是析构方法,但不保证及时调用,不推荐依赖它做资源释放。