Мини-библиотека Брайля на Python

Для моего небольшого проекта мне придется много работать с переводом шрифта Брайля. Для этого я уже закончил небольшую программу, которую могу использовать для перевода шрифта Брайля на английские буквы. Чтобы сделать все это немного более модульным, я экспортировал всю свою логику Брайля в небольшой модуль:

from typing import NoReturn, List
from bitmap import BitMap
from enum import Enum
from bidict import bidict


class Dot(Enum):
    TOP_LEFT = 0
    MIDDLE_LEFT = 1
    BOTTOM_LEFT = 2
    TOP_RIGHT = 3
    MIDDLE_RIGHT = 4
    BOTTOM_RIGHT = 5


class BrailleGlyph(BitMap):

    UNICODE_BLOCK = 10240
    MAPPING = bidict(
        {"00000000": " ",
         "00000001": "A",
         "00000011": "B",
         "00001001": "C",
         "00011001": "D",
         "00010001": "E",
         "00001011": "F",
         "00011011": "G",
         "00010011": "H",
         "00001010": "I",
         "00011010": "J",
         "00000101": "K",
         "00000111": "L",
         "00001101": "M",
         "00011101": "N",
         "00010101": "O",
         "00001111": "P",
         "00011111": "Q",
         "00010111": "R",
         "00001110": "S",
         "00011110": "T",
         "00100101": "U",
         "00100111": "V",
         "00111010": "W",
         "00101101": "X",
         "00111101": "Y",
         "00110101": "Z", }
    )

    def __init__(self, maxnum: int = 8) -> NoReturn:
        super().__init__(maxnum)
        self.history: List[Dot] = []

    def __str__(self) -> str:
        ones = int(self.tostring()[4:], 2)
        tens = int(self.tostring()[:4], 2) * 16
        return chr(BrailleGlyph.UNICODE_BLOCK + tens + ones)

    @classmethod
    def from_char(cls, char: str) -> "BrailleGlyph":
        return BrailleGlyph.fromstring(cls.MAPPING.inverse[char.upper()])

    def is_empty(self) -> bool:
        return self.history == []

    def click(self, index: Dot) -> NoReturn:
        if not self.test(index.value):
            self.set(index.value)
            self.history.append(index)

    def delete(self) -> NoReturn:
        self.flip(self.history.pop().value)

    def get_dots(self) -> List[Dot]:
        return self.history

    def to_ascii(self) -> str:
        try:
            return BrailleGlyph.MAPPING[self.tostring()]

        except KeyError:
            return "?"


class BrailleTranslator:

    def __init__(self) -> NoReturn:
        self.glyphs = [BrailleGlyph()]

    def __str__(self) -> str:
        return "".join(map(str, self.glyphs))

    def new_glyph(self) -> NoReturn:
        self.glyphs.append(BrailleGlyph())

    def delete(self) -> NoReturn:
        if self.glyphs[-1].is_empty():
            if len(self.glyphs) != 1:
                self.glyphs.pop()

        else:
            self.glyphs[-1].delete()

    def click(self, index: Dot) -> NoReturn:
        self.glyphs[-1].click(index)

    def get_current_glyph(self) -> BrailleGlyph:
        return self.glyphs[-1]

    def translate(self) -> str:
        return "".join(map(lambda x: x.to_ascii(), self.glyphs))

Основным вариантом использования будет — я нажимаю точки (или некоторые кнопки на моей клавиатуре, которые будут устанавливать точки на глифе), и модуль перевода просто запомнит, что было нажато, и отобразит результаты в режиме реального времени.

Чтобы не изобретать слишком много, я использовал внешний bitmap а также bidict библиотеки, которые кажутся очень подходящими для такой задачи.

Есть ли способ сделать это более питоническим? А может быть, какие-то очевидные недочеты?

Вот также пример использования:

def braille_translator(self) -> NoReturn:
    graph = self.window["-GRAPH-"]
    output = self.window["-BRAILLE_OUTPUT-"]

    graphed_dots = []
    translator = BrailleTranslator()
    circle_mapping = {Dot.BOTTOM_LEFT: (50, 250),
                      Dot.BOTTOM_RIGHT: (150, 250),
                      Dot.MIDDLE_LEFT: (50, 150),
                      Dot.MIDDLE_RIGHT: (150, 150),
                      Dot.TOP_LEFT: (50, 50),
                      Dot.TOP_RIGHT: (150, 50), }
    dot_mapping = {"1": Dot.BOTTOM_LEFT,
                   "2": Dot.BOTTOM_RIGHT,
                   "4": Dot.MIDDLE_LEFT,
                   "5": Dot.MIDDLE_RIGHT,
                   "7": Dot.TOP_LEFT,
                   "8": Dot.TOP_RIGHT, }

    while True:
        event, values = self.window.read()
        if event in (None, 'Exit') or "EXIT" in event:
            exit()

        elif event in "124578":  # Left side of numpad!
            translator.click(dot_mapping[event])

        elif event == " ":
            translator.new_glyph()

        elif "BackSpace" in event:
            translator.delete()

        current_dots = translator.get_current_glyph().get_dots()
        for circle in [d for d in graphed_dots if d not in current_dots]:
            graph.delete_figure(circle)
            graphed_dots.remove(circle)

        for dot in [d for d in current_dots if d not in graphed_dots]:
            circle = graph.draw_circle(circle_mapping[dot],
                                       20, fill_color=GUI.THEME_COLOR)
            graphed_dots.append(circle)
        output.update(translator.translate())

А вот GIF из примера использования в действии:
Перевод Брайля

Это тонкая грань между чрезмерной подгонкой модуля перевода для моей текущей задачи и сохранением его модульности для будущего использования!

1 ответ
1

Для начала несколько мелочей:

Если вы ожидаете, что это будет использоваться в других программах, это должно быть задокументировано: комментарии для объяснения вариантов и деталей реализации и строки документации, чтобы объяснить, как использовать классы и функции в модуле.

Небольшое предложение: при моделировании часто бывает полезно, если интерфейс использует ту же нумерацию или номенклатуру, что и в реальном мире. Например, верхняя левая точка часто обозначается точкой 1, а не точкой 0. Таким образом, class Dot(enum) может использовать значения 1-6, а не 0-5. Но, возможно, имена — это интерфейс, а значения — внутренние.

Умножение на 16 аналогично сдвигу на 4 бита. То есть это:

def __str__(self) -> str:
    offset = int(self.tostring(), 2)
    return chr(BrailleGlyph.UNICODE_BLOCK + offset)

Такой же как

def __str__(self) -> str:
    ones = int(self.tostring()[4:], 2)
    tens = int(self.tostring()[:4], 2) * 16
    return chr(BrailleGlyph.UNICODE_BLOCK + tens + ones)

Использовать dict.get(key, default) со значением по умолчанию вместо try... except блокировать:

def to_ascii(self) -> str:
    return BrailleGlyph.MAPPING.get(self.tostring(), "?")

В BitMap библиотека избыточна и, кажется, мешает. Например, код преобразует биты в строку, а затем преобразует строку в int в базе 2. Но биты уже были сохранены в виде байта в битовой карте. Я подозреваю, что было бы проще реализовать битовые операции напрямую.

Кажется странным, что BrailleGlyph объект должен хранить список точек в том порядке, в котором они были добавлены (history). Это как буква «А», знающая, в каком порядке были добавлены строки. .history есть ли там, чтобы редактор мог реализовать функцию «отмены», но, похоже, это работа редактора, а не BrailleGlyph класс.

  • ты прав, history определенно не должно быть частью этого объекта! Что касается библиотеки BitMap, я определенно рассматривал возможность реализации этой части самостоятельно, но предполагал, что определенно существует некоторая обширная библиотека BitMap, которую я мог бы использовать. Оглядываясь назад, можно сказать, что библиотека далеко не обширна и даже не предоставляет инструментов, которые мне нужны больше всего.

    — Мантас Кандратавичюс

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *