Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
前面我们学习了 Python 的类与对象,也通过 Linux 服务器管理案例认识了继承与多态。
我们知道,不同类型的对象可以拥有不同的行为,子类还可以继承父类的属性和方法。
但在实际编程中,还有一个很常见的问题:当程序拿到一个对象时,怎么知道它是什么类型?怎么判断它是不是某个类的实例?又该如何判断两个类之间有没有继承关系?
比如,我们编写一个服务器管理程序,读取了一份配置文件,却发现本来应该是整数的端口号变成了字符串。程序应该如何发现这个问题?
再比如,我们想判断一台服务器是不是
WebServer,或者判断WebServer是否继承了Server,又该怎么做?Python 已经为我们准备了几个实用的内置函数:
type()、isinstance()和issubclass()。这篇文章就从零开始,通过实际代码把它们讲明白。
为什么需要判断数据类型
先看一个日常工作中的例子。
假设我们正在编写服务器管理程序,需要记录服务器名称、IP 地址和端口号。
1 | server_name = "web01" |
运行结果:
1 | web01 |
这里有三个数据:
server_name保存的是服务器名称,例如web01。server_ip保存的是 IP 地址,例如192.168.1.10。server_port保存的是端口号,例如8080。
虽然它们都是通过 = 保存数据,但数据的类型并不相同。
服务器名称和 IP 地址都是字符串,端口号则是整数。
为什么要关心类型?
因为不同类型的数据通常适合不同的操作。例如,整数可以进行数值计算,字符串主要用于保存和处理文本,列表则可以保存多个数据。
如果程序没有弄清楚数据的类型,就可能执行不符合预期的操作,甚至报错。
因此,我们先来学习第一个函数:type()。
使用 type() 查看数据类型
type() 是 Python 的内置函数,可以用来查看一个对象的类型。
这里先解释一下什么是内置函数。
你可以把内置函数理解为 Python 已经准备好的工具。我们不需要自己编写它们,只要按照规定的方式调用,就可以完成相应的操作。
例如,之前使用过的 print() 可以输出信息,input() 可以读取用户输入,而 type() 可以查看对象的类型。
新建一个文件 type_demo.py,写入下面的代码:
1 | server_name = "web01" |
运行程序:
1 | python type_demo.py |
输出结果:
1 | <class 'str'> |
我们逐个理解。
<class 'str'>表示字符串类型。<class 'int'>表示整数类型。<class 'float'>表示浮点数类型,也就是可以表示小数的数值类型。<class 'bool'>表示布尔类型,主要用于表示真或假。
为什么输出结果中会出现 class?
这里的 class 表示类。Python 中,对象都有自己的类型,而类型通常由类来描述。现在不必深入研究类的内部机制,只需要知道:type() 返回的结果可以帮助我们确认对象的直接类型。
type() 的实际使用场景
只查看几个变量的类型,是为了帮助我们认识这个函数。在实际编程中,type() 还可以用来排查数据问题。
例如,我们从配置文件或其他程序中读取了服务器端口号。本来期望得到整数 8080,实际拿到的却是字符串 "8080"。
这两种数据看起来很相似,但类型不同。
1 | server_port = "8080" |
运行结果:
1 | <class 'str'> |
为什么没有得到数字 8080 加上数字 0 的结果?
因为 server_port 保存的是字符串 "8080",而不是整数 8080。这里的 + 执行的是字符串拼接,把两个字符串连接起来,所以结果变成了 "80800"。
如果我们希望执行数值计算,就需要先确认数据的类型,再根据实际情况进行转换。
1 | server_port = "8080" |
运行结果:
1 | <class 'str'> |
这里的 int() 用于把可以转换为整数的字符串转换成整数。
第一次执行 type(server_port),结果是 str;经过 int() 转换后,再查看类型,结果就变成了 int。
最后,server_port + 1 执行的是整数加法,所以结果为 8081。
这个例子说明:当程序的计算结果不符合预期时,使用 type() 查看数据类型,是一种简单而实用的排查方法。
不过要注意,int() 并不是对所有字符串都能成功转换。如果字符串中包含不能解析为整数的内容,例如 "eight",转换就会产生异常。后续学习异常处理时,我们会介绍如何处理这种情况。
使用 type() 检查配置数据
再来看一个更接近实际运维工作的例子。
假设程序需要读取服务器配置,并且要求端口号必须是整数。我们可以先用 type() 检查它。
1 | server_port = 8080 |
运行结果:
1 | 端口号类型正确 |
这里的 == 用于比较两边是否相等。因此,type(server_port) == int 就是在判断变量的直接类型是否为 int。
因为 server_port 保存的是整数,所以条件成立,程序输出“端口号类型正确”。
如果把变量改成:
1 | server_port = "8080" |
那么判断结果就会变成 False,程序会输出“端口号类型错误”。
这就是 type() 的一种使用场景:在调试程序或检查数据时,确认数据的直接类型是否符合预期。
不过,type() 并不是所有类型检查场景的首选。尤其是在面向对象编程中,我们经常需要考虑继承关系。接下来就要认识一个更灵活的函数:isinstance()。
使用 isinstance() 判断对象属于什么类型
先来看一个问题。
前面我们学习过继承,知道子类可以继承父类。
假设有一个 Server 父类和一个 WebServer 子类:
1 | class Server: |
这里有三个需要理解的名称:
Server是父类。WebServer是继承Server的子类。web01是通过WebServer创建的对象。
现在,我们想回答两个问题:
web01是不是WebServer类型的对象?web01是不是Server类型的对象?
第一个问题很好理解。既然 web01 是通过 WebServer 创建的,那么它当然属于 WebServer 类型。
但第二个问题呢?
虽然 web01 是通过 WebServer 创建的,而不是直接通过 Server 创建的,但 WebServer 继承了 Server。因此,web01 也属于 Server 这个父类所代表的类型体系。
这时就可以使用 isinstance()。
1 | class Server: |
运行结果:
1 | True |
两次判断的结果都是 True。
原因是 isinstance() 不仅可以判断对象是否属于指定的类型,还会考虑这个对象是否属于该类型的子类。
换句话说,判断 web01 是否属于 Server 时,Python 会考虑 WebServer 与 Server 之间的继承关系。
这里要特别注意:isinstance() 检查的是对象,而不是直接检查两个类之间的继承关系。
它的基本写法是:
1 | isinstance(对象, 类型或类) |
例如:
1 | print(isinstance("web01", str)) |
输出结果:
1 | True |
为什么第三行是 True?
因为 [] 表示一个空列表,而 list 是 Python 中列表对应的类型。
因此,isinstance([], list) 就是在询问:这个空列表是不是一个列表对象?
答案当然是 True。
type() 和 isinstance() 有什么区别
刚刚我们已经认识了两个函数,它们都能帮助我们判断类型,但判断方式有所不同。
看下面的例子:
1 | class Server: |
运行结果:
1 | True |
为什么会出现这样的区别?
type(web01) == Server 的结果是 False,因为 web01 的直接类型是 WebServer,而不是 Server。
但是,isinstance(web01, Server) 的结果是 True,因为 WebServer 继承了 Server。
可以这样记忆:
| 函数或写法 | 主要作用 |
|---|---|
type(obj) | 获取对象的直接类型 |
type(obj) == SomeClass | 判断对象的直接类型是否就是指定的类 |
isinstance(obj, SomeClass) | 判断对象是否属于指定的类或其子类 |
在实际编程中,如果你只关心对象的直接类型,可以使用 type()。如果你关心对象是否属于某个类型体系,通常优先考虑 isinstance()。
bool 为什么是 int 的子类
现在来看一个有点出人意料的结果:
1 | print(type(True)) |
输出结果:
1 | <class 'bool'> |
可能有人会疑惑:True 明明表示真,为什么会被认为是整数类型?
这里需要先区分两个概念:
type(True)返回bool,说明True的直接类型是布尔类型。isinstance(True, int)返回True,说明布尔值也属于int的类型体系。issubclass(bool, int)返回True,说明bool是int的子类。
bool 是 int 的子类,但布尔值的直接类型仍然是 bool。 这两个结论并不矛盾。
这是 Python 的一个类型设计特点:布尔类型只有两个值,True 和 False。在数值运算中,它们分别具有数值 1 和 0 的行为,因此布尔类型继承自整数类型。
我们可以通过运算观察这个特点。
1 | print(True + 1) |
输出结果:
1 | 2 |
为什么会这样?
因为在 Python 中:
True在数值运算中相当于1。False在数值运算中相当于0。
所以:
True + 1相当于1 + 1,结果为2。False + 1相当于0 + 1,结果为1。True == 1的结果是True。False == 0的结果是True。
bool 的运算边界:可以参与数值运算,不代表什么都能算
理解了上面的结果之后,还需要知道一个重要边界:布尔值可以参与某些数值运算,但它并不等同于普通整数,也不是所有运算都可以随意混用。
我们先看加法。
1 | print(True + 10) |
输出结果:
1 | 11 |
这是因为加法运算会把 True 和 False 当作数值 1 和 0 处理。
那么,能不能把布尔值直接和字符串拼接呢?
1 | print(True + "1") |
这段代码会产生 TypeError,因为 + 的两边分别是布尔值和字符串,Python 不会自动把它们转换成同一种类型。
如果想把布尔值转换成文字,可以显式使用 str():
1 | print(str(True) + "1") |
输出结果:
1 | True1 |
这里 str(True) 将布尔值转换为字符串 "True",然后 + 执行字符串拼接。
再来看一个运算结果容易让初学者误解的例子:
1 | print(True / 2) |
输出结果:
1 | 0.5 |
这两次运算都能执行,是因为布尔值在数值运算中分别相当于 1 和 0。除法 / 会返回浮点数,因此结果分别是 0.5 和 0.0。
但如果把布尔值放进一个需要严格整数语义的业务场景,就要格外小心。
例如,我们正在检查服务器的端口号:
1 | server_port = True |
输出结果:
1 | True |
这看起来似乎很奇怪。端口号明明应该是一个整数,为什么布尔值也通过了检查?
原因正是 bool 继承了 int。因此,isinstance(server_port, int) 不仅接受普通整数,也接受布尔值。
如果业务明确要求必须是普通整数,而不能是 True 或 False,可以这样判断:
1 | server_port = True |
输出结果:
1 | 端口号类型不符合要求 |
这里的 and 表示两个条件都必须成立,not 表示对后面的判断结果取反。
程序先判断 server_port 是否属于整数类型体系,再判断它是否不是布尔类型。由于 server_port 是 True,第二个条件不成立,所以最终进入 else 分支。
如果你只需要判断一个值是真是假,就应该直接使用布尔值,而不是把它当作数字来处理。例如,服务器是否运行可以使用 True 或 False 表示;端口号则应该使用符合要求的整数。
这个例子也提醒我们:类型判断要结合业务需求,不能只看某个函数返回了 True,就认为数据一定符合业务要求。
使用 issubclass() 判断类之间的继承关系
前面我们用 isinstance() 判断对象属于什么类型。
那么,如果手上没有对象,只知道两个类的名称,想判断它们之间有没有继承关系,应该怎么办?
例如:
WebServer是否继承了Server?DatabaseServer是否继承了Server?Server是否继承了WebServer?
这时就可以使用 issubclass()。
先看一个完整示例:
1 | class Server: |
运行结果:
1 | True |
我们逐个理解。
第一行:
1 | issubclass(WebServer, Server) |
是在判断 WebServer 是否为 Server 的子类。
因为 WebServer 继承了 Server,所以结果是 True。
第二行:
1 | issubclass(DatabaseServer, Server) |
同样是在检查继承关系。由于 DatabaseServer 也继承了 Server,结果仍然为 True。
第三行:
1 | issubclass(Server, WebServer) |
这次把父类和子类的位置交换了。
Server 是父类,而 WebServer 是子类,所以 Server 不是 WebServer 的子类,结果为 False。
issubclass() 的基本写法是:
1 | issubclass(子类, 父类) |
这里的两个参数都应该是类,而不是通过类创建出来的普通对象。
例如,下面的写法就是错误的:
1 | class Server: |
运行时会产生 TypeError,因为 web01 是一个对象,不是类。
记住这个区别,就不容易把两个函数混淆:
isinstance():判断对象是否属于某个类型。issubclass():判断一个类是否是另一个类的子类。
用一张图串起三个函数
学到这里,我们已经认识了三个函数。可以用下面的关系图帮助记忆:
flowchart TD
A["需要判断类型或继承关系"] --> B{"手上有什么?"}
B -->|"想查看对象的直接类型"| C["使用 type()"]
B -->|"想检查对象属于什么类型体系"| D["使用 isinstance()"]
B -->|"想判断两个类的继承关系"| E["使用 issubclass()"]
C --> F["获取对象的直接类型"]
D --> G["检查对象是否属于指定类型或其子类"]
E --> H["检查类之间的继承关系"]例如:
1 | type(web01) |
获取 web01 的直接类型。
1 | isinstance(web01, Server) |
判断 web01 是否属于 Server 或其子类。
1 | issubclass(WebServer, Server) |
判断 WebServer 是否为 Server 的子类。
三者的用途不同,但都能帮助我们更清楚地理解程序中的对象和类。
动手实践:完成服务器类型检查程序
概念理解之后,我们通过一个完整的小任务巩固所学知识。
任务目标
编写一个名为 server_type_check.py 的程序,完成以下要求:
- 定义
Server父类。 - 定义继承
Server的WebServer子类。 - 创建一个
WebServer对象。 - 使用
type()查看对象的直接类型。 - 使用
isinstance()判断对象是否属于WebServer和Server。 - 使用
issubclass()验证WebServer与Server之间的继承关系。 - 再创建一个普通的
Server对象,比较两个对象的判断结果。 - 尝试检查端口号,并理解为什么布尔值可能通过普通的整数类型检查。
编写完整代码
创建 server_type_check.py 文件,将下面的代码写入文件:
1 | class Server: |
运行程序
打开终端,进入文件所在目录,执行:
1 | python server_type_check.py |
如果你的环境使用 python3 命令,则运行:
1 | python3 server_type_check.py |
预期输出:
1 | === 查看对象的直接类型 === |
对照输出理解结果
先看 type(web01) 的结果:
1 | <class '__main__.WebServer'> |
这表示 web01 的直接类型是 WebServer。__main__ 是 Python 在直接运行的程序中经常使用的模块名称,这里不需要深入研究它。
再看:
1 | isinstance(web01, Server) |
结果是 True,因为 WebServer 继承了 Server。
而:
1 | isinstance(server01, WebServer) |
结果为 False,因为 server01 是直接通过 Server 创建的对象,并不会因为存在一个 WebServer 子类,就自动变成 WebServer 对象。
最后,issubclass(WebServer, Server) 返回 True,证明子类与父类之间的继承关系确实存在。
端口号检查部分则验证了另一个知识点:True 属于 int 的类型体系,但如果业务不允许把布尔值当作普通整数使用,就需要额外排除 bool。
如果你能解释这些结果为什么出现,就已经掌握了本篇最重要的内容。
写在最后
这一篇,我们学习了 Python 中三个非常实用的内置函数。
type() 用来获取对象的直接类型,适合在调试和排查数据问题时确认对象究竟是什么类型;isinstance() 用来判断对象是否属于指定的类型或其子类;issubclass() 则用来判断类之间的继承关系。
我们还通过 bool 继承 int 的例子,理解了为什么布尔值可以参与部分数值运算,以及为什么不能因此就把布尔值当作普通整数使用。
到这里,Python 类与对象这一章的基础内容就告一段落了。从定义类、创建对象,到理解属性、方法、继承、多态,再到判断类型与继承关系,我们已经逐步建立起面向对象编程的基本认识。
但程序运行过程中,并不总是一帆风顺。
例如,用户输入了错误的数据、程序尝试打开一个不存在的文件,或者调用某个操作时出现问题,程序可能会产生异常。
下一章,我们将学习 Python 的异常处理,认识程序出错时会发生什么,以及如何使用 try、except 等语句,让程序更稳健地处理意外情况。
