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. 总结

下表汇总了获取对象信息的关键技术:

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

:::note 最后的建议

  • 反射虽然强大,但会降低可读性、增加调试难度,不要滥用!
  • 优先遵循鸭子类型原则,关注对象的“能力”而非“身份”;
  • 如果必须检查类型,请默认使用 isinstance(),远离 type()。 :::