Changing values of a list of namedtuples
Solution 1
Named tuples are immutable, so you cannot manipulate them.
Right way of doing it:
If you want something mutable, you can use recordtype
.
from recordtype import recordtype
Book = recordtype('Book', 'author title genre year price instock')
books = [
Book('Suzane Collins','The Hunger Games', 'Fiction', 2008, 6.96, 20),
Book('J.K. Rowling', "Harry Potter and the Sorcerer's Stone", 'Fantasy', 1997, 4.78, 12)]
for book in books:
book.price *= 1.1
print(book.price)
PS: You may need to pip install recordtype
if you don't have it installed.
Bad way of doing it:
You may also keep using namedtuple
with using the _replace()
method.
from collections import namedtuple
Book = namedtuple('Book', 'author title genre year price instock')
books = [
Book('Suzane Collins','The Hunger Games', 'Fiction', 2008, 6.96, 20),
Book('J.K. Rowling', "Harry Potter and the Sorcerer's Stone", 'Fantasy', 1997, 4.78, 12)]
for i in range(len(books)):
books[i] = books[i]._replace(price = books[i].price*1.1)
print(books[i].price)
Solution 2
In Python >= 3.7 you can use dataclass decorator with the new variable annotations feature to produce mutable record types:
from dataclasses import dataclass
@dataclass
class Book:
author: str
title: str
genre: str
year: int
price: float
instock: int
BSI = [
Book("Suzane Collins", "The Hunger Games", "Fiction", 2008, 6.96, 20),
Book(
"J.K. Rowling",
"Harry Potter and the Sorcerer's Stone",
"Fantasy",
1997,
4.78,
12,
),
]
for item in BSI:
item.price *= 1.10
print(f"New price for '{item.title}' book is {item.price:,.2f}")
Output:
New price for 'The Hunger Games' book is 7.66
New price for 'Harry Potter and the Sorcerer's Stone' book is 5.26
Solution 3
This looks like a task for Python's data analysis library, pandas. It's really, really easy to do this sort of thing:
In [6]: import pandas as pd
In [7]: df = pd.DataFrame(BSI, columns=Book._fields)
In [8]: df
Out[8]:
author title genre year \
0 Suzane Collins The Hunger Games Fiction 2008
1 J.K. Rowling Harry Potter and the Sorcerers Stone Fantasy 1997
price instock
0 6.96 20
1 4.78 12
In [9]: df['price'] *= 100
In [10]: df
Out[10]:
author title genre year \
0 Suzane Collins The Hunger Games Fiction 2008
1 J.K. Rowling Harry Potter and the Sorcerer's Stone Fantasy 1997
price instock
0 696 20
1 478 12
Now isn't that just much, much better than labouring with namedtuple
s?
Leon Surrao
Updated on July 09, 2022Comments
-
Leon Surrao almost 2 years
I have a list of namedtuples named
Books
and am trying to increase theprice
field by 20% which does change the value ofBooks
. I tried to do:from collections import namedtuple Book = namedtuple('Book', 'author title genre year price instock') BSI = [ Book('Suzane Collins','The Hunger Games', 'Fiction', 2008, 6.96, 20), Book('J.K. Rowling', "Harry Potter and the Sorcerer's Stone", 'Fantasy', 1997, 4.78, 12)] for item in BSI: item = item.price*1.10 print(item.price)
But I keep getting :
Traceback (most recent call last): print(item.price) AttributeError: 'float' object has no attribute 'price'
I understand that I cannot set the fields in a namedtuple. How do I go about updating
price
?I tried to make it into a function:
def restaurant_change_price(rest, newprice): rest.price = rest._replace(price = rest.price + newprice) return rest.price print(restaurant_change_price(Restaurant("Taillevent", "French", "343-3434", "Escargots", 24.50), 25))
but I get an error with replace saying:
rest.price = rest._replace(price = rest.price + newprice) AttributeError: can't set attribute
Can someone let me know why this is happening?
-
Leon Surrao almost 9 yearssorry.. It was supposed to be BSI not Books.. I edited the question.
-
Leon Surrao almost 9 yearsIs there a way to achieve the same using the _replace method?
-
Leon Surrao almost 9 yearsIs there a way to achieve the same using the _replace method?
-
Sait almost 9 years@LeonSurrao I updated the post with
_replace()
method as you wish. -
mike3996 almost 9 yearsI don't understand the python community... That
namedtuple._replace()
is pretty idiomatic in functional languages, why is that "private" here!? It really shouldn't be a bad way to do it, it's certainly better than to convert to mutable madness -
Sait almost 9 years@progo Assume you have millions of items in your tuple. If you use
_replace
, you need to copy all items again to another tuple and trash the old one. That doesn't sound like the best way to me. -
mike3996 almost 9 years@Sait: only the references are copied, and tuples aren't designed to be large varying-length lists for that matter. It may not be ideal in Python but there's a reason people prefer immutability in data mangling.
-
Leon Surrao almost 9 years@Sait. I made a function out of it that just takes a restaurant object. But I kept getting the same Attribute error. Can you tell me what am I doing wrong? I updated the question.
-
Sait almost 9 yearsInstead of
rest.price = rest._replace(price = rest.price + newprice)
, dorest = rest._replace(price = rest.price + newprice)
. Note that_replace()
returns you a new instance ofnamedtuple
. -
Yoel Gluschnaider about 7 years@progo,
_replace
is actually not "private" or internal. based on this doc: To prevent conflicts with field names, the method and attribute names start with an underscore. -
Jonathan Geisler almost 6 yearsrecordtype looks nice, but unfortunately it only supports Python 2.x (as of 2018-07), and the question is explicitly tagged 3.x.
-
Jonathan Geisler almost 6 yearsAs a Python 3 alternative to
recordtype
, I've switched to namedlist, which works pretty much exactly the same and has Python 3 out of the box. -
Luis Vito over 5 years@Sait "Assume you have millions of items in your tuple." This is not a likely scenario, is it?
-
Minh Tran over 5 yearsWhat is meant by "immutable" in the context of namedtuples? The value of a field of a namedtuple object can't be modified/changed?
-
Evan almost 5 yearsThe reason _replace() is "privateish" is to avoid conflicting with attributes defined by the user. For that reason, namedtuples do not define any methods that don't exist on tuple (count and index). IMHO A better API choice here would be to have all the namedtuple helper functions in their own namespace rather than as member functions.
-
WestCoastProjects almost 5 years
pandas
is fairly heavyweight - i'd use it when some more serious data manipulations were required -
pauljohn32 almost 4 yearsdataclass is a newer, built-in to Python way to get this done. Hence, the answer below by @vlad-belzen should be preferred, IMHO. See PEP 557 for dataclass introduction, python.org/dev/peps/pep-0557, it mentions recordtype as a valuable module that is incorporated in the functionality of dataclass.
-
hlongmore almost 4 yearsI wish I could give more upvotes--this maintains the simplicity of a
namedtuple
, adds the mutability of a class, and does not require any other packages, lightweight or heavy. -
bluenote10 over 3 yearsConsidering that
_replace
is not private, it is actually a very good solution, because it keeps immutability, which is indeed a standard pattern in functional programming. The "right" solution is an external dependency and introduces mutability. Depending on the use case, the answer gets "right" and "bad" wrong. It would be better to just state pros/cons instead of making opinionated judgements. -
Zim almost 2 yearsBest answer, uses only builtins. Exactly what dataclass is for.