April 26, 2005
cool python tricks
Man I love Python. I came up with a neat trick yesterday that also couldn't be done in any static language. Needless to say, I'm pretty pleased with myself. This trick isn't slow or hard to understand, and actually makes a lot of my code very simple, and avoids a lot of boilerplate that I would have had to write in another language
I needed a way to given a basic color to a class, and then have easy access to various tints of that color for painting different aspects of an object. The tints are based on HSV, not RGB, but all the callers need to deal with RGB.
The solution: wrap the property() descriptor with my own descriptor.
I start by defining my class
class TintedColors(object):
def __init__(self, color):
"""
color is an RGB triple
"""
self.color = color
self.hue = rgb_to_hsv(*color)[0]
Now I just need to define the property wrapper:
def tintedColor(saturation, value=1.0):
def getColor(self):
hsv = (self.hue, saturation, value)
return hsv_to_rgb(*hsv)
return property(getColor)
Finally, I can define all my tints as class attributes:
gradientLeft = tintedColor(0.4)
gradientRight = tintedColor(0.2)
outlineColor = tintedColor(0.5)
textColor = tintedColor(0.67, 0.6)
What is so amazing about this is that because property is a runtime thing, and because classes are evaluated, not declared, the actual value of "f.gradientLeft" will be a descriptor as generated by property(). Each descriptor will call a special version of getColor() defined for each and every tint. The dynamic nature of this might make it look like it should be slower than a simple property() call, but in fact it is just as fast because the specialized versions of getColor() are very simple, and in memory act as though they were declared as:
def getColor(self):
hsv = (self.hue, 0.4, 1.0)
return hsv_to_rgb(*hsv)
which is pretty darn fast.
Lets look at a similar option in C++:
class TintedColors {
private:
color mColor;
float mHue;
color GetSaturatedColor(float saturation, float value=1.0) {
hsv = make_hsv(this.mHue, saturation, value);
return hsv_to_rgb(hsv);
}
public:
TintedColors(const color& c) const {
mColor = c;
this.mHue = rgb_to_hsv(c).hue
}
color& GetGradientLeft() const {
return GetSaturatedColor(0.4);
}
color& GetOutlineColor() const {
return GetSaturatedColor(0.2);
}
color& GetTextColor() const {
return GetSaturatedColor(0.67, 0.6);
}
}
There we go. I've tried to follow "good C++" guidelines with respect to consts, references, and so forth. And there we have the same pattern in C++ but with way more work: the 4 boilerplate calls meant I had to type GetSaturatedColor() 4 times, and the verbosity allows you to loose what's really different between these functions - the color saturation and value.
And that's all assuming that I've got a "color" type (which I'd have to define seperately - python just uses the built-in triples for a small value like that) and that hsv_to_rgb is part of that color system.. that may seem like a small assumption. It may not be part of the language, but there are so many extra easy routines just built in to Python that you can't just write that off.
April 14, 2005
How python can be fast
So now that I've been working with python for a good 3-4 months, I'm starting to understand why people like it so much.. a bit part of it is that its just so damn simple. Its so easy to write code that works very quickly. The syntax is very elegant and easy to read. I've come to actually prefer the indent-based code grouping over most other language's braces.
But one thing that has been plaguing me is how a dynamiclanguage could actually be faster than a static language like C++ or Java. I have faith in the theory, but its just that: faith. Until today, I didn't have any real world examples. I needed something that you simply can't do in a static language without bending over backwards. Conversely, I needed something that required the dynamicism of a language.
I just picked up the Python Cookbook the other day and I've found a very concrete example about how a dynamic language can be fast.
The example is from the beginning of the chapter on sorting and searching. They discuss the idea of decorate-sort-undecorate as a pattern for doing fast searching.
What I particularly like is the 'key' function that you can now pass to the sort method in Python 2.4. You can simply pass a method that retrieves the 'key' to a sort. Then when the sort is performed, the keys are more or less cached, and a fast internal comparison can be used.
For example:
MyList = [(2,3,4), (5,3,4), (1,2,3)] def get_key(v): return v[2] MyList.sort(key=get_key)
Now compare this to a traditional language, which might allow you to pass in a sort callback:
int *MyList[] = [ [2,3,4], [5,3,4], [1,2,3] ]
int MyCompare(void* a, void* b) {
key1 = ((int*)a)[2]
key2 = ((int*)b)[2]
return key1 - key2
}
qsort(MyList, MyCompare)
Type definitions aside, aside what we see here is that MyCompare is going to be called much more than get_key. In this simple case, obviously it doesn't matter much, but if you're sorting something big and retrieving/calculating the key is slow, you're going to retrieve and calculate the key way more often in C than in Python.
The reason this works only in a dynamic language is that a dynamic comparison function (MyCompare) in C++ is still not as dynamic as the key-retrieval function. The key retrieval function is a function who takes an arbitrary object as a parameter and returns an arbitrary object. Then the sort routine needs to arbitrarily compare two objects. You just can't do that in C without a whole lot of work.
[As an aside, I know yes you could whack the hell out of a sort routine in C to do all sorts of crazy casting but nothing would be as general as the Python case and your code would be hopelessly unmaintainable.]
In the end, I think Python wins this one. It even gets extra points for implementing a readable get_key in one line.