0

0

Python的对象模型是什么

王林

王林

发布时间:2023-05-18 21:02:43

|

1731人浏览过

|

来源于亿速云

转载

在面向对象的理论中,有两个核心的概念:类和实例。类可以看成是一个模板,实例就是根据这个模板创建出来的对象。在python中,类和实例都被视为对象,即类对象(或类型对象)和实例对象。

为了避免后续出现歧义,我们这里把对象分为三种:

  • 内置类对象:比如 int、str、list、type、object 等等;

  • 自定义类对象:通过 class 关键字定义的类,当然我们也会把它和上面的内置类对象统称为类对象(或者类型对象);

  • 实例对象:由类对象(内置类对象或自定义类对象)创建的实例;

    立即学习Python免费学习笔记(深入)”;

而对象之间存在以下两种关系:

  • is-kind-of:对应面向对象理论中子类和父类之间的关系;

  • is-instance-of:对应面向对象理论中实例对象和类对象之间的关系;

我们举例说明:

class Girl(object):

    def say(self):
        return "古明地觉"

girl = Girl()
print(girl.say())  # 古明地觉

这段代码便包含了上面的三种对象:object(内置类对象),Girl(自定义类对象),girl(实例对象)。

显然 Girl 和 object 之间是 is-kind-of 关系,即 Girl 是 object 的子类。值得一提的是,Python3 里面所有的类(除 object)都是默认继承自 object,即便我们这里不显式继承 object,也会默认继承的,但为了说明,我们就写上了。

除了 Girl 是 object 的子类,我们还能看出 girl 和 Girl 之间存在 is-instance-of 关系,即 girl 是 Girl 的实例。当然如果再进一步的话,girl 和 object 之间也存在 is-instance-of 关系,girl 也是 object 的实例。

class Girl(object):
    pass
    
girl = Girl()
print(issubclass(Girl, object))  # True 
print(type(girl))  # <class '__main__.Girl'>
print(isinstance(girl, Girl))  # True
print(isinstance(girl, object))  # True

Girl被实例化后得到一个girl实例,因此调用type(girl)会返回Girl类对象。Girl是Object类的一个实例对象,因为它继承了Object类。至于这其中的原理,我们会慢慢介绍。

Python 也提供了一些手段可以探测这些关系,除了上面的 type 之外,还可以使用对象的 __class__ 属性探测一个对象和其它的哪些对象之间存在 is-instance-of 关系。

而通过对象的 __bases__ 属性则可以探测一个对象和其它的哪些对象之间存在着 is-kind-of 关系。此外 Python 还提供了两个函数 issubclass 和 isinstance 来验证两个对象之间是否存在着我们期望的关系。

class Girl(object):
    pass 

girl = Girl()
print(girl.__class__)  # <class '__main__.Girl'>
print(Girl.__class__)  # <class 'type'>
# __class__是查看自己的类型是什么,也就是生成自己的类
# 而在介绍 Python 对象的时候,我们就看到了
# 任何一个对象都至少具备两个东西: 一个是引用计数、一个是类型
# 所以 __class__ 是所有对象都具备的

# __base__只显示直接继承的第一个类
print(Girl.__base__)  # <class 'object'>
# __bases__ 会显示直接继承的所有类,以元组的形式
print(Girl.__bases__)  # (<class 'object'>,)

我们画一张图总结一下:

Python的对象模型是什么

另外需要注意里面的 type 和 object:

  • type 和 object 存在 is-kind-of 关系,因为 type 是 object 的子类;

  • object 和 type 存在 is-instance-of 关系,因为 object 是 type 的实例对象;

可能有人会好奇为什么会是这样,而关于这一点,我在 type 与 object 的恩怨纠葛这篇文章讲得很详细了,感兴趣可以点击阅读一下。

简单来说就是,type 在底层对应的结构体为 PyType_Type、object 在底层对应的结构体为 PyBaseObject_Type。而在创建 object 的时候,将内部的 ob_type 设置成了&PyType_Type;在创建type的时候,将内部的 tp_base 设置成了&PyBaseObject_Type。

因此这两者的定义是彼此依赖的,两者是同时出现的,我们后面还会看到。

另外 type 的类型就是 type 本身,所以:

  • 实例对象的类型是类型对象,类型对象的类型是元类;

    DreamStudio
    DreamStudio

    SD兄弟产品!AI 图像生成器

    下载
  • 所有类型对象的基类都收敛于 object;

  • 所有对象的类型都收敛于 type;

Python的对象模型是什么

因此 Python 算是将一切皆对象的理念贯彻到了极致,也正因为如此,Python 才具有如此优秀的动态特性。

但还没有结束,我们看一下类对象 Girl 的行为,首先它支持属性设置:

class Girl(object):
    pass

print(hasattr(Girl, "name"))  # False
Girl.name = "古明地觉"
print(hasattr(Girl, "name"))  # True
print(Girl.name)  # 古明地觉

在其他静态语言中,一旦定义一个类,就不能再添加属性,但在我们的语言中可以。Python是如何实现动态添加属性的呢?一般我们会想到字典

正如 global 名字空间一样,我们猜测类应该也有自己的属性字典,往类里面设置属性的时候,等价于向字典中添加键值对,同理其它操作也与之类似。

class Girl(object):
    pass

print(Girl.__dict__.get("name", "不存在"))  # 不存在
Girl.name = "古明地觉"
print(Girl.__dict__.get("name"))  # 古明地觉

和操作全局变量是类似的,但是有一点需要注意:我们不能直接通过类的属性字典来设置属性。

try:
    Girl.__dict__["name"] = "古明地觉"
except Exception as e:
    print(e)  
# 'mappingproxy' object does not support item assignment

虽然叫属性字典,但其实是 mappingproxy 对象,该对象本质上就是对字典进行了一层封装,在字典的基础上移除了增删改操作,也就是只保留了查询功能。要给类增加属性,可以使用直接赋值的方式或调用 setattr 函数。

但在介绍如何篡改虚拟机的时候,我们提到过一个骚操作,可以通过 gc 模块拿到 mappingproxy 对象里的字典。

import gc

class Girl(object):
    pass

gc.get_referents(Girl.__dict__)[0]["name"] = "古明地觉"
print(Girl.name)  # 古明地觉

并且这种做法除了适用于自定义类对象,还适用于内置类对象。但是工作中不要这么做,知道有这么个操作就行。

除了设置属性之外,我们还可以设置函数。

class Girl(object):
    pass

Girl.info = lambda name: f"我是{name}"
print(Girl.info("古明地觉"))  # 我是古明地觉

# 如果实例调用的话,会和我们想象的不太一样
# 因为实例调用的话会将函数包装成方法
try:
    Girl().info("古明地觉")
except TypeError as e:
    print(e) 
"""
<lambda>() takes 1 positional argument but 2 were given
"""    

# 实例在调用的时候会将自身也作为参数传进去
# 所以第一个参数 name 实际上接收的是 Girl 的实例对象
# 只不过第一个参数按照规范来讲应该叫做self
# 但即便你起别的名字也是无所谓的
print(Girl().info())  
"""
我是<__main__.Girl object at 0x000001920BB88760>
"""

所以我们可以有两种做法:

# 将其包装成一个静态方法
# 这样类和实例都可以调用
Girl.info = staticmethod(lambda name: f"我是{name}")
print(Girl.info("古明地觉"))  # 我是古明地觉
print(Girl().info("古明地觉"))  # 我是古明地觉

# 如果是给实例用的,那么带上一个 self 参数即可
Girl.info = lambda self, name: f"我是{name}"
print(Girl().info("古明地觉"))  # 我是古明地觉

此外我们还可以通过 type 来动态地往类里面进行属性的增加、修改和删除。

class Girl(object):

    def say(self):
        pass

print(hasattr(Girl, "say"))  # True
# delattr(Girl, "say") 与之等价
type.__delattr__(Girl, "say")
print(hasattr(Girl, "say"))  # False
# 我们设置一个属性吧
# 等价于 Girl.name = "古明地觉"
setattr(Girl, "name", "古明地觉")
print(Girl.name)  # 古明地觉

事实上调用 getattr、setattr、delattr 等价于调用其类型对象的__getattr__、__setattr__、__delattr__。

所以,一个对象支持哪些行为,取决于其类型对象定义了哪些操作。并且通过对象的类型对象,可以动态地给该对象进行属性的设置。Python 所有类型对象的类型对象都是 type,通过 type 我们便可以控制类的生成过程,即便类已经创建完毕了,也依旧可以进行属性设置。

但是注意:type 可以操作的类只能是通过 class 定义的动态类,而像 int、list、dict 等静态类,它们是在源码中静态定义好的,只不过类型设置成了 type。一言以蔽之,type 虽然是所有类对象的类对象,但 type 只能对动态类进行属性上的修改,不能修改静态类。

try:
    int.name = "古明地觉"
except Exception as e:
    print(e)
"""
can't set attributes of built-in/extension type 'int'
"""

try:
    setattr(int, "ping", "pong")
except Exception as e:
    print(e)
"""
can't set attributes of built-in/extension type 'int'     
"""

内置类和扩展类的属性无法被设置,这是由于内置类在解释器启动后就已被初始化。可以通过报错信息观察到这点。我们所说的扩展类,是指我们使用 Python/C API 编写的扩展模块中的类,其与内置类具有相同的地位。

因此内置类和使用 class 定义的类本质上是一样的,都是 PyTypeObject 对象,它们的类型在 Python 里面都是 type。不同的是,内置类在底层是以静态初始化方式实现的,因此我们无法通过动态设置属性的方式来操作它们(除非使用 gc 模块)。

但是为什么不可以对内置类和扩展类进行属性设置呢?首先我们要知道 Python 的动态特性是虚拟机赐予的,而虚拟机的工作就是将 PyCodeObject 对象翻译成 C 的代码进行执行,所以 Python 的动态特性就是在这一步发生的。

同理,扩展类和内置类在解释器启动后都已经被静态初始化,并直接指向 C 一级的数据结构。它们与解释执行绕开了相应过程,因此无法在其上动态添加属性。

不光内置的类本身,还有它的实例对象也是如此。

a = 123
print(hasattr(a, "__dict__"))  # False

我们发现它甚至连自己的属性字典都没有,因为解释器对于内置类对象的实例对象,其内部的属性和方法是已知的。由于底层代码已被固定且不允许修改,因此在实现虚拟机时无需创建属性字典,以节省内存。

相关文章

python速学教程(入门到精通)
python速学教程(入门到精通)

python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

58

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

65

2025.11.27

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

97

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

106

2025.09.18

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

490

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

202

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1051

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

615

2024.08.29

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号