Search code examples
pythonsqlitecherrypy

Python method default argument initialization issue in CherryPy


I have a functional piece of code that works fine (in single-threaded environment)

class SQLEngine(object):
    def __init__(self, DB_PATH, referential_integrity=False):
        self.path = DB_PATH
        self.con = sqlite.connect(DB_PATH)
        self.cur = self.con.cursor()
        if referential_integrity:
          self.executeUpdate('PRAGMA foreign_keys = ON')   


class EntityAdapter(object):
    def __init__(self, database = SQLEngine('etc.db'):
        self.db = database

    def addFeature(self, feature_id, description):
        self.db.executeUpdateAndCommit('INSERT INTO features (id, description) values (?, ?)', (feature_id,description))

adapter = EntityAdapter()
adapter.addFeature(1, 'super feature')

However in CherryPy environment I see a very interesting phenomena, which I don't have an explanation for.

adapter = EntityAdapter() //Does not work
adapter.addFeature(1, 'super feature')

fails with

SQLite objects created in a thread can only be used in that same thread.The object was created in thread id ...

I understand the implications of using sqlite in multithreaded environment... but it means in the line below the default value is being assigned in a different thread

def __init__(self, database = SQLEngine('etc.db'):

The simplest fix for my issue is to provide an explicit db value

adapter = EntityAdapter(SQLEngine('etc.db')) //works
adapter.addFeature(1, 'super feature')

I was wondering how is it possible for default value to be calculated in a different thread?


Solution

  • In Python, default arguments to a function are bound when the function is defined, not when the function is called. This means that every time the init function is called with a default argument, the same instance of the database object is used. Therefore, if you construct multiple instances of EntityAdapter with no arguments, every single instance will refer to the same database object. What you want to do is the following.

    def __init__(self, database = None):
        if database is None:
            self.db = SQLEngine('etc.db')
        else:
            self.db = database