2

Of course I have tried:

class PdfContentRecord(NamedTuple):
    filename: str
    page: int
    cache: dict
    data: dict = dict()
    accessed: str = None

    def __new__(cls, *args, **kwargs):
        self = super().__new__(*args, **kwargs)
        self.accessed = datetime.now().isoformat()
        return self

But I get exactly the same error as How to provide additional initialization for a subclass of namedtuple?

I can't tell if attrs can help me (too difficult to comprehend). dataclasses.dataclass probably can help, but it only supports Python 3.7.

Or I could write my Class, probably also with __slots__...

Edit:

Did you read the answer in the question you linked?

works with from collection import namedtuple, but not from typing import NamedTuple.

Polv
  • 1,918
  • 1
  • 20
  • 31

2 Answers2

3

If you're using dataclasses you can just use the default_factory of a field. If you're using attrs you can similarly provide a factory callable.

If you're using typing.NamedTuple, you should be able to do this with an extra layer of types:

from datetime import datetime
from typing import NamedTuple


class _PdfContentRecord(NamedTuple):
    filename: str
    page: int
    cache: dict
    data: dict = None
    accessed: str = None


class PdfContentRecord(_PdfContentRecord):

    def __new__(cls, filename, page, cache, data=None, accessed=None):
        if data is None:
            data = {}
        if accessed is None:
            accessed = datetime.now().isoformat()
        return super().__new__(cls, filename, page, cache, data, accessed)

Arguably, though, you lose some of the benefits of using a NamedTuple in the first place, and may as well just write the subtype directly.

wim
  • 338,267
  • 99
  • 616
  • 750
  • Can you explain overriding `__new__` here. It seems you are able to set the class attributes at class creation, correct? – pylang Sep 07 '18 at 02:10
  • Not set them, exactly, but intercept the arguments. Overriding `__new__` instead of `__init__` is typical when subclassing immutable types such as tuples, datetimes, etc – wim Sep 07 '18 at 02:24
1

Quite a beautiful answer, that requires pip install attrs (which inspires dataclass)

import attr

@attr.s
class PdfFileRecord:
    name: str = attr.ib()
    type: str = attr.ib()
    cache: dict = attr.ib()
    data: dict = attr.ib(factory=dict)
    accessed: str = attr.ib(factory=lambda: datetime.now().isoformat())

For the dataclass version, Python 3.7+ or the backport for Python 3.6 is needed.

import dataclasses

@dataclasses.dataclass
class PdfFileRecord:
    name: str
    type: str
    cache: dict
    data: dict = dataclasses.field(default_factory=dict)
    accessed: str = dataclases.field(default_factory=lambda: datetime.now().isoformat())
wim
  • 338,267
  • 99
  • 616
  • 750
Polv
  • 1,918
  • 1
  • 20
  • 31
  • I am using Python 3.7, but I plan to share my code. – Polv Sep 06 '18 at 23:07
  • 2
    Note: Now that you've fixed your code per @wim, you might have noticed you don't need `__attrs_post_init__` at all. You could just define `accessed` as `accessed: str = attr.ib(init=False, factory=lambda: datetime.now().isoformat())` and now you don't need `__attrs_post_init__` at all (and you've avoided separating the information about the default value from the information about the attribute itself). – ShadowRanger Sep 07 '18 at 00:12