Ruby is a great little language, and when I'm not programming in Ruby, I sometimes miss some of its syntactic sugar. Recently I was doing some Python work and wanted to call a method on every element in a list.
In Ruby you would do something like
lst = ["foo", "bar", "baz"]
lst.map(&:capitalize)
# ["Foo", "Bar", "Baz"]
The magical &:capitalize is a bit of shorthand that basically says "Take the symbol 'capitalize' and convert it into a Proc (like a lambda in Python)" It is essentially the same thing as
lst = ["foo", "bar", "baz"]
lst.map{|item| item.capitalize}
# ["Foo", "Bar", "Baz"]
In Python, you would do something like
lst = ['foo', 'bar', 'baz']
map(lambda item: item.title, lst)
# ["Foo", "Bar", "Baz"]
But since I do this fairly often, I created a wrapper around map to behave more like ruby
def mapattr(attr, lst):
"creates a lambda for attr and maps it over items in lst"
return map(lambda item: getattr(item, attr)(), lst)
lst = ['foo', 'bar', 'baz']
mapattr('title', lst)
# ["Foo", "Bar", "Baz"]
This will work in the case where you are calling a function, but sometimes you want to access a field on a class
class Person:
def __init__(self, first, last):
self.first_name = first
self.last_name = last
people = [Person("Gary", "Player"), Person("Sam", "Snead"), Person("Ben", "Hogan")]
mapattr('last_name', people)
# TypeError: 'str' object is not callable
Since class fields are accessed directly and not as function calls, we need to modify mapattr
def mapattr(attr, lst):
"creates a lambda for attr and maps it over items in lst"
def helper(item):
attribute = getattr(item, attr)
if callable(attribute):
return attribute()
else: return attribute
return map(helper, lst)
mapattr('last_name', people)
# ['Player', 'Snead', 'Hogan']
Awesome. One problem, I now have to remember to use mapattr. What would really be nice is if I could change the behaviour of map itself and let map take in a lambda or an attribute as its first argument. I was told this was not possible in the #python irc chat room, but if there is a will there is a way.
import __builtin__
__builtin__.old_map = map
def map(lambda_or_attr, sequences):
import types
fun = lambda_or_attr
if type(lambda_or_attr) is types.StringType:
def helper(item):
attribute = getattr(item, lambda_or_attr)
if callable(attribute):
return attribute()
else:
return attribute
fun = helper
return old_map(fun, sequences)
__builtin__.map = map
lst = ['foo', 'bar', 'baz']
map('title', lst)
# ['Foo', 'Bar', 'Baz']
map(lambda item: item.title(), ['foo', 'bar', 'baz'])
# ['Foo', 'Bar', 'Baz']
people = [Person("Gary", "Player"), Person("Sam", "Snead"), Person("Ben", "Hogan")]
map('last_name', people)
# ['Player', 'Snead', 'Hogan']
This isn't a complete implementation since the function signature for the built in map is a bit different, but it is a start.