杰瑞科技汇

Python entrypoints是什么?如何使用?

Of course! Let's dive deep into Python entry points. This is a crucial concept for creating well-behaved, installable Python packages.

Python entrypoints是什么?如何使用?-图1
(图片来源网络,侵删)

What are Entry Points?

In simple terms, entry points are bridges between your installed Python package and the outside world (like the command line or other Python applications).

They allow you to:

  1. Create command-line tools that users can run directly (e.g., my-cli-tool --help).
  2. Register plugins for other applications (e.g., a static site generator that can load plugins from various installed packages).

Without entry points, a user would have to manually figure out where your package's script was installed (e.g., in a bin/ directory inside their virtual environment) and run it from there. Entry points make this process seamless and standard.


The Two Main Types of Entry Points

There are two primary types you'll define in your pyproject.toml file:

Python entrypoints是什么?如何使用?-图2
(图片来源网络,侵删)
  1. Console Scripts: These create command-line executables.
  2. Entry Points (for plugins): These register Python functions or classes that other applications can discover and use at runtime.

How to Define Entry Points

Since the adoption of the modern Python packaging standard (PEP 517/518), entry points are defined in the [project.scripts] and [project.entry-points] tables inside your pyproject.toml file.

Let's look at a practical example.

Project Structure

Imagine we have a simple project structure like this:

my_cli_app/
├── pyproject.toml
├── src/
│   └── my_cli_app/
│       ├── __init__.py
│       └── cli.py
└── README.md

The Python Code (src/my_cli_app/cli.py)

First, we need a function in our package that will be the entry point for our command-line tool. A common convention is to name it main().

# src/my_cli_app/cli.py
import argparse
import sys
def main():
    """Main entry point for the application."""
    parser = argparse.ArgumentParser(description="A simple CLI tool made with Python entry points.")
    parser.add_argument("name", help="The name to greet.")
    parser.add_argument("--count", type=int, default=1, help="Number of times to greet.")
    args = parser.parse_args()
    for _ in range(args.count):
        print(f"Hello, {args.name}!")
    # It's good practice to exit with a status code
    sys.exit(0)

The Configuration (pyproject.toml)

This is where the magic happens. We tell Python's packaging tools (like setuptools, flit, or hatch) to create a console script that points to our main() function.

# pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-cli-app"
version = "0.1.0"
authors = [
  { name="Your Name", email="you@example.com" },
]
description = "A small example package demonstrating entry points"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
# --- THIS IS THE IMPORTANT PART ---
[project.scripts]
# The key is the command name the user will type.
# The value is a path to the function: <module_name>.<function_name>
my-cli-tool = "my_cli_app.cli:main"

Breaking down [project.scripts] entry:

  • my-cli-tool: This is the name of the command that will be available in the user's terminal after they install your package.
  • my_cli_app.cli:main: This is the "path" to your function.
    • my_cli_app: The name of your Python package (the directory src/my_cli_app).
    • cli: The name of the module (the file src/my_cli_app/cli.py).
    • main: The name of the function to call when the command is executed.

Installation and Usage

Now, let's see it in action.

  1. Build and Install the Package:

    Navigate to the root directory of your project (my_cli_app/) and run:

    # Build a "wheel" (a distributable package format)
    python -m build
    # Install the package in "editable" mode for development
    pip install -e .
    • python -m build is the modern standard for building packages.
    • pip install -e . installs your package. The -e or --editable flag means it's installed in "editable" mode, so changes you make to the source code are immediately reflected without needing to reinstall.
  2. Run the Command:

    Now, you can run your tool from anywhere in your terminal, not just from the project directory!

    # On macOS/Linux
    my-cli-tool --help
    # On Windows, it might be lowercase
    my-cli-tool --help

    Expected Output:

    usage: my-cli-tool [-h] [--count COUNT] name
    A simple CLI tool made with Python entry points.
    positional arguments:
      name            The name to greet.
    options:
      -h, --help      show this help message and exit
      --count COUNT   Number of times to greet.

    And with arguments:

    my-cli-tool Alice --count 3

    Expected Output:

    Hello, Alice!
    Hello, Alice!
    Hello, Alice!

Entry Points for Plugins (The "Other" Type)

This is a more advanced but extremely powerful use case. It allows your application to be extensible by other packages.

Let's imagine we have a simple application that can process data. We want to allow other developers to write their own "processors" as separate packages, and our main application can find and use them.

The Main Application (my_app/main.py)

This application will look for all registered plugins under a specific "group" name.

# my_app/main.py
import importlib
import pkg_resources
# This is the "entry point group" name. All plugins will register under this.
# It's good practice to use a reverse domain name.
ENTRY_POINT_GROUP = "my_app.processors"
def load_processors():
    """Discovers and loads all processors registered under the entry point group."""
    processors = {}
    for entry_point in pkg_resources.iter_entry_points(group=ENTRY_POINT_GROUP):
        try:
            # The entry point's name is the key (e.g., 'uppercase')
            # The entry point's load() method calls the function (e.g., 'get_processor')
            processor_class = entry_point.load()
            processors[entry_point.name] = processor_class()
            print(f"Loaded processor: '{entry_point.name}'")
        except Exception as e:
            print(f"Failed to load processor '{entry_point.name}': {e}")
    return processors
def main():
    print("Discovering processors...")
    processors = load_processors()
    if not processors:
        print("No processors found. Exiting.")
        return
    print("\nRunning processors...")
    # Example data
    data = "hello world from my app"
    for name, processor in processors.items():
        print(f"\n--- Running '{name}' processor ---")
        result = processor.process(data)
        print(f"Input:  '{data}'")
        print(f"Output: '{result}'")
if __name__ == "__main__":
    main()

The Plugin Package (my_plugin_one/pyproject.toml)

Now, let's create a separate package that provides a plugin.

# my_plugin_one/pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-plugin-one"
version = "0.1.0"
description = "A plugin for my_app"
# --- THIS IS THE PLUGIN DEFINITION ---
[project.entry-points."my_app.processors"]
# The key is the name of the plugin as it will be known to the main app.
# The value is the path to the plugin's class/function.
uppercase = "my_plugin_one.processors:UppercaseProcessor"

And the plugin code itself (my_plugin_one/my_plugin_one/processors.py):

# my_plugin_one/my_plugin_one/processors.py
class UppercaseProcessor:
    def __init__(self):
        print("UppercaseProcessor initialized.")
    def process(self, text: str) -> str:
        """Converts text to uppercase."""
        return text.upper()

How It Works

  1. You would install both my-app and my-plugin-one in the same environment.
    pip install -e ./my_app
    pip install -e ./my_plugin_one
  2. When you run python -m my_app.main, the pkg_resources.iter_entry_points() function scans all installed packages.
  3. It finds the entry point definition under the group my_app.processors in my-plugin-one.
  4. It successfully loads the UppercaseProcessor class.
  5. Your main application can now use this processor without knowing anything about the my-plugin-one package beforehand.

This is the exact mechanism used by frameworks like pytest (for discovering test functions), Flask (for extensions), and many others.


Key Takeaways

  • Standardization: Entry points are the standard, declarative way to define command-line tools and plugins.
  • pyproject.toml: Define them in the [project.scripts] (for CLI) and [project.entry-points] (for plugins) tables.
  • Syntax: The format is <command_name> = <module.path:function_or_class_name>.
  • Installation: Users install your package with pip, and the entry points are automatically registered.
  • Discovery: Plugins are discovered at runtime using pkg_resources.iter_entry_points() (the classic way) or the newer importlib.metadata (the modern way, preferred for Python 3.8+).
  • Power: They are the foundation of the rich Python ecosystem of pluggable applications.
分享:
扫描分享到社交APP
上一篇
下一篇