I'd like to override __new__
in a child class to force it to create instances of the parent class, providing defaults for some of the keyword arguments of the parent.
It's designed to just be some nice sugar when creating parent class instances.
- Is this a terrible idea? If so, what's the right way to do this?
- Is it a known pattern?
- How will my favourite type checker be able to cope?
class Person:
def __init__(self, name: str, profession: str):
self.name = name
self.profession = profession
class Teacher(Person):
def __new__(self, name: str):
return Person(name=name, profession="teacher")
rita = Person("Rita", profession="surgeon")
bob = Teacher("Bob") # type checkers think `bob` is a Teacher.
CodePudding user response:
Perhaps the OO-wizards on StackOverflow can come up with an even more appealing method than this, but this satisfies my requirements, and my type checker is happy that bob
is indeed a Person
.
CodePudding user response:
Here's a suggestion that's a slight tweak on @LondonRob's answer: if possible, use an Enum
instead of magic strings to specify a Person
's profession.
from enum import Enum, auto
from typing import TypeVar
class Profession(Enum):
TEACHER = auto()
SURGEON = auto()
P = TypeVar('P')
class Person:
def __init__(self, name: str, profession: Profession) -> None:
self.name = name
self.profession = profession
@classmethod
def create_teacher(cls: type[P], name: str) -> P:
return cls(name=name, profession=Profession.TEACHER)
This has the advantage that you can test the person's profession much more cleanly:
>>> Bob = Person.create_teacher('Bob')
>>> Bob.profession is Profession.TEACHER
True
In fact, you could even generalise your alternative constructor by making it an instance method of your Profession
class, rather than a classmethod on your Person
class:
from __future__ import annotations
from enum import Enum, auto
class Person:
def __init__(self, name: str, profession: Profession) -> None:
self.name = name
self.profession = profession
class Profession(Enum):
TEACHER = auto()
SURGEON = auto()
def create(self, name: str) -> Person:
return Person(name=name, profession=self)
In usage:
>>> Bob = Profession.TEACHER.create(name='Bob')
>>> Suzie = Profession.SURGEON.create(name='Suzie')
As an aside, if your class is essentially a wrapper around structured data, then you could also consider using dataclasses
(whether or not you go the Enum
route):
from enum import Enum, auto
from dataclasses import dataclass
from typing import TypeVar
class Profession(Enum):
TEACHER = auto()
SURGEON = auto()
P = TypeVar('P')
@dataclass
class Person:
name: str
profession: Profession
@classmethod
def create_teacher(cls: type[P], name: str) -> P:
return cls(name=name, profession=Profession.TEACHER)