Python object type checking and method acquisition guide

When writing flexible and robust Python code, we often encounter objects of "unknown origin" - passed in from the outside, dynamically generated, or need to adapt to multiple types. Getting object information (types, properties, methods) becomes the first step: it can help us avoid errors, optimize branch logic, and even unlock the magic of dynamic calls. This article will systematically introduce type checking, reflection three-piece set and practical applications.


1. Type checking

1.1 Basic but "not inherited"type()

type()It is Python's built-in precise type detector. It will strictly return the current class of the object, regardless of inheritance relationships. Suitable for "non-inheritance" scenarios such as checking basic data types and metatypes of functions/classes themselves:

# 基础数据类型
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()Inheritance relationship will not be recognized! The instance type of the subclass will not be equal to the parent class. Never use it when involving inheritance chains.

1.2 "Precise segmentation" of function family:typesmodule

When we need to check not ordinary class instances, but various function-related types (ordinary functions, anonymous functions, generators, built-in functions, etc.), we can use the standard library directlytypesPredefined type constants in:

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 Inheritance friendlyisinstance()

In oop, parent classes can accept subclass instances (the basis of polymorphism). Must be used at this timeisinstance(obj, class_info), it will checkobjIs itclass_infoAn instance of ** itself or any of its subclasses **.

The second parameter can also be passed in a type tuple to check multiple types at once:

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(同时检查列表和元组)
Best Practices

Unless must strictly match the current class (for example, subclasses are explicitly prohibited from participating), please use it first in 99% of cases.isinstance()


2. Get the properties and methods of the object

The type is just the entrance. Next we need to know what capabilities this object has - what methods it can call, what public properties it has, and even how it needs to be dynamically manipulated at runtime.

2.1 A clear view:dir()

dir(obj)Returns an ordered string list, each string corresponds to a property name or method name of the object. Which start and end with double underscore (such as__len__) are Python's "special methods/properties" that usually correspond to built-in operations:

# 查看字符串的全部能力
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 Dynamic search, retrieval and setting: three-piece reflection set

Python is a dynamic language that allows you to obtain and modify the properties and methods of objects at runtime (not compile time). This is "reflection".

What is reflection?

Simply put, it is the ability of a program to inspect and modify the structure and behavior of itself or other objects while it is running.

The core tools arehasattr()getattr()andsetattr()

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. Practical application examples

Reflection and type checking are often used together to make code more flexible and Pythonic.

3.1 Duck Type: Only about "ability", not "identity"

There is a famous saying in Python: "If it walks like a duck and quacks like a duck, then it is a duck." - This is duck typing: we do not check the specific type of the object, only check whether it has the methods we need.

For example, write a genericread_datafunction, as long as the object hasread()The method is called directly, otherwise it is converted into a string:

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 Dynamically calling methods based on data type

Suppose we have a processor, and text and numbers require different processing logic. combineisinstance()andgetattr()Can be flexibly scheduled and even replace defective methods dynamically:

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 行为

Note: Boolean values ​​in Python areintsubclass ofTrueEquivalent to1, and therefore will be captured by the digital branch.


4. Summary

The following table summarizes the key techniques for obtaining object information:

TechnologyUsage
type(obj)Exactly matches the current class of the object (not applicable to inheritance scenarios)
typesModule constantsSpecial types such as subdivision functions, generators, and Lambdas
isinstance(obj, cls)Recommended type checking method, supports inheritance chain and type tuples
dir(obj)List all properties and methods of the object (including special methods)
hasattr/getattr/setattrDynamically check, retrieve, and set properties/methods to implement reflection

:::note Final suggestions

  • Although reflection is powerful, it will reduce readability and increase debugging difficulty, so don’t abuse it!
  • Prioritize following the duck typing principle and focus on the "ability" of the object rather than the "identity";
  • If you must check the type, default toisinstance(),keep awaytype()。 :::