views:

62

answers:

3

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.

+4  A: 

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)
Andrew Dalke
A: 

an object that has values that are accessible either by attribute name, or by index

I'm not sure what you're finding hard about this.

A collection accessible by index implements __getitem__.

A collection accessible by names implements __getattr__ (or __getattribute__).

You can implement both without any trouble at all. Or, you can use namedtuple.

To make life simpler, you could extend the tuple class so you don't have to implement your own __getitem__. Or you can define an ordinary class that also has __getitem__ so you didn't have to mess with __getattr__.

For example

>>> class Foo( object ):
...     def __init__( self, x, y, z ):
...         self.x= x
...         self.y= y
...         self.z= z
...     def __getitem__( self, index ):
...         return { 0: self.x, 1: self.y, 2: self.z }[index]
... 
>>> f= Foo(1,2,3)
>>> f.x
1
>>> f[0]
1
>>> f[1]
2
>>> f[2]
3
>>> f.y
2
S.Lott
+2  A: 

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.

Jonas Gorauskas