# Python类和对象:类是蓝图,对象是实例
面试官问:"Python中的类和对象是什么关系?为什么叫self?"
这题很多人会说"类是模板,对象是实例"就完了。但真正要讲清楚的是:类定义了对象的结构和行为,对象是类的一个具体副本;每个对象都有自己的属性字典,而方法是通过self绑定到具体对象上的。 另外,Python对象不像其他语言那样有内存布局的限制,属性可以随时增删。
# 简要回答
类是一个代码模板,对象是类的一个运行时实例。 定义类时,用class声明属性和方法;创建对象时,调用类名(触发__new__和__init__),得到一个真实存在的对象。
self是方法接收的第一个参数,指向调用该方法的对象本身。 当你写dog.bark()时,Python自动把dog这个对象作为self传给bark()方法。self让方法能访问和修改对象自己的属性。
每个对象维护自己的属性字典__dict__。 实例属性存在对象的__dict__里,类属性存在类的__dict__里。访问属性时,Python先查对象的__dict__,再查类的__dict__。
# 详细回答
# 类与对象的关系
类是定义,对象是实例。定义类时写的代码在类体执行一次;创建对象时,会触发__new__(创建对象),然后调用__init__(初始化属性)。
class Dog:
species = "Canis familiaris" # 类属性
def __init__(self, name):
self.name = name # 实例属性
def bark(self):
return f"{self.name} says: Woof!"
# 创建两个Dog对象
dog1 = Dog("Buddy")
dog2 = Dog("Max")
print(dog1.name) # Buddy
print(dog2.name) # Max
print(dog1.bark()) # Buddy says: Woof!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
每个对象是独立的,修改dog1.name不会影响dog2。
# self的本质
方法的第一个参数self是Python约定,用来指向调用该方法的对象。当你写dog1.bark()时,Python会把这个调用改写成Dog.bark(dog1):
class Dog:
def bark(self):
print(f"self is {self}")
print(f"self.name is {self.name}")
dog = Dog()
dog.name = "Buddy"
dog.bark()
# 等价于:Dog.bark(dog)
2
3
4
5
6
7
8
9
这就是为什么第一个参数必须是self——它接收的是调用方的对象。self只是个约定名字,技术上可以写成其他名字,但会让代码难以理解。
# 对象的属性字典
每个对象都有一个__dict__,存放它的实例属性:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
print(p.__dict__) # {'name': 'Alice', 'age': 30}
# 可以直接修改或增删属性
p.city = "Beijing"
print(p.__dict__) # {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
del p.age
print(p.__dict__) # {'name': 'Alice', 'city': 'Beijing'}
2
3
4
5
6
7
8
9
10
11
12
13
14
这是Python对象模型的灵活性:属性不是固定的,对象就像一个活字典。
# 属性查找的顺序
访问obj.attr时,Python按以下顺序查找:
- 对象自己的
__dict__(实例属性)。 - 类的
__dict__及其父类(类属性和方法)。 - 描述符协议(属性、类方法、静态方法等特殊对象)。
- 如果都没找到,抛出
AttributeError。
class Dog:
breed = "Mixed" # 类属性
def __init__(self, name):
self.name = name # 实例属性
def info(self): # 方法
return f"{self.name} is a {self.breed}"
dog = Dog("Buddy")
print(dog.name) # 从dog.__dict__查找
print(dog.breed) # dog.__dict__里没有,查找Dog类
print(dog.info) # 找到方法,返回一个绑定方法对象
2
3
4
5
6
7
8
9
10
11
12
13
当访问dog.info时,Python实际返回的是一个绑定方法对象,它记住了dog这个对象,所以调用dog.info()时会自动传递self。

# 为什么对象可以动态添加属性
与编译型语言不同,Python对象在运行时可以添加、修改或删除属性。这是因为属性不是在类定义时固定的结构,而是存在对象的__dict__里:
class Empty:
pass
obj = Empty()
# 可以随时添加属性
obj.x = 10
obj.y = 20
print(obj.__dict__) # {'x': 10, 'y': 20}
# 不同的对象可以有不同的属性
obj2 = Empty()
obj2.name = "test"
print(obj2.__dict__) # {'name': 'test'}
2
3
4
5
6
7
8
9
10
11
12
13
这种灵活性很有用,但也容易出错。如果你期望对象有某个属性却没有,会抛异常。
# 知识拓展
Q1:为什么一定要在__init__里初始化属性?
你不一定非得这样做。Python允许在任何时候添加属性。但惯例是在__init__里初始化,这样别人读代码时能一眼看出对象有什么属性。如果属性分散在各个方法里添加,代码会很混乱。
Q2:如果类属性和实例属性同名会怎样?
实例属性会优先被找到。实例属性在对象的__dict__里,查找时先检查对象__dict__,再查类。
class Test:
value = "class"
t = Test()
print(t.value) # class,还没有实例属性
t.value = "instance"
print(t.value) # instance,现在t.__dict__有value了
2
3
4
5
6
7
Q3:能不能在类定义外面直接改类属性?
可以。类也是对象,可以在运行时修改它的属性。但这样修改的是所有共享这个类的对象看到的值:
class Dog:
species = "Canis"
print(Dog.species) # Canis
Dog.species = "Canis familiaris"
print(Dog.species) # Canis familiaris
2
3
4
5
6
Q4:__dict__里的内容可以直接修改吗?
可以,但不建议。直接操作__dict__会绕过属性访问协议(比如__getattr__、描述符等):
obj.__dict__['x'] = 100 # 直接写进字典
# vs
obj.x = 100 # 正常赋值,会触发property或descriptor
2
3
Q5:为什么方法的第一个参数是self而不是this?
这是Guido的设计选择。用self强调"这是方法要操作的对象",比this更明确。这样看代码时,self出现的每一个地方都能提醒你"现在在用当前对象的属性"。
下一篇:实例属性和类属性有什么区别?
评论
验证登录状态...