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:
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:
- 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