2

我有一个程序可以从 CSV 文件或数据库中读取对象(任务)。这两个来源的共同点是您必须在使用资源后明确关闭对资源的访问。

我遵循使 CSV 和 DB 类都可迭代的方法,因此迭代它们会返回任务。这对于使用它们很方便,但是我不相信它是干净的,并且我有以下问题:

  • 访问文件或数据库的最佳方法是什么?我为此使用构造函数,但我不确定多线程等...
  • 关闭资源(文件,光标)的最佳方法是什么。外部对象是否应该通知访问完成,或者 CSV 或 DB 对象是否应该检测到我们位于文件末尾并关闭它?

我不确定我做对了(它适用于单次运行,但我的意思是这要插入网站,因此可以进行多次访问)

class CSV(AbstractDAO):
    def __init__(self, sourcePath):
        self.sourcePath = sourcePath
        self.csvFile = codecs.open(sourcePath, 'rb', 'UTF-8')

    def __iter__(self):
        return self

    def next(self):
        return self._buildTaskFromLine(self.csvFile.next())

    def deleteAllTasks(self):
        pass

    def loadTask(self, taskID):
        csvFile = codecs.open(self.sourcePath, 'rb', 'UTF-8')
        for line in csvFile:
            taskValues = line.split(";")
            if taskValues[0] == unicode(taskID):
                return self._buildTaskFromLine(line)
            else:
                return None

    def saveTask(self, task):
        pass

    def loadPredecessorsID(self, task):
        csv = codecs.open(self.sourcePath, 'rb', 'UTF-8')
        for line in csv:
            taskValues = line.split(";")
            if taskValues[0] == unicode(task.id):
                return taskValues[2].split(",")
        return None

    def _buildTaskFromLine(self, line):
        taskValues = line.split(";")
        taskID = taskValues[0]
        taskName = taskValues[1]
        taskAncestors = taskValues[2]
        taskDuration = int(taskValues[3])
        return Task(taskID, taskName, taskDuration)

这是数据库实现

class SQLite(AbstractDAO):
    def __init__(self, sourcePath):
        self.connection = sqlite3.connect(sourcePath)
        self.cursor = None

    def __iter__(self):
        self.cursor = self.connection.cursor()
        self.cursor.execute("select * from Tasks")
        return self

    def next(self):
        if self.cursor is not None:
            row = self.cursor.fetchone()
            if row is None:
                self.cursor.close()
                raise StopIteration
            else:
                return self._buildTaskFromRow(row)

    def deleteAllTasks(self):
        cursor = self.connection.cursor()
        cursor.execute("delete from Tasks")
        self.connection.commit()
        cursor.close()

    def loadTask(self, id):
        cursor = self.connection.cursor()
        param = (id,)
        cursor.execute("select * from Tasks t where t.id = ? ", param)
        taskRow = cursor.fetchone()
        task = self._buildTaskFromRow(taskRow)
        cursor.close()
        return task

    def saveTask(self, task):
        cursor = self.connection.cursor()
        param = (task.id,)
        cursor.execute("select * from Tasks t where t.id = ? ", param)
        taskRow = cursor.fetchone()
        if taskRow is None:
            param = (task.id, task.name, task.duration)
            cursor.execute("insert into Tasks values (?,?,?)", param)
            self.connection.commit()
            cursor.close()

        else:
            param = (task.id, task.name, task.duration)
            cursor.execute("update Tasks \
            set description = ?, duration = ?  \
            where id = ? ", param)
            self.connection.commit()
            cursor.close()

    def loadPredecessors(self, task):
        pass

    def _buildTaskFromRow(self, row):
        taskId = row[0]
        taskName = row[1]
        taskDuration = row[2]
        return Task(taskId, taskName, taskDuration)

最后,上面的代码例如被 ma TaskTree 这样调用(它是一个包含所有任务的对象)

def loadTreeFrom(self, source, sourcePath):
    if source not in ('CSV', 'DB'):
        raise AttributeError('Unknown source : supported sources are CSV or DB')

    dao = None
    if source == 'CSV':
        dao = CSV(sourcePath)
    elif source == "DB":
        dao = SQLite(sourcePath)

    #populate the tasks first
    for task in dao:
        self.tasks[unicode(task.id)] = task

    # then populate the dependencies
    for item in self.tasks.iteritems():
        ancestorsID = dao.loadPredecessorsID(item[1])
        self.addDependencies(item[1], ancestorsID)
4

1 回答 1

2

这是对您的问题的一种侧面回答,但根据您的描述,我认为您应该考虑将这些对象变成context manager。这样,您可以简单地使用with块,而不是让您的“外部对象通知访问完成”。进入块时,__enter__会调用上下文管理器对象的方法;当它退出时,你的上下文管理器对象的__exit__方法被调用。这是一个(非常)简单的例子:

>>> class DummyManager(object):
...     def __enter__(self):
...         print 'entering with block!'
...         return 'foo'
...     def __exit__(self, exc_type, exc_val, exc_tb):
...         print 'exiting with block!'
...         print 'this is exception info, if an exception was raised:'
...         print exc_type, exc_val, exc_tb
... 
>>> with DummyManager() as dummy:
...     print dummy
... 
entering with block!
foo
exiting with block!
this is exception info, if an exception was raised:
None None None

这是迭代对象让您的 DB/CVS 对象知道不再需要它的好方法。

老实说,我不知道你应该如何处理并发访问等——因为我不知道你的整体设计。但是__enter__and__exit__方法可能是处理锁等的好地方,如果你需要的话。

基于此系统重构类的一种方法是编写方法,假设资源是打开的,而不是一直打开和关闭它。然后总是引用with块内对象的实例;该with语句负责初始化和打开资源,并在控制离开块时关闭它们。因此,例如,您的loadTaskandsaveTask方法将不再需要x.open(...)andx.close()开头和结尾的行,并且只要正在使用资源,它就会完全打开。

如果您愿意,您可以创建公共的打开和关闭方法,然后只需拥有__enter____exit__调用它们。然后您的用户(或您)可以决定是使用 with 块还是以经典样式打开和关闭对象。无论哪种方式,对象的行为都类似于 Python 中的文件。(我有没有提到文件也是上下文管理器?)

根据您的新代码,您将调用资源,如下所示:

def loadTreeFrom(self, source, sourcePath):
    if source not in ('CSV', 'DB'):
        raise AttributeError('Unknown source : supported sources are CSV or DB')

    dao = None
    if source == 'CSV':
        dao = CSV            # dao just a reference to the class now
    elif source == "DB":
        dao = SQLite

    with dao() as tasks:
        #populate the tasks first
        for task in tasks:
            self.tasks[unicode(task.id)] = task

        # then populate the dependencies
        for item in self.tasks.iteritems():
            ancestorsID = dao.loadPredecessorsID(item[1])
            self.addDependencies(item[1], ancestorsID)

现在,当您调用时loadPredecessorsID,它不会每次都打开和关闭资源。

于 2012-04-22T14:05:15.267 回答