Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infer generic lambda parameters from another generic lambda's parameters

Tags:

python

mypy

Consider the following code:

from typing import Callable, TypeVar

T = TypeVar('T')

def middle_man(
    producer: Callable[[], T],
    consumer: Callable[[T], None]
) -> None:
    consumer(producer())

middle_man(
    lambda: "HELLO",
    lambda s: print(s.lower())
)

This code runs without error and works as expected, however, mypy fails to infer the type of s in the second lambda, giving the error:

"object" has no attribute "lower"`

Right now, my only workaround is either to cast s, which can be a pain in a more complex lambda or with a more complex type, or to add # type: ignore, which I'd rather not do.

Is there a better workaround, or a proper way to get mypy to recognize the type?

like image 643
P Daddy Avatar asked Jul 26 '19 16:07

P Daddy


People also ask

How do you pass multiple parameters in Lambda Expression Python?

Just like a normal function, a Lambda function can have multiple arguments with one expression. In Python, lambda expressions (or lambda forms) are utilized to construct anonymous functions. To do so, you will use the lambda keyword (just as you use def to define normal functions).

Can lambda take two arguments?

A lambda function can take any number of arguments, but can only have one expression.

What is the correct syntax for Lambda Expression in C++11?

Lambdas can both capture variables and accept input parameters. A parameter list (lambda declarator in the Standard syntax) is optional and in most aspects resembles the parameter list for a function. auto y = [] (int first, int second) { return first + second; };


1 Answers

It seems to me that you have two conflicting concerns here:

  • you want your T TypeVar to be generic so that the type annotation for the producer and consumer parameters to middle_man can be flexible
  • you want the type annotations for a specific call to middle_man to be specific enough that mypy recognizes that you are calling it with a str type

In order to achieve this, I would annotate the arguments to a specific call to middle_man by storing the arguments as variables first:

from typing import Callable, TypeVar

T = TypeVar('T')

def middle_man(
    producer: Callable[[], T],
    consumer: Callable[[T], None]
) -> None:
    consumer(producer())

producer: Callable[[], str] = lambda: "HELLO"
consumer: Callable[[str], None] = lambda s: print(s.lower())

middle_man(producer, consumer)

EDIT

Another suggestion:

Since you are using a generic TypeVar anyway, you can replace it with the Any type without losing type information and mypy will not raise an error because it skips static type checking for Any types.

from typing import Callable, Any

def middle_man(
    producer: Callable[[], Any],
    consumer: Callable[[Any], None]
) -> None:
    consumer(producer())

middle_man(
    lambda: "HELLO",
    lambda s: print(s.lower())
)

I know this solution is not very satisfying because it essentially defeats the purpose of using mypy for static type checking but unless you want to restrict the upper bound of your TypeVar to a class with a lower method then this might be your best option.

like image 152
bunji Avatar answered Oct 21 '22 07:10

bunji