Home > Enterprise >  Is overriding __new__ in a child class to create particular parent class instances an antipattern?
Is overriding __new__ in a child class to create particular parent class instances an antipattern?

Time:10-05

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:

As Autcomplete with create_teacher as a suggestion

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)
  • Related