Skip to content

Python 获取对象信息:给对象做"体检"

引言:医生的诊断工具箱

医生面对病人,不会直接开药——先问诊、再检查:量体温、测血压、拍 X 光。搞清楚状况,才能对症下药。

写代码也一样。当我们拿到一个对象,尤其是来源不确定的对象(比如用户传入的参数、第三方库返回的值),需要先"体检":

  • 它是什么类型?(type() / isinstance()
  • 它有哪些能力?(dir()
  • 它有没有某个属性或方法?(hasattr() / getattr()

这一篇,我们来学习 Python 提供的这套"诊断工具箱"。


一、type():看对象的"身份证"

1.1 基本用法

python
>>> type(123)
<class 'int'>

>>> type('hello')
<class 'str'>

>>> type(None)
<class 'NoneType'>

type() 返回对象的类(Class)——就像身份证上写着"汉族""回族",type 告诉你"这是 int""这是 str"。

1.2 判断类型是否相同

python
>>> type(123) == type(456)
True

>>> type(123) == int
True

>>> type('abc') == type(123)
False

注意type() 比较的是精确类型,不认可继承关系:

python
class Animal: pass
class Dog(Animal): pass

>>> type(Dog()) == Animal
False    # 即使 Dog 继承 Animal,type 也不认!

1.3 types 模块:判断特殊类型

python
import types

def fn(): pass

>>> type(fn) == types.FunctionType
True

>>> type(abs) == types.BuiltinFunctionType
True

>>> type(lambda x: x) == types.LambdaType
True

>>> type((x for x in range(10))) == types.GeneratorType
True

生活化理解type() 像看身份证上的"民族"一栏——精确匹配,不认可"混血"(继承)。


二、isinstance():认可继承关系的类型判断

2.1 为什么需要 isinstance()?

type() 的问题是:只看表面,不看血缘

python
object -> Animal -> Dog -> Husky
python
>>> h = Husky()

>>> type(h) == Husky
True

>>> type(h) == Dog
False    # 明明 Husky 是 Dog 的一种!

>>> type(h) == Animal
False    # 明明 Husky 也是 Animal 的一种!

isinstance()沿继承链往上找

python
>>> isinstance(h, Husky)
True

>>> isinstance(h, Dog)      # Husky 继承 Dog,认可!
True

>>> isinstance(h, Animal)   # Dog 继承 Animal,也认可!
True

>>> isinstance(h, object)   # 万物皆 object
True

2.2 但反过来不成立

python
>>> d = Dog()

>>> isinstance(d, Dog)
True

>>> isinstance(d, Husky)    # Dog 不是 Husky
False

规则:子类实例父类类型,父类实例不是子类类型。

生活化理解:哈士奇是狗,狗是动物;但动物不一定是狗,狗不一定是哈士奇。

2.3 判断多种类型

python
>>> isinstance([1, 2, 3], (list, tuple))
True

>>> isinstance((1, 2, 3), (list, tuple))
True

传入一个元组,表示"是其中任意一种就行"。

2.4 type() vs isinstance() 怎么选?

场景推荐理由
精确匹配类型(不考虑继承)type()严格、明确
考虑继承关系isinstance()灵活、安全
不确定时isinstance()官方推荐,能将子类"一网打尽"

提示:廖雪峰老师明确建议——总是优先使用 isinstance()


三、dir():看对象的"能力清单"

3.1 列出所有属性和方法

python
>>> dir('ABC')
['__add__', '__class__', ..., '__subclasshook__', 'capitalize', 'casefold', ..., 'zfill']

dir() 返回一个字符串列表,包含对象的所有属性和方法名

3.2 结果分两类

类型例子说明
__xxx__ 开头结尾__len__, __add__, __class__Python 特殊方法,系统调用
普通名字lower, upper, split对象提供的公开方法

3.3 特殊方法的妙用:__len__

python
>>> len('ABC')
3

>>> 'ABC'.__len__()
3

len('ABC') 实际上调用了 'ABC'.__len__()

这意味着:我们可以给自己的类定义 __len__,让它支持 len()

python
class MyDog(object):
    def __len__(self):
        return 100

dog = MyDog()
>>> len(dog)
100

生活化理解len() 像遥控器上的"音量+"按钮——你按按钮,电视内部执行的是 __len__ 这个"内部指令"。给自家电视(自定义类)装上这个指令,遥控器就能控制它。


四、hasattr / getattr / setattr:动态操作属性

4.1 三剑客登场

python
class MyObject(object):
    def __init__(self):
        self.x = 9

    def power(self):
        return self.x * self.x

obj = MyObject()

hasattr:有没有?

python
>>> hasattr(obj, 'x')
True

>>> hasattr(obj, 'y')
False

getattr:拿出来!

python
>>> getattr(obj, 'x')
9

>>> getattr(obj, 'y')
AttributeError: 'MyObject' object has no attribute 'y'

# 加默认值,不存在时不报错
>>> getattr(obj, 'y', 404)
404

setattr:塞进去!

python
>>> setattr(obj, 'y', 19)
>>> obj.y
19

>>> hasattr(obj, 'y')
True

4.2 操作的不只是数据,还有方法

python
>>> hasattr(obj, 'power')
True

>>> fn = getattr(obj, 'power')
>>> fn
<bound method MyObject.power of <__main__.MyObject object ...>>

>>> fn()    # 等价于 obj.power()
81

getattr 拿到的是方法本身,可以赋值给变量,再调用。

4.3 生活化理解

函数类比
hasattr问服务员:"你们有这道菜吗?"
getattr服务员把菜端上桌
setattr你自己往菜单上加了一道菜

五、什么时候用这些工具?

5.1 正确示范:鸭子类型的防御性检查

python
def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None

不假设 fp 一定是文件对象,先检查有没有 read 方法——有就读,没有就算了。

这是鸭子类型的典型应用:不关心你是什么,只关心你能不能做这件事。

5.2 错误示范:能直接访问就别绕弯子

python
# ❌ 多此一举
sum = getattr(obj, 'x') + getattr(obj, 'y')

# ✅ 直接写
sum = obj.x + obj.y

原则:只有不知道对象信息时(比如处理用户输入、第三方数据),才用 hasattr/getattr 做防御性检查。自己写的代码,直接访问属性即可。


六、常见误区与避坑指南

6.1 误区一:用 type() 判断继承关系

python
# ❌ 错误:Husky 继承 Dog,但 type() 不认
if type(h) == Dog:
    print('是狗')

# ✅ 正确:isinstance() 认可继承链
if isinstance(h, Dog):
    print('是狗')

6.2 误区二:getattr 不加默认值,导致意外崩溃

python
# ❌ 危险:属性不存在直接报错
value = getattr(obj, 'config')

# ✅ 安全:给默认值
value = getattr(obj, 'config', {})

6.3 误区三:过度使用动态操作,降低可读性

python
# ❌ 故意炫技,别人看不懂
for attr in ['name', 'age', 'score']:
    print(getattr(obj, attr))

# ✅ 直接访问更清晰
print(obj.name)
print(obj.age)
print(obj.score)

动态操作适合属性名来自变量的场景(比如从 JSON 读取配置),不适合替代正常访问。

6.4 误区四:hasattr 的"假阳性"

python
class MyClass(object):
    @property
    def x(self):
        raise AttributeError('出错了')

obj = MyClass()
>>> hasattr(obj, 'x')
False    # 明明有 x 属性,但 hasattr 返回 False!

原因hasattr 会尝试调用属性,如果 @property 抛出 AttributeErrorhasattr 就返回 False。

对策:确保 @property 内部不抛 AttributeError,或者改用 getattr 配合默认值。


七、实战练习

练习 1:安全的属性读取器

写一个函数 safe_get(obj, attr_name, default=None)

  • 如果对象有该属性,返回属性值
  • 如果没有,返回 default
  • 如果属性是方法,返回方法本身(不调用)
python
class User:
    def __init__(self):
        self.name = 'Alice'
    def greet(self):
        return 'Hello'

u = User()
print(safe_get(u, 'name'))        # Alice
print(safe_get(u, 'age'))         # None
print(safe_get(u, 'greet'))       # <bound method ...>
参考答案
python
def safe_get(obj, attr_name, default=None):
    return getattr(obj, attr_name, default)

练习 2:类型分发器

写一个函数 process(data)

  • 如果是字符串,返回大写
  • 如果是数字(int 或 float),返回平方
  • 如果是列表,返回长度
  • 其他类型返回 "不支持的类型"
python
print(process('hello'))   # HELLO
print(process(5))         # 25
print(process([1,2,3]))   # 3
print(process(None))      # 不支持的类型
参考答案
python
def process(data):
    if isinstance(data, str):
        return data.upper()
    elif isinstance(data, (int, float)):
        return data ** 2
    elif isinstance(data, list):
        return len(data)
    else:
        return '不支持的类型'

练习 3:找出 bug

下面代码想检查 obj 是否有 name 属性,有就打印,没有就打印"匿名"。但总是打印"匿名",找出原因:

python
class Person:
    def __init__(self):
        self.name = 'Bart'

p = Person()

if hasattr(p, 'name'):
    print(getattr(p, 'name'))
else:
    print('匿名')
参考答案

这段代码本身没有 bug,会正常打印 Bart

但题目说"总是打印匿名",说明实际运行中可能遇到了 @property 抛异常的情况,或者 name 被设置为 None 导致判断逻辑出了问题。

一个更健壮的写法:

python
name = getattr(p, 'name', None)
if name:
    print(name)
else:
    print('匿名')

八、小结

  1. type() 看身份证——精确类型,不认可继承;
  2. isinstance() 看血缘——认可继承链,优先使用
  3. dir() 看能力清单——所有属性和方法名;
  4. hasattr / getattr / setattr 动态操作属性——防御性检查的利器;
  5. 正确用法:不知道对象信息时才用动态检查,能直接访问就直接访问;
  6. 避坑要点:继承判断用 isinstancegetattr 加默认值、别过度动态化、注意 @property 的异常。

这套"体检工具"让 Python 代码在灵活安全之间找到平衡——既不假设对象一定是什么,也不放弃验证的权利。