Reputation: 599
I have a class for decoding binary data using struct and storing in a NamedTuple as below:
class HEADER1(NamedTuple):
name: str
u2: int
tracetime: int
u4: int
u5: int
u6: int
u7: int
struct = Struct('<8s6L')
@classmethod
def unpack(cls, data):
return cls(*cls.struct.unpack(data))
This works without issue and I can use as follows:
h = HEADER1.unpack(b'svpt_str\x03\x01\x00\x00\xae\xa6i_\xd0\x03\xfe3\x00\x00\x00\x00P\xa0\xdc3\x00\x00\x00\x00')
However if I try to change it to inherit the classmethod as follows it fails:
class NamedTupleUnpack(NamedTuple):
struct = Struct('x')
@classmethod
def unpack(cls, data):
return cls(*cls.struct.unpack(data))
class HEADER1(NamedTupleUnpack):
name: str
u2: int
tracetime: int
u4: int
u5: int
u6: int
u7: int
struct = Struct('<8s6L')
Then it errors with TypeError: __new__() takes 1 positional argument but 8 were given
.
I understand there are issues with inheriting from NamedTuple but wondered if there is a work around?
EDIT: as hinted by others below it looks like dataclasses are the way to go: A way to subclass NamedTuple for purposes of typechecking
Upvotes: 0
Views: 390
Reputation: 599
I tried to use a mixin, for example:
class HEADER1(Unpack, NamedTuple):
# Mixins evaluate right to left so NamedTuple is the base
...
However, NameTuple also does not work with mixins and the unpack method is unavailable in the derived tuple.
So I used the dataclass, which does work (and added some more functionality):
from typing import NamedTuple
from struct import Struct
from dataclasses import dataclass, fields
from abc import ABCMeta, abstractproperty
from collections.abc import Sequence
class Unpack(Sequence, metaclass=ABCMeta):
@abstractproperty
def struct(self):
pass
@classmethod
def unpack(cls, data):
return cls(*cls.struct.unpack(data))
def __getitem__(self, i):
return getattr(self, fields(self)[i].name)
def __len__(self):
return len(fields(self))
@dataclass(order=True)
class HEADER1(Unpack):
# Note Mixin's evalute right to left so NamedTuple is the base
name: str
u2: int
tracetime: int
u4: int
u5: int
u6: int
u7: int
struct = Struct('<8s6L')
h = HEADER1.unpack(b'svpt_str\x03\x01\x00\x00\xae\xa6i_\xd0\x03\xfe3\x00\x00\x00\x00P\xa0\xdc3\x00\x00\x00\x00')
h
HEADER1(name=b'svpt_str', u2=259, tracetime=1600759470, u4=872285136, u5=0, u6=870096976, u7=0)
Upvotes: 0
Reputation: 281528
typing.NamedTuple
doesn't provide the feature you want, because adding fields to a subclass of a namedtuple class conflicts with the design intent of namedtuples.
The design intent is that if Foo
is a namedtuple class with n
fields, instances of Foo
should be and behave like n
-element tuples. For example, if n==3
, then you should be able to take an arbitrary instance of Foo
and do
a, b, c = foo
Adding fields breaks this. If you could create a subclass class Bar(Foo)
with a fourth field, then instances of Bar
would be instances of Foo
for which you could not do
a, b, c = bar
Your NamedTupleUnpack
is a namedtuple class with 0 fields. You cannot add fields in the HEADER1
subclass.
You should probably use a regular class, or dataclasses
, or put unpack
in a mixin.
Upvotes: 2