Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structure accessible by attribute name or index options

I am very new to Python, and trying to figure out how to create an object that has values that are accessible either by attribute name, or by index. For example, the way os.stat() returns a stat_result or pwd.getpwnam() returns a struct_passwd.

In trying to figure it out, I've only come across C implementations of the above types. Nothing specifically in Python. What is the Python native way to create this kind of object?

I apologize if this has been widely covered already. In searching for an answer, I must be missing some fundamental concept that is excluding me from finding an answer.

like image 632
Bruno DeGoia Avatar asked Mar 25 '10 01:03

Bruno DeGoia


2 Answers

Python 2.6 introduced collections.namedtuple to make this easy. With older Python versions you can use the named tuple recipe.

Quoting directly from the docs:

>>> Point = namedtuple('Point', 'x y')
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)
like image 168
Andrew Dalke Avatar answered Nov 30 '22 21:11

Andrew Dalke


You can't use the same implementation as the result object of os.stat() and others. However Python 2.6 has a new factory function that creates a similar datatype called named tuple. A named tuple is a tuple whose slots can also be addressed by name. The named tuple should not require any more memory, according to the documentation, than a regular tuple, since they don't have a per instance dictionary. The factory function signature is:

collections.namedtuple(typename, field_names[, verbose])  

The first argument specifies the name of the new type, the second argument is a string (space or comma separated) containing the field names and, finally, if verbose is true, the factory function will also print the class generated.

Example

Suppose you have a tuple containing a username and password. To access the username you get the item at position zero and the password is accessed at position one:

credential = ('joeuser', 'secret123')  
print 'Username:', credential[0]  
print 'Password:', credential[1]  

There's nothing wrong with this code but the tuple isn't self-documenting. You have to find and read the documentation about the positioning of the fields in the tuple. This is where named tuple can come to the rescue. We can recode the previous example as follows:

import collections  
# Create a new sub-tuple named Credential  
Credential = collections.namedtuple('Credential', 'username, password')  

credential = Credential(username='joeuser', password='secret123')  

print 'Username:', credential.username  
print 'Password:', credential.password  

If you are interested of what the code looks like for the newly created Credential-type you can add verbose=True to the argument list when creating the type, in this particular case we get the following output:

import collections  
Credential = collections.namedtuple('Credential', 'username, password', verbose=True)  

class Credential(tuple):                                       
    'Credential(username, password)'                       

    __slots__ = ()   

    _fields = ('username', 'password')   

    def __new__(_cls, username, password):  
        return _tuple.__new__(_cls, (username, password))   

    @classmethod  
    def _make(cls, iterable, new=tuple.__new__, len=len):  
        'Make a new Credential object from a sequence or iterable'  
        result = new(cls, iterable)                                 
        if len(result) != 2:                                        
            raise TypeError('Expected 2 arguments, got %d' % len(result))  
        return result  

    def __repr__(self):  
        return 'Credential(username=%r, password=%r)' % self  

    def _asdict(t):  
        'Return a new dict which maps field names to their values'  
        return {'username': t[0], 'password': t[1]}  

    def _replace(_self, **kwds):  
        'Return a new Credential object replacing specified fields with new values'  
        result = _self._make(map(kwds.pop, ('username', 'password'), _self))  
        if kwds:  
            raise ValueError('Got unexpected field names: %r' % kwds.keys())  
        return result  

    def __getnewargs__(self):  
        return tuple(self)  

    username = _property(_itemgetter(0))  
    password = _property(_itemgetter(1))  

The named tuple doesn't only provide access to fields by name but also contains helper functions such as the _make() function which helps creating an Credential instance from a sequence or iterable. For example:

cred_tuple = ('joeuser', 'secret123')  
credential = Credential._make(cred_tuple) 

The python library documentation for namedtuple has more information and code examples, so I suggest that you take a peek.

like image 37
λ Jonas Gorauskas Avatar answered Nov 30 '22 21:11

λ Jonas Gorauskas