I was looking forward to using the somewhat new typing.NamedTuple
class, which allows the creation of named tuple classes using the usual Python class syntax (including the ability to add docstrings and methods, provide default values, type hints, etc etc).
However: the class at the bottom is producing the following error message:
AttributeError: Cannot overwrite NamedTuple attribute __new__
From this I gather just what it says: overriding __new__
is a still no-no. This is very disappointing.
The "old way" of going about this would be to inherit from a named tuple class, but this requires what I consider to be some ugly boilerplate code:
from collections import namedtuple
class FormatSpec(namedtuple('FormatSpecBase', 'fill align sign alt zero '
'width comma decimal precision type')):
__slots__ = ()
def __new__(cls, fill, align, sign, alt, zero,
width, comma, decimal, precision, type):
to_int=lambda x: int(x) if x is not None else x
zero=to_int(zero)
width=to_int(width)
precision=to_int(precision)
return super().__new__(cls, fill, align, sign, alt, zero,
width, comma, decimal, precision, type)
FormatSpec.__doc__=_FormatSpec.__doc__.replace('FormatSpecBase','FormatSpec')
Is there some other alternate way I can cast the zero
, width
, and precision
arguments below to int
prior to the creation of the named tuple, but still using the same class creation syntax? Or am I stuck using the old way?
from typing import NamedTuple, Optional
class FormatSpec(NamedTuple):
"""Represents a string that conforms to the [Format Specification
Mini-Language][1] in the string module.
[1]: https://docs.python.org/3/library/string.html#formatspec
"""
fill: Optional[str]
align: Optional[str]
sign: Optional[str]
alt: Optional[str]
zero: Optional[int]
width: Optional[int]
comma: Optional[str]
decimal: Optional[str]
precision: Optional[int]
type: str
def __new__(cls, fill, align, sign, alt, zero, width, comma, decimal, precision, type):
to_int=lambda x: int(x) if x is not None else x
zero=to_int(zero)
width=to_int(width)
precision=to_int(precision)
return super().__new__(cls, fill, align, sign, alt, zero,
width, comma, decimal, precision, type)
def join(self):
return ''.join('{!s}'.format(s) for s in self if s is not None)
def __format__(self, format_spec):
try:
return format(self.join(), format_spec)
except (TypeError, ValueError):
return super().__format__(format_spec)
One way would be to split this up into two classes, and do the arguments modification in the child class:
from typing import NamedTuple, Optional
class FormatSpecBase(NamedTuple):
"""Represents a string that conforms to the [Format Specification
Mini-Language][1] in the string module.
[1]: https://docs.python.org/3/library/string.html#formatspec
"""
fill: Optional[str]
align: Optional[str]
sign: Optional[str]
alt: Optional[str]
zero: Optional[int]
width: Optional[int]
comma: Optional[str]
decimal: Optional[str]
precision: Optional[int]
type: str
def join(self):
return ''.join('{!s}'.format(s) for s in self if s is not None)
def __format__(self, format_spec):
try:
return format(self.join(), format_spec)
except (TypeError, ValueError):
return super().__format__(format_spec)
class FormatSpec(FormatSpecBase):
__slots__ = ()
def __new__(cls, fill, align, sign, alt, zero, width, comma, decimal, precision, type):
to_int=lambda x: int(x) if x is not None else x
zero=to_int(zero)
width=to_int(width)
precision=to_int(precision)
return super().__new__(cls, fill, align, sign, alt, zero,
width, comma, decimal, precision, type)
I don't much care for this approach, but at least it is more readable than the "old way" (even though it still needs that hanging __slots__
nonsense).
Another way would be a factory:
def MakeFormatSpec(cls, fill, align, sign, alt, zero,
width, comma, decimal, precision, type):
to_int=lambda x: int(x) if x is not None else x
zero=to_int(zero)
width=to_int(width)
precision=to_int(precision)
return FormatSpec(fill, align, sign, alt, zero,
width, comma, decimal, precision, type)
fspec = MakeFormatSpec(*parse_format_spec(some_format_spec_string))
...or a factory method:
@classmethod
def make(cls, fill, align, sign, alt, zero, width, comma, decimal, precision, type):
to_int=lambda x: int(x) if x is not None else x
zero=to_int(zero)
width=to_int(width)
precision=to_int(precision)
return cls(fill, align, sign, alt, zero,
width, comma, decimal, precision, type)
fspec = FormatSpec.make(*parse_format_spec(some_format_spec_string))
However, these are both pretty clunky compared to simply being able to do:
fspec = FormatSpec(*parse_format_spec(some_format_spec_string))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With