Python 对象类型检查与方法获取指南
在编写灵活、健壮的 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'>
# 直接比较类型(推荐用 == )
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 模块
当我们需要检查的不是普通类实例,而是各种函数相关类型(普通函数、匿名函数、生成器、内置函数等),可以直接使用标准库 types 中预定义的类型常量:
import types
def my_func(): pass # 普通函数
lambda_func = lambda x: x # 匿名函数(lambda)
gen = (x for x in range(3)) # 生成器
builtin_func = abs # 内置函数
print(type(my_func) == types.FunctionType) # True
print(type(lambda_func) == types.LambdaType) # True
print(type(gen) == types.GeneratorType) # True
print(type(builtin_func) == types.BuiltinFunctionType) # True
1.3 继承友好的 isinstance()
在oop中,父类可以接受子类实例(多态的基础)。此时必须使用 isinstance(obj, class_info),它会检查 obj 是否为 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) 返回一个有序的字符串列表,每个字符串对应对象的一个属性名或方法名。其中以双下划线开头和结尾的(如 __len__)是 Python 的“特殊方法/属性”,它们通常对应内置操作:
# 查看字符串的全部能力
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. 实际应用示例
反射和类型检查经常搭配使用,让代码更灵活、更 Pythonic。
3.1 鸭子类型:只关「能力」,不关「身份」
Python 中有一句名言:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”——这就是鸭子类型:我们不检查对象的具体类型,只检查它是否具备我们需要的方法。
例如,编写一个通用的 read_data 函数,只要对象有 read() 方法就直接调用,否则转成字符串:
def read_data(source):
if hasattr(source, 'read'): # 只关心“能不能读”
return source.read()
else:
return str(source)
# 普通字符串(无 read 方法)
print(read_data("hello")) # 'hello'
# 自定义的“模拟文件流”类(有 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):
# 原始实现有 bug:数字不能直接和字符串拼接
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'
# 动态修复 process_number:用新的正确实现替换
def fixed_process(num):
return f"{num * 2} (NUMBER)"
setattr(processor, 'process_number', fixed_process)
else:
continue
# 动态获取方法并调用
method = getattr(processor, method_name)
print(method(item))
# 输出:
# HELLO WORLD (TEXT)
# 84 (NUMBER)
# 6.28 (NUMBER)
# 2 (NUMBER) # True 被当作 1 处理,符合 isinstance 行为
注:Python 中布尔值是 int 的子类,True 等价于 1,因此会被数字分支捕获。
4. 总结
下表汇总了获取对象信息的关键技术:
:::note 最后的建议
- 反射虽然强大,但会降低可读性、增加调试难度,不要滥用!
- 优先遵循鸭子类型原则,关注对象的“能力”而非“身份”;
- 如果必须检查类型,请默认使用
isinstance(),远离 type()。
:::