Пользовательские классы с утиным вводом

Таким образом, основной язык Python и встроенные модули используют много утиной печати. Но для нашего собственного кода, скажем, я хочу создать свой собственный класс с методом, который имеет дело с «определенными типами» объектов, более идиоматично использовать утиную печать:

class MerkleTree:

    def method(self, document):
        try:
            hash_ = document.sha256_hash()
        except AttributeError:
            raise TypeError()
        do_smth_with_hash(hash_)

или более идиоматично просто использовать обычную проверку типа:

class MerkleTree:

    def method(self, document):
        if isinstance(document, SHA256Hashable):
            raise TypeError()
        hash_ = document.sha256_hash()
        do_smth_with_hash(hash_)

person busukxuan    schedule 19.02.2020    source источник
comment
Используйте утиную типизацию, иначе большая часть вашего кода будет состоять только из проверок типов.   -  person BlackBear    schedule 19.02.2020
comment
@BlackBear Является ли код, в основном состоящий из try ... except AttributeError, лучше, чем код, состоящий в основном из проверок типов?   -  person DeepSpace    schedule 19.02.2020
comment
@DeepSpace Есть ли лучший способ проверить тип утки с помощью if? Я чувствую, что этот пример кода будет ловить AttributeErrors, не связанные с самим атрибутом sha256_hash, что было бы ошибкой.   -  person busukxuan    schedule 19.02.2020
comment
На самом деле ни один из них не является утиной печатью. Утиная типизация означает, что вы используете объект, предполагая, что его тип правильный (где правильный означает наличие атрибутов, необходимых для этой конкретной функции). Если он не имеет того же типа, будет выдана ошибка, но тогда вы, вероятно, допустили программную ошибку в какой-то другой момент. Вы можете использовать подсказки типов, если хотите, но ключевым моментом является то, что каждая функция/класс предполагает, что она будет использоваться правильно ( конечно небезопасно, но на динамическом языке такого быть не может).   -  person jdehesa    schedule 19.02.2020
comment
@DeepSpace не нужно пытаться, кроме как   -  person BlackBear    schedule 19.02.2020
comment
Подводя итог тому, что (я думаю) @jdehesa получает (если можно), ни один из способов не идиоматичен - просто используйте объект и разрешите вызывать исключения, когда ему передается неправильный тип аргумента. Вызывающий может иметь дело с ними в случае необходимости.   -  person martineau    schedule 19.02.2020
comment
Просите прощения, а не разрешения. Не каждый класс будет явно реализовывать нужный вам интерфейс, хотя в любом случае он может иметь метод, который вы ищете.   -  person Green Cloak Guy    schedule 19.02.2020


Ответы (2)


С чисто типизированной точки зрения MerkleTree.method будет принимать аргументы любого типа; Python не имеет возможности ограничить это.

С точки зрения утиного набора текста вы обещаете, что

def method(self, document):
    hash_ = document.sha256_hash()
    do_smth_with_hash(hash_)

будет работать до тех пор, пока document.sha256_hash является вызываемым, возвращаемое значение которого подходит для использования do_smth_with_hash, но method не выполняет никаких принудительных действий. В вашей документации указано предварительное условие для вызова method, но выполнение этого предварительного условия зависит от вызывающего абонента, и любые последствия его нарушения являются проблемой вызывающего, а не method.

Вы можете предоставить подсказку типа (используя класс Protocol), которая более формально документирует это предварительное условие таким образом, чтобы его могли проверить статические инструменты проверки типов, такие как mypy.

class ShaHashable(typing.Protocol):
    def sha256_hash(self):
        pass


class MerkleTree:

    def method(self, document: ShaHashable):
        hash_ = document.sha256_hash()
        do_smth_with_hash(hash_)

Обычно вы не заходите так далеко, чтобы просто перехватить одно исключение, чтобы вызвать исключение другого типа. Из документации пользователь знает, что method может вызвать AttributeError, если document не имеет соответствующего метода, а mypy может помочь отловить эту ошибку без запуска кода.

person chepner    schedule 19.02.2020
comment
@busukxuan: см. этот мой ответ на вопрос по связанной теме для получения дополнительной информации. - person martineau; 19.02.2020
comment
Итак, суть в том, что в соответствии с философией утиной печати, если пользователь класса передает неправильный тип, а затем видит всплывающее странное исключение, скажем, на 6 или 7 уровнях вниз по стеку вызовов, на самом деле автор класса не несет ответственности за это. скажи им, что они взяли не тот тип, правильно? - person busukxuan; 19.02.2020
comment
@busukxuan: Ага. - person martineau; 20.02.2020

Вам лучше использовать утиную печать для небольших или даже средних проектов и PEP484+mypy для крупных проектов.

EG:

python3 -m mypy --disallow-untyped-calls ${files}

И:

def pad(number: int) -> str:

Кстати, если у вас уже есть большой проект без аннотаций типа PEP484, вы можете использовать что-то вроде MonkeyType, чтобы добавить их.

person dstromberg    schedule 19.02.2020