Can't set attribute for subclasses of namedtuple
Question:
It looks like this or this are somewhat related threads, but still haven’t figured things out 🙂
I’m trying to create a subclass of namedtuple
and provide different initializers so that I can construct objects in different ways. For example:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __init__(self, obj) : # Initialize a C instance by copying values from obj
... self.x = obj.a
... self.y = obj.b
... def __init__(self, x, y) : # Initialize a C instance from the parameters
... self.x = x
... self.y = y
However, that doesn’t work:
>>> c = C(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
AttributeError: can't set attribute
After some poking around (for example, see this thread) I tried to use constructors instead of initializers:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __new__(cls, obj) :
... self = super(C, cls).__new__(cls, obj.a, obj.b)
... def __new__(cls, x, y) :
... self = super(C, cls).__new__(cls, x, y)
which seemed to construct an object but then I can’t read its attributes:
>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'
Where am I going wrong here? How can I create a subclass with multiple constructors or initializers?
Answers:
Named tuples are immutable, so you cannot manipulate them in the __init__
initializer. Your only option is to override the __new__
method:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, obj):
return super(C, cls).__new__(cls, obj.x, obj.y)
Note that because __new__
is a factory method for new instances, you do need to return the newly created instance. If you do not use return
in the __new__
method, the default return value is None
, which gives you your error.
Demo with an object with x
and y
attributes:
>>> class C(namedtuple('C', 'x, y')):
... __slots__ = ()
... def __new__(cls, obj):
... return super(C, cls).__new__(cls, obj.x, obj.y)
...
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)
Python does not support method overloading; generally you either use optional keyword arguments or extra class methods as factory methods.
The datetime
module, for example, has several such factory methods to let you create objects that do not fit the standard constructor. datetime.datetime.fromtimestamp()
creates a datetime.datetime
instance from a single numeric value, and so does datetime.datetime.fromordinal()
; except that they interpret the number in different ways.
If you wanted to support variable arguments, do:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, x, y=None):
if y is None:
# assume attributes
x, y = x.x, x.y
return super(C, cls).__new__(cls, x, y)
Here, y
is an optional argument, defaulting to None
if not supplied by the caller:
>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
The alternative, using a class method, would be:
class C(namedtuple('C', 'x, y')):
@classmethod
def from_attributes(cls, obj):
return cls(obj.x, obj.y)
Now there are two factory methods; one default and one named:
>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
Two things: one, you’re not really getting much out of namedtuple here, as far as i can tell. So maybe you should just switch to a normal class. Also, you can’t overload the
Second, other possibilities which might help with your problem:
Factory design pattern – instead of putting the different parameters in the constructor, have a class that takes different kinds of parameters and calls the constructor with appropriate arguments, outside the object.
recordtype – a mutable namedtuple, that allows defaults but would also let you write your subclass the way you originally wanted.
bunch – not exactly a named tuple, but lets you create somewhat arbitrary objects.
There is a workaround to changing the attribute of a namedtuple.
import collections
def updateTuple(NamedTuple,nameOfNamedTuple):
## Convert namedtuple to an ordered dictionary, which can be updated
NamedTuple_asdict = NamedTuple._asdict()
## Make changes to the required named attributes
NamedTuple_asdict['path']= 'www.google.com'
## reconstruct the namedtuple using the updated ordered dictionary
updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)
return updated_NamedTuple
Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")
I suggest you use the the _replace
method
from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)
I create a named tuple then later calculate leverage based on foods list in the named tuple. Finally, I replace the named tuple in the list
def get_leverage(antecedent, consequent):
#Compute support for antecedent AND consequent
supportAC = np.logical_and(antecedent, consequent).mean()
# Compute support for antecedent
supportA = antecedent.mean()
# Compute support for consequent
supportC = consequent.mean()
# Return leverage
return supportAC - supportA*supportC
DataRecord = namedtuple("DataRecord", "foods support confidence lift leverage conviction")
lstData=[]
for record in association_results:
foods=[i for i in record.items]
support=round(record.support,2)
confidence=round(record.ordered_statistics[0].confidence,2)
lift=round(record.ordered_statistics[0].lift,2)
dataRecord = DataRecord(foods,support,confidence,lift,0,0)
lstData.append(dataRecord)
print(lstData[:3])
for index,record in enumerate(lstData):
old_food=""
lstLeverage=[]
for food in record.foods:
#print(food)
if len(food)>0 and len(old_food)>0:
leverage=get_leverage(onehot[food],onehot[old_food])
lstLeverage.append(leverage)
old_food=food
#print(np.mean(lstLeverage))
record=record._replace(leverage=round(np.mean(lstLeverage),3))
lstData[index]=record
#print(record)
print(lstData[:3])
It looks like this or this are somewhat related threads, but still haven’t figured things out 🙂
I’m trying to create a subclass of namedtuple
and provide different initializers so that I can construct objects in different ways. For example:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __init__(self, obj) : # Initialize a C instance by copying values from obj
... self.x = obj.a
... self.y = obj.b
... def __init__(self, x, y) : # Initialize a C instance from the parameters
... self.x = x
... self.y = y
However, that doesn’t work:
>>> c = C(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __init__
AttributeError: can't set attribute
After some poking around (for example, see this thread) I tried to use constructors instead of initializers:
>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
... __slots__ = ()
... def __new__(cls, obj) :
... self = super(C, cls).__new__(cls, obj.a, obj.b)
... def __new__(cls, x, y) :
... self = super(C, cls).__new__(cls, x, y)
which seemed to construct an object but then I can’t read its attributes:
>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'
Where am I going wrong here? How can I create a subclass with multiple constructors or initializers?
Named tuples are immutable, so you cannot manipulate them in the __init__
initializer. Your only option is to override the __new__
method:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, obj):
return super(C, cls).__new__(cls, obj.x, obj.y)
Note that because __new__
is a factory method for new instances, you do need to return the newly created instance. If you do not use return
in the __new__
method, the default return value is None
, which gives you your error.
Demo with an object with x
and y
attributes:
>>> class C(namedtuple('C', 'x, y')):
... __slots__ = ()
... def __new__(cls, obj):
... return super(C, cls).__new__(cls, obj.x, obj.y)
...
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)
Python does not support method overloading; generally you either use optional keyword arguments or extra class methods as factory methods.
The datetime
module, for example, has several such factory methods to let you create objects that do not fit the standard constructor. datetime.datetime.fromtimestamp()
creates a datetime.datetime
instance from a single numeric value, and so does datetime.datetime.fromordinal()
; except that they interpret the number in different ways.
If you wanted to support variable arguments, do:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, x, y=None):
if y is None:
# assume attributes
x, y = x.x, x.y
return super(C, cls).__new__(cls, x, y)
Here, y
is an optional argument, defaulting to None
if not supplied by the caller:
>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
The alternative, using a class method, would be:
class C(namedtuple('C', 'x, y')):
@classmethod
def from_attributes(cls, obj):
return cls(obj.x, obj.y)
Now there are two factory methods; one default and one named:
>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
Two things: one, you’re not really getting much out of namedtuple here, as far as i can tell. So maybe you should just switch to a normal class. Also, you can’t overload the
Second, other possibilities which might help with your problem:
Factory design pattern – instead of putting the different parameters in the constructor, have a class that takes different kinds of parameters and calls the constructor with appropriate arguments, outside the object.
recordtype – a mutable namedtuple, that allows defaults but would also let you write your subclass the way you originally wanted.
bunch – not exactly a named tuple, but lets you create somewhat arbitrary objects.
There is a workaround to changing the attribute of a namedtuple.
import collections
def updateTuple(NamedTuple,nameOfNamedTuple):
## Convert namedtuple to an ordered dictionary, which can be updated
NamedTuple_asdict = NamedTuple._asdict()
## Make changes to the required named attributes
NamedTuple_asdict['path']= 'www.google.com'
## reconstruct the namedtuple using the updated ordered dictionary
updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)
return updated_NamedTuple
Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")
I suggest you use the the _replace
method
from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)
I create a named tuple then later calculate leverage based on foods list in the named tuple. Finally, I replace the named tuple in the list
def get_leverage(antecedent, consequent):
#Compute support for antecedent AND consequent
supportAC = np.logical_and(antecedent, consequent).mean()
# Compute support for antecedent
supportA = antecedent.mean()
# Compute support for consequent
supportC = consequent.mean()
# Return leverage
return supportAC - supportA*supportC
DataRecord = namedtuple("DataRecord", "foods support confidence lift leverage conviction")
lstData=[]
for record in association_results:
foods=[i for i in record.items]
support=round(record.support,2)
confidence=round(record.ordered_statistics[0].confidence,2)
lift=round(record.ordered_statistics[0].lift,2)
dataRecord = DataRecord(foods,support,confidence,lift,0,0)
lstData.append(dataRecord)
print(lstData[:3])
for index,record in enumerate(lstData):
old_food=""
lstLeverage=[]
for food in record.foods:
#print(food)
if len(food)>0 and len(old_food)>0:
leverage=get_leverage(onehot[food],onehot[old_food])
lstLeverage.append(leverage)
old_food=food
#print(np.mean(lstLeverage))
record=record._replace(leverage=round(np.mean(lstLeverage),3))
lstData[index]=record
#print(record)
print(lstData[:3])