Each function should accept an existing instance of Dog and modify it, then return the modified instance. That way, you can simply compose the functions. (This is sort of a functional equivalent of the Builder pattern, though the example below is somewhat awkward and clunky.)
class Dog:
def __init__(self, **very_many_kwargs):
pass
def create_police_dog(dog, department):
dog.race = 'pitbull_terrier'
police_academy = PoliceAcademy()
police_academy.train(dog)
return dog
def create_scary_dog(dog):
dog.teeth_size = 'MAX'
dog.eye_color = fetch_angry_eye_colors('https://dogs.com')
dog.experience_unhappy_childhood()
return dog
scary_police_dog = create_scary_dog(
create_police_dog(
Dog(),
'vice'
)
)
Here is, I think, a more typical implementation of the Builder pattern.
class Dog:
...
class DogBuilder:
def __init__(self):
self.kwargs = {}
self._train = False
self._experience_unhappy_childhood = False
def build(self):
d = Dog(**self.kwargs)
if self._train:
PoliceAcademy().train(d)
if self._experience_unhappy_childhood:
d.experience_unhappy_childhood()
return d
def train(self):
self._train = True
def set_race(self, r):
self.kwargs['race'] = r
def experience_unhappy_childhood(self):
self._experience_unhappy_childhood = True
def make_police_dog(self, department):
self.kwargs['department'] = department
return self.set_race('pitbull_terrier').train()
def make_scary_dog(self):
return self.set_eye_color(fetch_angry_eye_colors('https://dogs.com')).
set_teeth_size('MAX').
experience_unhappy_childhood()
scary_police_dog = (DogBuilder()
.make_police_dog('vice')
.make_scary_dog()
.build()
Here's an approach using multiple inheritance (more specifically, cooperative multiple inheritance).
class Dog:
def __init__(self, *, race, teeth_size='Min', eye_color=None, **kwargs):
super().__init__(**kwargs)
self.race = race
self.eye_color = eye_color
self.teeth_size = teeth_size
class PoliceDog(Dog):
def __init__(self, *, department, **kwargs):
kwargs['race'] = 'pitbull_terrier'
super().__init__(**kwargs)
PoliceAcademy().train(self)
class ScaryDog(Dog):
def __init__(self, **kwargs):
kwargs['teeth_size'] = 'MAX'
kwargs['eye_color'] = fetch_angry_eye_colors('https://dogs.com')
super().__init__(**kwargs)
self.experience_unhappy_childhood()
class ScaryPoliceDog(PoliceDog, ScaryDog):
pass
d = ScaryPoliceDog(department="vice")
Unless there is something specific about a scary police dog that doesn't apply to an ordinary police dog or scary dog, nothing special needs to be done in ScaryPoliceDog. It's all handled via delegation using super.
In particular:
- The MRO for
ScaryPoliceDog is PoliceDog, ScaryDog, Dog, object. The __init__ method for each will be called in turn.
super() in PoliceDog does not refer to Dog; it refers to ScaryDog, because that's the class following PoliceDog in the MRO.
ScaryPoliceDog does not need to specify a race. Although that field is required by Dog, it will be supplied by PoliceDog, as PoliceDog.__init__ will be called first.
class PoliceDog(Dog),class ScaryDog(Dog)andclass ScaryPoliceDog(ScaryDog, PoliceDog).