GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • 卡码笔记-最强八股文
  • 计算机基础
  • Python
  • C++
  • Java
  • Go
  • ChatGPT充值
  • Claude充值
  • 国内使用Claude
  • 中转站推荐
  • 🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经

This view

  • Original page
  • New address

GeezerVison is showing notes.kamacoder.com — Python类和对象:类是蓝图,对象是实例,属性和方法怎么定义

# 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!
1
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)
1
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'}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这是Python对象模型的灵活性:属性不是固定的,对象就像一个活字典。

# 属性查找的顺序

访问obj.attr时,Python按以下顺序查找:

  1. 对象自己的__dict__(实例属性)。
  2. 类的__dict__及其父类(类属性和方法)。
  3. 描述符协议(属性、类方法、静态方法等特殊对象)。
  4. 如果都没找到,抛出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)    # 找到方法,返回一个绑定方法对象
1
2
3
4
5
6
7
8
9
10
11
12
13

当访问dog.info时,Python实际返回的是一个绑定方法对象,它记住了dog这个对象,所以调用dog.info()时会自动传递self。

对象属性查找与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'}
1
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了
1
2
3
4
5
6
7

Q3:能不能在类定义外面直接改类属性?

可以。类也是对象,可以在运行时修改它的属性。但这样修改的是所有共享这个类的对象看到的值:

class Dog:
    species = "Canis"

print(Dog.species)  # Canis
Dog.species = "Canis familiaris"
print(Dog.species)  # Canis familiaris
1
2
3
4
5
6

Q4:__dict__里的内容可以直接修改吗?

可以,但不建议。直接操作__dict__会绕过属性访问协议(比如__getattr__、描述符等):

obj.__dict__['x'] = 100  # 直接写进字典
# vs
obj.x = 100  # 正常赋值,会触发property或descriptor
1
2
3

Q5:为什么方法的第一个参数是self而不是this?

这是Guido的设计选择。用self强调"这是方法要操作的对象",比this更明确。这样看代码时,self出现的每一个地方都能提醒你"现在在用当前对象的属性"。


下一篇:实例属性和类属性有什么区别?

← Python模块和包是什么?import是怎么工作的? 实例属性和类属性有什么区别? →

评论

验证登录状态...

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.