如何为transaction函数添加类型注解,使类型检查器能推导返回值类型?
Great question! To make your transaction function play nicely with type checkers (like mypy or Pyright) and correctly infer the return type based on the input type tuple, you'll want to leverage variadic generics (available in Python 3.10+ via the standard library, or in older versions with typing_extensions). Here's a step-by-step breakdown:
Step 1: Import Required Typing Utilities
First, grab the tools needed to handle variable-length type tuples:
# For Python 3.10+ (standard library) from typing import TypeVarTuple, Unpack, Tuple, Protocol # For Python 3.9 or earlier, install typing_extensions first: # from typing_extensions import TypeVarTuple, Unpack
Step 2: Define Generic Type Variables
We'll use a TypeVarTuple to capture the arbitrary set of types passed to the function. Optionally, add a protocol to enforce that input types support your deserialization logic:
# Define a variadic type variable to represent our input type tuple Ts = TypeVarTuple('Ts') # Optional: Protocol to ensure types can be deserialized (adjust to your needs) class Deserializable(Protocol): @classmethod def from_dict(cls, data: dict) -> "Deserializable": """Required method for deserializing from raw transaction data""" ...
If you want to enforce that all input types follow this deserialization rule, update the TypeVarTuple with a bound:
Ts = TypeVarTuple('Ts', bound=Deserializable)
Step 3: Annotate the Transaction Function
Link the input type tuple directly to the return type tuple so type checkers can map them 1:1:
Case 1: Function accepts variable positional arguments (e.g., transaction(User, Order))
def transaction(*types: Unpack[Tuple[Unpack[Ts]]]) -> Tuple[Unpack[Ts]]: # Simulate transaction execution and raw data retrieval # In real code, replace this with your actual transaction logic raw_transaction_data = [{"user_id": 1}, {"order_id": 100}] # Deserialize each data entry into the corresponding type instance result = [] for cls, data in zip(types, raw_transaction_data): # Use your actual deserialization logic here instance = cls.from_dict(data) # Uses the Deserializable protocol # Or cls(**data) if your type accepts kwargs in __init__ result.append(instance) return tuple(result)
Case 2: Function accepts a single tuple argument (e.g., transaction(types=(User, Order)))
If your function takes a tuple parameter instead of positional args, adjust the annotation like this:
def transaction(types: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]: # Same implementation as above ...
Step 4: Test the Type Inference
When you use the function, type checkers will automatically infer the correct return type:
class User(Deserializable): def __init__(self, user_id: int): self.user_id = user_id @classmethod def from_dict(cls, data: dict) -> "User": return cls(user_id=data["user_id"]) class Order(Deserializable): def __init__(self, order_id: int): self.order_id = order_id @classmethod def from_dict(cls, data: dict) -> "Order": return cls(order_id=data["order_id"]) # Type checker infers this returns Tuple[User, Order] user, order = transaction(User, Order) # Type checkers will flag errors if you try to assign to the wrong type wrong_type: str = user # Mypy/Pyright will throw an error here
Key Notes
- Type Checker Support: Ensure you're using a tool that supports variadic generics (mypy 0.910+, Pyright, or PyCharm 2021.3+).
- Older Python Versions: For Python 3.9 or earlier, install
typing_extensionsand importTypeVarTuple/Unpackfrom there instead oftyping. - Flexibility: This approach works for any number of input types—whether you pass 1, 5, or 10 types, the return type will match exactly.
内容的提问来源于stack exchange,提问作者Andrey Cizov

