1

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?

bioffe
  • 6,283
  • 3
  • 50
  • 65
  • 2
    Yet another victim, in yet another unique way, of The Mutable Default Argument Trap: http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument?rq=1 – Mark Amery Jul 03 '13 at 22:18

1 Answers1

2

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
Zhehao Mao
  • 1,789
  • 13
  • 13
  • does it mean that default value is being evaluated at class loading time? – bioffe Jul 03 '13 at 22:18
  • 1
    @bioffe It's at the time that the function object that has the default argument is created. So, yeah. – Mark Amery Jul 03 '13 at 22:19
  • It's bound at the time the function is created. Since it is a method, this would be at the same time the class is created. I will edit my answer to clarify. – Zhehao Mao Jul 03 '13 at 22:20