Too Much Abstraction
Object Oriented & Duck Typed
If you're not familiar with Python, you owe yourself a few minutes to check it out. It's a beautiful language that lets you get stuff done quickly, with little ceremony.
That being said, I just can't understand why one would want to write object-oriented code in the language, even given that it really pushes you to write in the OO-style. The primary reasons for writing classes (the poor man's types) is so that you can abstract away a a piece of information, or create a new data type.
The latter use makes sense, though most of the structures you'd want to use in Python are there, and the ones that aren't are generally better implemented in C. The former use mostly results in boilerplate code. You don't get the beautiful code that results from having static types; no, the language is duck-typed. You have no safety, and no gentle enforcement. Then you have the normal sins of OO: you have your getters, you have your setters, and then you have some domain-specific query and processing methods. In this case, your domain is your class. And what a sad, small domain that is.
A slightly more appealing option is to inherit from core data structures like UserDict. But you're still writing more code than you need to be.
Instead, use the core data structures.
Data as Data
I recently found myself need to convert from X arbitrary unit to Y arbitrary unit (and so forth) defined by a text file of equalities. A sample:
4 foo = bar
bar = 3 baz
baz = 2 nar
quark = 7 cthulu
foo = f
bar = b
f = 15 hur
The idea was to allow me to convert any arbitrary amount of one unit to another, even if it meant many conversions in between. Say, for instance, that I wanted to see how many nars 27 foos was. You could eye the relatively simple list above and see that you'd need to follow the unit path of foo > bar > baz > nar, and apply scalar transformations to the original amount along the way. The transformation looks like this: 27 foo > 6.75 bar > 20.25 baz > 40.5 nar.
But how to represent that in Python? A directed, weighted graph seemed like the best choice to me, so that's what I implemented. The direction indicated the direction of the conversion, with the weight being the scalar by which the units differ by.
A graph is a set of two sets. The first is a set of nodes, the second is a set of edges. I decided to use a tuple of sets, one of nodes and one of edges. Nodes are strings representing the units (e.g. foo, bar), and edges are tuples of edges and the conversion weight (i.e. (foo, bar, 1/4)). Here's how I wrote that in my code:
"""
graph = (set(node), set(edge))
node = String
edge = (node[origin], node[dest], Fraction(C))
where C is the fractional conversion factor between node[origin] and
node[dest]
"""
Note that it's a comment. Every method I need to access and manipulate my data is already there, and everyone who might read my code is familiar with them. I could provide a simple interface if I wanted (e.g. nodes() or edges()), but that would be a little superfluous. Alternatively, this is how I could have described by structure if I had been writing OO code:
class Graph(Object):
def __init__(self):
self.nodes = set()
self.edges = set()
def nodes(self):
return self.nodes
def edges(self):
return self.edges
def addEdge(self, src, dst, c):
self.edges.add(src, dst, c)
def addNode(self, node):
self.nodes.add(node)
Which appears to be the same thing to me, though obfuscated and verbose, not to mention a bit slower.
Hedging
Maybe I'm way off the mark here, and the increasing amount of functional programming I have been exposed to has tainted my otherwise pristine intellect. But why hide the data structures behind your datastructure any more than necessary? And in this case, the obfuscation only serves to name the actions being carried out in the context it takes place.
It's important to note, lest a strawman be made, that this post isn't meant to disparage the abstractions that classes, types, and interfaces provide a programmer. Nor should it apply in the case of an API, where an interface should be specific and clear.
Instead, I find myself struggling and questioning the need for what I perceive to be ineffectual abstractions. I suppose much of depends on just how much power you're comfortable giving yourself and whoever may use your code, and how much you need to hide in order to keep the code grokable.