Python suggest to use duck typing, and “asking for forgiveness instead of permission”. This means that your insertCard()
method would accept any object as long as it behaves like a Card. Here, that would mean that the object should have a cardId
property. Asking for “forgiveness” would mean to just assume that this property exists – the caller probably knows what they are doing. If this is an expected problem, you can use a try–except.
Personally, I don't think that this traditional Python advice is that sensible. It is good to not be overly strict with your types, but types are really helpful for building correct and maintainable software.
First, we can consider how to check our duck-typing properties in order to fail early. For example, we can use the hasattr()
function:
def insert(card):
assert hasattr(card, 'card_id')
...
This will helpfully raise an exception if the card doesn't have a property with that name. Of course, additional properties can be verified as well, such as ensuring that this property contains an int.
Next, we could create Java-like inheritance hierarchies. I actually quite like this approach, at least for internal code. We can then verify that the object has a particular type:
class Card:
...
def insert(card):
assert isinstance(card, Card)
...
To create classes that are more like Java interfaces, you can create abstract classes with abc.ABC
(abstract base class).
Finally, you can consider type annotations. Python does not actually enforce that annotations describe correct types. Instead, you will have to use an external type checker such as MyPy. With type annotations, we can write:
def insert(card: Card) -> None:
assert isinstance(card, Card)
...
Note that an assert
statement is still useful because type annotations have no runtime effect – they are completely ignored. So you could call a function with a mismatched type and Python wouldn't know.
In addition to using built-in types and user-defined classes, type annotations can also use various constructs from the typing
module. This includes protocols, which can be used to describe duck-typing in a static manner (like interfaces in TypeScript). This code should be the static equivalent to the hasattr()
check at the beginning:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Card(Protocol):
card_id: int
def insert(card: Card) -> None:
assert isinstance(card, Card)
...
Note that this isinstance() check does not verify any inheritance or is-a relationship, but just verifies that the given object has a card_id
property. Currently, it cannot verify that this property contains an int, though.