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 中获取对象信息的核心技术,它们是动态编程和元编程的基础:

技术用途
type(obj)精准匹配对象的当前类(不推荐继承场景)
types 模块常量精准细分函数、生成器等特殊类型
isinstance(obj, cls)推荐的类型检查方式,支持继承链类型元组
dir(obj)列出对象的所有属性名和方法名(包含特殊方法)
hasattr/getattr/setattr动态查/取/设属性/方法,实现反射

:::note 最后的建议

  • 反射虽然强大,但会让代码可读性下降、难以调试,不要滥用!
  • 优先遵循鸭子类型原则,关注对象的「能力」而非「身份」;
  • 必须用类型检查时,选 isinstance() 而非 type()。 :::