Hello everyone. I am currently making some database connection modules in Python inb order to learn Python's approach to OOP and also to lear how to connect to databases via Python. I successfully made a package with modules for postgres, mysql, sqlite3, and MongoDB. The classes simply connect, disconnect and run queries of any type. Everything is working fine now.

However I noticed that the code is nearly the same. For example, the connect and disconnect methods are exactly the same. Also, postgress and mysql connections have exactly the same number of attributes. Inheritance came to mind, but some classes have less attributes. For example, my sqlite3 class has less attributes because this connection doesn't care about server, port, username or password.

So, what should I do? I can use inheritance but how do I remove the unwanted attributes? Should I make an abstract class stating the methods (they are nearly the same for all subclasses) but no attributes?

Recommended Answers

All 16 Replies

I think you can follow the simple rule that code which is nearly the same in 2 classes is a good candidate to be written in a common superclass for these 2 classes. You can build a hierarchy based on this principle.

The principle can be applied to attributes too, but there is some freedom here. An attribute is easily added in the __init__'s method body. I mean that an attribute may be defined in several subclasses without being defined in the ancestor classes. It does not change many lines of code to do so.

Abstract classes in the sense of the abc module should be used seldom and with good reasons to do so. Used differently, they may get in the way and clutter the code without significant profit. Instead of this, you can very easily add a method such as

class BaseFoobar(object):

    def qux(self, i, j, k):
        raise NotImplementedError(self)

Such pseudo abstract methods are very useful and don't get in the way. They trigger the right exception when something is missing in your code.

I see. I think I'll go with the simple superclass-subclass model. BUt there is then one question left: if the superclass has attributes A,B,C and D, but I only need A and B, what should the child class do to not inherit C, and D?

Look at this code:

class MySQLConnection:

    def __init__(self, a_server, a_database, the_user, the_password):

        self.server_ip = a_server
        self.database = a_database
        self.user = the_user
        self.password = the_password

        self.connection = None
        self.cursor = None
        self.error = None

And the this one:

class SQLite3Connection:

    def __init__(self, db_path):

        self.path_to_database = db_path

        self.connection = None
        self.cursor = None
        self.error = None

The second class needs only three of the attributes of the first one, and adds a new one (the connect method initializes the = None attributes later). When I inherit, will the child class inherit the useless attributes too? Should I write a new constructor for the child class? If I do so will it forget the inherited attributes?

If the child class has only attributes A and B, then the superclass should not have attributes A B C D. If it is not the case, it means that your model is incorrect. Remember that the relation of a subclass is the IS A relation. So if a Socrates instance IS A Man instance and men have a .mortal attribute, then a Socrates has a .mortal attribute.

What you can do here is a common ancestor with the 3 attributes

class BaseConnection(object):
    def __init__(self):
        self.connection = None
        self.cursor = None
        self.error = None

class MySQLConnection(BaseConnection):

    def __init__(self, a_server, a_database, the_user, the_password):

        BaseConnection.__init__(self)

        self.server_ip = a_server
        self.database = a_database
        self.user = the_user
        self.password = the_password

class Sqlite3Connection(BaseConnection):

    def __init__(self, db_path):

        BaseConnection.__init__(self)
        self.path_to_database = db_path

Edit: Notice how a Sqlite3 connection IS NOT A MySQL connection !

Hmm, I think I got it. If inheritance is a specialization, then my most general class should be the parent. So it goes like this:

Base (common attributes and methods) -> MySQL | Sqlite.

Now there is one issue left: I have a postgres connection which looks exaclty like a mysql connection:

The MySQL connection:

class MySQLConnection:

    def __init__(self, a_server, a_database, the_user, the_password):


        self.server_ip = a_server
        self.database = a_database
        self.user = the_user
        self.password = the_password

        self.connection = None
        self.cursor = None
        self.error = None

The postgres class:

class PostgresConnection:

    def __init__(a_server, a_database, the_user, the_password):

        self.server_ip = a_server
        self.database = a_database
        self.user = the_user
        self.password = the_password

        self.connection = None
        self.cursor = None
        self. error = None

They are twins! Except for the fact that their connect, disconnect and run_query methods use very similar but not equal code to perform those actions. A PostgreSQL connection IS NOT a MySQL connection, although they look really similar in that they have the same basic attributes BESIDES the base attributes of any connection.

What should I do then? Another base class that adds the host, database, user and password attributes and that is the aprent of postgres and mysql? Or simply leave them as twins?

There is no general rule. You can make another base class which would inherit the first base class, or you can leave them as twin classes.

I think twin methods between 2 classes are a much more serious reason to build common ancestors than common attributes. Don't hesitate to build intermediary classes. Python is very flexible and it is easy to copy and paste a method from one class to another if you change your mind later.

Hmm, I'm currently creating the intermediate pseudoabstract classes. I really need the attributes, so question: should the NotImplementedError be raised by the constructor too? Or should I have the constructor create the attributes I want so that child classes can inherit them?

The constructor must not raise NotImplementedError because the constructor must be called by the subclasses. You could give the attributes a default value such as None or NotImplemented for example.

Also the pseudo abstract class can implement some methods, raise NotImplementedError on other methods, etc.

Thank you very much. I have made several pseudoasbtract classses with NotImplementedError and NotImplemented. I notice that I went from very gneral classes to very specific classes, so I think it's correct. I'll write the final classes later and let you know of the final result (I hope i don't come across any errors in the nonabstract classes.)

Great, I'm looking forward to seeing your classes. If all this doesn't suffice, there are other tools such as multiple inheritance and mix-in classes, but remember the zen of python:

Simple is better than complex.
Complex is better than complicated.

Here is my class diagram. Took me a while to upload it due to technical issues with my computer, but here it is. The classess at the bottom are non-abstract. The other ones (5) are abstract. The run_query and connect methods are all different for each of the bottom classes. The disconnect() method is exactly the same, so I implemented it in the BaseConnection class.

One thing to note is that the concrete classes have no new attributes, it's the implemented methods that make them different from their parents and siblings.

ClassDiagram.jpg

It looks nice. How did you produce the UML diagram ?

No, I don't see any problem. The most annoying point is that the query language is database-specific. Later on, you may feel the need for methods performing some queries in a DB-independent way.

Yes, that's true. I think that later on I'll have to use some sort of ORM to solve that problem.

Not necessarily. The drawbacks of ORMs is that they are heavy, and often slow. For example on a concrete problem, I prefer by far to use the mysqldb module than sqlalchemy. What you can do is specialize some of your classes for a specific problem with only relevant requests implemented.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.