Distributing methods of a class across multiple files

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Jan 25 06:10:05 EST 2012


On Tue, 24 Jan 2012 19:54:24 -0800, lh wrote:

> Is this possible please?  I have done some searching but it is hard to
> narrow down Google searches to this question. What I would like to do
> is, for example:
> 1) define a class Foo in file test.py... give it some methods 
> 2) define a file test2.py which contains a set of methods that are
> methods of class Foo defined in test.py.

Technically, yes, this is possible, but you shouldn't need to do it. 
Needing to split a single class across multiple files is a sign of bad 
design. If the class is that huge, then it probably does too many things 
and should be broken up into multiple classes and then reassembled using 
composition.



[...]
> In short I would like to distribute code for one class across multiple
> files so a given file doesn't get ridiculously long.

What do you call "ridiculously long"? 

One of the largest modules in the Python standard library is decimal. I 
consider decimal to be about as big as a single module should get: over 
5000 lines of code. Any larger, and you should consider splitting it into 
a package with multiple files. 

But note that those 5000 lines include over 500 lines of comments, 
details documentation, plenty of blank lines, 14 public classes, 3 public 
functions, and at least 17 private functions or classes. The main class, 
decimal.Decimal, is about 2800 lines of code.

If your class is smaller than that, I don't think you need to worry about 
splitting it.

But let's suppose you really do have a good reason to split the class 
into multiple files. And not just "because that's how I'd do it in Java".

Suppose you have a class Spam, and it has two methods, spam() and eggs(). 
You want to split the methods into different files. (Perhaps you want to 
win a bet.) There are three main possibilities:

(1) Inheritance.

(2) Composition or delegation.

(3) Dynamic code injection.


Let's start with inheritance.

In module a.py, create a class:

class EggMixin:
    def eggs(self):
        c = self.colour
        print("%s eggs go well with %s spam" % (c, c))

Notice that as a mixin, EggsMixin isn't required to provide the 
self.colour attribute.

Technically it isn't necessary for this to be a mixin, but it is probably 
the best design.

Now in module main.py, create the Spam class you really want, using 
multiple inheritance to inherit from the "real" superclass and the mixin:

import a
class Spam(SpamParent, a.EggMixin):
    def __init__(self, colour='green'):
        print("Spam spam spam LOVELY SPAM!!!")
        self.colour = colour
    def spam(self, n):
        return "spam!"*n


By the way, SpamParent is optional. If you don't need it, just leave it 
out.


Now, on to composition. First, let's redesign the egg class in a.py:

class Egg:
    def __init__(self, owner):
        self.owner = owner
    def eggs(self):
        c = self.owner.colour
        print("%s eggs go well with %s spam" % (c, c))


And in main.py:

import a
class Spam(object):
    def __init__(self, colour='green'):
        print("Spam spam spam LOVELY SPAM!!!")
        self.colour = colour
        # Create an Egg that has this Spam instance as the owner.
        egg = a.Egg(owner=self)
        # And store it for later use.
        self._egg = egg
    def spam(self, n):
        return "spam!"*n
    def eggs(self):
        # Delegate to the saved egg.
        return self._egg.eggs()


Last but not least, lets try dynamic code injection. In a.py, create a 
function using a placeholder self parameter:


def eggs(self):
    c = self.colour
    print("%s eggs go well with %s spam" % (c, c))
    

And here is your main module:


import a
class Spam(object):
    def __init__(self, colour='green'):
        print("Spam spam spam LOVELY SPAM!!!")
        self.colour = colour
    def spam(self, n):
        return "spam!"*n

# Add the extra method that we want.
Spam.eggs = a.eggs


So now you have three ways of doing something that shouldn't be done :)


-- 
Steven



More information about the Python-list mailing list