2

I'm learning how to program and handle Python classes and have found this problem.

I have this files (example.py) and inside this file I'm importing two classes.

example.py

..
   from class_folder.automobile import Class_Automobile
   from class_folder.catalog import Class_Catalog

   class_automobile = Class_Automobile()
   class_catalog = Class_Catalog()
..

Inside automobile.py have this code:

from pdo_mysql import Pdo_Mysql

class Class_Automobile(object):
    """
        Description
    """
    __pdo_mysql_intern = None

    def __init__(self):
        self.__pdo_mysql_intern = Pdo_Mysql('host', 'user', 'password', 'db')
    ## End def init

    def Validate_Make(self, make_search):
      query_make = 'make_text = %s '
      result  = self.__pdo_mysql_intern.select('make', query_make, 'id_make', make=make_search)
      return result
    ## End function Validate Make

And inside the catalog.py file has this information:

from pdo_mysql import Pdo_Mysql

class Class_Catalog(object):
    """
        Description
    """
    __pdo_mysql_intern = None

    def __init__(self):
        self.__pdo_mysql_intern = Pdo_Mysql('host', 'user', 'password', 'db2')
    ## End def init

And finally the pdo_mysql.py file has this:

    import MySQLdb, sys

    class Pdo_Mysql(object):
        """
            Description
        """

        __instance   = None
        __host       = None
        __user       = None
        __password   = None
        __database   = None
        __session    = None
        __connection = None


        def __new__(cls, *args, **kwargs):
            if not cls.__instance:
                cls.__instance = super(Pdo_Mysql, cls).__new__(cls,*args,**kwargs)
            return cls.__instance
        ## End __new__

        def __init__(self, host='localhost', user='root', password='', database=''):
            self.__host     = host
            self.__user     = user
            self.__password = password
            self.__database = database
        ## __init__

       def _open(self):
          try:
            cnx = MySQLdb.connect(self.__host, self.__user, self.__password, self.__database)
            self.__connection = cnx
            self.__session    = cnx.cursor()
          except MySQLdb.Error as e:
            print "Error %d: %s" % (e.args[0],e.args[1])
       ## End function open

      def _close(self):
        self.__session.close()
        self.__connection.close()
      ## End function close

      def select(self, table, where=None, *args, **kwargs):
         result = None
         query = 'SELECT '
         keys = args
         values = tuple(kwargs.values())
         l = len(keys) - 1

         for i, key in enumerate(keys):
            query += "`"+key+"`"
            if i < l:
                query += ","
         ## End for keys

         query += 'FROM %s' % table

         if where:
            query += " WHERE %s" % where
         ## End if where

        self._open()
        self.__session.execute(query, values)
        result = [item[0] for item in self.__session.fetchall()]
        self._close()
        return result
      ## End function select

When I run the example.py I have this problem, somehow when I call this

validate_information = class_automobile.Validate_Make(make)

I obtain this response

File "/Library/Python/2.7/site-packages/MySQLdb/cursors.py", line 205, in execute
    self.errorhandler(self, exc, value)
  File "/Library/Python/2.7/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
_mysql_exceptions.ProgrammingError: (1146, "Table 'db2.make' doesn't exist")

This problem it is because the db from Class_Catalog is mixing when I'm calling the Class_Automobile. It is solved when I put the Class_Catalog before the Class_Automobile but I want to know how to solve properlly.


Update:

Thanks to Adam Smith found the problem. I just had to change this file pdo_mysql.py:

class Pdo_Mysql(object):
    # all your existing definitions

    def __new__(cls, *args, **kwargs):
        **if not cls.__instance or not cls.__database:**
            cls.__instance = super(Pdo_Mysql, cls).__new__(cls,*args,**kwargs)
        return cls.__instance
3
  • 1). can you post code to the function Validate_Make() as well in the Class_Automobile()? 2). What is the argument make that you are passing into that function? 3). Does that parameter cover db2 database as well? Commented Dec 22, 2014 at 17:29
  • I think this could use a new title that more accurately describes the issue (which in this case seems to have something to do with how to correctly use the singleton pattern). Commented Dec 22, 2014 at 17:42
  • 1
    My suggestion: drop all that pile of code and rewrite from scratch following proper python idioms and coding standards. There are so many things that don't make sense that I really don't want to waste my time pointing all them out, to finally discover which bug is it that it creates the problems. Commented Dec 22, 2014 at 17:47

1 Answer 1

1

Here is what's happening. When you call class_automobile = Class_Automobile() a new Class_Automobile instance is created and the __init__ method is called on that instance. Inside __init__ you're calling Pdo_Mysql('host', 'user', 'password', 'db'). Pdo_Mysql.__new__ is invoked with Pdo_Mysql as the cls variable and ('host', 'user', 'password', 'db') as the args variable. Because this is the first time a Pdo_Mysql object has been requested the class variable __instance is None so your super call is run and the result is set on the Pdo_Mysql.__instance variable. This is a class level assignment. You then return this instance. If you look at the Python docs you'll see that :

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked

So now you've got the instance of Pdo_Mysql from the super __new__ call and you return it. The __init__ method will be called on it with the args and kwargs sent to __new__. In __init__ you set the various connection attributes.

All is fine at this point. If you were to make calls to your class_automobile instance they would work as you expected. Let's look at what happens when you make your next object, the instance of Class_Catalog.

It begins in the same way as Class_Automobile until we get to the self. __pdo_mysql_intern = Pdo_Mysql call. This time when Pdo_Mysql.__new__ is invoked the cls.__instance variable is set to an instance of Pdo_Mysql so you don't make a new one. BUT you still return this instance from __new__ so the __init__ method is still run on this instance but with new args and kwargs. The instance variables are set to the new values. So now the instances class_automobile and class_catalog have a __pdo_mysql_intern variable pointing to the same instance and that instance has __host, __user, __password, and __database set to whatever the last arguments were used to initialize an instance.

Now on to one possible solution...

It appears you're trying to do some kind of connection sharing so you don't open a new connection for each object that connects to a database. However you're only allowing one instance of Pdo_Mysql to ever exist. This means you cannot connect to more than one database and only as a single user.

You need a pool of connections (Pdo_Mysql objects) to choose from so that when you want to connect to another db or as a different user you can do so. In your Pdo_Mysql class you could change the __instance = None variable to __instances = {}. Use the args as the key to this dictionary and change the __new__ method to look up the instance using the args. Note that this ignores the kwargs, I leave it to you to figure out how to include them (hint, you cannot use mutable objects as keys in dictionaries). You'll also want to add a __initialized variable to the Pdo_Mysql class. Default it to False and set it to True in __init__. This was you can make sure you only run __init__ on each instance once.

Going a step further I would suggest you not use __new__ to create singletons but instead create a helper method in pdo_mysql.py called get_conenction or something. Have it take the same 4 arguments and it can check it's pool of instances for any that match and either return an existing connection or make a new one.

Sign up to request clarification or add additional context in comments.

1 Comment

Perfect, and far better than my half-hearted answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.