返回顶部
首页 > 资讯 > 后端开发 > Python >详解Python中type与object的恩怨纠葛
  • 384
分享到

详解Python中type与object的恩怨纠葛

Python type objectPythonPython object 2023-05-15 08:05:41 384人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

在学习 python 的时候,你肯定听过这么一句话:Python 中一切皆对象。没错,在 Python 世界里,一切都是对象。整数是一个对象、字符串是一个对象、字典是一个对象,甚至

学习 python 的时候,你肯定听过这么一句话:Python 中一切皆对象。没错,在 Python 世界里,一切都是对象。整数是一个对象、字符串是一个对象、字典是一个对象,甚至 int, str, list 等等,再加上我们使用 class 关键字自定义的类,它们也是对象。

像 int, str, list 等基本类型,以及我们自定义的类,由于它们可以表示类型,因此我们称之为类型对象;类型对象实例化得到的对象,我们称之为实例对象。但不管是哪种对象,它们都属于对象。

因此 Python 将面向对象理念贯彻的非常彻底,面向对象中的类和对象在 Python 中都是通过对象实现的。

在面向对象理论中,存在着类和对象两个概念,像 int、dict、tuple、以及使用 class 关键字自定义的类型对象实现了面向对象理论中类的概念,而 123、(1, 2, 3),"xxx" 等等这些实例对象则实现了面向对象理论中对象的概念。但在 Python 里面,面向对象的类和对象都是通过对象实现的。

我们举个例子:

# dict 是一个类,因此它属于类型对象
# 类型对象实例化得到的对象属于实例对象
print(dict)
"""
<class 'dict'>
"""
print(dict(a=1, b=2))
"""
{'a': 1, 'b': 2}
"""

因此可以用一张图来描述面向对象在Python中的体现:

而如果想查看一个对象的类型,可以使用 type,或者通过对象的 __class__ 属性。

numbers = [1, 2, 3]
# 查看类型
print(type(numbers))
"""
<class 'list'>
"""
print(numbers.__class__)
"""
<class 'list'>
"""

如果想判断一个对象是不是指定类型的实例对象,可以使用 isinstance。

numbers = [1, 2, 3]
# 判断是不是指定类型的实例对象
print(isinstance(numbers, list))
"""
True
"""

但是问题来了,按照面向对象的理论来说,对象是由类实例化得到的,这在 Python 中也是适用的。既然是对象,那么就必定有一个类来实例化它,换句话说对象一定要有类型。

至于一个对象的类型是什么,就看这个对象是被谁实例化的,被谁实例化那么类型就是谁,比如列表的类型是 list,字典的类型是 dict 等等。

而我们说 Python 中一切皆对象,所以像 int, str, tuple 这些内置的类对象也是具有相应的类型的,那么它们的类型又是谁呢?

我们使用 type 查看一下。

>>> type(int)
<class 'type'>
>>> type(str)
<class 'type'>
>>> type(dict)
<class 'type'>
>>> type(type)
<class 'type'>

我们看到类型对象的类型,无一例外都是 type。而 type 我们也称其为元类,表示类型对象的类型。至于 type 本身,它的类型还是 type,所以它连自己都没放过,把自己都变成自己的对象了。

因此在 Python 中,你能看到的任何对象都是有类型的,我们可以使用 type 查看,也可以获取该对象的 __class__ 属性查看。所以:实例对象、类型对象、元类,Python 中任何一个对象都逃不过这三种身份。

到这里可能有人会发现一个有意思的点,我们说 int 是一个类对象,这显然是没有问题的。因为站在整数(比如 123)的角度上,int 是一个不折不扣的类对象;但如果站在 type 的角度上呢?显然我们又可以将 int 理解为实例对象,因此 class 具有二象性。

至于 type 也是同理,虽然它是元类,但本质上也是一个类对象。

注:不仅 type 是元类,那些继承了 type 的类也可以叫做元类。

这些概念上的东西读起来可能会有一点绕,但如果实际动手敲一敲代码的话,还是很好理解的。

然后 Python 中还有一个关键的类型(对象),叫做 object,它是所有类型对象的基类。不管是什么类,内置的类也好,我们自定义的类也罢,它们都继承自 object。因此 object 是所有类型对象的基类、或者说父类。

那如果我们想获取一个类都继承了哪些基类,该怎么做呢?方式有三种:

class A: pass

class B: pass

class C(A): pass

class D(B, C): pass

# 首先 D 继承自 B 和 C, C 又继承 A
# 我们现在要来查看 D 继承的父类

# 方法一: 使用 __base__
print(D.__base__)  
"""
<class '__main__.B'>
"""

# 方法二: 使用 __bases__
print(D.__bases__)  
"""
(<class '__main__.B'>, <class '__main__.C'>)
"""

# 方法三: 使用 __mro__
print(D.__mro__)
"""
(<class '__main__.D'>, <class '__main__.B'>, 
 <class '__main__.C'>, <class '__main__.A'>, 
 <class 'object'>)
"""
  • __base__:如果继承了多个类,那么只显示继承的第一个类,没有显式继承则返回 <class 'object'>
  • __bases__:返回一个元组,会显示所有直接继承的父类,没有显式继承, 则返回 (<class 'object'>,)
  • __mro__: mro(Method Resolution Order)表示方法查找顺序,会从自身出发,找到最顶层的父类。因此返回自身、继承的基类、以及基类继承的基类, 一直找到 object

而如果想查看某个类型是不是另一个类型的子类,可以通过 issubclass。

print(issubclass(str, object))
"""
True
"""

因此,到目前为止,关于 type 和 object,我们可以得出以下两个结论:

  • type站在类型金字塔的最顶端, 任何一个对象按照类型追根溯源, 最终得到的都是type;
  • object站在继承金字塔的最顶端, 任何一个类型对象按照继承关系追根溯源, 最终得到的都是object;

但要注意的是,我们说 type 的类型还是 type,但 object 的基类则不再是 object,而是 None。

print(
    type.__class__
)  # <class 'type'>

# 注:以下打印结果容易让人产生误解
# 它表达的含义是 object 的基类为空
# 而不是说 object 继承 None
print(
    object.__base__
)  # None

但为什么 object 的基类是 None,而不是它自身呢?其实答案很简单,Python 在查找属性或方法的时候,自身如果没有的话,会按照 __mro__ 指定的顺序去基类中查找。所以继承链一定会有一个终点,否则就会像没有出口的递归一样出现死循环了。

我们用一张图将对象之间的关系总结一下:

  • 实例对象的类型是类型对象,类型对象的类型是元类;
  • 所有类型对象的基类都收敛于 object;
  • 所有对象的类型都收敛于 type;

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

但是还没结束,我们再重新审视一下上面那张图,会发现里面有两个箭头看起来非常的奇怪。object 的类型是 type,type 又继承了 object。

>>> type.__base__
<class 'object'>
>>> object.__class__
<class 'type'>

因为 type 是所有类的元类,而 object 是所有类的基类,这就说明 type 要继承自 object,而 object 的类型是 type。很多人都会对这一点感到奇怪,这难道不是一个先有鸡还是先有蛋的问题吗?其实不是的,这两个对象是共存的,它们之间的定义其实是互相依赖的。而具体是怎么一回事,我们一点一点分析。

首先在这里必须要澄清一个事实,类对象的类型是 type,这句话是没有问题的;但如果说类对象都是由 type 创建的,就有些争议了。因为 type 能够创建的是自定义的类,而内置的类在底层是预先定义好的。

# int、tuple、dict 等内置类型
# 在底层是预先定义好的,以全局变量的形式存在
# 我们直接就可以拿来用
print(int)  # <class 'int'>
print(tuple)  # <class 'tuple'>

# 但对于自定义的类,显然就需要在运行时动态创建了
# 而创建这一过程,就交给 type 来做
class Girl:
    pass

而 type 也只能对自定义类进行属性上的增删改,内置的类则不行。

class Girl:
    pass

# 给类对象增加一个成员函数
type.__setattr__(
    Girl,
    "info",
    lambda self: "name: 古明地觉, age: 17"
)
# 实例化之后就可以调用了
print(Girl().info())  # name: 古明地觉, age: 17

# 但内置的类对象,type 是无法修改的
try:
    type.__setattr__(int, "a", "b")
except TypeError as e:
    print(e)
"""
can't set attributes of built-in/extension type 'int'
"""

而 Python 所有内置的类对象,在解释器看来,都是同级别的。因为它们都是由同一个结构体实例化得到的。

所有内置的类对象都是 PyTypeObject 结构体实例,只不过结构体字段的值不同,得到的类也不同。所以元类 type 和普通的类对象,在解释器看来都是等价的。

在解释器看来,它们无一例外都是PyTypeObject结构体实例。换句话说,它们都是基于这个结构体创建出的全局变量罢了,这些变量代表的就是 Python 的类。

而每一个对象都有引用计数和类型,然后解释器将这些类对象的类型都设置成了 type,我们以 object 为例:

我们看到它的类型被设置成了 type,所以结论很清晰了,虽然内置类对象可以看做是 type 的实例对象,但它却不是由 type 实例化得到的。所有内置的类对象,在底层都是预定义好的,以静态全局变量的形式出现。

至于 type 也是同理:

解释器只是将 type 的类型设置成了它自身而已,所以内置的类对象之间不存在谁创建谁。它们都是预定义好的,只是在定义的时候,将自身的类型设置成 type 而已,包括 type 本身。这样一来,每一个对象都会具有一个类型,从而将面向对象理念贯彻的更加彻底。

print(int.__class__)
print(tuple.__class__)
print(set.__class__)
print(type.__class__)
"""
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
"""

print(
    type.__class__.__class__.__class__ is type
)  # True

print(
    type(type(type(type(type(type))))) is type
)  # True

现在 object 的类型是 type 我们已经搞清楚是怎么一回事了,然后是基类的问题。PyTypeObject 结构体内部有一个 tp_base,它表示的就是类对象继承的基类。

但令我们吃鲸的是,它的 tp_base 居然是个 0,如果为 0 的话则表示没有这个属性。不是说 type 的基类是 object 吗?为啥 tp_base 是 0 呢。

事实上如果你去看 PyFloat_Type 以及其它类型的话,会发现它们内部的 tp_base 也是 0。为 0 的原因就在于我们目前看到的类型对象是一个半成品,因为 Python 的动态性,显然不可能在定义的时候就将所有成员属性都设置好、然后解释器一启动就得到我们平时使用的类型对象。

目前看到的类型对象是一个半成品,有一部分成员属性是在解释器启动之后再动态完善的,而这个完善的过程被称为类型对象的初始化,它由函数 PyType_Ready 负责。

首先代码中的 type 只是一个普通的参数,当解释器发现一个类对象还没有初始化时,会将其作为参数传递进来,进行初始化。base 则显然是它的基类,然后如果基类为空,并且该类不是 object 的话,那么就将它的基类设置成 object。所以 python3 中,所有的类默认都继承 object,当然除了 object 本身。

因此到目前为止,type 和 object 之间的恩怨纠葛算是真相大白了,总结一下:

1)和自定义类不同,内置的类不是由 type 实例化得到的,它们都是在底层预先定义好的,不存在谁创建谁。只是内置的类在定义的时候,它们的类型也都被设置成了 type。这样不管是内置的类,还是自定义类,在调用时都会执行 type 的 __call__ 方法,从而让它们的行为是一致的。

2)虽然内置的类在底层预定义好了,但还有一些瑕疵,因为有一部分逻辑无法以源码的形式体现,只能在解释器启动的时候再动态完善。而这个完善的过程,便包含了基类的填充,会将基类设置成 object。

所以 type 和 object 是同时出现的,它们的存在需要依赖彼此。首先这两者会以不完全体的形式定义在源码中,并且在定义的时候将 object 的类型设置成 type;然后当解释器启动的时候,再经过动态完善,进化成完全体,而进化的过程中会将 type 的基类设置成 object。

所以 object 的类型是 type,type 继承 object 就是这么来的。

以上就是详解Python中type与object的恩怨纠葛的详细内容,更多关于Python type object的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解Python中type与object的恩怨纠葛

本文链接: https://lsjlt.com/news/208716.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解Python中type与object的恩怨纠葛
    在学习 Python 的时候,你肯定听过这么一句话:Python 中一切皆对象。没错,在 Python 世界里,一切都是对象。整数是一个对象、字符串是一个对象、字典是一个对象,甚至 ...
    99+
    2023-05-15
    Python type object Python Python object
  • 最新整理Python中的type和object的示例详解
    目录开始之前基本概念进入对象( Bring In The Objects)准备结束总结本文是在上篇文章Python中的type和object,做的补充,希望大家喜欢。 这篇博客主要描...
    99+
    2022-12-08
    Python中的type和object Python type和object
  • Python中的type与isinstance的区别详解
    目录type()函数isinstance()函数Python中的type()函数和isinstance()函数是两个常用的类型判断函数,它们可以用来判断变量的类型,接下来让我们一起来...
    99+
    2023-05-16
    Python type isinstance
  • 详解Python中的 type()函数
    目录你好类型type()和数字序列类型自定义数据类型Python type() 函数摘要将通过各种例子来了解如何在 Python 中使用 type() 函数。 你好类型 打印 &qu...
    99+
    2024-04-02
  • 详解TypeScript中type与interface的区别
    目录类型别名 type接口 interfaceinterface和type的相似之处都可以描述 Object和FunctionTypeInterface二者都可以被继承interfa...
    99+
    2024-04-02
  • Mongoose中document与object的区别示例详解
    前言 本文主要给大家总结介绍了关于Mongoose中document与object区别的相关内容,分享出来供大家参考学习,其实这个问题其实是mongoose非常常见的问题,经常有很多以前没遇到这个问题的人都...
    99+
    2022-06-04
    示例 详解 区别
  • 详解Python中range()与xrange()的区别
    目录前言返回类型记忆操作使用Speed前言 range() 和 xrange() 是两个函数,可用于在 Python的 for 循环中迭代一定次数。在 Python 3 中,没有 x...
    99+
    2024-04-02
  • 详解Python中类的定义与使用
    类顾名思义,就是一类事物、或者叫做实例,它用来描述具有共同特征的一类事物。我们在python中声明类的关键词是class,类还有功能和属性,属性就是这类事物的特征,而功能就是它能做什么,也是就是方法或者函数...
    99+
    2022-06-04
    详解 定义 Python
  • python中API调用的详解与示例
    本篇文章给大家带来了关于python的相关知识,其中主要介绍了关于API调用的相关问题,包括了API的调用和数据接口的调用、请求方法、几种常见API调用实例等等内容,下面一起来看一下,希望对大家有帮助。在日常工作中,可能需要结合网上现在的一...
    99+
    2022-06-16
    python
  • Python中is与==的使用区别详解
    目录一、== 是比较两个对象的内容是否相等二、is 比较的是两个实例对象是不是完全相同三、使用is注意python对于小整数使用对象池存储问题四、使用is注意python关于字符串的...
    99+
    2024-04-02
  • Python中类的mro与继承关系详解
    目录前言类继承mro总结前言 版本: windows 10.0python 3.8 类 在Python数字比较与类结构中有简略提到类,那么什么是类呢? 在python中定义一个类很简...
    99+
    2024-04-02
  • 详解Python中的Numpy、SciPy、MatPlotLib安装与配置
    用Python来编写机器学习方面的代码是相当简单的,因为Python下有很多关于机器学习的库。其中下面三个库numpy,scipy,matplotlib,scikit-learn是常用组合,分别是科学计算包...
    99+
    2022-06-04
    详解 Python Numpy
  • 详解Python编程中包的概念与管理
    Python中的包 包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的Python的应用环境。 考虑一个在Phone目录下的pots.py文件。这个文件有如下源代码: #!...
    99+
    2022-06-04
    详解 概念 Python
  • 详解Python中的元组与逻辑运算符
    Python元组 元组是另一个数据类型,类似于List(列表)。 元组用"()"标识。内部元素用逗号隔开。但是元素不能二次赋值,相当于只读列表。 #!/usr/bin/python # -*- cod...
    99+
    2022-06-04
    详解 运算符 逻辑
  • Python数据结构与算法中的栈详解
    目录0. 学习目标1. 栈的基本概念1.1 栈的基本概念1.2 栈抽象数据类型1.3 栈的应用场景2. 栈的实现2.1 顺序栈的实现2.1.1 栈的初始化2.1.2 求栈长2.1.3...
    99+
    2024-04-02
  • 详解Python中sorted()和sort()的使用与区别
    目录sort()方法是什么如何妙用sorted() 方法总结在 Python 中,你可以使用 sorted() 方法或 sort() 方法对数据进行排序。 在本文中,我将提供 sor...
    99+
    2024-04-02
  • Python中bytes和str的区别与联系详解
    目录Bytes和Str的区别Bytes与Str间的转换读写文件的注意事项总结Bytes和Str的区别 在Python3中,字符序列有两种类型:bytes和str。bytes类型是无符...
    99+
    2024-04-02
  • Python中类的mro与继承关系详解(二)
    目录前言多重继承思考片刻总结前言 版本: windows 10.0python 3.8 多重继承 在Python数字比较与类结构中有简略提到类,且在Python中类的mro与继承关系...
    99+
    2024-04-02
  • Python中numpy数组的计算与转置详解
    目录前言1、numpy数组与数的运算2、numpy相同尺寸的数组运算3、numpy不同尺寸的数组计算4、numpy数组的转置总结:前言 本文主要讲述numpy数组的计算与转置,讲相同...
    99+
    2024-04-02
  • 关于Python中Inf与Nan的判断问题详解
    大家都知道 在Python 中可以用如下方式表示正负无穷: float("inf") # 正无穷 float("-inf") # 负无穷 利用 inf(infinite) 乘以 0 会得到 not-a...
    99+
    2022-06-04
    详解 Python Inf
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作