学习 Python 的类时,很容易被下面这些写法绕晕:
python
self.name
cls.count
Person()
obj.show()
datetime.now().year
尤其有 Java 基础时,会感觉 Python 一会儿使用 .,一会儿使用 (),有时前面是实例,有时又是类,函数甚至还可以直接作为参数传递。
其实这些写法背后首先要掌握两条最基本的规则:
.:从前面的对象中查找一个属性。
():调用前面的可调用对象。
类、实例、函数、方法、self、cls 和链式调用,很多都可以在这两条规则的基础上理解。
1. 类和实例
例如:
python
class Person:
species = 'Human'
def __init__(self, name):
self.name = name
创建实例:
python
p = Person('Tom')
这里:
| 名称 | 含义 |
|---|---|
Person |
类 |
p |
Person 类创建出来的实例 |
可以简单理解为:
类用来定义一种对象;实例是根据这个类真正创建出来的具体对象。
例如:
python
p1 = Person('Tom')
p2 = Person('Jerry')
p1 和 p2 都属于 Person 类,但它们是两个不同的实例对象。
2. 类属性和实例属性
类属性
定义在类中、方法外面的属性:
python
class Person:
species = 'Human'
这里:
python
species
是 Person 的类属性。
可以通过类访问:
python
Person.species
也可以通过实例访问:
python
p.species
当 Python 查找:
python
p.species
时,可以先简单理解成:
| 查找过程 |
|---|
p.species |
| ↓ |
先在实例 p 自己身上寻找 species |
| ↓ 没找到 |
再到 Person 类中寻找 |
| ↓ |
找到 species = 'Human' |
所以:
python
Person.species
是直接从类中查找。
而:
python
p.species
会先从实例中查找,如果实例中没有,再继续到类中查找。
实例属性
例如:
python
class Person:
def __init__(self, name):
self.name = name
这里:
python
self.name = name
是在当前实例上设置 name 属性。
例如:
python
p1 = Person('Tom')
p2 = Person('Jerry')
此时:
python
p1.name # Tom
p2.name # Jerry
所以:
类属性属于类;实例属性属于某一个具体实例。
类本身通常不能直接读取某个具体实例的属性:
python
Person.name
因为可能同时存在:
text
p1.name = Tom
p2.name = Jerry
类本身并不知道你要的是哪一个实例的 name。
3. 类和实例都可以添加属性
对于普通的自定义类:
python
class Person:
pass
可以给类添加属性:
python
Person.country = 'China'
然后:
python
print(Person.country)
得到:
text
China
也可以给实例添加属性:
python
p = Person()
p.name = 'Tom'
p.age = 18
所以:
| 写法 | 含义 |
|---|---|
Person.xxx = value |
给类设置属性 |
p.xxx = value |
给实例设置属性 |
这也是后面理解:
python
self.xxx
cls.xxx
的基础。
需要注意,Python 中也存在限制动态属性的类,例如使用了 __slots__ 的类,以及部分内置类型。因此"可以随意添加属性"主要是针对普通自定义类。
4. Python 类中的三种常见方法
Python 类中常见的方法主要有三种:
| 类型 | 常见写法 | 自动得到什么 |
|---|---|---|
| 实例方法 | def show(self): |
当前实例 self |
| 类方法 | @classmethod |
当前类 cls |
| 静态方法 | @staticmethod |
不自动得到实例或类 |
实例方法
例如:
python
class Person:
def show(self):
print('Hello')
通常通过实例调用:
python
p = Person()
p.show()
调用时 Python 会把当前实例 p 自动传给 self。
所以:
python
p.show()
可以理解成:
python
Person.show(p)
也就是:
text
self = p
因此:
self表示当前调用这个实例方法的实例。
类方法
类方法使用:
python
@classmethod
例如:
python
class Person:
@classmethod
def show(cls):
print(cls)
通过类调用:
python
Person.show()
此时:
text
cls = Person
实例也可以调用类方法:
python
p = Person()
p.show()
此时传给 cls 的仍然是实例所属的类 Person,而不是实例 p。
因此:
实例方法自动得到实例
self;类方法自动得到类cls。
静态方法
例如:
python
class Person:
@staticmethod
def add(a, b):
return a + b
调用:
python
Person.add(1, 2)
静态方法不会自动得到:
text
self
cls
可以先理解为:
它本质上不依赖当前实例或当前类,只是因为功能上与这个类相关,所以放在类中。
5. self 到底是什么?
self 不是一种特殊的数据类型,也不是 Python 的关键字。
它本质上只是一个参数名,只不过 Python 程序员约定把实例方法的第一个参数写成:
python
self
例如:
python
class Person:
def set_name(self, name):
self.name = name
调用:
python
p = Person()
p.set_name('Tom')
此时可以理解成:
text
self = p
name = 'Tom'
所以:
python
self.name = name
实际上操作的就是:
python
p.name = 'Tom'
因此看到:
python
self.xxx
可以直接理解成:
当前实例的
xxx属性。
6. cls 到底是什么?
cls 和 self 类似,同样只是一个约定俗成的参数名。
区别是:
| 参数 | 表示 |
|---|---|
self |
当前实例 |
cls |
当前类 |
例如:
python
class Person:
@classmethod
def set_country(cls):
cls.country = 'China'
调用:
python
Person.set_country()
此时:
text
cls = Person
所以:
python
cls.country = 'China'
实际上相当于:
python
Person.country = 'China'
因此:
| 写法 | 理解 |
|---|---|
self.xxx |
操作当前实例 |
cls.xxx |
操作当前类 |
7. 为什么 cls() 又能创建实例?
例如:
python
class Person:
def __init__(self, name):
self.name = name
@classmethod
def create(cls, name):
return cls(name)
调用:
python
p = Person.create('Tom')
进入 create() 后:
text
cls = Person
因此:
python
cls(name)
实际上就是:
python
Person(name)
而:
python
Person('Tom')
通常会创建一个新的 Person 实例。
整个过程可以表示为:
| 执行过程 |
|---|
cls |
| ↓ |
当前指向 Person 类 |
| ↓ |
cls('Tom') |
| ↓ |
Person('Tom') |
| ↓ |
创建一个 Person 实例 |
所以并不是 cls 有什么特殊功能。
真正的原因是:
cls此时指向的是一个类,而类本身就是可调用对象。
8. 函数和方法有什么区别?
在类外面定义:
python
def hello():
print('Hello')
通常称为:
text
函数 function
而:
python
class Person:
def hello(self):
print('Hello')
通过实例:
python
p.hello()
使用时,通常称为:
text
方法 method
初学时可以先理解成:
| 情况 | 名称 |
|---|---|
类外单独定义的 def |
函数 |
| 类中定义并通过类或实例使用 | 方法 |
更准确地说:
类中使用
def时也会创建函数对象;当这个函数通过实例或类被访问并发生相应绑定后,我们通常把得到的结果称为方法。
因此函数和方法并不是完全没有关系的两种东西。
9. 函数名为什么可以作为参数传递?
例如:
python
def say_hello(name):
print(name)
定义完成以后:
python
say_hello
这个名字绑定到了一个函数对象。
可以理解成:
| 关系 |
|---|
say_hello |
| ↓ |
| 对应的函数对象 |
因此:
python
other = say_hello
是完全合法的。
此时:
python
other('Tom')
仍然可以调用原来的函数。
函数也可以作为参数:
python
def use_function(func):
func('Tom')
use_function(say_hello)
调用以后,可以理解成:
text
func = say_hello
所以:
python
func('Tom')
实际上就是:
python
say_hello('Tom')
10. 不加 () 和加 () 有什么区别?
例如:
python
say_hello
表示:
函数对象本身。
而:
python
say_hello('Tom')
表示:
调用这个函数。
所以:
python
use_function(say_hello)
传进去的是函数对象本身。
而:
python
use_function(say_hello('Tom'))
会先执行:
python
say_hello('Tom')
然后把这个函数的返回值传给 use_function()。
因此:
| 写法 | 含义 |
|---|---|
func |
函数对象本身 |
func() |
调用函数 |
11. . 到底是什么意思?
这是理解 Python 类和对象最重要的一条规则。
看到:
python
a.b
首先不要急着判断 b 是普通数据还是方法。
应该先理解成:
从
a这个对象上查找名字叫b的属性。
这里的"属性"是一个比较广泛的概念。
最终查找到的东西可能是:
text
普通数据
方法
函数
类
另一个实例
property
其他对象
例如:
python
p.name
表示:
从
p身上查找name。
python
p.show
表示:
从
p身上查找show。
python
datetime.now
表示:
从
datetime身上查找now。
至于最终找到的是什么,需要看它真正的定义。
12. 怎么知道 . 后面找到的是方法还是普通属性?
例如:
python
datetime.now
我们知道 now 是方法,不是因为名字叫 now,而是因为它实际就是一个可调用的方法。
可以查看:
python
print(datetime.now)
或者:
python
print(type(datetime.now))
同样,对于:
python
obj.xxx
这段代码本身只说明:
Python 从
obj上找到了一个叫xxx的属性。
至于它实际是什么,可以通过:
python
type(obj.xxx)
或者查看:
text
类的定义
IDE 提示
官方文档
进行确认。
所以:
.只负责查找,不能单独告诉你查到的是普通数据、方法还是其他对象。
13. () 到底是什么意思?
看到:
python
a()
意思就是:
调用
a。
前提是:
python
a
必须是一个可调用对象。
例如:
| 写法 | 含义 |
|---|---|
say_hello() |
调用函数 |
p.show() |
调用方法 |
Person() |
调用类 |
这三种写法前面的对象类型并不一样,但都可以使用:
python
()
因为它们都是可调用对象。
所以:
()不是函数专属符号,而是"调用"的意思。
14. "调用类"到底是什么意思?
例如:
python
p = Person('Tom')
这里:
python
Person('Tom')
不是在调用普通函数,也不是在调用实例方法。
而是在:
调用
Person这个类对象。
初学时可以把整个过程理解成:
| 执行过程 |
|---|
Person('Tom') |
| ↓ |
调用 Person 类 |
| ↓ |
创建新的 Person 实例 |
| ↓ |
| 执行初始化 |
| ↓ |
| 得到实例 |
因此:
| 写法 | 调用的对象 |
|---|---|
hello() |
函数 |
p.show() |
方法 |
Person() |
类 |
共同点只有一个:
()前面的东西是可调用对象。
15. Python 中函数或方法返回的是不是对象?
Python 中几乎所有值都是对象。
例如:
python
x = 10
10 是:
text
int 类的实例对象
例如:
python
x = 'Hello'
'Hello' 是:
text
str 类的实例对象
例如:
python
x = [1, 2, 3]
这个列表是:
text
list 类的实例对象
所以函数或者方法返回一个值以后,更准确的问题应该是:
返回的是哪一种对象?
而不是:
返回的是不是对象?
即使一个函数没有显式写:
python
return
Python 也会返回:
python
None
而 None 本身同样是一个对象。
16. 如何判断返回的是哪一种对象?
最直接的方法是使用:
python
type(...)
例如:
python
from datetime import datetime
x = datetime.now()
print(type(x))
结果:
text
<class 'datetime.datetime'>
这里不是说:
text
x 是一个 class
而是:
type(x)返回的是x所属的类。
也就是:
| 过程 |
|---|
x |
| ↓ |
| 一个实例对象 |
↓ type(x) |
| 查看这个实例属于哪个类 |
| ↓ |
datetime.datetime |
还可以使用:
python
isinstance(x, datetime)
判断:
python
x
是不是 datetime 类的实例。
例如:
python
print(isinstance(x, datetime))
结果:
text
True
17. 为什么 type(x) 显示 <class '...'> 而不是 object?
因为:
python
type(x)
不是用来回答:
x是不是对象?
而是用来回答:
x属于哪个类?
例如:
python
type(10)
得到:
text
<class 'int'>
意思是:
text
10
→ int 类的实例对象
再例如:
python
type('Hello')
得到:
text
<class 'str'>
意思是:
text
'Hello'
→ str 类的实例对象
所以:
python
type(datetime.now())
得到:
text
<class 'datetime.datetime'>
表示:
datetime.now()返回的是datetime.datetime类的实例对象。
18. 为什么是 datetime.datetime,两个 datetime 是什么意思?
因为这两个 datetime 身份不同:
| 第一部分 | 第二部分 |
|---|---|
datetime |
datetime |
| 模块名 | 类名 |
Python 中存在一个模块:
python
datetime
这个模块中又定义了一个类:
python
datetime
所以这个类的完整名字就是:
python
datetime.datetime
例如:
python
import datetime
x = datetime.datetime.now()
整个层级是:
| 执行过程 |
|---|
datetime |
| ↓ |
datetime 模块 |
↓ .datetime |
模块中的 datetime 类 |
↓ .now |
找到 now 方法 |
↓ () |
调用 now |
19. 模块和类有什么区别?
可以先记住:
模块主要用来组织代码。
类主要用来定义一种对象。
例如存在文件:
text
school.py
这个文件通常可以作为一个模块。
里面可以写:
python
school_name = 'ABC School'
def show_school():
print(school_name)
class Student:
pass
那么:
| 写法 | 含义 |
|---|---|
school |
模块 |
school.Student |
模块中的 Student 类 |
school.Student() |
调用这个类,创建 Student 实例 |
层级关系可以理解成:
| 层级 |
|---|
| 模块 |
| ↓ 可以包含 |
| 变量、函数、类 |
| ↓ 类可以创建 |
| 实例 |
因此:
| 概念 | 主要作用 |
|---|---|
| 模块 | 组织代码 |
| 类 | 定义一种对象 |
| 实例 | 类创建出来的具体对象 |
20. datetime.now().year 到底发生了什么?
假设:
python
from datetime import datetime
这里已经直接从:
text
datetime 模块
中导入了:
text
datetime 类
因此当前代码中的:
python
datetime
表示的是 datetime 类。
现在来看:
python
datetime.now().year
不要整体分析,而是从左向右一步一步看。
第一步:datetime
python
datetime
当前表示:
text
datetime 类
第二步:.now
python
datetime.now
表示:
从
datetime类上查找now。
这里还没有 (),所以只是拿到了 now 本身,并没有调用它。
可以直接查看它的类型:
python
print(type(datetime.now))
结果:
text
<class 'builtin_function_or_method'>
说明:
datetime.now当前得到的是now这个方法本身。
第三步:()
继续:
python
datetime.now()
这里增加了:
python
()
表示:
调用刚才找到的
now方法。
调用以后,得到的已经不再是 now 方法本身,而是这个方法的返回结果。
可以查看:
python
print(type(datetime.now()))
结果:
text
<class 'datetime.datetime'>
这里:
text
datetime.datetime
↑ ↑
模块名 类名
所以这表示:
datetime.now()返回的是datetime模块中datetime类的实例对象。
也可以先保存:
python
temp = datetime.now()
此时:
python
temp
就是这个 datetime 实例。
第四步:.year
既然:
python
datetime.now()
已经返回了一个 datetime 实例,就可以继续:
python
datetime.now().year
也就是从刚才返回的实例中查找:
python
year
属性。
可以继续查看结果的类型:
python
print(type(datetime.now().year))
结果:
text
<class 'int'>
说明:
.year最终得到的是一个int对象,也就是当前年份这个整数。
例如:
python
datetime.now().year
可能得到:
text
2026
用 type() 把整个过程连起来看
python
print(type(datetime.now))
# <class 'builtin_function_or_method'>
print(type(datetime.now()))
# <class 'datetime.datetime'>
print(type(datetime.now().year))
# <class 'int'>
对应关系:
| 写法 | 当前得到的东西 | 类型 |
|---|---|---|
datetime.now |
now 方法本身 |
builtin_function_or_method |
datetime.now() |
调用 now 后返回的实例 |
datetime.datetime |
datetime.now().year |
实例中的年份 | int |
整个变化过程:
| 执行过程 |
|---|
datetime |
| ↓ |
datetime 类 |
↓ .now |
得到 now 方法本身 |
↓ () |
调用 now 方法 |
| ↓ |
返回 datetime 实例 |
↓ .year |
读取实例的 year 属性 |
| ↓ |
得到 int 对象 |
所以:
python
datetime.now().year
完全可以拆成:
python
temp = datetime.now()
result = temp.year
真正发生的是:
找到方法 → 调用方法 → 得到实例对象 → 从实例中读取属性。
这里最值得注意的是 ():
text
datetime.now
→ 方法本身
datetime.now()
→ 调用方法以后得到的返回对象
所以并不是"方法后面突然又出现了一个属性",而是方法调用以后已经返回了一个新的对象,后面的 .year 操作的是这个新对象。
21. 为什么方法调用以后还能继续 .xxx?
因为:
方法调用以后会得到返回值,而返回值本身又是一个对象。
例如:
python
datetime.now()
返回:
text
datetime 对象
因此可以继续:
python
datetime.now().year
再例如:
python
obj.get_list()
如果 get_list() 返回的是:
text
list 对象
就可以继续:
python
obj.get_list().append(10)
整个过程是:
| 执行过程 |
|---|
obj.get_list() |
| ↓ |
返回一个 list 对象 |
↓ .append |
从这个列表对象中找到 append 方法 |
↓ () |
调用 append(10) |
所以:
前一步得到的对象,可以继续成为下一步操作的对象。
22. 如何理解 a.b.c().d.e()?
这种代码看起来复杂,但其实没有新的语法。
只是在不断重复:
text
. → 查找属性
() → 调用
例如:
python
class Address:
def show(self):
return 'Beijing'
class Profile:
def __init__(self):
self.address = Address()
class User:
def get_profile(self):
return Profile()
class App:
def __init__(self):
self.user = User()
创建:
python
app = App()
然后执行:
python
app.user.get_profile().address.show()
第一步:app.user
python
app.user
表示:
从
app实例中查找user属性。
这个属性保存的是一个:
text
User 实例
所以:
text
app.user
→ User 实例
第二步:.get_profile
继续:
python
app.user.get_profile
表示:
从刚才得到的
User实例中查找get_profile。
找到的是:
text
get_profile 方法
第三步:()
python
app.user.get_profile()
表示:
调用刚才找到的
get_profile方法。
这个方法:
python
def get_profile(self):
return Profile()
会返回:
text
Profile 实例
因此:
text
app.user.get_profile()
→ Profile 实例
第四步:.address
继续:
python
app.user.get_profile().address
现在实际是在:
text
Profile 实例
上寻找:
text
address
而代码中:
python
self.address = Address()
说明 address 保存的是一个:
text
Address 实例
所以:
text
app.user.get_profile().address
→ Address 实例
第五步:.show
继续:
python
app.user.get_profile().address.show
表示:
从这个
Address实例中找到show方法。
第六步:()
最后:
python
app.user.get_profile().address.show()
表示:
调用
show方法。
而:
python
def show(self):
return 'Beijing'
最终返回:
text
'Beijing'
整个过程可以整理成:
| 执行过程 |
|---|
app |
↓ .user |
User 实例 |
↓ .get_profile |
找到 get_profile 方法 |
↓ () |
调用 get_profile |
| ↓ |
返回 Profile 实例 |
↓ .address |
得到 Address 实例 |
↓ .show |
找到 show 方法 |
↓ () |
调用 show |
| ↓ |
返回 'Beijing' |
这样比全部写在一个左对齐代码框中更容易看清每一步之间的关系。
23. 链式调用为什么可以一直写下去?
例如:
python
app.user.get_profile().address.show()
每一步都会产生一个结果:
| 表达式 | 得到的结果 |
|---|---|
app.user |
User 实例 |
get_profile() |
Profile 实例 |
.address |
Address 实例 |
show() |
str 对象 'Beijing' |
前一步得到的对象,可以继续:
python
.xxx
从它身上找东西。
如果找到的是可调用对象,还可以继续:
python
.xxx()
调用它。
所以 Python 中经常会看到:
python
a.b.c().d.e()
这样的链式写法。
24. 以后应该如何分析链式代码?
以后看到:
python
a.b.c().d.e()
不要整体分析。
只需要从左到右不断问三个问题:
| 顺序 | 要问的问题 |
|---|---|
| ① | 当前这个东西是什么对象? |
| ② | . 后面查找到了什么? |
| ③ | 如果出现 (),调用以后返回了什么? |
可以把整个过程理解成:
| 分析过程 |
|---|
| 当前对象 |
↓ . |
| 查找一个属性 |
| ↓ |
| 得到一个对象 |
↓ 如果有 () |
| 调用这个对象 |
| ↓ |
| 得到新的对象 |
| ↓ |
再继续使用 . 查找 |
所以:
链式调用没有额外的新规则,本质上只是不断重复"查找 → 调用 → 得到对象 → 继续查找"。
总结
| 写法 | 含义 |
|---|---|
Class.attr |
从类中查找属性 |
obj.attr |
从实例中查找属性 |
self |
当前实例 |
cls |
当前类 |
self.x = value |
给当前实例设置属性 |
cls.x = value |
给当前类设置属性 |
func |
函数对象本身 |
func() |
调用函数 |
obj.method() |
找到方法并调用 |
Class() |
调用类,通常创建实例 |
type(obj) |
查看对象所属的类 |
isinstance(obj, Class) |
判断对象是不是某个类的实例 |
datetime.datetime |
datetime 模块中的 datetime 类 |
datetime.now() |
调用 now,得到 datetime 实例 |
datetime.now().year |
从返回的 datetime 实例中读取 year 属性 |
最终真正需要记住的是:
.:从前面的对象中查找属性。
()`:调用前面的可调用对象。
self:当前实例。
cls:当前类。
函数、方法或类被调用以后会得到一个结果,这个结果本身又可以是对象,因此可以继续使用.。
看到链式代码时,不要整体理解,只需要从左向右判断:当前是什么对象、.查找到了什么、()调用以后又返回了什么。