https://us.pycon.org/2012/schedule/presentation/352/
Abstract
Classes must be nouns but not every noun must be a class. If your class only has two methods and one of them is init you probably meant to write a function.
MuffinMail recently refactored their API; it went from 20 classes scattered in 22 modules down to 1 class just 15 lines long. It was a welcome change, but we’ll further refactor that down to a single function 3 lines long.
The Python stdlib is an example of a namespace that is relatively flat. You won’t find packages that consist of a single module defining an exception, and you won’t find many exceptions at all – just 165 kinds in 200k lines of code. That’s a tiny ratio compared to most projects including Django.
Of course there are things, like containers, that should be classes. As a final example we’ll add a Heap type to the heapq module (admit it, you already have one in your utils.py).
Notes
A Greeting class
- Looks like a class.
- Name is a noun.
- Takes arguments and stores data in
init
. - Has a method that uses that state to do something else.
- Instantiates a
Greeting
, then uses it do something.
Key finding that this is not a class is that it has two signatures and one is init.
The class is instantiated just once, then thrown away.
This class can be better written as a function.
If you find that you’re always passing the same argument to that function, use functools.partial
.
Benefits of classes?
- Separation of concerns
- Decoupling
- Encapsulation
- Implementation Hiding
If someone pulls out these terms, be careful.
Classes are overused because then think they might need it later (like MuffinHash
below). They don’t. Or if they do, they should just make a class later.
What’s wrong with this class?
The problem: Just two methods, one of which is init
. Not even trying to hide it: the other method is call
.
MuffinAPI.API(key='SECRET_API_KEY').call(('Mailing', 'stats'), {id: 1})
can be aliased like
MuffinAPI.request = API(key='SECRET_API_KEY').call
and used like the function it should have been
MuffinAPI.request(('Mailing', 'stats'), {id: 1})
This is when you know that you shouldn’t have been using a class.
Here’s what the API request function should look like:
Namespaces are not for creating taxonomies.
Namespaces are for preventing name collisions.
If you’re just using a class to hold functions together, with no need to allow the user to decide the implementation of a function (e.g. with inheritance and overridden methods), just use a module. Use a class if you actually need a taxonomy.
Unnecessary custom exception classes
services.crawler.crawlerexceptions.ArticleNotFoundException
- requires typing ‘crawler’ and ‘exception’ twice each! <- excessive!
- EmptyBeer or BeerError or BeerNotFound
- but not EmptyBeerNotFoundError <- excessive
- but why not LookupError? <- just use standard library exceptions, except for specific conditions
Adding the word Exception
to the name doesn’t help. Anything after a raise
or except
is going to be an exception.
The Python Standard Library
- 200K SLOC
- 200 top loevel modules
- averages 10 files per package
- defines just 165
Exception
s <- anytime you think you need to define an exception, remember that thestdlib
only needs 165 for 200K SLOC
Sometimes you do want a class
- containers are a great use case for classes
heapq
(an array that always stays sorted) is astdlib
container without a class. But all 10 functions operate on the samedata
object, implying that it should be a class.
Classes encourage atrocities
- Oauth2 Implementation
- http://code.google.com/p/google-api-python-client/source/browse/
- 10K SLOC, 115 modules, 207 classes
Conway’s Game of Life
This all looks really good. But this is a class with two functions, one of which is init
.
That’s the whole thing.
Conclusions
- Don’t make classes with just one function that’s not init.
- Don’t make new Exceptions when you don’t need to.