There is an obvious trick to avoiding self hell: don't ever use self
.
A good friend of mine once described two complaints about Python, the more serious of the two being self hell. Self hell is usually something Pythonistas grumble about from time to time. Basically, you find that every line in your program begins with self
. Every other word in the program is self
. For a language without needless braces, begins, ends or other useless syntax, the over abundance of a single token really stands out. This is self hell.
Don't ever suggest that to an experienced python programmer. If t...
Chain of implications: No self
means no classes. No classes means no object orientedness. If you were a Java or C++ programmer once, you probably think I am a crackpot.
There is more to life than OOP. Here is a simple little class, whose only purpose is window comparison.
class Window(object):
def __init__(self, minimum, maximum):
self.minimum = minimum
self.maximum = maximum
def __call__(self, x):
return self.minimum <= x <= self.maximum
Does not seem so bad.
Self
requires on average five characters per line, nothing horrible.
And here is it in use:
>>> between = Window(1,5)
>>> between(2.4)
True
>>> between(-7)
False
>>> between(12)
False
Now, let's say you need to add a feature. Defensively protect the input so that minimum
and maximum
may arrive out of order.
class Window(object):
def __init__(self, minimum, maximum):
self.minimum = minimum
self.maximum = maximum
self.minimum, self.maximum = min(self.minimum, self.maximum), max(self.minimum, self.maximum)
def __call__(self, x):
return self.minimum <= x <= self.maximum
You can avoid the initial assignment and just do:
self.mini...
That newest line sums up what self hell is all about. It could be mitigated by swapping min
& max
before saving them to self.min
& self.max
, but eventually you'll need a line like this outside of init
, and there will be no other course but the gratuitous use of self
.
Let's remake it using closures. Closures, in a nutshell, allow functions to be defined on the fly while remembering the function's scope. Note this code already includes the defensive variable swap.
def Window(minimum, maximum):
def w(x):
return minimum <= x <= maximum
minimum, maximum = min(minimum, maximum), max(minimum, maximum)
return w
Self
never appears once. The use case is identical. It is smaller (quantitatively) and more elegant (qualitatively).
But what the heck is up with the nested def
s?
w()
is called an inner function. If you've never heard of it before, don't feel too bad, lots of really smart people have not either. Why, until recently Vim did not know how to automatically indent such code.
The closure part is not having to explicitly pass minimum
or maximum
to w()
. When python tries to execute w()
, it looks for min
and max
inside of w()
. It does not see them, so it checks the next scope up, Window()
. The double checking costs a few CPU cycles but saves a lot of keystrokes.
Some claim objects are needed for storing changing state between calls. Not so. For example, say you have a prime number generator. Every time you instance it, the generator starts over from 2, the first prime. Every time next()
is called, the generator returns the next prime. Simple enough.
A smarter prime number generator would cache the previous results, so each new instance does not have to recompute every number from the very beginning. Normally this screams "Use an object!". It can all be done with a single function:
def primes(p_cache=[2]):
for i in p_cache:
yield i
for i in count(p_cache[-1] + 1):
sqr = int(i**0.5) + 1
if all(i%p for p in takewhile(lambda x: x<sqr, p_cache)):
p_cache.append(i)
yield i
Ever been bitten by a Python's default argument behavior? This hack uses that classic gotcha to update the p_cache
list. It is not the most idiomatic code. While it proves you can do almost anything with just functions, it is also proof I should have my Python Licence revoked.
There are two cases were defining a class is pretty much unavoidable: defining new data structures and changing Python's syntax. If you want to make a Tree type, you'll need to class it. If you want to do stuff like this:
About a Tree type: Linux kernel developers (in C) and also Haskell...
>>> 'Do a barrel shift!' << 5
'barrel shift!Do a '
You'll have to make a new String
object inheriting from the old String
, extended with lshift
and rshift
.
I've found that a hybrid approach works best. All of your class methods that actually do computation should be moved into closures, and extensions to clean up syntax should be kept in classes. Stuff the closures into classes, treating the class much like a namespace. The closures are essentially static functions, and so do not require self
s*, even in the class.
-
Self
s, plural. Functions still need oneself
, in the function definition.
If it's private code then
my' instead of
self' is 50% shorter an...