Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

Partially Emulating collections.namedtuple

If you want to learn more about collections.namedtuple, then check out Write Pythonic and Clean Code With namedtuple.

00:00 Partially Emulating collections.namedtuple. As a final example of how to take advantage of .__new__() in your code, you can push your Python skills and write a factory function that partially emulates collections.namedtuple.

00:16 The namedtuple() function allows you to create subclasses of tuple with the additional feature of having named fields for accessing the items in the tuple.

00:27 Next, you’ll see code that implements a named_tuple_factory() function that partially emulates this functionality by overriding the .__new__() method of a nested class called NamedTuple.

00:42 First, you import itemgetter from the operators module. This function allows you to retrieve items using their index in the containing sequence.

00:51 Next, named_tuple_factory() is defined. This function takes a first argument called type_name, which will hold the name of the tuple subclass that you want to create.

01:02 The *fields argument allows you to pass an undefined number of field names as strings. Here, you define a local variable to hold the number of named fields provided by the user. Here, you define a nested class called NamedTuple, which inherits from the built-in tuple class.

01:20 This provides a .__slots__ class attribute. This attribute defines a tuple for holding instance attributes. This tuple saves memory by acting as a substitute for the instance’s dictionary, which would otherwise play a similar role.

01:34 Next you implement .__new__() with cls as its first argument. This implementation also takes the *args argument to accept an undefined number of field values.

01:45 Then you define a conditional statement that checks if the number of items to store in the final tuple differs from the number of named fields. If that’s the case, then the conditional raises a TypeError with an error message.

02:02 This sets the .__name__ attribute the current class to the value provided by type_name. These lines define a for loop that turns every name field into a property that uses itemgetter() to return the item at the target index.

02:16 The loop uses the built-in setattr() function to perform this action. Note that the built-in enumerate() function provides the appropriate index value.

02:27 This line returns a new instance of the current class by calling super().__new__() as usual. These lines define a .__repr__() special method for the tuple subclass.

02:49 Finally, this line returns the newly created NamedTuple class,

02:57 To try the named_tuple_factory() out, start an interactive session in the directory containing the named_tuple.py file and run the following code.

03:11 First, create a new Point class by calling named_tuple_factory(). The first argument in this call represents the name that the resulting class object will use.

03:21 The second and third arguments are the named fields available in the resulting class. Then you create a Point object by calling the class constructor with the appropriate values for the x and y fields.

03:35 To access the value of each named field, you can use dot notation. You can also use indices to retrieve the values because your class is a tuple subclass.

03:50 Because tuples are an immutable data type in Python, you can’t assign new values to the point’s coordinates in place. If you try to do that, you get an AttributeError.

04:02 Finally, calling dir() with your point instance as an argument reveals that your object inherits all of the attributes and methods that regular tuples have in Python.

04:13 Now that you’ve covered all the content in this course, in the next section, you’ll take a look back at what you’ve learned.

Michal B on March 13, 2023

Hi All,

In 1:02, is the

*fields 

in

def named_tuple_factory(type_name, *fields):

instead of

*args

?

I am aware that args and kwargs are arbitrary names, however if it’s the standard, then maybe we should be sticking to it? Unless ‘fields’ is something else?

Thanks!

Bartosz Zaczyński RP Team on March 14, 2023

@Michal B While the common standard is to use *args and **kwargs, it’s mainly for generic arguments. In this case, it’s more descriptive to use the term *fields since this function is specifically for creating a named tuple. So, yes, sometimes it makes more sense to use other argument names.

praghavan1973 on June 13, 2023

In the example program, what is the meaning of property(itemgetattr(index)) and why is it used to do setattr()?

setattr(cls, field, property(itemgetter(index)))

I changed the code to:

setattr(cls, field, args[index])

and this seems to be working fine too.

Any reason why this straightforward approach was not done?

Leodanis Pozo Ramos RP Team on June 13, 2023

@praghavan1973 The reason behind the use of property() is that we wanted to have full control over the setter method. Your code works but if you forget to use __slots__ = (), then assignments will work and the tuple won’t be immutable any longer.

mochibeepboop on Sept. 18, 2023

If I may offer some feedback, this course (especially the second part) needs practical use cases. And it would be great if there was also a quiz. It feels really theoretical and I don’t know where I would even start applying these concepts on an actual project. Thank you :)

Bartosz Zaczyński RP Team on Sept. 18, 2023

@mochibeepboop Thank you for your feedback. We’re constantly striving to update and improve our courses. We’ll consider your suggestion when revamping the material. As to the quizzes, we’re slowly adding those to the published courses and tutorials, so you should expect one to be included in the future!

Oleg Vovkodav on March 18, 2024

Interesting tutorial, thank you.

Have a question though - what is the purpose of __slots__ class attribute here? It’s not used explicitly and when I was trying to print its content, it shows empty. So what is it for?

Bartosz Zaczyński RP Team on March 19, 2024

@Oleg Vovkodav When a class defines the .__slots__ attribute, it usually contains the allowed attribute names of its instances. This is both a memory-optimization and safety feature. When you define the slots as an empty tuple, you disable the ability to dynamically add or remove attributes from your objects:

>>> class Empty:
...     __slots__ = ()
...
>>> empty = Empty()
>>> empty.custom_attribute = "value"
Traceback (most recent call last):
  ...
AttributeError: 'Empty' object has no attribute 'custom_attribute

Now, compare this to Python’s default behavior, which allows you to attach attributes after you’ve created an object:

>>> class Empty:
...     pass
...
>>> empty = Empty()
>>> empty.custom_attribute = "value"
>>> dir(empty)
[..., 'custom_attribute']

In contrast, instances of a class with slots don’t have the .__dict__ attribute.

Become a Member to join the conversation.