Saturday, February 24, 2007

Attribute mapper in Python

I really don't like redundancy. Mainly it's not about the kind that just makes you type more when you program. It's about concepts, having to think about some trivial stuff each and every time and not being able to perfectly focus on the task at hand. Many minor disturbances can have a nasty result (maybe just on the fun part).
I dislike programming patterns that use looping variables just because 'this is the standard way to do it'. Even functional iteration is in some cases redundant.
Some examples in python:

Do a map on list:

[fn(var) for var in list]


Here var is pretty redundant. Map is more concise:

map(fn, list)

But, when it comes to taking some attribute/calling a function on every element of a list, things get ugly for map,

map(lambda var: var.attribute, list)

but stay about the same for the list comprehension:

[var.attribute for var in list]

Something as concise as the first expression of map for attributes would be a welcome. Expressing it in python:

mapper(list).attribute

'.' is used to take the attribute because it's shorter than using a string attribute and is more intuitive.

A possible implementation:

class mapper(list):
def __getattr__(self, attr):
list.__init__(self, [getattr(x, attr) for x in self])
return self

def __call__(self, *args, **kws):
list.__init__(self, [x(*args, **kws) for x in self])
return self

Here you cannot use attributes that are methods in the list class.
The advantage can be noticed for accessing attributes that don't have a functional accesor (ie lambda x: x.attribute).

Examples of use:

mapper("aBcD").lower()
==> ['a', 'b', 'c', 'd']

class a: a = 1

mapper([a() for x in range(3)]).a
==> [1, 1, 1]

Other uses (call every function in a list):

mapper([lambda:1,lambda:2])()
==> [1, 2]

mapper("ab ac bc".split()).replace('a','x').capitalize()
==> ['Xb', 'Xc', 'Bc']

1 comment:

Anonymous said...

Well said.