Reputation: 2121
Minimum Reproducible Example:
from collections import namedtuple
class Test(namedtuple('Test', ['a','b'])):
def __init__(self, a, b):
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND'))
OUTPUT:
FIRSTSECOND
I thought when an __init__
function is defined, it overwrites the parent implementation. If that is the case, how do self.a
and self.b
exist with the correct values? If I forego the a
and b
parameters in __init__
, I get a TypeError: __init__() takes 1 positional argument but 3 were given
. I need to provide the parameters, but they're not being set explicitly in __init__
either, and I have no called to super()
.
Upvotes: 0
Views: 236
Reputation: 531868
self.a
and self.b
are set by the named tuple's __new__
method before __init__
is called. This is because a named tuple is immutable (aside from the ability to add additional attributes, as Test.__init__
does), so trying to set a
and b
after the tuple is created would fail. Instead, the values are passed to __new__
so that the values are available when the tuple is being created.
Here's an example of __new__
being overriden to swap the a
and b
values.
class Test(namedtuple('Test', ['a','b'])):
def __new__(cls, a, b, **kwargs):
return super().__new__(cls, b, a, **kwargs)
def __init__(self, a, b):
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND')) # outputs SECONDFIRST
Trying to do the same with __init__
would fail:
class Test(namedtuple('Test', ['a','b'])):
def __init__(self, a, b):
self.a, self.b = b, a
self.c = self.a + self.b
def __str__(self):
return self.c
print(Test('FIRST', 'SECOND')) # outputs SECONDFIRST
results in
Traceback (most recent call last):
File "/Users/chepner/advent-of-code-2020/tmp.py", line 11, in <module>
print(Test('FIRST', 'SECOND'))
File "/Users/chepner/advent-of-code-2020/tmp.py", line 5, in __init__
self.a, self.b = b, a
AttributeError: can't set attribute
To make c
immutable as well (while keeping it distinct from the tuple itself), use a property.
class Test(namedtuple('Test', ['a','b'])):
@property
def c(self):
return self.a + self.b
def __str__(self):
return self.c
Note that c
is not visible or accessible when treating an instance of Test
as a regular tuple:
>>> x = Test("First", "Second")
>>> x
Test(a='First', b='Second')
>>> len(x)
2
>>> tuple(x)
('First', 'Second')
>>> x[0]
'First'
>>> x[1]
'Second'
>>> x[2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>> x.c = "foo"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Upvotes: 1