-1

So Python's core language and built-ins use a lot of duck typing. But for our own code, say I want to create my own class with a method that deals with "certain types" of objects, is it more idiomatic to use duck typing:

class MerkleTree:

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

or is it more idiomatic to just use a normal type check:

class MerkleTree:

    def method(self, document):
        if isinstance(document, SHA256Hashable):
            raise TypeError()
        hash_ = document.sha256_hash()
        do_smth_with_hash(hash_)
martineau
  • 119,623
  • 25
  • 170
  • 301
busukxuan
  • 153
  • 1
  • 7
  • 3
    Use ducktyping, otherwise most of your code will only consist of type checks – BlackBear Feb 19 '20 at 16:01
  • @BlackBear Is a code mostly consisting of `try ... except AttributeError` better than a code mostly consisting of type checks? – DeepSpace Feb 19 '20 at 16:05
  • @DeepSpace Is there is a better way to do duck type checks with if's? I'm feeling this example code is going to catch `AttributeError`s unrelated to the `sha256_hash` attribute itself, which would be a bug. – busukxuan Feb 19 '20 at 16:07
  • 4
    Neither of those is duck typing, really. Duck typing means you use the object _assuming_ that its type is correct (where "correct" means "has the attributes that this particular function needs"). If it does not have the same type, an error will be raised, but then you probably made a programming error at some other point. You can use [type hints](https://stackoverflow.com/q/32557920/1782792) if you want, but the key point is that each function/class assumes it will be used correctly (of course it is not "safe", but it cannot be in a dynamic language). – jdehesa Feb 19 '20 at 16:11
  • 1
    @DeepSpace no need for try except – BlackBear Feb 19 '20 at 16:12
  • 2
    To summarize what (I think) @jdehesa is getting at (if I may), neither way is "idiomatic" — just use the object and allow exceptions to be raised when it's passed the wrong type of argument. The caller can deal with them if necessary. – martineau Feb 19 '20 at 16:28
  • "Ask forgiveness rather than permission." Not every class will explicitly implement the interface you want, though it might have the method you're looking for anyway. – Green Cloak Guy Feb 19 '20 at 16:34

2 Answers2

3

From a pure typing perspective, MerkleTree.method will accept an argument of any type; Python has no way of restricting it.

From a duck-typing perspective, you promise that

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

will work as long as document.sha256_hash is a callable whose return value is suitable for use by do_smth_with_hash, but method doesn't do any enforcement. Your documentation states a precondition for calling method, but it is up to the caller to meet that precondition and any consequences for violating it are the caller's problem, not method's.

You can provide a type hint (using the Protocol class) that more formally documents this precondition in a way that static type-checking tools like mypy can verify.

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_)

Normally, you don't go so far as to catch one exception simply to raise one of a different type. The user knows from reading the documentation that method could raise an AttributeError if document doesn't have the appropriate method, and mypy can help catch that error without having to run the code.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • @busukxuan: See this [answer](https://stackoverflow.com/a/3931746/355230) of mine to a question on a related topic for additional information. – martineau Feb 19 '20 at 16:54
  • So the gist is that in the philosophy of duck typing, if the user of the class passes in a wrong type, then sees a weird exception pop up from say 6 or 7 layers down the call stack, it's not really the class author's responsibility to tell them they got the wrong type, is that right? – busukxuan Feb 19 '20 at 18:43
  • @busukxuan: Yep. – martineau Feb 20 '20 at 18:39
0

You're better off with duck typing for small or even medium projects, and PEP484+mypy for large projects.

EG:

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

And:

def pad(number: int) -> str:

BTW, if you already have a large project without PEP484 type annotations, you can use something like MonkeyType to add them.

dstromberg
  • 6,954
  • 1
  • 26
  • 27