Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中管理连接创建?_Python_Design Patterns_Architecture_Database Connection - Fatal编程技术网

在Python中管理连接创建?

在Python中管理连接创建?,python,design-patterns,architecture,database-connection,Python,Design Patterns,Architecture,Database Connection,应用程序通常需要连接到其他服务(数据库、缓存、API等)。出于理智和干爽的考虑,我们希望将所有这些连接保留在一个模块中,以便我们的代码库的其余部分可以共享连接 为了减少样板文件,下游使用应简单: # app/do_stuff.py from .connections import AwesomeDB db = AwesomeDB() def get_stuff(): return db.get('stuff') # app/cli.py or some other main ent

应用程序通常需要连接到其他服务(数据库、缓存、API等)。出于理智和干爽的考虑,我们希望将所有这些连接保留在一个模块中,以便我们的代码库的其余部分可以共享连接

为了减少样板文件,下游使用应简单:

# app/do_stuff.py
from .connections import AwesomeDB

db = AwesomeDB()

def get_stuff():
    return db.get('stuff')
# app/cli.py or some other main entry point
from .connections import AwesomeDB

db = AwesomeDB()
db.init(username='stuff admin')    # Or os.environ['DB_USER']
设置连接也应该很简单:

# app/do_stuff.py
from .connections import AwesomeDB

db = AwesomeDB()

def get_stuff():
    return db.get('stuff')
# app/cli.py or some other main entry point
from .connections import AwesomeDB

db = AwesomeDB()
db.init(username='stuff admin')    # Or os.environ['DB_USER']
Django和Flask这样的Web框架做了类似的事情,但感觉有点笨重:

这样做的一个大问题是,我们希望引用实际的连接对象,而不是代理,因为我们希望在iPython和其他开发环境中保留制表符完成

那么什么是正确的方法(tm)呢?经过几次迭代后,我的想法如下:

#app/connections.py
from awesome_database import AwesomeDB as RealAwesomeDB
from horrible_database import HorribleDB as RealHorribleDB


class ConnectionMixin(object):
    __connection = None

    def __new__(cls):
        cls.__connection = cls.__connection or object.__new__(cls)
        return cls.__connection

    def __init__(self, real=False, **kwargs):
        if real:
            super().__init__(**kwargs)

    def init(self, **kwargs):
        kwargs['real'] = True
        self.__init__(**kwargs)


class AwesomeDB(ConnectionMixin, RealAwesomeDB):
    pass


class HorribleDB(ConnectionMixin, RealHorribleDB):
    pass
改进空间:将初始连接设置为通用ConnectionProxy,而不是None,这将捕获所有属性访问并引发异常


我已经在这里和各种OSS项目中做了很多探索,但还没有看到类似的东西。它感觉非常可靠,尽管它确实意味着一堆模块将在导入时实例化连接对象作为副作用。这会在我脸上爆炸吗?这种方法还有其他负面影响吗?

首先,从设计角度来看,我可能遗漏了一些东西,但我不明白为什么您需要重混合+单体机制,而不仅仅是定义这样的助手:

_awesome_db = None
def awesome_db(**overrides):
    global _awesome_db
    if _awesome_db is None:
        # Read config/set defaults.
        # overrides.setdefault(...)
        _awesome_db = RealAwesomeDB(**overrides)
    return _awesome_db
此外,还有一个bug可能看起来不像是受支持的用例,但无论如何:如果连续进行以下两次调用,即使传递了不同的参数,也会错误地获取相同的连接对象两次:

db = AwesomeDB()
db.init(username='stuff admin')

db = AwesomeDB()
db.init(username='not-admin')    # You'll get admin connection here.
一个简单的解决方法是使用输入参数上的连接记录

现在,关于问题的实质

我认为答案取决于“连接”类的实际实现方式

我认为您的方法的潜在缺点是:

  • 在多线程环境中,从多个线程对全局连接对象的非同步并发访问可能会出现问题,除非它已经是线程安全的。如果您关心这一点,您可以稍微更改代码和接口,并使用线程局部变量

  • 如果一个进程在创建连接后分叉怎么办?Web应用程序服务器倾向于这样做,这可能不安全,这同样取决于底层连接

  • 连接对象是否具有状态?如果连接对象变得无效(由于连接错误/超时),会发生什么情况?您可能需要用新连接替换断开的连接,以便在下次请求连接时返回

连接管理通常已经通过客户端库中的一个应用程序高效、安全地实现

例如,redis py redis客户端使用以下实现:

然后,Redis客户端使用连接池,如下所示:

  • 从连接池
  • 在连接上
  • 如果连接失败,客户端将停止连接
  • 在任何情况下,最终都是这样,它可以被后续调用或其他线程重用
因此,由于Redis客户端在幕后处理所有这些问题,因此您可以安全地直接做您想做的事情。将延迟创建连接,直到连接池达到全部容量

# app/connections.py
def redis_client(**kwargs):
    # Maybe read configuration/set default arguments
    # kwargs.setdefault()
    return redis.Redis(**kwargs)
类似地,SQLAlchemy也可以使用

总而言之,我的理解是:

  • 如果您的客户端库支持连接池,那么您不需要做任何特殊的事情来共享模块甚至线程之间的连接。您可以定义一个类似于
    redis\u client()
    的助手来读取配置或指定默认参数

  • 如果客户端库只提供低级连接对象,则需要确保对它们的访问是线程安全和fork安全的。此外,您需要确保每次返回有效连接时(如果无法建立或重用现有连接,则引发异常)


感谢您的全面回复!我并没有真正考虑过线和叉的安全性,我一定会考虑一下。回复:连接参数错误,捕捉良好,修复良好。Re:客户端池,这也是一个很好的观点,但即使使用连接池,您也需要集中初始化,并保证在整个应用程序中使用相同的池。Re:simple helper函数,我最初考虑过这种方法。由于每个模块都需要导入awesome_db并调用它,所以很快就会变得很烦人。