Sqlalchemy 之上的抽象层

Nyx*_*nyx 0 python sqlalchemy python-3.x

我在一个使用 Sqlalchemy 的 Python 3.6 项目中,我们希望在 Sqlalchemy 上有另一个抽象层。如果需要,这将使我们能够更轻松地将 Sqlalchemy 替换为另一个库。

在这个例子中,它是DbHelper类:

数据库助手.py

from dbconn import dbconn
from models.animals import Dogs

class DbHelper():
    @staticmethod
    def get_dog_by_nickname(nickname):
        return dbconn.session.query(Dogs).get(nickname)
Run Code Online (Sandbox Code Playgroud)

主文件

from dbhelper import DbHelper

class Dog():
    def __init__(self, nickname, breed, age):
        self.nickname = nickname
        self.breed = breed
        self.age = age

    @classmethod
    def using_nickname(cls, nickname):
        row = DbHelper.get_dog_by_nickname(nickname)
        return Dog(row.id, row.breed, row.age)

dog = Dog.using_nickname('Tom')
Run Code Online (Sandbox Code Playgroud)

问题:有没有比创建DbHelper类用作容器并只staticmethod在其中包含更好的方法?我读过这不是pythonic。

当我们这样做时,将所有staticmethod函数转换dbhelper.py为常规方法将填充命名空间from dbhelper import *

aba*_*ert 5

是的,有一个比创建一个充满staticmethods的类更好的解决方案:只是不要创建这个类,并将它们全部设为模块级函数:

from models.animals import Dogs

dbconn = ... 

def get_dog_by_nickname(nickname):
    return dbconn.session.query(Dogs).get(nickname)
Run Code Online (Sandbox Code Playgroud)

一个充满staticmethods的类的唯一真正意义是为所有这些函数提供一个命名空间。一个模块已经所有这些函数的命名空间。而且,因为它更直接地被语言语法支持,它意味着当你想明确地这样做时,你可以更容易地绕过命名空间(例如,from dbhelper import ...)。


你说:

当我们这样做时,将 dbhelper.py 中的所有静态方法函数转换为常规方法将填充命名空间 from dbhelper import *

但答案很明显:不要from dbhelper import *,只是import dbhelper。然后,您将使用的所有代码都DbHelper.spam()变为dbhelper.spam().

如果你真的想要两级命名空间,你可以只使用带有子模块的包,而不是带有类的模块。但是我看不出有什么好的理由需要在这里使用两级命名空间。


另一种选择(如 juanpa.arrivillaga 在评论中所建议的)是将其变成一个真正的类,其中每个实例(即使您的实际代码中可能只有一个)都有自己的实例,self.dbconn而不是使用全局模块。这dbconn既可以传递到__init__,也可以直接在 内部构造__init__。例如:

class DbHelper:
    def __init__(self, dbname, otherdbparam):
        self.dbconn = dblib.connect(dbname, otherdbparam)
    def get_dog_by_nickname(self, nickname):
        return self.dbconn.session.query(Dogs).get(nickname)
Run Code Online (Sandbox Code Playgroud)

请注意,我们正在使用普通方法,并访问普通实例变量。这就是类的用途——将某些状态与转换该状态的方法包装在一起。

你如何在两者之间做出决定?好吧,如果dbconn每个进程只有一个,那么它们在功能上是等效的,但在概念上它们具有不同的含义。如果您将 DbHelper 视为一个数据库,无论是连接还是数据库行为,它都应该是一个类,并且您应该实例化该类的一个实例并以这种方式使用它。如果你认为它只是一堆辅助函数,它们运行在一个独立存在的 dbconn 上,那么它应该是一个扁平模块。


在某些语言(如 Java)中,使用充满 -staticmethod等价物的类还有另外一点:该语言要么不支持“自由函数”,要么使它们成为与方法完全不同的东西。但这在 Python 中并非如此。


当我们这样做时,您是否希望您的模块导出Dogsdbconn作为界面的“公共”部分?如果没有,您应该__all__在模块顶部添加一个规范,如下所示:

from models.animals import Dogs

__all__ = [
    'get_dog_by_nickname',
    ...
]

dbconn = ... 

def get_dog_by_nickname(nickname):
    return dbconn.session.query(Dogs).get(nickname)
Run Code Online (Sandbox Code Playgroud)

或者,用下划线命名所有“私有”模块成员:

from models.animals import Dogs as _Dogs

_dbconn = ... 

def get_dog_by_nickname(nickname):
    return _dbconn.session.query(_Dogs).get(nickname)
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,您的模块的用户仍然可以访问“私有”数据,但它不会显示在 from dbhelper import *help(dbhelper)、许多 IDE 中的默认自动完成等中。