在Python中为模块(而不是类)中的函数实现类型?

问题描述 投票:0回答:2
def f(x, y):
    return x & 1 == 0 and y > 0

g = lambda x, y: x & 1 == 0 and y > 0

现在 Haskell 中也有同样的事情:

import Data.Bits

f :: Int -> Int -> Bool
f x y = (.&.) x 1 == 0 && y > 0

这可行,但这不行:

g = \x y -> (.&.) x 1 == 0 && y > 0

这是给出的错误:

someFunc :: IO ()
someFunc = putStrLn $ "f 5 7: " ++ ( show $ f 5 7 ) ++ "\tg 5 7: " ++ ( show $ g 5 7 )

• Ambiguous type variable ‘a0’ arising from the literal ‘1’
  prevents the constraint ‘(Num a0)’ from being solved.
  Relevant bindings include
    x :: a0 (bound at src/Lib.hs:13:6)
    g :: a0 -> Integer -> Bool (bound at src/Lib.hs:13:1)
  Probable fix: use a type annotation to specify what ‘a0’ should be.
  These potential instances exist:
    instance Num Integer -- Defined in ‘GHC.Num’
    instance Num Double -- Defined in ‘GHC.Float’
    instance Num Float -- Defined in ‘GHC.Float’
    ...plus two others
    ...plus one instance involving out-of-scope types
    (use -fprint-potential-instances to see them all)
• In the second argument of ‘(.&.)’, namely ‘1’
  In the first argument of ‘(==)’, namely ‘(.&.) x 1’
  In the first argument of ‘(&&)’, namely ‘(.&.) x 1 == 0’
   |
13 | g = \x y -> (.&.) x 1 == 0 && y > 0
   |                     ^

如何在 Python 中得到同样的错误? - 当输入与预期不符时如何得到错误?

具体来说,我怎么说函数/lambda 必须具有:

  • 数量为 5
  • 每个参数必须是数字
  • 每个参数必须实现
    __matmul__
    (
    @
    )
  • 返回
    bool

我知道我可以粗略地通过以下方式做到这一点:(文档字符串和/或 PEP484)与

abc
;用于课程。但是对于模块中的“松散”功能我该怎么办?

python typeclass python-module python-typing abc
2个回答
1
投票

通常您有三种可能的方法:

  1. 让运行时产生不兼容操作的错误(例如
    1 + 'foo'
    TypeError
    )。
  2. 对某些属性进行显式运行时检查;尽管这通常是不鼓励的,因为 Python 大量使用鸭子类型。
  3. 使用类型注释和静态类型检查器。

您的具体观点:

  • 数量为 5

定义五个参数:

def f(a, b, c, d, e): ...
  • 每个参数必须是数字

静态类型注释:

def f(a: int, b: int, c: int, d: int, e: int): ...

和/或运行时检查:

def f(a, b, c, d, e):
    assert all(isinstance(i, int) for i in (a, b, c, d, e))

def f(a, b, c, d, e):
    if not all(isinstance(i, int) for i in (a, b, c, d, e)):
        raise TypeError

assert
用于调试目的并且可以禁用,显式
if..raise
则不能。考虑到这一点的冗长和鸭子打字哲学,这些方法并不是很Pythonic。

  • 每个参数必须实现
    __matmul__
    (
    @
    )

最实用的方法可能是,如果传递的值不支持该操作,则让运行时在本质上引发错误,即只需执行以下操作:

def f(a, b):
    return a @ b  # TypeError: unsupported operand type(s) for @: ... and ...

如果您想要对此进行静态类型检查,您可以使用

typing.Protocol
:

from typing import Protocol

class MatMullable(Protocol):
    def __matmul__(self, other) -> int:
        pass

def f(a: MatMullable, ...): ...

在实践中,您可能希望将其与之前的“每个参数必须是数字”和满足这两个要求的类型的类型提示结合起来。

  • 返回
    bool
def f(...) -> bool: ...

特别是考虑到

@
运算符主要由 numpy 等第三方软件包使用,实际上,此类函数的最 Pythonic 实现可能是这样的:

import numpy as np
from numpy import ndarray

def f(a: ndarray, b: ndarray, c: ndarray, d: ndarray, e: ndarray) -> bool:
    return np.linalg.det(a @ b @ c @ d @ e) > 0  # or whatever

您无法将相同的输入期望直接从 Haskell 转换为 Python。 Haskell 是一种极其强类型的语言,而 Python 几乎完全相反。


要输入提示接受此类函数作为参数的高阶函数,请使用

typing.Callable
:

from typing import Callable

def hof(f: Callable[[ndarray, ndarray, ndarray, ndarray, ndarray], bool]): ...

0
投票

我发现的一个技巧是使用回调协议

from typing import Optional, Iterable
from sys import version_info

if version_info > (3, 8):
    from typing import List, Literal, Optional, Protocol, Tuple, Union
else:
    from typing_extensions import Protocol


class Combiner(Protocol):
    def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: ...

适用于我的问题,只需复制:

def combiner0(*vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: return []
def combiner1(*vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: return []

# Unused var just for type checking
_combiner: Combiner = combiner0
_combiner: Combiner = combiner1
del _combiner
© www.soinside.com 2019 - 2024. All rights reserved.