杰瑞科技汇

Python annotation有哪些实用技巧?

Of course! In Python, "annotation" most commonly refers to type hints. This is a powerful feature that allows you to add type information to your code, which can then be used by various tools for static analysis, IDEs for autocompletion and error-checking, and even at runtime.

Python annotation有哪些实用技巧?-图1
(图片来源网络,侵删)

Let's break down everything you need to know about Python annotations.


What are Type Hints?

Type hints are a way of declaring the expected data type of a variable, function parameter, or return value. They are not enforced by the Python interpreter at runtime (by default), but they serve as a form of documentation and a contract for your code.

Before Type Hints (Python < 3.5):

def add(a, b):
    return a + b
result = add(10, 20)  # Works fine
result = add("hello", "world") # Also works, but is this intended?

This code works, but it's not clear what types a and b should be. It could accept numbers, strings, lists, etc.

Python annotation有哪些实用技巧?-图2
(图片来源网络,侵删)

With Type Hints (Python 3.5+):

def add(a: int, b: int) -> int:
    return a + b
result = add(10, 20)  # Works fine
result = add("hello", "world") # Also works, but a type checker will flag this!

The a: int part means "parameter a is expected to be an integer." The -> int part means "this function is expected to return an integer."


The Basics of Syntax

Here are the fundamental ways to use type hints.

A. Variable Annotations

You can annotate variables directly.

Python annotation有哪些实用技巧?-图3
(图片来源网络,侵删)
name: str = "Alice"
age: int = 30
height: float = 5.9
is_student: bool = True

B. Function Parameter Annotations

This is the most common use case. You annotate the parameters of a function.

def greet(name: str) -> str:
    return f"Hello, {name}!"

C. Return Value Annotations

Use the -> symbol followed by the type to annotate the return value.

def get_age() -> int:
    return 30

Advanced Type Hinting

Python's type system is quite rich and can handle complex scenarios.

A. typing Module

For more complex types, you need to import the typing module.

List, Dict, Tuple, Set

from typing import List, Dict, Tuple, Set
# A list of integers
scores: List[int] = [95, 88, 76]
# A dictionary mapping strings to integers
student_grades: Dict[str, int] = {"Alice": 95, "Bob": 88}
# A tuple with specific types for each element
person: Tuple[str, int, float] = ("Charlie", 40, 6.1)
# A set of strings
unique_tags: Set[str] = {"python", "coding", "tutorial"}

Optional and Union Optional[T] is a shortcut for Union[T, None]. It means the value can be of type T or None.

from typing import Optional, Union
# A function that might return a string or None
def find_user(user_id: int) -> Optional[str]:
    # ... logic to find user ...
    if user_found:
        return "user123"
    else:
        return None
# Union means it can be one of several types
def process_data(data: Union[int, str]) -> str:
    if isinstance(data, int):
        return f"Processed number: {data}"
    else:
        return f"Processed string: {data}"

Any Any signifies that a value can be of any type. It's useful when you don't know the type or want to bypass type checking for a specific part of your code.

from typing import Any
def flexible_function(value: Any) -> Any:
    # This function can accept and return anything
    return value * 2 

B. Literal Type

Use Literal to restrict a value to one of a specific set of values.

from typing import Literal
def set_status(status: Literal["active", "inactive", "pending"]) -> None:
    print(f"Status set to: {status}")
set_status("active")  # OK
set_status("approved") # Error! Type checker will flag this.

C. Callable Type

Use Callable to represent a function or other callable object.

from typing import Callable
# A function that takes two numbers and returns a number
# The Callable is defined as [list of argument types, return type]
def apply_operation(x: int, y: int, operation: Callable[[int, int], int]) -> int:
    return operation(x, y)
result = apply_operation(10, 5, lambda a, b: a + b) # result will be 15

D. Custom Classes as Types

You can use your own classes as type hints.

class User:
    def __init__(self, name: str):
        self.name = name
def create_user(name: str) -> User:
    return User(name)
current_user: User = create_user("David")

Why Use Type Hints? (Benefits)

  1. Static Analysis and Error Catching: This is the biggest benefit. Tools like Mypy can analyze your code without running it to find potential bugs, like passing a string to a function that expects an integer.

    # mypy your_script.py
    # Example error: error: Argument 1 to "add" has incompatible type "str"; expected "int"
  2. Improved IDE Support: Modern IDEs like VS Code, PyCharm, and PyDev use type hints to provide:

    • Autocompletion.
    • In-line error highlighting.
    • Better "Go to Definition" and "Find Usages" features.
  3. Self-Documenting Code: Type hints act as built-in documentation. Anyone reading your function signature immediately understands what it expects and what it returns, making the code easier to understand and maintain.

  4. Refactoring Confidence: When you rename a class or change a function's return type, a type checker will immediately tell you all the places in your code that need to be updated, preventing bugs.


How to Use Type Hints in Practice

A. Running a Type Checker (Mypy)

Mypy is the de-facto standard for static type checking in Python.

  1. Install Mypy:

    pip install mypy
  2. Write a Python file (example.py):

    from typing import List
    def get_average(scores: List[int]) -> float:
        if not scores:
            return 0.0
        return sum(scores) / len(scores)
    # --- Correct usage ---
    my_scores = [85, 90, 78]
    average = get_average(my_scores)
    print(f"The average is: {average}")
    # --- Incorrect usage (will be caught by mypy) ---
    # This line will cause a type error at static analysis time
    wrong_scores = [85, "90", 78] 
    average2 = get_average(wrong_scores)
  3. Run Mypy on the file:

    mypy example.py

    Output:

    example.py:17: error: Argument 1 to "get_average" has incompatible type "List[Union[int, str]]"; expected "List[int]"
    Found 1 error in 1 file (checked 1 source file)

    Mypy caught the error where you tried to pass a list containing a string into a function that expects a list of integers.

B. Enforcing Types at Runtime

If you want Python to enforce types at runtime, you can use a library like Pydantic. This is extremely popular for data validation, especially in web applications (e.g., with FastAPI).

Example with Pydantic:

  1. Install Pydantic:

    pip install pydantic
  2. Write a script (pydantic_example.py):

    from pydantic import BaseModel, ValidationError
    class User(BaseModel):
        id: int
        name: str
        is_active: bool = True # Optional with a default value
    # --- Correct usage ---
    user_data = {"id": 1, "name": "Eve"}
    user = User(**user_data)
    print(user)
    #> id=1 name='Eve' is_active=True
    # --- Incorrect usage (will raise an error at runtime) ---
    try:
        invalid_data = {"id": "two", "name": "Frank"} # id should be an int
        invalid_user = User(**invalid_data)
    except ValidationError as e:
        print(f"\nValidation Error: {e}")

    Output:

    id=1 name='Eve' is_active=True
    Validation Error: 1 validation error for User
    id
      Input should be a valid integer [type=int_type, input_value='two', input_type=str]

Summary

Feature Description Example
Basic Annotation Declare types for variables and functions. def greet(name: str) -> str:
typing Module For complex types like List, Dict, Optional. from typing import List, Optional
scores: List[int]
def find() -> Optional[str]:
Static Checkers Tools like Mypy find type errors without running code. mypy my_script.py
Runtime Enforcement Libraries like Pydantic validate data at runtime. class User(BaseModel): id: int
Benefits Improves code quality, readability, and maintainability. Catches bugs early, enables better IDE support.
分享:
扫描分享到社交APP
上一篇
下一篇