Как напечатать функцию, которая принимает вызываемый объект и его аргументы в качестве параметров - например, численное интегрирование функции?
Проблема
У меня есть следующая функция (на основе scipy.integrate.quad
):
def simple_quad(func: Any, a: float, b: float, args: tuple = ()) -> float:
def strips(n: int):
for i in range(n):
x = a + (b - a) * i / n
yield func(x, *args) * 1 / n
return sum(strips(1000))
... который в основном оценивает диапазон значений и использует полосы фиксированной ширины для вычисления площади под графиком. При желании параметры могут быть переданы через
args
кортеж.
Как видите, я сделал несколько начальных подсказок по типам (на самом деле это заглушка .pyi для scipy), однако меня не устраивает, что набор функций func и args слишком расплывчатый. Я хочу, чтобы mypy защитил меня от двух вещей:
- - это вызываемый объект, который должен иметь первый позиционный параметр, который является, и возвращать
float
, и может иметь необязательный- т.е.
f(x:float, ...) -> float
по крайней мере - Я предполагаю, что он также может иметь **kwargs (хотя не может иметь требуемых параметров только для имени или требуемых позиционных параметров, кроме x)
- т.е.
- Необязательный позиционный
*args
чтобы должны соответствовать содержимому splatted ARGS кортежа
Примеры
def cubic(x: float, a: float, b: float, c: float, d: float) -> float:
"Function to evaluate a cubic polynomial with coefficients"
return a + b * x + c * x ** 2 + d * x ** 3
simple_quad(cubic, a=1, b=10, args=(1, 2, 3, 4)) # fine, passes args to a,b,c,d and int is compatible with float signature
simple_quad(cubic, a=1, b=10) # error from mypy as *args to `cubic` don't have default args so are all required
simple_quad(cubic, a=1, b=10, args=("a", "b", "c", "d")) # arg length correct but type mismatch since cubic expects floats
x_squared: Callable[[float], float] = lambda x: x * x
simple_quad(x_squared, a=1, b=10, args=()) # should be fine as x_squared doesn't take any positional args other than x
def problematic(x: float, *, y: float) -> float: ... # can't pass kwargs y via simple_quad, so can't be integrated
Что я пробовал
Для
func
Я пробовал что-то с протоколом и дженериками:
class OneDimensionalFunction(Protocol, Generic[T]): #double inheritance, although maybe I can get by with a metaclass for Generic
def __call__(self, x: float, *args: T) -> float: ...
... в надежде, что смогу написать
def simple_quad(func: OneDimensionalFunction[T], a: float, b: float, args: tuple[T] = ()) -> float:
simple_quad(cubic, 1, 10, 10, (1,2,3,4)) # infer type requirement of args based on signature of func
# or
simple_quad[float,float,float,float](cubic, ...) #pass in additional type info above and beyond the expected Callable[[x:float,...], float]
... у которого есть много проблем, которые я знаю, также протокол не работает с Callable, если, например, я хочу передать лямбду как func.
Я назвал этот Python 3.10, поскольку думаю, что новые переменные спецификации параметров могут помочь, но я видел только те, которые используются в декораторах, поэтому я не уверен, как их здесь применить. Позвольте мне знать ваши мысли
1 ответ
Используйте перегрузки с протоколами. Это не очень хорошо, но позволяет максимально приблизиться к реальной проверке.
from typing import Protocol, Tuple, Any, overload, Union, Optional
class C1(Protocol):
def __call__(self, a: float, ) -> float: ...
class C2(Protocol):
def __call__(self, a: float, b: float, ) -> float: ...
class C3(Protocol):
def __call__(self, a: float, b: float, c: float) -> float: ...
ET = Tuple[()]
T1 = Tuple[float]
T2 = Tuple[float, float]
T3 = Tuple[float, float, float]
@overload
def quad(func: C1, a: float, b: float, args: Union[ET, T1] = ()) -> float: ...
@overload
def quad(func: C2, a: float, b: float, args: Union[ET, T2] = ()) -> float: ...
@overload
def quad(func: C3, a: float, b: float, args: Union[ET, T3] = ()) -> float: ...
def quad(func: Any, a: float, b: float, args: tuple = ()) -> float:
return 0
def cubic(a: float, b: float, c: float, *, s: Optional[str] = None) -> float:
return 0
quad(cubic, 0, 1)