python - 如何对通用函数进行类型检查

标签 python python-3.x typechecking mypy

我创建了一个实现 BFS 搜索的通用版本的类:

from collections import deque


class NodeSolver:
    '''Class for modeling problems that can be modeled as a directed graph with goal nodes'''
    def __init__(self, namer, detector, expander, follower):
        '''
        Whatever decides to use this gets to define what structure info is.
        namer: takes info representing a node and returns a hashable object (the name);
               names must be equal if and only if the nodes are equal
        detector: takes info and returns True if the node represented by the info is the goal node,
                  ...False otherwise.
        expander: takes info and returns an iterable of moves that can be made from the node
                  ...that the info represents. (a.k.a. paths leading out of that node)
        follower: takes info and a move that can be made from the node that the info represents,
                  ...and returns the info that represents the node that the move leads to
        '''
        self.get_name = namer
        self.is_goal = detector
        self.get_moves = expander
        self.follow_move = follower


class BFSSolver(NodeSolver):
    '''Class for solving node problems with breadth-first-search to reach the goal node'''
    def solve(self, start_info):
        '''
        Returns the list of moves needed to reach the goal node
        ...from the node represented by the parameter.
        Uses Breadth-first Search to go through the node tree
        '''
        if self.is_goal(start_info):
            return []

        start_name = self.get_name(start_info)

        # data is in the form (info, path)
        name_to_data = {start_name: (start_info, [])}
        queue = deque()

        queue.appendleft(start_name)

        while queue:
            current_name = queue.pop()
            current_info, current_path = name_to_data[current_name]

            expanded_moves = self.get_moves(current_info)
            # print("possible moves from {} is {}".format(current_info, expanded_moves))
            for move in expanded_moves:
                child_info = self.follow_move(current_info, move)
                child_path = current_path[:]
                child_path.append(move)

                if self.is_goal(child_info):
                    return child_path

                child_name = self.get_name(child_info)
                if child_name not in name_to_data:
                    # new, needs to be expanded
                    name_to_data[child_name] = (child_info, child_path)
                    queue.appendleft(child_name)
        return None

infomovename 的实现完全取决于使用该类的人.

但是,一旦确定了这些定义,它们就应该保持一致。例如,info 可以是用户创建的Info 对象类型。 move 可能只是一个整数。 name 可能只是一个字符串。

选择这些定义后,我希望类型检查器能够确保用户坚持这些定义。

如何使用类型检查来完成这样的事情?

目前,我已经完成了:

from collections import deque
from typing import Callable, Any, Iterable, List, Deque, Tuple, Dict, Optional, Hashable

# pylint: disable=invalid-name
# Type definitions

# Can be literally anything
Info = Any
Name = Hashable
# Can be literally anything
Move = Any
# pylint: enable=invalid-name

class NodeSolver:
    '''Class for modeling problems that can be modeled as a directed graph with goal nodes'''
    def __init__(self, namer: Callable[[Info], Name],
                 detector: Callable[[Info], bool],
                 expander: Callable[[Info], Iterable[Move]],
                 follower: Callable[[Info, Move], Info]):
        '''
        Whatever decides to use this gets to define what structure info is.
        namer: takes info representing a node and returns a hashable object (the name);
               names must be equal if and only if the nodes are equal
        detector: takes info and returns True if the node represented by the info is the goal node,
                  ...False otherwise.
        expander: takes info and returns an iterable of moves that can be made from the node
                  ...that the info represents. (a.k.a. paths leading out of that node)
        follower: takes info and a move that can be made from the node that the info represents,
                  ...and returns the info that represents the node that the move leads to
        '''
        self.get_name = namer
        self.is_goal = detector
        self.get_moves = expander
        self.follow_move = follower


class BFSSolver(NodeSolver):
    '''Class for solving node problems with breadth-first-search to reach the goal node'''
    def solve(self, start_info: Info) -> Optional[List[Move]]:
        '''
        Returns the list of moves needed to reach the goal node
        ...from the node represented by the parameter.
        Uses Breadth-first Search to go through the node tree
        '''
        if self.is_goal(start_info):
            return []

        start_name = self.get_name(start_info)

        # data is in the form (info, path)
        name_to_data: Dict[Name, Tuple[Info, List[Move]]] = {start_name: (start_info, [])}
        queue: Deque[Name] = deque()

        queue.appendleft(start_name)

        while queue:
            current_name = queue.pop()
            current_info, current_path = name_to_data[current_name]

            expanded_moves = self.get_moves(current_info)
            # print("possible moves from {} is {}".format(current_info, expanded_moves))
            for move in expanded_moves:
                child_info = self.follow_move(current_info, move)
                child_path = current_path[:]
                child_path.append(move)

                if self.is_goal(child_info):
                    return child_path

                child_name = self.get_name(child_info)
                if child_name not in name_to_data:
                    # new, needs to be expanded
                    name_to_data[child_name] = (child_info, child_path)
                    queue.appendleft(child_name)
        return None

但是,我不确定使用它的类如何能够设置这些变量以进行类型检查,并且我也不确定如果多个类使用具有不同 定义的此类会发生什么信息移动名称

此外,我还收到有关 Any 到处都是的错误。

最佳答案

使用TypeVars and generics :

from collections import deque
from typing import Callable, Any, Iterable, List, Deque, Tuple, Dict, Optional, Hashable, TypeVar, Generic


TInfo = TypeVar('TInfo')
TName = TypeVar('TName', bound=Hashable)
TMove = TypeVar('TMove')

class NodeSolver(Generic[TInfo, TName, TMove]):
    '''Class for modeling problems that can be modeled as a directed graph with goal nodes'''
    def __init__(self,
                 namer: Callable[[TInfo], TName],
                 detector: Callable[[TInfo], bool],
                 expander: Callable[[TInfo], Iterable[TMove]],
                 follower: Callable[[TInfo, TMove], TInfo]):
        self.get_name = namer
        self.is_goal = detector
        self.get_moves = expander
        self.follow_move = follower

def namer1(info: int) -> str: ...
def detector1(info: int) -> bool: ...
def expander1(info: int) -> Iterable[bool]: ...
def follower1(info: int, move: bool) -> int: ...

def namer2(info: str) -> int: ...
def detector2(info: str) -> bool: ...
def expander2(info: str) -> Iterable[float]: ...
def follower2(info: str, move: float) -> str: ...

solver1 = NodeSolver(namer1, detector1, expander1, follower1)
solver2 = NodeSolver(namer2, detector2, expander2, follower2)

reveal_type(solver1)  # Revealed type is 'test.NodeSolver[builtins.int*, builtins.str*, builtins.bool*]'
reveal_type(solver2)  # Revealed type is 'test.NodeSolver[builtins.str*, builtins.int*, builtins.float*]'

gibberish = NodeSolver(namer1, detector2, expander1, follower2)  # Cannot infer type argument 1 of "NodeSolver"
reveal_type(gibberish)                                           # Revealed type is 'test.NodeSolver[Any, Any, Any]'

TypeVar 本质上充当推断类型的占位符,并将参数化您的 NodeSolver 类型。

关于python - 如何对通用函数进行类型检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56481840/

相关文章:

Python将一个类的属性继承给另一个类

python - 使用 pydub 进行多次淡入淡出效果后质量很差

python - 每个答案都有多个分数类别的 Django 调查问卷

javascript - python3 opencv 不保存可读视频

python - 调用变量时运行进程

python - 在不导入类型 Foo 的情况下确定对象是否为 Foo 类型

python - 在 tensorflow 中使用 gabor 过滤器,或任何其他过滤器而不是默认过滤器

python - 了解python的主要方法

programming-languages - erlang 中的静态类型检查

javascript - 使用 Typescript 声明了一个类型,但 Javascript 在内部使用了错误的类型