Protocols in decorated function parameters fail type-check somehow #1384
-
Hello, I have a question regarding decorators and protocols. I am trying to define a decorator that accepts any function that accepts an argument matching a certain protocol, in order to read that argument's values. However, mypy 1.1.1 complains that the function is not compatible (unfortunately not explaining why). Below is a snippet with a minimal example. import uuid
from dataclasses import dataclass
from functools import wraps
from typing import Protocol, Callable
from uuid import UUID
class HasBlockUuid(Protocol):
# @property
# def block_uuid(self) -> UUID:
# ...
block_uuid: UUID
# Fails with either the property or the field, expected
# https://mypy.readthedocs.io/en/latest/common_issues.html?highlight=reveal_type#covariant-subtyping-of-mutable-protocol-members-is-rejected
# to be the problem
# Objective: create a decorator for functions that receive
# any object with a "block_uuid", property, in order to do something with it.
# The returned function has exactly the same type signature.
def check_block(func: Callable[[HasBlockUuid], None]) -> Callable[[HasBlockUuid], None]:
@wraps(func)
def wrapper(event: HasBlockUuid) -> None:
print("block uuid is", event.block_uuid)
return func(event)
return wrapper
@dataclass
class Example:
block_uuid: UUID
@check_block
# ^ Argument 1 to "check_block" has incompatible type "Callable[[Example], None]"; expected "Callable[[HasBlockUuid], None]" [arg-type]
def do_something(example: Example) -> None:
print("doing something with", example)
do_something(Example(block_uuid=uuid.uuid4()))
def do_something_else(has_block_uuid: HasBlockUuid) -> None:
print("block uuid is", has_block_uuid.block_uuid)
do_something_else(Example(block_uuid=uuid.uuid4())) # OK Pylance complains on the same line as mypy with:
However, creating a second function that uses the protocol in its arguments, and calling it with a concrete type, has no issue, even though Furthermore, pycharm does not complain about this. I am wondering if I am missing something with parameter variance, but I haven't found what it is yet. Does anyone know? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 6 replies
-
Function parameters are contravariant. While This makes sense if you think about the decorator |
Beta Was this translation helpful? Give feedback.
Function parameters are contravariant. While
Example
is a subtype ofHasBlockUuid
, the converse isn't true.This makes sense if you think about the decorator
check_block
. It says that it accepts a function that expects to receive an argument that conforms to theHasBlockUuid
protocol. That meanscheck_block
would be free to call this function with any object that conforms toHasBlockUuid
. But thedo_something
function has more stringent requirements. It requires that its first parameter is anExample
instance (or subtype thereof). If thecheck_block
were to call it with some other object that happens to conform to theHasBlockUuid
protocol (but is not a subtype ofExample
), it could crash…