Python_面向对象
我们从一个类比开始。
假如你要建一栋房子。在动工之前,你肯定需要一张设计图。设计图上规定了房子有几间卧室、门朝哪开、窗户多大。但设计图本身不是房子,你无法住进去。你必须根据这张设计图,在工地上真正盖出一栋房子来——而且同一张设计图,可以盖出很多栋一模一样的房子。
在 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 中很多问题用简单的函数和字典就能解决。
更多推荐


所有评论(0)