Tyler Brocious was the presenter covering Chapter 5: Classes and Interfaces, and he had the brilliant idea of taking the concepts and tying them in to character classes from D&D, with the exercise being to code up character classes in such a way that we could have classes that were a modification of another class, or a combination of other classes, or a class with a special modification for stealth checks. This led to some entertaining conversations that kept people more engaged with the topic as we talked through classes, inheritance, mixins, and how these things work in Python. It's also worth noting that there was no need for us to stick strictly to how D&D actually works, and we varied away from the standard in order to make or tie in important concepts and points. Here's the code that resulted from the exercise:
import random import math class MixinStealthCheck: def stealth_check(self): roll = random.randint(1, 20) if not self.dexterity: modifier = 0 else: modifier = math.floor(self.dexterity % 10 / 2) if self.dexterity < 10: modifier = modifier * -1 print(f'{self.name} rolled {roll} with modifier {modifier}') return roll class Character: def __init__(self, name, strength, dexterity, intelligence, charisma): self.name = name self.strength = strength self.dexterity = dexterity self.intelligence = intelligence self.charisma = charisma class Ranger(Character, MixinStealthCheck): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity+1, intelligence, charisma) class Warlock(Character, MixinStealthCheck): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma+1) class Beastmaster(Ranger): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma+1) class Witcher(Warlock, Ranger): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma) heroes = [Beastmaster('Bally', strength=12, dexterity=17, intelligence=12, charisma=8), Warlock('Tiresias', strength=7, dexterity=6, intelligence=18, charisma=18), Witcher('Barron', strength=10, dexterity=10, intelligence=10, charisma=10)] print() for hero in heroes: print(hero.__dict__) hero.stealth_check() print(Witcher.mro())Then as an interesting follow up, the presenter for the following week was Rhys Childs covering Chapter 6: Metaclasses and Attributes, and he decided to play off of the previous week's presentation by Tyler, and utilized the same D&D character classes to explore the property and setter annotations, and take a look at one way in which to do descriptors. We looked at the problem such that we started with the previous week's code, and we need to modify it such that a given stat should have a min value and a max value, first exploring doing so for a single stat using annotations, and then exploring a solution that could be better applied to all stats by using a descriptor. Here's the code that resulted from the exercise:
class Stat: def __init__(self): self.name = None self.internal_name = None def __set_name__(self, owner, name): self.name = name self.internal_name = '_' + name def __get__(self, instance, instance_type): if instance is None: return self return getattr(instance, self.internal_name, '') def __set__(self, instance, value): if 6 <= value <= 20: setattr(instance, self.internal_name, value) elif value <= 6: setattr(instance, self.internal_name, 6) else: setattr(instance, self.internal_name, 20) class Character: strength = Stat() dexterity = Stat() intelligence = Stat() charisma = Stat() def __init__(self, name, strength, dexterity, intelligence, charisma): self.name = name self.strength = strength self.dexterity = dexterity self.intelligence = intelligence self.charisma = charisma # @property # def intelligence(self): # return self._intelligence # # @intelligence.setter # def intelligence(self, value): # if 6 <= value <= 20: # self._intelligence = value # elif value <= 6: # self._intelligence = 6 # else: # self._intelligence = 20 class Ranger(Character): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity+1, intelligence, charisma) class Warlock(Character): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma+1) class Beastmaster(Ranger): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma+1) class Witcher(Warlock, Ranger): def __init__(self, name, strength, dexterity, intelligence, charisma): super().__init__(name, strength, dexterity, intelligence, charisma) heroes = [Beastmaster('Bally', strength=12, dexterity=17, intelligence=12, charisma=8), Warlock('Tiresias', strength=7, dexterity=6, intelligence=18, charisma=18), Witcher('Barron', strength=10, dexterity=10, intelligence=10, charisma=10)] print() for hero in heroes: print(f'Before: {hero.__dict__}') hero.intelligence += 3 print(f'After: {hero.__dict__}')Overall, it seemed that these presentations worked quite well, in that they were able to take concepts that were perhaps a little complicated from the reading, and brought them across in a way that was both entertaining and relatable, which helped to solidify an understanding of those principles for a number of people in the group.