Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to define constructor for Python's new NamedTuple type?

Tags:

python

oop

As you may know, this is most recent type of defining named tuples in python:

from typing import NamedTuple


class MyType(NamedTuple):
    id: int = 0
    name: str = 0

After defining the type, Python interpreter defines a default constructor getting id and name and you can instantiate a new object using your fields. Now I want to initialise a new object using a string and within the function I parse it. How can I define another constructor without spoiling the good default ones?

like image 730
Kamyar Avatar asked Mar 22 '26 19:03

Kamyar


1 Answers

How can I define another constructor without spoiling the good default ones?

You can't. Python classes can't have multiple __new__ methods (or, if you meant "initializer", __init__ methods), just one.


But there's an easy way to work around this: the alternate constructor idiom: you write a @classmethod that provides an alternate way to construct instances. There are plenty of examples in the standard library, like datetime.now and datetime.utcfromtimestamp. There are even a few examples in the basic builtin types, like int.from_bytes.

Here's how that works:

class MyType(NamedTuple):
    id: int = 0
    name: str = 0

    @classmethod
    def from_string(cls, string_to_parse):
        id, name = … your parsing code here …
        return cls(id, name)

This is, of course, the same thing you'd do with a collections.namedtuple subclass, a @dataclass, or a plain-old class that had too many different ways to construct it.


If you really want to, the other way to do it is to provide an ugly constructor with either keyword-only parameters, or parameters that have different meanings depending on what you pass. With NamedTuple, you'll have to either insert an extra class in the way, or monkeypatch the class after creation, because otherwise there's no documented way of getting at the default constructor implementation.

So:

class _MyType(NamedTuple):
    id: int = 0
    name: str = 0

class MyType(_MyType):
    def __new__(cls, id: int=None, name: str=None, *, parseything: str=None):
        if parseything:
            if id is not None or str is not None:
                raise TypeError("don't provide both")
            id, name = … your parsing code here …
        return super().__new__(cls, id, name)

… or, if you prefer monkeypatching:

class MyType(NamedTuple):
    id: int = 0
    name: str = 0

_new = MyType.__new__
def __new__(cls, id=None, name=None, *, parseything=None):
    if parseything:
        if id is not None or str is not None:
            raise TypeError("don't provide both")
        id, name = … your parsing code here …
    return _new(cls, id, name)
MyType.__new__ = __new__
del _new
del __new__

… or, if you want more of a range-style ugly API you can do either of the above with:

def __new__(cls, id_or_parsey_thing: Union[int,str]=None, 
            name: str=None):
    if isinstance(id_or_parsey_thing, str):
        if name is not None:
            raise TypeError("don't provide both")
        id, name = … your parsing code here …
    else:
        id = id_or_parsey_thing
    # super().__new__ or _new here
like image 144
abarnert Avatar answered Mar 25 '26 08:03

abarnert



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!