Welcome, guest | Sign In | My Account | Store | Cart

In recipe 303439 (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303439) Andrew Durdin presents a customized tuple class that permits individual item access via a named attribute. I think this concept is quite novel and, as Andrew points out, can greatly improve readability of code. In this recipe I implement his tuple concept in a slightly different manner.

Instead of requiring the named attributes list for each instantiation of the tuple, my implementation 'Spawns' a derived tuple class that is taylored to the named attributes specified. And it is this Spawned class that is then used to instantiate tuples. My approach effectively separates the definition of the attribute names from the data specification. And in doing so, this approach diminishes instantiation hassles and further improves clarity.

Python, 44 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class NamedTuple( tuple ):
    def __new__( cls, names ):
        return NamedTuple.Spawn( names )

    def __getattr__( self, name ):
        try:
            return tuple.__getitem__( self, tuple.__getattribute__( self, '_names' )[name] )
        except KeyError:
            raise AttributeError, "object has no attribute named '%s'" % name
        except AttributeError:
            raise RuntimeError, "'NamedTuple' class cannot be used directly, use subclass generated by Spawn instead"
    
    def __setattr__( self, name, value ):
        raise TypeError, "'NamedTuple' object has only read-only attributes (assign to .%s)" % name

    def Spawn( names ):
        class __temp( NamedTuple ): 
            def __new__( cls, values ):
                return tuple.__new__( cls, values )

        __temp._names = dict( zip( names, range( len( names ) )))
        return __temp

    Spawn = staticmethod( Spawn )


if __name__ == '__main__':

    # There are two ways to Spawn a tuple class:
    Employee = NamedTuple.Spawn(['first', 'last', 'id'])
    Product = NamedTuple(['name', 'cost', 'profit', 'qty'])

    emp1 = Employee(['Jane', 'Foo', 1])

    # The number of values is not limited to the number of 
    # named attributes defined.
    emp2 = Employee(['John', 'Bar', 2, 'What about this?'])

    print emp1, emp1.last, emp1[2]
    print emp2, emp2.first, emp2[3]

    pro1 = Product(['Box', .10, .05, 100])

    print pro1, pro1.profit, pro1[0]

There are several things to notice here about using this modified NamedTuple class, and most notably is the clarity and ease of instantiation. Now the syntax looks more like the native tuple syntax, and requires less tricks like the zip function needed in the prior implementation.

The number of data values is not limited to the number of defined attributes, although it would be easy to enforce if desired.

There are two ways to Spawn an attributed tuple class. The only difference between the two is preference in readability.

With so many different ways to group data in Python using classes, dicts, lists, and tuples, I find Andrew's approach to be very fascinating. It affords the use of named arguments as a class or modified dict would, but yet retains the immutability of a tuple (among other tuple features). I certainly appreciate Andrew's contribution and hope my additions to his work will help others exploit this concept in their applications.

5 comments

Andrew Durdin 17 years, 2 months ago  # | flag

Class construction. I re-did my original implementation to use a metaclass and a class factory function, in recipe #303481. This "spawning" recipe is much easier to understand than the metaclass implementation; however, my tests show that the metaclass version is about 40% faster for named attribute access.

If that's not enough, I just added an improved version of the metaclass implementation which reduces memory consumption and adds another 1400% speed improvement. You could probably get most of the same speed benefits in this recipe by using the same optimization of creating an actual attribute for each member and forgoing __getattr__.

Gonçalo Rodrigues 17 years, 2 months ago  # | flag

Not new. The idea is far from being nove. See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/218485.

Jon Schull 17 years, 2 months ago  # | flag

attrdicts. In a footnote to recipe http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52313 "Method for constructing a dictionary without excessive quoting"

Ganesan Rajagopal introduced the the attrdict...upon which I have become quite dependent: here it is again (in its entirety)

class attrdict(dict):
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value

data = attrdict(red=1, green=2, blue=3)
print data.red
data.black = 4

Doesn't this do everything the named tuples (do with additional power?)

Gonçalo Rodrigues 17 years, 2 months ago  # | flag

Depends. It depends on what you mean by "it does everything named tuples do (but additional power)". They are different. For one named tuples are immutable. Also, in all of the variants, they have a tuple-like interface (e.g. they allow getting the elements by index) not a dict-like interface. I would guess that name access is also faster. Prolly they also eat less memory.

Derrick Wallace (author) 17 years, 2 months ago  # | flag

Not novel indeed. Thanks for mentioning your recipe. And while the named tuple, meta-class concept is not novel, there are certainly several different implementations available to readers now (at least recipes: 218485, 303481, and this recipe).

Created by Derrick Wallace on Tue, 7 Sep 2004 (PSF)
Python recipes (4591)
Derrick Wallace's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks