The standard python library contains the specialized type "namedtuple", which seems to not receive the attention it deserves. This is one of the beautiful features in python, which is hidden at first sight.
Named tuples can be an excellent alternative to defining classes and they have some other interesting features that I want to show you in this article.
So what is a named tuple and what makes it so specialized? A good way to think about this is to consider named tuples as extensions to regular tuples.
Python tuples are a simple data structure for grouping arbitrary objects. Tuples are immutable - they cannot be changed after they are created.
>>> tup = ('hello', object(), 42) >>> tup ('hello', <object object at 0x105e76b70>, 42) >>> tup[2] 42 >>> tup[2] = 23 TypeError: "'tuple' object does not support item assignment"
The flip side of tuples is that we can get data from them using only numeric indices. You cannot name the individual elements stored in a tuple. This may affect the readability of the code.
Also, a tuple is always a highly specialized structure. It is difficult to ensure that two tuples have the same field numbers and the same properties stored in them. This makes them easy to get acquainted with “slip-of-the-mind” bugs, where it is easy to confuse the order of the fields.
The goal of named tuples is to solve these two problems.
First of all, named tuples are immutable like ordinary tuples. You cannot change them after you have placed something in them.
In addition, named tuples are, um, named tuples . Each object stored in them can be accessed through a unique, readable person identifier. This frees you from memorizing integer indices or inventing workarounds like defining integer constants as mnemonics for your indices.
Here is the named tuple:
>>> from collections import namedtuple >>> Car = namedtuple('Car' , 'color mileage')
To use a named tuple, you need to import the collections
module. Named tuples have been added to the standard library in Python 2.6. In the example above, we defined the simple data type "Car" with two fields: "color" and "mileage".
You may find the syntax a bit strange here. Why do we transfer fields as a string encoded with "color mileage"?
The answer is that the named tuple factory function calls the split()
method on strings with field names. So it’s really just a shorthand to say the following:
>>> 'color mileage'.split() ['color', 'mileage'] >>> Car = namedtuple('Car', ['color', 'mileage'])
Of course, you can also pass the list directly with field name strings if you prefer this style. The advantage of using the list is that in this case it is easy to reformat this code if you need to divide it into several lines:
>>> Car = namedtuple('Car', [ ... 'color', ... 'mileage', ... ])
No matter how you decide, you can now create new "car" objects through the factory Car
function. The behavior will be the same as if you decide to define the Car
class manually and give it a constructor that takes the values ​​"color" and "mileage":
>>> my_car = Car('red', 3812.4) >>> my_car.color 'red' >>> my_car.mileage 3812.4
The unpacking of tuples and the '*' operator for unpacking the arguments of functions also work as expected:
>>> color, mileage = my_car >>> print(color, mileage) red 3812.4 >>> print(*my_car) red 3812.4
Despite access to the values ​​stored in the named tuple through their identifier, you can still access them through their index. This property of named tuples can be used to decompress them into a regular tuple:
>>> my_car[0] 'red' >>> tuple(my_car) ('red', 3812.4)
You can even get beautiful string display of objects for free, which will save you time and save from redundancy:
>>> my_car Car(color='red' , mileage=3812.4)
Named tuples, like regular tuples, are immutable. When you try to overwrite one of their fields, you get an AttributeError
exception:
>>> my_car.color = 'blue' AttributeError: "can't set attribute"
Named tuple objects are internally implemented in python as regular classes. When it comes to memory use, they are also “better” than regular classes and are just as effective in using memory as regular tuples.
A good way to judge them is to assume that named tuples are a short form for manually creating an efficiently working memory with an immutable class.
Since the named tuples are built on ordinary classes, you can add methods to the class inherited from the named tuple.
>>> Car = namedtuple('Car', 'color mileage') >>> class MyCarWithMethods(Car): ... def hexcolor(self): ... if self.color == 'red': ... return '#ff0000' ... else: ... return '#000000'
Now we can create MyCarWithMethods objects and call their hexcolor () method as expected:
>>> c = MyCarWithMethods('red', 1234) >>> c.hexcolor() '#ff0000'
Although it may be a little awkward, such an implementation can be worth it if you want to get a class with immutable properties. But you can easily shoot yourself in this case.
For example, adding a new immutable field is a tricky operation because of how named tuples are arranged inside. An easier way to create a hierarchy of named tuples is to use the base tuple’s ._fields property:
>>> Car = namedtuple('Car', 'color mileage') >>> ElectricCar = namedtuple( ... 'ElectricCar', Car._fields + ('charge',))
This gives the desired result:
>>> ElectricCar('red', 1234, 45.0) ElectricCar(color='red', mileage=1234, charge=45.0)
In addition to the _fields property, each instance of a named tuple also provides several other helper methods that you may find useful. All their names begin with an underscore, which tells us that the method or property is "private" and is not part of the established class or module interface.
As for named tuples, the naming convention starting with an underscore has a different meaning here: these auxiliary methods and properties are part of the open interface of named tuples. The underscore in these names was used to avoid name collisions with tuple fields defined by the user. So feel free to use them if you need them!
I want to show you some scenarios where the helper methods of the named tuple can be useful. Let's start with the _asdict () method. It returns the contents of the named tuple as a dictionary:
>>> my_car._asdict() OrderedDict([('color', 'red'), ('mileage', 3812.4)])
This is great for avoiding typos when creating JSON, for example:
>>> json.dumps(my_car._asdict()) '{"color": "red", "mileage": 3812.4}'
Another useful helper is the _replace () function. It creates a shallow copy of the tuple and allows you to selectively replace some fields:
>>> my_car._replace(color='blue') Car(color='blue', mileage=3812.4)
Finally, the _make () class method can be used to create a new instance of a named tuple from a sequence:
>>> Car._make(['red', 999]) Car(color='red', mileage=999)
Named tuples can be an easy way to build your code, making it more readable and providing better structuring of your data.
I find, for example, that the path from highly specialized data structures with a rigidly defined format, such as dictionaries to named tuples, helps me express my intentions more purely. Often when I try to refactor it, I magically achieve the best solutions to the problems I encounter.
Using named tuples instead of unstructured tuples and dictionaries can also make the life of my coworkers easier, because these data structures help to create data in a self-explanatory (sufficiently) way.
On the other hand, I try not to use named tuples for their benefit, unless they help me write cleaner, more readable and do not make the code easier to maintain. Too many good things can be a bad thing.
However, if you use them with care, then named tuples can no doubt make your Python code better and more expressive.
Source: https://habr.com/ru/post/330034/
All Articles