我们从一个类比开始。

假如你要建一栋房子。在动工之前,你肯定需要一张设计图。设计图上规定了房子有几间卧室、门朝哪开、窗户多大。但设计图本身不是房子,你无法住进去。你必须根据这张设计图,在工地上真正盖出一栋房子来——而且同一张设计图,可以盖出很多栋一模一样的房子。

在 Python 里:

设计图就是

盖出来的房子就是对象

这就是面向对象编程最核心的两个概念。下面我们由浅入深,把这个体系完整搭建起来。

──────────────────────────────────────────────────

第 1 层:类与对象(从无到有)

1.1 定义一个最简单的类


class Dog:
    pass
 

这就定义了一个名为 Dog 的类,里面什么都没写。pass 表示“什么也不做”。

1.2 根据类创建对象


dog1 = Dog()
dog2 = Dog()
 
print(dog1)        # <__main__.Dog object at 0x7f...>
print(type(dog1))  # <class '__main__.Dog'>
 

dog1 和 dog2 是两个独立的对象,它们都属于 Dog 这个类。

1.3 给对象绑定属性

Python 很灵活,即使类中没有定义,也可以动态地给对象添加属性:


dog1.name = "旺财"
dog1.age = 3
dog2.name = "来福"
 

但这种方式很乱,每只狗的属性都是临时拼凑的,缺乏规范。我们要把属性写进类的设计图里。

──────────────────────────────────────────────────

第 2 层:实例属性与构造方法 `__init__`

2.1 构造方法

__init__ 是一个特殊方法,在创建对象时自动调用,用来初始化这个对象的状态。习惯上把 self 作为第一个参数,它代表当前正在被创建的那个对象本身


class Dog:
    def __init__(self, name, age):
        self.name = name    # self.name
是对象的属性,name 是传入的参数
        self.age = age
 

现在创建对象必须提供参数:


dog1 = Dog("旺财", 3)
dog2 = Dog("来福", 1)
 
print(dog1.name)  # 旺财
print(dog2.age)   # 1
 

self.name 和 self.age 被称为实例属性,因为每一个对象都有自己的一份。

──────────────────────────────────────────────────

第 3 层:实例方法

除了属性(数据),对象还能有行为,用方法来表示。


class Dog:
    def __init__(self, name):
        self.name = name
 
    def bark(self):
        print(f"{self.name}
在汪汪叫!")
 
    def sit(self):
        print(f"{self.name} 坐下了。")
 

调用方法时,Python 会自动把调用对象传给 self:


d = Dog("大黄")
d.bark()   # 大黄 在汪汪叫!
d.sit()    # 大黄 坐下了。
 

**关键直觉**:类把**数据**(属性)和**操作数据的函数**(方法)封装在一起。这就是“封装”的第一层含义。

──────────────────────────────────────────────────

第 4 层:类属性(类变量)

有些数据不属于某一只具体的狗,而属于“狗”这个物种整体,比如狗都有四条腿。这时用类属性


class Dog:
    legs = 4                     #
类属性
 
    def __init__(self, name):
        self.name = name         # 实例属性
 
print(Dog.legs)     # 4
d1 = Dog("阿黄")
d2 = Dog("小黑")
print(d1.legs)      # 4  —— 实例可以访问类属性
print(d2.legs)      # 4
 

如果通过类修改 Dog.legs = 3,所有实例都会跟着变(除非某个实例显式绑定了自己的 legs)。通常,类属性用于保存所有实例共享的状态或常量

──────────────────────────────────────────────────

第 5 层:封装与属性访问控制

Python 没有绝对的私有属性,但有约定名称改写机制

写法

含义

name

公开属性,想怎么访问就怎么访问

_name

“私有”约定,告诉别人别动它,但实际依然可以动

__name

名称改写(name mangling),Python 会自动改成 _类名__name,防止被子类无意覆盖


class BankAccount:
    def __init__(self, balance):
        self.__balance = balance    #
不希望你从外部直接修改
 
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
 
    def get_balance(self):
        return self.__balance
 
acc = BankAccount(1000)
# print(acc.__balance)            # 报错 AttributeError
print(acc._BankAccount__balance)  # 1000  (可以强制访问,但不该这么做)
print(acc.get_balance())          # 1000  (正确方法)
 

这种机制把数据保护起来,仅通过方法接口对外暴露,是封装的第二层含义:隐藏内部实现,只暴露安全操作

──────────────────────────────────────────────────

第 6 层:继承——复用与扩展

假如我们要定义 Dog 类,后来还需要 Cat 类。它们有很多共同点,比如都有名字、年龄,都能叫。这时可以抽出一个父类(基类)Animal,让 Dog 和 Cat 继承它。


class Animal:
    def __init__(self, name):
        self.name = name
 
    def speak(self):
        raise NotImplementedError("
子类必须实现 speak 方法")
 
class Dog(Animal):
    def speak(self):
        return f"{self.name} 汪汪叫!"
 
class Cat(Animal):
    def speak(self):
        return f"{self.name} 喵喵叫!"
 
animals = [Dog("旺财"), Cat("咪咪")]
for a in animals:
    print(a.speak())
 

输出:


旺财 汪汪叫!
咪咪 喵喵叫!
 

父类 Animal 定义了公共接口,子类只需给出自己的实现。你还可以在子类中增加新的方法,或者覆盖(override)父类的方法。

──────────────────────────────────────────────────

第 7 层:多态——同一个接口,不同表现

上一节最后那个 for 循环就是多态的体现:同一个方法名 speak,针对不同的对象产生不同的行为。多态让你可以编写更通用的代码,而不必为每个具体类型写一堆 if/else。

“鸭子类型”是多态的 Pythonic 实现:

如果走起来像鸭子,叫起来像鸭子,那它就是鸭子。

不需要严格的继承关系,只要对象实现了某个方法,就可以拿来用。


class Duck:
    def sound(self):
        return "
嘎嘎"
 
class Car:
    def sound(self):
        return "滴滴"
 
def make_sound(thing):
    print(thing.sound())
 
make_sound(Duck())  # 嘎嘎
make_sound(Car())   # 滴滴
 

──────────────────────────────────────────────────

第 8 层:特殊方法——让对象像内置类型一样

Python 的__str__、__len__、__eq__ 等方法,让你自定义的类可以和内置类型无缝交互。


class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages
 
    def __str__(self):
        return f"
《{self.title}》"
 
    def __len__(self):
        return self.pages
 
    def __eq__(self, other):
        return self.title == other.title and self.pages == other.pages
 
b1 = Book("Python入门", 300)
b2 = Book("Python入门", 300)
 
print(b1)            # 《Python入门》
print(len(b1))       # 300
print(b1 == b2)      # True
 

        常见特殊方法还有:__repr__(给开发者看的字符串),__add__(+运算符),__getitem__(obj[key]),__iter__(迭代)等。这是 Python 对象魅力的源头。

        特殊方法知识补充:__len__、__str__、__eq__ 这类方法的名字和触发机制是 Python 语言本身规定好的,不是用户自己创造的新方法名。你可以把它理解为 Python 给开发者预留的“接口”——只要你实现了这个接口,你的对象就能融入 Python 的内置操作体系。

        虽然这些方法是 Python 内置的协议,但默认情况下你的自定义类并不拥有它们(或只有从object继承来的默认版本)。你需要在自己的类中定义这些方法,赋予它们符合你需求的逻辑

        异常重要的一点:你不能随意写一个 __my_magic__ 并期望 Python 解释器自动在某种情况下调用它。Python 只会调用它已知的那些特殊方法名(大约 100 多个)。所以,特殊方法的“创建权”在语言设计者,而“实现权”在开发者。

──────────────────────────────────────────────────

第 9 层:类方法与静态方法

除了普通的实例方法(操作实例数据),还有两种方法:


class MyClass:
    class_var = 0
 
    def __init__(self, value):
        self.value = value
 
    #
实例方法:第一个参数是 self,操作实例
    def instance_method(self):
        return self.value
 
    # 类方法:第一个参数是 cls,操作类本身
    @classmethod
    def increment_class_var(cls):
        cls.class_var += 1
 
    # 静态方法:没有自动传入 self 或 cls,与普通函数类似,但放在类里
    @staticmethod
    def utility(x, y):
        return x * y
 

类方法常用于创建工厂方法(例如使用不同方式构建对象),或修改类级状态。

静态方法主要用于将某个与类相关的工具函数放在类的命名空间下。

──────────────────────────────────────────────────

第 10 层:属性装饰器 `@property`

有时你希望访问属性时触发一些计算,但又不想让调用者用像 get_xxx() 这样的方法。@property 让你把方法伪装成属性。


class Circle:
    def __init__(self, radius):
        self._radius = radius
 
    @property
    def radius(self):
        return self._radius
 
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("
半径不能为负")
        self._radius = value
 
    @property
    def area(self):
        return 3.14159 * self._radius ** 2
 
c = Circle(5)
print(c.area)        # 78.53975
c.radius = 10        # 用 setter
print(c.area)        # 314.159
 

这样,既保持了接口的简洁(c.radius 而不是 c.get_radius()),又能在赋值时进行校验。

──────────────────────────────────────────────────

第 11 层:抽象基类

如果你想强制子类必须实现某些方法,可以用 abc 模块。


from abc import ABC, abstractmethod
 
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
 
    @abstractmethod
    def perimeter(self):
        pass
 
class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
    def area(self):
        return self.width * self.height
 
    def perimeter(self):
        return 2 * (self.width + self.height)
 
# s = Shape()  #
报错!不能实例化抽象类
r = Rectangle(3, 4)
print(r.area())  # 12
 

抽象基类定义了一个协议,告诉别人:“想当我的子类,就必须实现这些方法”。

──────────────────────────────────────────────────

第 12 层:多重继承与 MRO

Python 支持一个类同时继承多个父类。


class Flyable:
    def fly(self):
        print("
我在飞")
 
class Swimmable:
    def swim(self):
        print("我在游泳")
 
class Duck(Flyable, Swimmable):
    pass
 
d = Duck()
d.fly()
d.swim()
 

但这会引出问题:如果多个父类有同名方法怎么办?Python 使用 MRO(方法解析顺序) 来决定调用哪个。你可以通过 类名.__mro__ 查看这个顺序,它基于 C3 线性化算法

一个常见的模式是 Mixin(混入类):写一些功能单一、可以被任意组合的小类,比如 LogMixin、JSONSerializeMixin,然后用多重继承把它们“混入”到主类中。这与“is-a”继承不同,更偏向“can-do”的组合。


class JsonMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)
 
class Person(JsonMixin):
    def __init__(self, name):
        self.name = name
 
p = Person("
张三")
print(p.to_json())  # {"name": "张三"}
 

多重继承很强大,但很容易让代码变得混乱。优先考虑组合(把一个对象放在另一个对象内部),而不是多重继承。

──────────────────────────────────────────────────

第 13 层:`__slots__` 与内存优化

当你需要创建成千上万个相同类的实例时,可以使用 __slots__ 来固定属性集合,从而减少每个实例的内存开销。


class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y
 

此时不能再随意添加新的属性 p.z = 10。__slots__ 去掉了默认的 __dict__,使实例变得更轻。

──────────────────────────────────────────────────

第 14 层:元类——类的类

这是一个高阶主题。在 Python 里,类本身也是对象,而创建这个类对象的“东西”就是元类。默认元类是 type。


# 用 type 动态创建类
MyClass = type('MyClass', (object,), {'x': 10})
obj = MyClass()
print(obj.x)  # 10
 

你可以定义自己的元类(继承自 type),从而在类被创建的那一刻介入,做自动验证、自动注册等神奇操作。绝大多数日常开发不需要元类,但它在框架和库中大量存在(如 Django 的模型、SQLAlchemy 等)。

──────────────────────────────────────────────────

面向对象的学习路径图


对象/类基础 → 实例属性/方法 → 类属性
             ↓
          封装(私有属性,property)
             ↓
          继承(代码复用)
             ↓
          多态(接口统一,鸭子类型)
             ↓
          高级特性(特殊方法,类方法,静态方法,抽象类)
             ↓
          组合优于继承 → Mixin → __slots__ → 元类(进阶)
 

核心心法

     类是设计图,对象是实物

     把数据和对数据的操作塞在一起,就是封装初步。

     少用继承,多用组合;需要复用行为时可以考虑 Mixin。

     面向对象不是银弹。当你的类层次深得像一棵大树时,停下来,想想是不是过度设计了。Python 中很多问题用简单的函数和字典就能解决。

更多推荐