Python 类与对象的核心逻辑:从 self、cls 到点号、括号与链式调用

学习 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:当前类。
函数、方法或类被调用以后会得到一个结果,这个结果本身又可以是对象,因此可以继续使用 .。
看到链式代码时,不要整体理解,只需要从左向右判断:当前是什么对象、. 查找到了什么、() 调用以后又返回了什么。

相关推荐
顶点多余43 分钟前
C/C++常见面试题
开发语言·c++
(Charon)1 小时前
【C++面试】右值引用与移动语义:std::move到底做了什么
开发语言·c++
TheITSea1 小时前
JavaScript Web APIs
开发语言·前端·javascript
空心木偶☜1 小时前
A2A2A(多智能体协作)
开发语言·python·ai·ai编程
Cc.Y1 小时前
Java零基础入门:类与对象深度掌握 —— 从“零散变量“到“打包管理“
java·开发语言·python
\光辉岁月/1 小时前
2.java变量
java·开发语言·变量
AINative软件工程1 小时前
LLM Prompt Registry 工程实践:集中管理 Prompt,让模型调用不再散落在代码各处
后端·python·llm
夏天的清晨1 小时前
C++入门学习
开发语言·c++·学习
Patrick在香港1 小时前
MCP 的 initialize 握手真的没了?67 行标准库实测 2026-07-28 规范
python·agent·claude·mcp·json-rpc