类
前面学了函数和装饰器,这一篇来认识 Python 中面向对象编程的核心概念——类(class)。类是创建对象的模板,对象是类的实例。通过类,我们可以把数据和操作数据的方法封装在一起,让代码更结构化、更易复用。
1. 什么是面向对象编程
面向对象编程(Object-Oriented Programming,简称 OOP)是一种非常流行的编程范式。它把现实世界的事物抽象成对象,每个对象都有自己的属性(数据)和行为(方法)。
在 Python 中,面向对象编程有三大核心特性:
- 封装:把数据和操作数据的方法打包在一起,隐藏内部细节,只暴露必要的接口。
- 继承:从已有的类派生出新的类,复用已有代码。
- 多态:同样的消息作用于不同类型的对象,可以产生不同的行为。
类(class)是抽象的模板,对象(object)是类的具体实例。比如「学生」是一个类,而「张三」是这个类的一个对象。
2. 定义类与创建对象
在 Python 中,用 class 关键字定义类。类中定义的函数称为方法,方法的第一个参数通常叫 self,代表调用该方法的对象本身。
class Student:
"""学生类"""
def study(self, course_name):
print(f'{course_name} 学习中')
def play(self):
print('休息一会儿')
# 创建对象
stu1 = Student()
stu2 = Student()
print(stu1) # <__main__.Student object at 0x...>
print(stu2) # <__main__.Student object at 0x...>给对象发消息(调用方法)有两种方式:
# 1. 对象.方法() 最常用
stu1.study('Python')
# 2. 类.方法(对象) 等价写法
Student.study(stu1, 'Python')推荐用
对象.方法()的方式,更符合面向对象的习惯。
3. 初始化方法 init
创建对象时,如果需要给对象设置初始属性,可以使用 __init__ 方法。它在构造对象时自动调用,因此被称为初始化方法。
class Student:
"""学生类"""
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print(f'{self.name} 正在学习 {course_name}')
def play(self):
print(f'{self.name} 正在玩游戏')
stu1 = Student('骆昊', 44)
stu2 = Student('王大锤', 25)
stu1.study('Python 程序设计') # 骆昊 正在学习 Python 程序设计
stu2.play() # 王大锤 正在玩游戏self.name和self.age是对象的属性。self代表当前对象,调用方法时不需要手动传入。
4. 实例属性和类属性
属性既可以在 __init__ 里绑定到实例上,也可以直接定义在类上。两种属性的存储位置不同,行为也不同。
4.1 实例属性
实例属性绑定到实例上,每个对象独立拥有一份,互不影响。
class Dog:
def __init__(self, name):
self.name = name # 实例属性
dog1 = Dog('旺财')
dog2 = Dog('来福')
dog1.name = '大黄'
print(dog1.name) # 大黄
print(dog2.name) # 来福4.2 类属性
类属性定义在类体里,不属于某个对象,而是被所有实例共享。
class Dog:
kind = '犬科' # 类属性
def __init__(self, name):
self.name = name
dog1 = Dog('旺财')
dog2 = Dog('来福')
print(dog1.kind) # 犬科
print(dog2.kind) # 犬科
# 通过类名修改类属性
Dog.kind = '哺乳动物'
print(dog1.kind) # 哺乳动物
print(dog2.kind) # 哺乳动物4.3 访问类属性的陷阱
通过实例读取类属性时,Python 会先查找实例自己有没有这个属性,如果没有再去类里找。但如果通过实例给类属性赋值,Python 不会修改类属性,而是会在实例上新建一个同名的实例属性。
class Dog:
kind = '犬科'
def __init__(self, name):
self.name = name
dog1 = Dog('旺财')
dog2 = Dog('来福')
dog1.kind = '宠物犬' # 注意:这里创建的是 dog1 的实例属性,不是修改类属性
print(dog1.kind) # 宠物犬
print(dog2.kind) # 犬科
print(Dog.kind) # 犬科所以如果真想修改类属性,要用 类名.属性名 赋值。新手很容易在这里踩坑,记住一句话:读取可以走实例,修改最好用类名。
5. 封装与访问可见性
封装的核心思想是:隐藏内部细节,只暴露必要的接口。类中的方法其实就是一种封装。
Python 中没有像 Java 那样严格的 private / protected 关键字,但可以通过属性名前加下划线来表示访问可见性:
__name:双下划线前缀,表示私有属性( name mangling,外部不建议直接访问)。_name:单下划线前缀,表示受保护属性(约定,仍然可以访问)。
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
def study(self, course_name):
print(f'{self.__name} 正在学习 {course_name}')
stu = Student('王大锤', 20)
stu.study('Python 程序设计')
# 下面这行会报错
# print(stu.__name) # AttributeError在 Python 中,通常信奉「大家都是成年人」的约定,不会强制把属性私有。多数情况下直接用普通属性即可,需要控制访问时再用
__或@property。
6. @property 装饰器
@property 可以把一个方法变成属性访问,这样获取值时不需要加括号。
class User:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
user = User('juzicoding')
# 像访问属性一样获取值
print(user.name) # juzicoding
# 像设置属性一样设置值
user.name = 'tom'
print(user.name) # tom@property 常用于对属性进行封装,在取值和赋值时可以做额外校验或处理。
7. 类方法和静态方法
类中除了对象方法(第一个参数是 self),还有类方法和静态方法。
- 对象方法:发给对象的消息,第一个参数是
self。 - 类方法:发给类的消息,用
@classmethod装饰,第一个参数是cls。 - 静态方法:既不需要
self也不需要cls,用@staticmethod装饰。
class MyClass:
@staticmethod
def static_method():
print('静态方法')
@classmethod
def class_method(cls):
print(f'类名: {cls.__name__}')
MyClass.static_method() # 静态方法
MyClass.class_method() # 类名: MyClass什么时候用类方法或静态方法?比如判断三条边能否构成三角形,这个判断在三角形对象创建之前就可以做,更适合设计成静态方法或类方法。
class Triangle:
"""三角形"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
@staticmethod
def is_valid(a, b, c):
"""判断三条边能否构成三角形"""
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self.a + self.b + self.c
def area(self):
p = self.perimeter() / 2
return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5
if Triangle.is_valid(3, 4, 5):
t = Triangle(3, 4, 5)
print(t.perimeter()) # 12
print(t.area()) # 6.0
else:
print('无效的边长')8. 继承
继承是从已有的类(父类)创建新类(子类)的机制。子类会自动拥有父类的属性和方法,同时还可以扩展自己的属性和方法。
class Person:
"""人"""
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(f'{self.name} 正在吃饭')
def sleep(self):
print(f'{self.name} 正在睡觉')
class Student(Person):
"""学生"""
def study(self, course_name):
print(f'{self.name} 正在学习 {course_name}')
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self.title = title
def teach(self, course_name):
print(f'{self.title}{self.name} 正在讲授 {course_name}')
stu = Student('白元芳', 21)
stu.eat() # 白元芳 正在吃饭
stu.study('Python 程序设计') # 白元芳 正在学习 Python 程序设计
tea = Teacher('武则天', 35, '副教授')
tea.teach('Python 程序设计') # 副教授武则天 正在讲授 Python 程序设计Student(Person)表示Student继承自Person。super().__init__(name, age)调用父类的初始化方法。- 所有类默认继承自
object,它是 Python 中的顶级类。
9. 多态与抽象类
多态的意思是:调用相同的方法,不同的对象做出不同的行为。子类继承父类后,可以对方法进行重写。
class Animal:
def speak(self):
print('动物发出声音')
class Dog(Animal):
def speak(self):
print('汪汪')
class Cat(Animal):
def speak(self):
print('喵喵')
for animal in [Dog(), Cat(), Animal()]:
animal.speak()
# 输出:
# 汪汪
# 喵喵
# 动物发出声音当某个父类的方法暂时无法具体实现,只希望子类去重写时,可以把它声明为抽象方法。Python 通过 abc 模块实现抽象类。
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
"""结算月薪"""
pass
class Manager(Employee):
def get_salary(self):
return 15000.0
class Programmer(Employee):
def __init__(self, name, working_hour=0):
super().__init__(name)
self.working_hour = working_hour
def get_salary(self):
return 200 * self.working_hour
# 下面的代码会报错,因为 Employee 是抽象类
# emp = Employee('张三')
mgr = Manager('刘备')
print(mgr.get_salary()) # 15000.0抽象类不能被直接实例化,它的作用是定义子类必须实现的接口。
10. 获取对象信息
Python 提供了一组内置函数,用来在运行时了解对象的信息。
10.1 type()
type() 返回对象的具体类型。
a = 123
b = 'hello'
c = []
print(type(a)) # <class 'int'>
print(type(b)) # <class 'str'>
print(type(c)) # <class 'list'>10.2 isinstance()
isinstance() 判断对象是否是指定类型或其子类的实例,比 type() 更灵活。
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(type(dog) == Dog) # True
print(type(dog) == Animal) # False
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True当需要考虑继承关系时,优先使用 isinstance()。
10.3 dir()
dir() 返回对象的所有属性和方法列表,是了解一个对象的「能力清单」。
s = 'abc'
print(dir(s))
# 输出包含 __len__、upper、split 等大量属性和方法10.4 getattr() / setattr() / hasattr()
这三个函数可以动态地读取、设置和判断对象属性。
class Student:
def __init__(self, name):
self.name = name
stu = Student('骆昊')
# 判断是否有某个属性
print(hasattr(stu, 'name')) # True
# 获取属性
print(getattr(stu, 'name')) # 骆昊
# 获取不存在的属性时,可以指定默认值
print(getattr(stu, 'age', 18)) # 18
# 设置属性
setattr(stu, 'age', 44)
print(stu.age) # 44这些函数在编写通用代码、反射、动态加载时非常有用。
11. 魔术方法
Python 中有很多以双下划线开头和结尾的方法,称为魔术方法(magic method)。它们可以让对象支持更自然的操作。
常用的魔术方法:
| 方法 | 作用 |
|---|---|
__init__ | 初始化对象 |
__str__ | 返回对象的字符串描述,print() 调用 |
__repr__ | 返回对象的可打印表示 |
__eq__ | 判断两个对象是否相等 == |
__lt__ | 小于比较 < |
__gt__ | 大于比较 > |
__len__ | 返回对象长度,len() 调用 |
__getitem__ | 按下标取值,obj[index] 调用 |
__setitem__ | 按下标赋值,obj[index] = value 调用 |
__delitem__ | 删除元素,del obj[index] 调用 |
__getattr__ | 访问不存在的属性时调用(详见 12.4 节) |
小技巧:
__str__和__repr__的实现通常一样,可以直接写__repr__ = __str__,省掉重复代码。
class Point:
"""平面上的点"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return f'({self.x}, {self.y})'
def __lt__(self, other):
"""比较两个点到原点的距离"""
return self.x ** 2 + self.y ** 2 < other.x ** 2 + other.y ** 2
def distance_to(self, other):
"""计算与另一个点的距离"""
dx = self.x - other.x
dy = self.y - other.y
return (dx * dx + dy * dy) ** 0.5
p1 = Point(3, 5)
p2 = Point(6, 9)
print(p1) # (3, 5)
print(p1.distance_to(p2)) # 5.0
print(p1 < p2) # True12. 让对象支持迭代、索引和调用
魔术方法能让对象表现得像 Python 内置对象一样自然。这里介绍三个最常用的:让对象可被遍历、可下标访问、可像函数一样调用。
12.1 让对象可被迭代
实现 __iter__() 方法,对象就可以被 for...in 遍历。
class CountDown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
for n in CountDown(5):
print(n, end=' ')
# 输出: 5 4 3 2 112.2 让对象支持下标访问
实现 __getitem__() 方法,就可以像访问列表那样用 obj[index] 取值。
class Fibonacci:
def __init__(self, max_n):
self.max_n = max_n
def __getitem__(self, index):
if isinstance(index, int):
if index < 0 or index >= self.max_n:
raise IndexError('索引越界')
return self._fib(index)
elif isinstance(index, slice):
return [self._fib(i) for i in range(*index.indices(self.max_n))]
else:
raise TypeError('索引必须是整数或切片')
def _fib(self, n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
fib = Fibonacci(10)
print(fib[0]) # 0
print(fib[5]) # 5
print(fib[0:5]) # [0, 1, 1, 2, 3]与之对应的是 __setitem__() 方法,让对象像列表或字典一样支持赋值,比如 obj[0] = value;以及 __delitem__() 方法,让 del obj[0] 也能删除元素。实现这三个方法后,自定义对象就能完全像 list、dict 一样按下标操作。
12.3 让对象可调用
实现 __call__() 方法,对象就能像函数一样被调用。
class Fibonacci:
def __init__(self, max_n):
self.max_n = max_n
def __getitem__(self, index):
if index < 0 or index >= self.max_n:
raise IndexError('索引越界')
a, b = 0, 1
for _ in range(index):
a, b = b, a + b
return a
def __call__(self, n):
return self[n]
fib = Fibonacci(10)
print(fib(5)) # 5
print(fib(6)) # 8怎么判断一个对象能不能被调用?用内置函数 callable():
# True,带 __call__ 方法的实例
print(callable(fib))
# True,函数本身
print(callable(max))
# False
print(callable([1, 2, 3]))
# False
print(callable(None))能返回 True 的对象(函数、带 __call__ 的实例等)就是「可调用对象」,这也是「把对象看成函数、把函数看成对象」的体现。
实现
__iter__、__getitem__、__call__之后,自定义对象就能像列表、函数一样自然使用,这也是 Python「鸭子类型」的体现。
12.4 拦截不存在的属性:getattr
正常情况下,访问对象不存在的属性会报 AttributeError。如果实现了 __getattr__() 方法,属性查找失败时就会被拦截,可以动态返回一个值:
class Student:
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
raise AttributeError(f'Student 对象没有属性 {attr!r}')
stu = Student()
# Michael,已存在的属性不会走 __getattr__
print(stu.name)
# 99,动态返回
print(stu.score)
# 抛出 AttributeError
# print(stu.age)注意:
__getattr__只在属性查找失败时才调用;如果方法末尾不抛出AttributeError,任意不存在的属性都会返回None或自定义值,容易掩盖 bug。
__getattr__ 还能让类支持链式调用。比如实现一个 REST API 的 SDK,URL 路径可以完全动态拼接,不用为每个接口写一个方法:
class Chain:
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain(f'{self._path}/{path}')
def __str__(self):
return self._path
__repr__ = __str__
# /status/user/timeline/list
print(Chain().status.user.timeline.list)这个例子同时用上了 __getattr__、__str__ 和 __repr__ = __str__ 偷懒写法。
13. 动态属性与 slots
Python 是动态语言,可以给对象动态添加属性。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('王大锤', 20)
stu.sex = '男' # 动态添加属性
print(stu.sex) # 男如果不希望对象动态添加属性,可以使用 __slots__ 限制可拥有的属性。
class Student:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('王大锤', 20)
# 下面这行会报错
# stu.sex = '男' # AttributeError14. 类装饰器
装饰器不仅可以装饰函数,还可以装饰类。类装饰器通常用于给类添加额外功能。
14.1 函数形式的类装饰器
def log_class(cls):
class Wrapper:
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.wrapped, name)
def display(self):
print('调用前')
self.wrapped.display()
print('调用后')
return Wrapper
@log_class
class MyClass:
def display(self):
print('原方法')
obj = MyClass()
# 输出: 调用前
# 输出: 原方法
# 输出: 调用后
obj.display()14.2 类形式的类装饰器
用类也可以实现装饰器。下面是一个单例模式的示例:
class SingletonDecorator:
def __init__(self, cls):
self.cls = cls
self.instance = None
def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.cls(*args, **kwargs)
return self.instance
@SingletonDecorator
class Database:
def __init__(self):
print('初始化')
db1 = Database()
db2 = Database()
print(db1 is db2) # True单例模式保证一个类只有一个实例。第一次调用 Database() 会创建实例,之后再调用都返回同一个实例。
15. 内置装饰器
Python 提供了几个常用的内置装饰器,专门用于类中。
| 装饰器 | 作用 |
|---|---|
@staticmethod | 定义静态方法 |
@classmethod | 定义类方法 |
@property | 把方法变成属性访问 |
这三个装饰器在前面已经分别介绍过。@staticmethod 和 @classmethod 在第 7 节,@property 在第 6 节。
16. 综合案例
16.1 扑克游戏
简单起见,这里的扑克只有 52 张牌(没有大小王),需要将牌发到 4 个玩家手上,每个玩家 13 张牌,按花色和点数从小到大排列。
首先定义花色枚举。枚举用符号常量代替数字,代码可读性更好。
from enum import Enum
class Suite(Enum):
"""花色"""
SPADE, HEART, CLUB, DIAMOND = range(4)
for suite in Suite:
print(f'{suite}: {suite.value}')
# 输出:
# Suite.SPADE: 0
# Suite.HEART: 1
# Suite.CLUB: 2
# Suite.DIAMOND: 3接下来定义牌类。
class Card:
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
def __repr__(self):
suites = '♠♥♣♦'
faces = ['', 'A', '2', '3', '4', '5', '6',
'7', '8', '9', '10', 'J', 'Q', 'K']
return f'{suites[self.suite.value]}{faces[self.face]}'
def __lt__(self, other):
"""比较两张牌的大小,用于排序"""
if self.suite == other.suite:
return self.face < other.face
return self.suite.value < other.suite.value
card1 = Card(Suite.SPADE, 5)
card2 = Card(Suite.HEART, 13)
print(card1) # ♠5
print(card2) # ♥K然后定义扑克类,负责洗牌和发牌。
import random
class Poker:
"""扑克"""
def __init__(self):
# 52 张牌
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
# 记录当前发牌位置
self.current = 0
def shuffle(self):
"""洗牌"""
self.current = 0
random.shuffle(self.cards)
def deal(self):
"""发牌"""
card = self.cards[self.current]
self.current += 1
return card
@property
def has_next(self):
"""是否还有牌"""
return self.current < len(self.cards)再定义玩家类。
class Player:
"""玩家"""
def __init__(self, name):
self.name = name
self.cards = []
def get_one(self, card):
"""摸牌"""
self.cards.append(card)
def arrange(self):
"""整理手牌"""
self.cards.sort()最后创建 4 个玩家,轮流发牌并输出结果。
poker = Poker()
poker.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
# 每人发 13 张牌
for _ in range(13):
for player in players:
player.get_one(poker.deal())
# 整理并输出
for player in players:
player.arrange()
print(f'{player.name}: ', end='')
print(player.cards)
# 输出示例:
# 东邪: [♠3, ♠7, ♠J, ♥2, ♥8, ♥Q, ♣4, ♣9, ♣K, ♦5, ♦10, ♦A, ♦J]
# ...这个案例把枚举、列表推导式、魔术方法 __repr__ / __lt__、@property、类之间的关系(扑克 has-a 牌、玩家 has-a 牌)都串了起来。
16.2 数字时钟
import time
class Clock:
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.min = minute
self.sec = second
def run(self):
"""走字"""
self.sec += 1
if self.sec == 60:
self.sec = 0
self.min += 1
if self.min == 60:
self.min = 0
self.hour += 1
if self.hour == 24:
self.hour = 0
def show(self):
"""显示时间"""
return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
clock = Clock(23, 59, 58)
while True:
print(clock.show())
time.sleep(1)
clock.run()16.3 工资结算系统
某公司有三种员工:部门经理月薪固定 15000 元;程序员按工作时间(每小时 200 元)计算;销售员底薪 1800 元加销售额 5% 提成。
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
"""结算月薪"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self.working_hour = working_hour
def get_salary(self):
return 200 * self.working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self.sales = sales
def get_salary(self):
return 1800 + self.sales * 0.05
emps = [
Manager('刘备'),
Programmer('诸葛亮'),
Manager('曹操'),
Programmer('荀彧'),
Salesman('张辽')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = 160
elif isinstance(emp, Salesman):
emp.sales = 50000
print(f'{emp.name} 本月工资为: ¥{emp.get_salary():.2f}')
# 输出:
# 刘备 本月工资为: ¥15000.00
# 诸葛亮 本月工资为: ¥32000.00
# 曹操 本月工资为: ¥15000.00
# 荀彧 本月工资为: ¥32000.00
# 张辽 本月工资为: ¥4300.00
isinstance(emp, Programmer)可以判断对象是否属于某个类或其子类,比type()更灵活(详见第 10 节)。
17. 枚举类
当变量只能取有限个固定值时,比如星期、颜色、状态,可以使用枚举类,避免用魔法数字。
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED) # Color.RED
print(Color.RED.name) # RED
print(Color.RED.value) # 1枚举类还有两种常用写法。第一种是函数式定义,直接传入成员名元组,value 会自动从 1 开始赋值:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'))
# Month.Jan
print(Month.Jan)
# 1
print(Month.Jan.value)
# 迭代所有成员
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)第二种是配合 @unique 装饰器,保证成员值不重复,有重复时直接报错:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
# 如果再定义一个成员值也是 1,会抛出 ValueError
# Wed = 1
# 按成员名访问
# Weekday.Tue
print(Weekday['Tue'])
# 按成员值访问
# Weekday.Mon
print(Weekday(1))
# 2
print(Weekday.Tue.value)
# True,成员可以直接比较
print(Weekday.Mon == Weekday(1))枚举类让代码可读性更好,也更容易维护。
18. 元类
元类(metaclass)是创建类的类。普通类通过 class 定义,默认由 type 创建。通过自定义元类,可以在创建类时自动做一些事情。
# 默认情况下,以下两种写法等价
class MyClass:
pass
MyClass = type('MyClass', (), {})绝大多数情况下不需要自定义元类,只要了解「类本身也是对象,由 type 创建」即可。
class Meta(type):
def __new__(cls, name, bases, attrs):
print(f'创建类: {name}')
return super().__new__(cls, name, bases, attrs)
class Demo(metaclass=Meta):
pass
# 创建类: Demo元类是 Python 中比较高级的概念,日常开发中很少直接用到,了解它能帮助理解类的工作原理。
总结
类是创建对象的模板,__init__ 负责初始化对象属性,self 代表对象本身。通过封装可以隐藏内部细节,通过继承可以复用代码,通过多态可以让不同对象对同一消息做出不同反应。
理解属性时要区分实例属性(每个对象独立)和类属性(所有实例共享);修改类属性时记得通过类名修改,避免在实例上赋值时意外创建同名实例属性。
type()、isinstance()、dir()、getattr()/setattr()/hasattr() 是运行时了解对象信息的好工具,其中 isinstance() 在判断继承关系时比 type() 更灵活。
魔术方法可以让对象表现得像内置类型一样自然:__str__、__repr__、__eq__、__lt__、__gt__、__len__ 用于描述和比较,__iter__、__getitem__、__call__ 让对象可遍历、可下标访问、可调用。再加上 @staticmethod、@classmethod、@property、类装饰器、枚举类和元类,基本就能应付日常大部分面向对象编程场景了。想真正掌握 OOP,还是要多写代码、多读代码。