Changing values of a list of namedtuples

47,697

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 namedtuples?

Share:
47,697
Leon Surrao
Author by

Leon Surrao

Updated on July 09, 2022

Comments

  • Leon Surrao
    Leon Surrao almost 2 years

    I have a list of namedtuples named Books and am trying to increase the price field by 20% which does change the value of Books. 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
    Leon Surrao almost 9 years
    sorry.. It was supposed to be BSI not Books.. I edited the question.
  • Leon Surrao
    Leon Surrao almost 9 years
    Is there a way to achieve the same using the _replace method?
  • Leon Surrao
    Leon Surrao almost 9 years
    Is there a way to achieve the same using the _replace method?
  • Sait
    Sait almost 9 years
    @LeonSurrao I updated the post with _replace() method as you wish.
  • mike3996
    mike3996 almost 9 years
    I 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
    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
    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
    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
    Sait almost 9 years
    Instead of rest.price = rest._replace(price = rest.price + newprice), do rest = rest._replace(price = rest.price + newprice). Note that _replace() returns you a new instance of namedtuple.
  • Yoel Gluschnaider
    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
    Jonathan Geisler almost 6 years
    recordtype looks nice, but unfortunately it only supports Python 2.x (as of 2018-07), and the question is explicitly tagged 3.x.
  • Jonathan Geisler
    Jonathan Geisler almost 6 years
    As 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
    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
    Minh Tran over 5 years
    What is meant by "immutable" in the context of namedtuples? The value of a field of a namedtuple object can't be modified/changed?
  • Evan
    Evan almost 5 years
    The 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
    WestCoastProjects almost 5 years
    pandas is fairly heavyweight - i'd use it when some more serious data manipulations were required
  • pauljohn32
    pauljohn32 almost 4 years
    dataclass 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
    hlongmore almost 4 years
    I 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
    bluenote10 over 3 years
    Considering 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
    Zim almost 2 years
    Best answer, uses only builtins. Exactly what dataclass is for.