杰瑞科技汇

Python如何实现自省机制?

Of course! "Introspection" in Python refers to the ability of a program to examine the type or properties of an object at runtime. This is a powerful feature that allows you to write more dynamic, flexible, and "Pythonic" code.

Python如何实现自省机制?-图1
(图片来源网络,侵删)

Let's break down how Python introspection works, from the built-in functions to more advanced techniques.


The Built-in Functions: The Core of Introspection

These are the most common and direct tools you'll use. They are built into the language and work on virtually any object.

type() and isinstance(): Checking an Object's Type

These are your primary tools for figuring out what kind of object you're dealing with.

  • type(obj): Returns the exact type of the object.
  • isinstance(obj, classinfo): Returns True if the object is an instance of the class or a subclass thereof. This is generally preferred over type() because it respects inheritance.
class Animal:
    pass
class Dog(Animal):
    pass
my_dog = Dog()
# Using type()
print(f"type(my_dog): {type(my_dog)}")
# Output: type(my_dog): <class '__main__.Dog'>
# Using isinstance()
print(f"isinstance(my_dog, Dog): {isinstance(my_dog, Dog)}")
# Output: isinstance(my_dog, Dog): True
print(f"isinstance(my_dog, Animal): {isinstance(my_dog, Animal)}")
# Output: isinstance(my_dog, Animal): True  # This is why isinstance is better!
print(f"isinstance(my_dog, str): {isinstance(my_dog, str)}")
# Output: isinstance(my_dog, str): False

dir(): Listing Attributes and Methods

dir() is your "directory" function. It returns a list of strings containing the names of an object's attributes and methods.

Python如何实现自省机制?-图2
(图片来源网络,侵删)
my_list = [1, 2, 3]
# Get all attributes and methods of the list object
print(dir(my_list))
# Output (partial list):
# ['__add__', '__class__', '__contains__', ..., 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

You can use this to programmatically check if an object has a certain capability.

if 'append' in dir(my_list):
    print("This object can append items.")
    my_list.append(4)
    print(my_list)
# Output:
# This object can append items.
# [1, 2, 3, 4]

hasattr(), getattr(), setattr(), delattr(): Interacting with Attributes

These four functions work together to get, set, and check for attributes dynamically.

  • hasattr(obj, 'name'): Returns True if the object has an attribute with the given name.
  • getattr(obj, 'name', default): Returns the value of the attribute. If the attribute doesn't exist, it returns the default value (or raises an AttributeError if no default is provided).
  • setattr(obj, 'name', value): Sets the attribute to the given value. If the attribute doesn't exist, it will be created.
  • delattr(obj, 'name'): Deletes the attribute.
class MyClass:
    def __init__(self):
        self.existing_attr = "I exist!"
obj = MyClass()
# Check for an attribute
print(f"Has 'existing_attr': {hasattr(obj, 'existing_attr')}")
# Output: Has 'existing_attr': True
print(f"Has 'new_attr': {hasattr(obj, 'new_attr')}")
# Output: Has 'new_attr': False
# Get an attribute's value
print(f"getattr(obj, 'existing_attr'): {getattr(obj, 'existing_attr')}")
# Output: getattr(obj, 'existing_attr'): I exist!
# Safely get an attribute that might not exist
print(f"getattr(obj, 'new_attr', 'default_value'): {getattr(obj, 'new_attr', 'default_value')}")
# Output: getattr(obj, 'new_attr', 'default_value'): default_value
# Set a new attribute
setattr(obj, 'new_attr', 'I was set dynamically!')
print(f"obj.new_attr: {obj.new_attr}")
# Output: obj.new_attr: I was set dynamically!
# Delete an attribute
delattr(obj, 'existing_attr')
print(f"After deletion, has 'existing_attr': {hasattr(obj, 'existing_attr')}")
# Output: After deletion, has 'existing_attr': False

callable(): Checking if an Object is Callable

This function tells you if you can "call" an object like a function.

def my_function():
    return "Hello"
class MyCallableClass:
    def __call__(self):
        return "Hello from a callable instance!"
my_obj = MyCallableClass()
print(f"callable(my_function): {callable(my_function)}")
# Output: callable(my_function): True
print(f"callable(my_obj): {callable(my_obj)}")
# Output: callable(my_obj): True (because it has a __call__ method)
print(f"callable(42): {callable(42)}")
# Output: callable(42): False

The inspect Module: Advanced Introspection

For more detailed analysis, Python's standard library provides the inspect module. It's like a powerful microscope for your code.

Python如何实现自省机制?-图3
(图片来源网络,侵删)
import inspect
import my_module  # Assume this module has functions and classes

Inspecting Source Code

  • inspect.getsource(object): Get the source code of an object (function, class, method).
  • inspect.getsourcelines(object): Get the source code as a list of lines and the starting line number.
def example_function(x, y):
    """This is a docstring."""
    return x + y
print(inspect.getsource(example_function))
# Output:
# def example_function(x, y):
#     """This is a docstring."""
#     return x + y

Inspecting Signatures

  • inspect.signature(callable): Get a Signature object representing the call signature of a function. This is incredibly useful for building libraries and decorators.
def another_func(a, b=10, *args, c, **kwargs):
    pass
sig = inspect.signature(another_func)
print(f"Signature: {sig}")
# Output: Signature: (a, b=10, *args, c, **kwargs)
# You can inspect the parameters
for name, param in sig.parameters.items():
    print(f"Parameter: {name}, Default: {param.default}, Kind: {param.kind}")
# Output:
# Parameter: a, Default: <class inspect._empty>, Kind: POSITIONAL_OR_KEYWORD
# Parameter: b, Default: 10, Kind: POSITIONAL_OR_KEYWORD
# Parameter: args, Default: <class inspect._empty>, Kind: VAR_POSITIONAL
# Parameter: c, Default: <class inspect._empty>, Kind: KEYWORD_ONLY
# Parameter: kwargs, Default: <class inspect._empty>, Kind: VAR_KEYWORD

Inspecting Call Stacks

  • inspect.stack(): Get a list of frame records from the current call stack. This is used for advanced debugging and logging.
def function_b():
    # Get the current call stack
    stack = inspect.stack()
    print("Call stack from function_b:")
    for frame_info in stack:
        print(f"  - File: {frame_info.filename}, Function: {frame_info.function}, Line: {frame_info.lineno}")
def function_a():
    function_b()
function_a()
# Output:
# Call stack from function_b:
#   - File: <stdin>, Function: function_b, Line: 18
#   - File: <stdin>, Function: function_a, Line: 22
#   - File: <stdin>, Function: <module>, Line: 25

"Magic" (Dunder) Methods: The Engine of Introspection

Many of the introspection tools work by calling special "dunder" (double underscore) methods on your objects. When you call len(my_list), Python is actually calling my_list.__len__(). When you call print(my_list), it calls my_list.__str__().

You can implement these in your own classes to make them work seamlessly with Python's built-in functions.

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    # Called by len()
    def __len__(self):
        return self.pages
    # Called by str()
    def __str__(self):
        return f"Book: {self.title} by {self.author}"
    # Called by repr()
    def __repr__(self):
        return f"Book('{self.title}', '{self.author}', {self.pages})"
my_book = Book("The Hobbit", "J.R.R. Tolkien", 310)
# These calls trigger the dunder methods
print(my_book)          # Calls __str__
# Output: Book: The Hobbit by J.R.R. Tolkien
print(repr(my_book))    # Calls __repr__
# Output: Book('The Hobbit', 'J.R.R. Tolkien', 310)
print(f"Length: {len(my_book)} pages") # Calls __len__
# Output: Length: 310 pages

Real-World Use Cases

  • Data Validation Libraries (e.g., Pydantic): These libraries inspect the type hints of your data classes to automatically validate input data.
  • Dependency Injection Frameworks: They inspect function signatures (inspect.signature) to know which dependencies (arguments) a function needs and provide them automatically.
  • Debuggers and Profilers: Tools like pdb use introspection to inspect variables, call stacks, and object states at runtime.
  • Serialization Libraries (e.g., for JSON): They use dir() and getattr() to iterate over an object's attributes and convert them into a dictionary for serialization.
  • Dynamic API Wrappers: You can create a class that, when asked for an attribute (getattr), makes an API call to fetch data and returns it as if it were a local attribute. This is how ORMs (Object-Relational Mappers) often work.
分享:
扫描分享到社交APP
上一篇
下一篇