Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.
like image 261
LondonRob Avatar asked Nov 02 '25 05:11

LondonRob


2 Answers

As this comment rightly points out, a user would be surprised to request a Teacher but not get one:

>>> bob = Teacher("Bob")
>>> isinstance(bob, Teacher)
False  # ???

This is a good argument against this pattern.

A more typical approach might involve some helper methods on the parent class to assist with creation:

class Person:
    def __init__(self, name: str, profession: str):
        self.name = name
        self.profession = profession

    @classmethod
    def create_teacher(cls, name: str) -> Person:
        return cls(name=name, profession="teacher")

This means users of your code can create teachers without having to remember the magic string "teacher". Instead they can use autocomplete:

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.

like image 52
LondonRob Avatar answered Nov 03 '25 19:11

LondonRob


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)
like image 21
Alex Waygood Avatar answered Nov 03 '25 18:11

Alex Waygood



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!