Python 对象类型检查与方法获取指南
1. 类型检查
面对代码里“来路不明”的对象,确定它的类型往往是第一步操作——这能帮我们规避属性错误、优化分支逻辑,甚至解锁动态调用的玩法。
1.1 基础但有限的 type()
type() 是 Python 内置的「精准类型检测器」,它会严格匹配对象的当前类,不会考虑继承关系。适合用来检查基础数据类型、函数/类本身的元类型等「非继承」场景:
# 基础类型检查
print(type(123)) # <class 'int'>
print(type('str')) # <class 'str'>
print(type(3.14)) # <class 'float'>
print(type(True)) # <class 'bool'>
print(type(None)) # <class 'NoneType'>
# 直接比较类型(推荐用 == 而非 is,但这个写法对类对象/基础类型也安全)
print(type(123) == int) # True
print(type('abc') == str) # True
print(type([1,2,3]) == list) # True
# 函数和类的元对象检查
def my_func(): pass
class MyClass: pass
print(type(my_func)) # <class 'function'>
print(type(MyClass)) # <class 'type'> (类本身是 type 类的实例)
print(type(MyClass())) # <class '__main__.MyClass'> (实例才是自定义类的类型)
Warning
type() 无法识别继承关系!比如:子类的实例类型不等于父类。如果有继承链需求,千万不能用它。
1.2 函数类型的「精准细分」:types 模块
当我们需要检查的不是普通的类实例,而是函数相关的类型(比如普通函数、匿名函数、生成器、内置函数)时,type() 配合 == 能做,但硬写字符串很麻烦。这时候可以用 Python 标准库的 types 模块,它提供了现成的类型常量:
import types
def my_func(): pass # 普通函数
lambda_func = lambda x: x # 匿名(lambda)函数
gen = (x for x in range(3)) # 生成器
print(type(abs)) # 内置函数(abs 是 builtins 的)
print(type(my_func) == types.FunctionType) # True
print(type(lambda_func) == types.LambdaType) # True
print(type(gen) == types.GeneratorType) # True
print(type(abs) == types.BuiltinFunctionType) # True
1.3 覆盖继承的「友好型检查」:isinstance()
在面向对象编程中,父类可以接受子类实例(这就是多态的基础)。这时候必须用 isinstance(obj, class_info),它会检查 obj 是否是 class_info 或其任何子类的实例。
class_info 还可以是一个类型元组,实现「一次检查多种类型」的需求:
class Animal: pass
class Dog(Animal): pass
class Husky(Dog): pass
a = Animal()
d = Dog()
h = Husky()
# 继承链检查
print(isinstance(h, Husky)) # True
print(isinstance(h, Dog)) # True
print(isinstance(h, Animal)) # True
print(isinstance(d, Husky)) # False(反过来不成立)
# 基础类型也能用
print(isinstance('a', str)) # True
print(isinstance(123, int)) # True
print(isinstance([1,2], (list, tuple))) # True(一次检查两种容器)
最佳实践
除非必须严格匹配当前类(比如禁止子类参与的场景),否则99% 情况下优先用 isinstance()!
2. 获取对象的属性和方法
确定类型只是第一步,接下来我们可能需要知道这个对象有哪些能力——比如能调用什么方法、有什么公开属性,甚至需要动态操作这些属性/方法。
2.1 列出所有「公开+隐藏」能力:dir()
dir(obj) 会返回一个包含字符串的有序列表,每个字符串对应 obj 的一个属性名或方法名。其中双下划线开头和结尾的是 Python 的「特殊方法/属性」,它们通常对应内置操作(比如 len(obj) 调用的是 obj.__len__()):
# 查看字符串的所有能力
print(dir('ABC')) # 输出很长,包含 'capitalize', 'split', '__len__' 等
# 特殊方法的实际使用
s = 'hello'
print(len(s) == s.__len__()) # True(len 只是语法糖)
# 自定义类也能实现特殊方法,让它支持内置操作
class MyCollection:
def __len__(self):
return 100
col = MyCollection()
print(len(col)) # 100
2.2 动态「查/取/设」属性/方法:反射三件套
Python 是一门动态语言,它允许我们在运行时(而非编译时)动态操作对象的属性和方法。这时候就需要用到「反射三件套」:hasattr(), getattr(), setattr()。
什么是反射?
简单来说,就是程序在运行时能够检查、修改自身或其他对象的结构和行为的能力。
class MyObject:
def __init__(self):
self.x = 9
def power(self):
return self.x ** 2
obj = MyObject()
# 1. hasattr(obj, name_str):检查是否存在该属性/方法
print(hasattr(obj, 'x')) # True
print(hasattr(obj, 'y')) # False
print(hasattr(obj, 'power')) # True(方法也是一种属性,绑定方法)
# 2. getattr(obj, name_str, [default]):获取属性/方法
print(getattr(obj, 'x')) # 9
print(getattr(obj, 'y', 404)) # 404(如果不存在,返回默认值,否则报错)
# 获取方法后可以直接调用
power_method = getattr(obj, 'power')
print(power_method()) # 81
# 3. setattr(obj, name_str, value):设置/新增属性/方法
setattr(obj, 'y', 10) # 新增属性
print(obj.y) # 10
# 新增简单方法(绑定/不绑定这里不展开,先演示)
def double(self):
return self.x * 2
setattr(MyObject, 'double', double) # 给类绑定方法
print(obj.double()) # 18
3. 实际应用示例
光说不练假把式,反射三件套和类型检查经常结合使用,让代码变得更灵活。
3.1 遵循「鸭子类型」的接口检查
Python 中有一句名言:「如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子」——这就是鸭子类型原则:我们不需要检查对象的具体类型,只需要检查它是否有我们需要的「能力」。
比如写一个通用的 read_data 函数,只要对象有 read() 方法,就直接调用,否则转成字符串:
def read_data(source):
if hasattr(source, 'read'): # 只检查「有没有 read 能力」
return source.read()
else:
return str(source)
# 测试用例1:普通字符串(无 read)
print(read_data("hello")) # 'hello'
# 测试用例2:自定义的「模拟文件流」类(有 read)
class StringIO:
def __init__(self, data):
self.data = data
def read(self):
return self.data
print(read_data(StringIO("hi from StringIO"))) # 'hi from StringIO'
3.2 根据数据类型动态调用方法
假设我们有一个处理器类,分别处理文本和数字,我们可以结合 isinstance() 和 getattr() 动态选择方法:
class Processor:
def process_text(self, text):
return text.upper() + " (TEXT)"
def process_number(self, num):
return num * 2 + " (NUMBER)" # 哦这里故意写错数字拼接
processor = Processor()
data = ['hello world', 42, 3.14, True] # True 是 int 的子类哦
for item in data:
if isinstance(item, str):
method_name = 'process_text'
elif isinstance(item, (int, float)):
method_name = 'process_number'
# 修正数字拼接
def process_fixed(num):
return f"{num * 2} (NUMBER)"
setattr(processor, 'process_number', process_fixed)
else:
continue
# 动态获取并调用
method = getattr(processor, method_name)
print(method(item))
# 输出:
# HELLO WORLD (TEXT)
# 84 (NUMBER)
# 6.28 (NUMBER)
# 2 (NUMBER)(True 被当成 1 处理了,符合 isinstance 行为)
4. 总结
本文介绍了 Python 中获取对象信息的核心技术,它们是动态编程和元编程的基础:
:::note 最后的建议
- 反射虽然强大,但会让代码可读性下降、难以调试,不要滥用!
- 优先遵循鸭子类型原则,关注对象的「能力」而非「身份」;
- 必须用类型检查时,选
isinstance() 而非 type()。
:::