openlang

`isdigit()` пропустил надстрочную двойку, которую `int()` не принял

Поле количества в небольшой форме проверяет s.isdigit(), а потом вызывает int(s). Кто-то вставил ² и получил ValueError ровно в той строке, которую проверка должна была защищать.

У них разные правила. isdigit() истинно для любого символа с цифровым значением, туда попадают надстрочные цифры и цифры в кружке вроде ⑦. int() нужны десятичные цифры, то есть множество isdecimal(), поэтому ² проходит первое и падает на втором.

for s in ["7", "²", "٣", "3"]:
    print(repr(s), s.isdigit(), s.isdecimal())
print(int("٣"), int("3"))  # 3 3

Если перевести проверку на isdecimal(), падение прекратится. Но тогда арабско-индийская ٣ и полноширинная 3 пройдут как 3, а поле никогда не показывало их как вариант. Так что у падения есть очевидное исправление, а принимать ли эти символы вообще, я пока не уверен. Что вы допускаете в числовом поле?

Разговор продолжается 4

Следуя доводам
  1. Ссылка на ответ

    Забавный угол. int() всё равно, из одной ли письменности цифры. int("1٣") даёт 13, одна цифра ASCII и одна арабско-индийская, а float("١.٥") даёт 1.5. Я ждал, что смешение отвергнут, как это делают некоторые проверки на похожие символы, но там просто складываются цифровые значения по символам.

    Так что с isdecimal() поле примет числа, которые ни одна раскладка не наберёт за раз. Вставьте 1٣ и посмотрите, что форма покажет в ответ.

  2. Ссылка на ответ

    Это меня убедило, только цифры ASCII. Первая попытка была re.match(r"\d+$", s), и в ней нашлись две проблемы, которые я заметил не сразу. \d в шаблоне для str совпадает с любой десятичной цифрой Unicode, так что ٣ всё равно проходила. А $ совпадает и прямо перед завершающим переводом строки, так что прошло и "7\n". Вот что поле использует теперь.

    import re
    
    def parse_quantity(s: str) -> int | None:
        if re.fullmatch(r"[0-9]+", s):
            return int(s)
        return None

    fullmatch с явным классом отвечает на один вопрос, цифры ASCII от первого символа до последнего. Пробелы, знаки и подчёркивания тоже отвергаются, хотя сам int() принял бы " 7 " и "1_000".

  3. Ссылка на ответ

    С fullmatch соглашусь, история с $ и переводом строки ловит людей постоянно. В отказе от полноширинных цифр уверенности меньше. Человек, который печатает через японский метод ввода, оставленный в полноширинном режиме, вводит 3, не замечая этого, и для него ошибка выглядит так, будто форма отвергла число 3. Таблица на десять пунктов решает этот один случай и не открывает дверь всему, что пропускает isdecimal().

    FULLWIDTH = str.maketrans("0123456789", "0123456789")
    parse_quantity(s.translate(FULLWIDTH))

    Надстрочные цифры и другие письменности по-прежнему отвергаются. Заслуживает ли полноширинный ввод исключения, вопрос суждения, и настаивать на нём дальше этого ответа не стану.

Другие стороны того же вопроса

  1. Разумно ли по умолчанию искать имена по ключу NFC?

    Так, пользователь вставляет имя, а в конце у него e и следом U+0301. Потом он набирает то же имя руками, и клавиатура даёт U+…

  2. Ключи поиска NFC и подписи JCS

    Подпись усложняет вопрос kim_dh про ключи поиска NFC, где отправленное имя отделено от ключа поиска NFC. В JSON-нагрузке есть…

    Ответы 4typescript
  3. Обрезал строку по байтам, как в C++, а Rust запаниковал

    Переношу на Rust кусок старого C++, который обрезает std::string до лимита в байтах обычным s.substr(0, n), и думал, что это…

    Ответы 6rustcpp