类和对象
约 1905 字大约 6 分钟
2025-04-22
类(Class)和对象(Object)是面向对象编程(OOP)中最核心的两个概念。
- 定义类
- 创建对象
- 初始化方法 init
- 理解 self
- 1写一个 Student(name, score) 类,分别实例化两个对象,确认它们的 name 互不影响。
- 2故意把可变默认(如 hobbies = [])写成类属性,体会两个对象共享同一个列表的坑;再改成 self.hobbies = []。
- 3对同一个类的实例方法、@classmethod、@staticmethod 各跑一次,观察 self/cls 的区别。
- 4试着访问 _x 和 __x:_x 只是“请别动”的约定,__x 触发 name mangling,外部可用 _ClassName__x 强行访问。
- 5写一个属性带 @property,体会“像访问属性一样调用方法”比 get_xxx / set_xxx 更地道。
类(Class)和对象(Object)是面向对象编程(OOP)中最核心的两个概念。
从理论上讲,类也是一种抽象数据类型(Abstract Data Type, ADT)。它不仅仅是数据的容器,更是对现实世界逻辑的抽象,封装了数据(属性)和行为(方法),明确了职责边界。
通俗地讲,类就是一个模具或蓝图,它描述了一类事物共有的特征(属性)和行为(方法)。而对象则是根据这个模具制造出来的具体的、鲜活的个体(实例)。
例如,“人类”是一个类,它定义了人应该有姓名、年龄,会说话、吃饭。而“你”和“我”则是“人类”这个模具下的两个具体对象。
定义类
在 Python 中,使用 class 关键字定义类。按照 PEP 8 规范,类名通常采用 大驼峰命名法(CapWords)。
类主要由两部分组成:
- 属性(Attributes):用于存储数据(变量)。
- 方法(Methods):用于定义行为(函数)。
class MyClass:
"""一个简单的示例类"""
# 类属性
i = 12345
# 实例方法
def f(self):
return 'hello world'创建对象
类定义好后,并不会直接产生数据,必须将其实例化才能创建对象。实例化非常简单,就像调用函数一样调用类即可:
c = MyClass() # 创建一个 MyClass 的实例(对象)创建对象后,可以通过点号 . 来访问其属性和方法:
python playground
class MyClass:
i = 12345
def f(self):
return 'hello world'
c = MyClass()
print(c.i) # 访问属性 -> 12345
print(c.f()) # 调用方法 -> hello world初始化方法 __init__
在创建对象时,我们通常需要对其进行初始化,比如设置对象的初始状态或属性值。Python 提供了一个特殊方法 __init__(注意前后各有两个下划线),当类被实例化时,该方法会自动执行。
class Student:
def __init__(self, name, score):
# 初始化实例属性
self.name = name
self.score = score
# 实例化时传递参数,这些参数会被传递给 __init__
s1 = Student('Alice', 95)
s2 = Student('Bob', 80)
print(f'{s1.name}: {s1.score}') # Alice: 95
print(f'{s2.name}: {s2.score}') # Bob: 80笔记
__init__ 并不是构造函数(Constructor),它只是初始化方法。真正的构造函数是 __new__,但日常开发中极少直接重写 __new__。
理解 self
在 Python 的类方法中,第一个参数通常被命名为 self。self 代表的是类的实例对象本身。
- 当定义方法时,必须显式定义
self参数。 - 当调用方法时,Python 会自动将调用该方法的对象作为第一个参数传递给
self,不需要手动传递。
class Box:
def set_content(self, content):
self.content = content # self.content 表示给当前对象设置一个属性 content
def open(self):
return f'Box contains: {self.content}'
b1 = Box()
b2 = Box()
b1.set_content('Apple') # 调用时不需要传 self,Python 会自动把 b1 传给 self
b2.set_content('Banana')
print(b1.open()) # self 是 b1 -> Box contains: Apple
print(b2.open()) # self 是 b2 -> Box contains: Banana如果你熟悉 Java 或 C++,self 相当于它们的 this 指针,只不过 Python 要求必须显式写出来。
类属性与实例属性
这是 Python 面向对象中容易混淆的一个点:
- 实例属性:绑定到具体对象上的属性,通常在
__init__中通过self.变量名定义。每个对象的实例属性互不干扰。 - 类属性:直接在类内部定义的变量。它属于类本身,被所有实例共享。
基本区别
python playground
class Dog:
species = 'Canis' # 类属性:所有狗都是犬科
def __init__(self, name):
self.name = name # 实例属性:每只狗名字不同
d1 = Dog('Buddy')
d2 = Dog('Max')
# 这里说明 species 是共享的
print(d1.species) # Canis
print(d2.species) # Canis
# 这里的 name 是独立的
print(d1.name) # Buddy
print(d2.name) # Max⚠️ 警惕:可变类属性的陷阱
如果类属性是一个可变对象(如列表、字典),修改它会影响所有实例!这是初学者常犯的错误。
python playground
class Student:
# 这是一个类属性,被所有学生共享!
hobbies = []
def __init__(self, name):
self.name = name
def add_hobby(self, hobby):
self.hobbies.append(hobby)
s1 = Student('Alice')
s2 = Student('Bob')
s1.add_hobby('Reading')
print(s1.hobbies) # ['Reading']
# 意外发生了:Bob 的爱好里也多了 Reading
print(s2.hobbies) # ['Reading']
print(Student.hobbies) # ['Reading']正确的做法是将可变属性作为实例属性,在 __init__ 中初始化:
class Student:
def __init__(self, name):
self.name = name
self.hobbies = [] # 每个学生有自己独立的爱好列表方法类型
Python 类中主要有三种方法:
- 实例方法(Instance Method):最常用的方法。第一个参数是
self,可以访问实例属性和类属性。 - 类方法(Class Method):使用
@classmethod装饰。第一个参数是cls(代表类本身),只能访问类属性,不能访问实例属性。 - 静态方法(Static Method):使用
@staticmethod装饰。不需要self或cls参数,既不访问实例属性也不访问类属性,相当于普通函数寄生在类中。
python playground
class DateUtil:
default_format = 'YYYY-MM-DD' # 类属性
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 实例方法:需要访问具体实例的数据 (self.year 等)
def get_date_string(self):
return f'{self.year}/{self.month}/{self.day}'
# 类方法:需要访问类属性 (cls.default_format),不需要访问具体实例
@classmethod
def set_default_format(cls, new_format):
cls.default_format = new_format
print(f'Format changed to {cls.default_format}')
# 静态方法:逻辑独立,不需要类或实例的信息,仅仅是放在类里方便管理
@staticmethod
def is_valid_year(year):
return year > 0 and year < 9999
d = DateUtil(2023, 10, 1)
print(d.get_date_string()) # 调用实例方法
DateUtil.set_default_format('MM-DD-YYYY') # 调用类方法
print(DateUtil.is_valid_year(2023)) # 调用静态方法访问控制(私有与受保护)
Python 没有像 Java 那样严格的 private 和 public 关键字,而是依靠命名约定来控制访问。
name:公有(Public)。内部外部均可由访问。_name:受保护(Protected)。以单下划线开头。约定仅供内部使用,不要在外部访问,但强制访问不会报错。__name:私有(Private)。以双下划线开头。Python 会对其进行名称改写(Name Mangling),使得外部无法通过原名直接访问。
class Account:
def __init__(self, id, password):
self.id = id # 公有
self.__password = password # 私有,外部无法直接访问
a = Account(1, '123456')
print(a.id)
# print(a.__password) # 这行代码会报错关于封装的详细机制、Name Mangling 以及如何使用 @property 进行更优雅的封装,请参阅文章 封装。
- 类是模板,对象是实例;类名用 CapWords,实例属性放 __init__ 里。
- self 是“当前实例”的显式入参,调用方法时 Python 自动塞进来。
- 类属性被所有实例共享 —— 可变默认(list/dict)绝对放到 __init__ 里做实例属性。
- 方法三类:实例方法(self)、类方法(@classmethod cls)、静态方法(@staticmethod 无)。
- 访问控制靠约定:_x 表示内部,__x 触发 name mangling,没有真正的 private。
版权所有
版权归属:Shuo Liu
