One class per file?

Matimus mccredie at gmail.com
Mon Sep 29 19:17:59 CEST 2008


> The book "Code Complete" recommends that you put only one class in a
> source file, which seems a bit extreme for me. It seems that many
> classes are small, so that putting several of them in a file seems
> reasonable. I noticed that the decimal.py module in the standard
> library has several classes, all of which of course revolve around the
> "decimal" topic. Perhaps a better rule of thumb is "one idea per
> file." I checked the Python style guide and there seems to be no
> mention of this topic. I know this is an elementary question, but what
> is the Python way of doing this?


I'm in a similar situation. I've been reading Robert C. Martins book
"Agile Software Development" and he suggests something similar. I
would highly recommend that book by the way. He also has a couple of
chapters on packaging where he makes some very good points about the
reasoning behind it. Basically that you want to organize your code in
such a way that package dependencies move in the direction of
increasing stability. In this case stability is a metric which is
defined as how likely the code is going to change in the future as
determined by how many packages depend on it as opposed to how many
packages it depends on. He paints a couple of scenarios in which he
groups packages together that _seem_ to be related, only to see that
it results in all of his packages needing to be re-built every time a
change is required. Obviously we don't have to re-build python code,
but it is still useful to organize your code in such a way that you
don't have to constantly re-visit collections of code.

I have actually started using his suggestion and have been putting one
class per file. When one class depends on another I include it with a
"from x import X". This makes it very easy to determine the
dependencies (well, the non-abstract dependencies anyway, interfaces
are a different story*). Then I put all of my classes together in a
package, and make the "public" classes visible on a package level by
importing them into the package __init__ module.

With that being said, If I made a class that was _only_ used by one
other single class, and it was clear that it would never be made
visible outside of that file, I would certainly put it in the same
file as that class. Heck, you might even refactor your code and
determine at that time that some piece of code is only used by one
other piece. It is much easier to put code together after the fact
than it is to separate it, especially later in the game.

My advice: don't knock it until you try it. I think my code has
improved quite a bit since taking this advice. It can be much more
difficult to determine which classes to package together until much
later in the development cycle. One thing that can help is to find an
IDE that helps you to easily switch between files. I use WingIDE, but
I have even used vim with a tags file and it wasn't terrible. I
wouldn't call it a hard rule, but at the same time I wouldn't just
ignore it. Give it a shot and adapt your development technique to see
what works best for you.

Example:

[mypackage/__init__.py]
__all__ = ["X"]

from .x import X
[end mypackage/__init__.py]

[mypackage/x.py]
from .y import Y

__all__ = ["X"]

class X(object):
   # code - using Y hopefully
[end mypackage/x.py]

[mypackage/y.py]
__all__ = ["Y"]

class Y(object):
   # code
[end mypackage/y.py]


Matt



* Interfaces in python represent an implicit dependency. The Zen of
Python states: "Explicit is better than implicit". I plan to
experiment with the ABC module in python 2.6/3.0. I want to make my
interfaces explicitly defined, but on the other hand I still want it
to be easy for people who use my code to duck-type. This _seems_ to be
possible since you can .register a class with an interface after it
has been created, but I'm not sure that it is very pythonic to
explicitly check for an interface every time it is used. It would seem
silly however to import an interface into a file where it isn't
explicitly used just to document the dependency. If anybody has
pointers let me know.



More information about the Python-list mailing list