This UNTITLED article is a mini-adventure.

This UNTITLED article is a mini-adventure.

This is somewhat a bit of a "weird" title. Hopefully, I will be able to articulate my thoughts appropriately and do this mini-adventure the justice it deserves. Very recently, I luckily got a slot in an advanced python programming class with one Mr David Beazley, whom I was awfully impressed with - not only by the depth of his knowledge but also his humility and his eagerness to continuously learn side-by-side his students by exploring problems that on the outside seemed easy to explain but in reality they were H.A.R.D to code.

This article is not about a hard problem such as coding the logic for an elevator  by designing an application to interface with the real-world and not a just a "simpler" (ahem... but not so simple after all) simulation - which did happen and was tremendous fun but was left unfinished on my part as I found myself inevitably gasping for breath between key-strokes, accompanied with what Mr David Beazley graciously called "head-explosions".

This article is a much simpler use case of the magical and potentially unknown __init_subclass__ (PEP 487) method. Maybe I should have titled this article exactly that, but simply couldn't, because it came bundled with a complex emotion of awe in realising how cool __init__subclass__ method could be in an event driven/messaging context.

これをやろう!

Disclaimer. This is only my interpretation of a partial solution to one of this course's exercises that had practical application to gaming events amongst others. I'm naturally happy to be told off for my rogue python practices (do I remember altering the __defaults__ attribute of a function to circumnavigate a problem? Yes I do.) Mistakes are always good ways to internalise new learnings.

Let's write some code:

class BaseMessage:

    def __init__(self, unique_id: str, game_id: int, timestamp: int, player_id: int):
        self.unique_id = unique_id
        self.game_id = game_id
        self.timestamp = timestamp
        self.player_id = player_id

class GameStart(BaseMessage):
    def __init__(self, a_start_specific_thing: str, **kwargs):
        super().__init__(**kwargs)
        self.a_start_specific_thing = a_start_specific_thing


class SomePlayerAction(BaseMessage):
    def __init__(self, action: str, **kwargs):
        super().__init__(**kwargs)
        self.action = action


class GameEnd(BaseMessage):
    def __init__(self, an_end_specific_thing: str, **kwargs):
        super().__init__(**kwargs)
        self.an_end_specific_thing = an_end_specific_thing

What we have here are 3 gaming classes that inherit from a base class. Imagine now someone gives you the below unfinished function to write and test:

def create_message(message_type: str, **kwargs) -> Dict[str, Any]:
	...
    ...
    ...
The unfinished function
game_start = {
    'unique_id': '5b8bbf2816a042b28bb0d68147fb844c',
    'game_id': 1,
    'timestamp': 1632659941,
    'player_id': 123456789,
    'a_start_specific_thing': 'start the engines'
}

some_player_action = {
    'unique_id': '01dfad5365074fec88d18d71371676cc',
    'game_id': 1,
    'timestamp': 1632661605,
    'player_id': 123456789,
    'action': 'got stuck on floor 3 inside an elevator because of an illogical race condition'
}

game_end = {
    'unique_id': '0532a01194ee4c19b6c2b51dd3544c4e',
    'game_id': 1,
    'timestamp': 1632661619,
    'player_id': 123456789,
    'an_end_specific_thing': 'player head explosion'
}

def test_create_message():
    assert create_message(message_type="GameStart", **game_start) == {"message_type": "GameStart", **game_start}
    assert create_message(message_type="SomePlayerAction", **some_player_action) == {"message_type": "SomePlayerAction", **some_player_action}
    assert create_message(message_type="GameEnd", **game_end) == {"message_type": "GameEnd", **game_end}
The test

Try and complete the create_message function before you continue.

First let, change the base class to the below:

class BaseMessage:
    message_type_registry = {}

    @classmethod
    def __init_subclass__(cls):
    """
    I really like this thing!
    """
        BaseMessage.message_type_registry[cls.__name__] = cls

    def __init__(self, message_type: str, unique_id: str, game_id: int, timestamp: str, player_id: int):
        self.message_type = message_type
        self.unique_id = unique_id
        self.game_id = game_id
        self.timestamp = timestamp
        self.player_id = player_id

There is the funky __init_subclass__ class method that will update the message_type_registry every time a new subclass is created. Let's drop into pdb++ and see it for ourselves.

(Pdb++) pp BaseMessage.message_type_registry
{
	'GameStart': <class '__main__.GameStart'>, 
    'SomePlayerAction': <class '__main__.SomePlayerAction'>, 
    'GameEnd': <class '__main__.GameEnd'>
}

How cool is that? Now the create_message function could potentially look something like:

def create_message(message_type: str, **kwargs) -> Dict[str, Any]:
    message_type_registry = BaseMessage.message_type_registry
    MessageType: Callable = message_type_registry[message_type]
    _message_type = MessageType(message_type=message_type, **kwargs)
    return _message_type.__dict__

Let's do some unpacking:

  1. Define the class registry dictionary:
message_type_registry = BaseMessage.message_type_registry

2. Get the class (otherwise known as the value of the registry dictionary from the  key passed):

MessageType: Callable = message_type_registry[message_type]

3. Pass the keyword arguments to the class (also read more about keyword agruments):

_message_type = MessageType(message_type=message_type, **kwargs)

4. Return the message

return _message_type.__dict__

5. Run the test

test_create_message()

C'est ça! Plutôt cool! N'est-ce pas?

Thank you for reading and thank you very much David for an unforgettable experience.

Antonio

Reading Material