Как проверить общую функцию - PullRequest
1 голос
/ 06 июня 2019

Я создал класс, который реализует обобщенную версию поиска 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

Реализация того, что считается info, move или name, - это всевплоть до человека, который использует класс.

Однако, как только для них будут приняты определения, они должны оставаться последовательными.Например, 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

Однако я не уверен, как класс, который использует это, сможет установитьэти переменные для проверки типов, и я также не уверен, что произойдет, если несколько классов используют этот класс с разными определениями для Info, Move и Name.

Кроме того, я получаюошибки о наличии Any повсюду.

1 Ответ

2 голосов
/ 06 июня 2019

Использование TypeVars и 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]'

TypeVars по существу действуют как заполнители для предполагаемых типов и параметризуют ваш тип NodeSolver.

...