1

I wanted to implement a method insertCard in Python which interacts with only a specific type of object called a Card.

The Card should always have a cardId and may have additional payload or fields that the user can specify.

In Java, I would do:

class Card {
    int cardId;
}

class PlayingCard extends Card {
    int cardId;
    int cardValue;
    String cardType;
}

class CardSystem {
    void insert(Card card);
}

How do I do this in Python? I want to ensure that insertCard always deals with a Card like object which has or can provide a cardId.

1
  • 2
    What do you understand as the difference between statically typed and dynamically typed languages? Commented May 18, 2021 at 20:08

1 Answer 1

2

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.

3
  • Great answer. Would your answer change if we wanted to impose a getCardId() method instead of CardId field as a way to identify a card. In other words, what else can we do so that we ensure a card has/can provide an id?
    – sbhatla
    Commented May 18, 2021 at 22:43
  • Also, I came across "type hints". Would that be another way to handle this? docs.python.org/3/library/typing.html
    – sbhatla
    Commented May 18, 2021 at 22:46
  • @sbhatla Sure, these solutions are all virtually unchanged if it was a method instead of a property. However, the runtime assert would have to use a condition like callable(getattr(card, 'get_card_id', None)). Of course, Python doesn't encourage getters and instead prefers properties: getter-like methods that act as ordinary fields. Type hints and type annotations are the same thing.
    – amon
    Commented May 19, 2021 at 5:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.