杰瑞科技汇

Python pkg resources 如何正确使用?

Of course! Let's dive deep into pkg_resources, a powerful but now legacy module from Python's setuptools library.

Python pkg resources 如何正确使用?-图1
(图片来源网络,侵删)

What is pkg_resources?

pkg_resources is a part of the setuptools library that provides a rich API for discovering and managing Python packages and their resources at runtime.

Think of it as a bridge between your running Python code and the metadata and data files associated with the installed packages.

Its core capabilities include:

  1. Package Discovery: Finding out what packages are installed and their versions.
  2. Resource Management: Accessing data files (like images, configuration files, JSON, etc.) that are included with a package, regardless of where the package is installed (e.g., in a standard site-packages directory, inside a .zip file, or an egg).
  3. Version Comparison: Easily comparing package versions.
  4. Entry Points: Finding plugins or console scripts defined by other packages.

Key Use Cases and Examples

Let's look at the most common use cases for pkg_resources.

Python pkg resources 如何正确使用?-图2
(图片来源网络,侵删)

Accessing Package Data (The Most Common Use)

This is pkg_resources killer feature. You have a package structure like this:

my_package/
├── my_package/
│   ├── __init__.py
│   └── data/
│       ├── config.json
│       └── banner.txt
└── setup.py

In setup.py, you declare the data files:

# setup.py
from setuptools import setup, find_packages
setup(
    name="my_package",
    version="0.1.0",
    packages=find_packages(),
    include_package_data=True, # Important!
    package_data={
        'my_package': ['data/*.txt', 'data/*.json'],
    }
)

Now, from within your code (e.g., in my_package/__init__.py), you can access config.json reliably:

# my_package/__init__.py
import pkg_resources
import json
def get_config():
    """Loads the package's config.json file."""
    try:
        # 'my_package.data.config.json' is the "resource path"
        # It's the path relative to the top-level package directory.
        config_path = pkg_resources.resource_filename('my_package', 'data/config.json')
        # The resource_string() or resource_stream() methods are often better
        # as they work even if the package is zipped.
        with open(config_path, 'r') as f:
            return json.load(f)
    except (FileNotFoundError, pkg_resources.DistributionNotFound):
        print("Could not find the config file.")
        return None
# --- Alternative, often better method using resource_stream() ---
def get_config_stream():
    """Returns a file-like stream to the config.json."""
    try:
        # resource_stream returns a file-like object
        config_stream = pkg_resources.resource_stream('my_package', 'data/config.json')
        return json.load(config_stream)
    except pkg_resources.DistributionNotFound:
        print("Could not find the config file.")
        return None
# How to use it
if __name__ == '__main__':
    config = get_config()
    if config:
        print("Config loaded successfully:", config)

Why is this better than a simple open()?

Python pkg resources 如何正确使用?-图3
(图片来源网络,侵删)
  • Location Independence: It works whether my_package is installed in site-packages, run from a source checkout, or even loaded from a .zip or .egg file. A simple open() would fail in the latter cases.
  • Robustness: It's the standard, idiomatic way to access package data in a complex Python environment.

Discovering Installed Packages and Versions

You can check what's installed and its version.

import pkg_resources
# Get a list of all installed distributions (packages)
distributions = pkg_resources.working_set
print(f"Found {len(distributions)} installed packages.")
# Find a specific package
try:
    # Get a Distribution object for a specific package
    requests_dist = pkg_resources.get_distribution('requests')
    print(f"Requests version: {requests_dist.version}")
    print(f"Requests location: {requests_dist.location}")
    print(f"Requires: {[str(req) for req in requests_dist.requires()]}")
except pkg_resources.DistributionNotFound:
    print("The 'requests' package is not installed.")

Version Comparison

Comparing version strings (like '1.2.3' vs '1.10.0') can be tricky. pkg_resources makes it easy.

import pkg_resources
v1 = pkg_resources.parse_version('1.2.3')
v2 = pkg_resources.parse_version('1.10.0')
v3 = pkg_resources.parse_version('1.2.3b1') # Also handles beta/alpha releases
print(f"v1 ({v1}) < v2 ({v2})?  {v1 < v2}")  # True
print(f"v1 ({v1}) == v3 ({v3})? {v1 == v3}") # False
print(f"v3 ({v3}) < v1 ({v1})?  {v3 < v1}")  # True (1.2.3b1 is older than 1.2.3)

Working with Entry Points (Plugin Systems)

This is a powerful pattern for creating extensible applications. Many tools (like pytest, flake8, setuptools itself) use entry points to discover plugins.

A package can declare an entry point in its setup.py:

# setup.py for a plugin package
setup(
    name="my_plugin",
    # ...
    entry_points={
        'my_app.plugins': [
            'my_first_plugin = my_plugin.plugins:first',
            'my_second_plugin = my_plugin.plugins:second',
        ]
    }
)

Your main application can then find all registered plugins:

# In your main application
import pkg_resources
def load_plugins():
    """Finds and loads all plugins for 'my_app.plugins'."""
    for entry_point in pkg_resources.iter_entry_points('my_app.plugins'):
        print(f"Loading plugin: {entry_point.name}")
        # entry_point.load() returns the object/function referenced
        plugin = entry_point.load()
        # Now you can call the plugin
        plugin()
# You would call load_plugins() in your application's startup logic.

The Modern Alternative: importlib.metadata and importlib.resources

pkg_resources is powerful but has significant drawbacks:

  • Performance: It's notoriously slow. Scanning for packages and parsing metadata can add noticeable overhead to application startup.
  • Overhead: It pulls in a large amount of setuptools code just for a few utility functions.
  • Legacy: It's considered the "old way" of doing things. The Python standard library now provides modern, faster, and more focused alternatives.

Since Python 3.8, the recommended replacements are in the standard library:

pkg_resources Functionality Modern importlib Equivalent Notes
Accessing Package Data importlib.resources The direct replacement. Part of the standard library since Python 3.7 (backported).
Finding Installed Packages importlib.metadata The direct replacement. Part of the standard library since Python 3.8.
Version Comparison packaging.version The packaging library is the new standard for parsing and comparing versions.

Example: Modern Package Data Access with importlib.resources

Using the same package structure as before:

# my_package/__init__.py
import importlib.resources
import json
def get_config_modern():
    """Loads the package's config.json using the modern approach."""
    try:
        # Use the 'files()' API (recommended)
        # The path is relative to the package
        config_file = importlib.resources.files('my_package').joinpath('data/config.json')
        # config_file is a Traversable object. You can open it directly.
        with config_file.open('r') as f:
            return json.load(f)
    except FileNotFoundError:
        print("Could not find the config file.")
        return None
# How to use it
if __name__ == '__main__':
    config = get_config_modern()
    if config:
        print("Config loaded successfully (modern way):", config)

Example: Modern Package Discovery with importlib.metadata

import importlib.metadata
# Get a list of all distributions
distributions = importlib.metadata.distributions()
print(f"Found {len(list(distributions))} installed packages.")
# Find a specific package
try:
    requests_version = importlib.metadata.version('requests')
    print(f"Requests version (modern way): {requests_version}")
    # Get more metadata
    metadata = importlib.metadata.metadata('requests')
    print(f"Requests Summary: {metadata['Summary']}")
except importlib.metadata.PackageNotFoundError:
    print("The 'requests' package is not installed.")

Summary: pkg_resources vs. Modern Alternatives

Feature pkg_resources importlib.metadata + importlib.resources
分享:
扫描分享到社交APP
上一篇
下一篇