python 中的单例模式问题

Issue with singleton pattern in python

我正在使用 python class 和 Singleton 模式来获得不同类型的好处,例如

但现在也有一些问题,所以让我知道如何解决这个问题,同时获得上面提到的 Singleton 模式的好处。 注意: 这里我使用 python zeep 进行 SOAP 调用。

示例代码:

from zeep.plugins import HistoryPlugin
from shared import Singleton
import zeep

class MySoapClient(metaclass=Singleton):
    """MyFancySoapClient"""

    def __init__(
        self,
        user: str = "username",
        password: str = "password",
        wsdl: str = "wsdl_url_goes_here",
    ):
        self._history = HistoryPlugin()
        self._soap_client = zeep.Client(wsdl, plugins=[self._history])



    def methodA():
        resp = self._soap_client.ServiceA(
            body
        )
        return resp
    
    def methodB():
        resp = self._soap_client.ServiceB(
            body
        )
        return resp

    def methodC(request_proxy_url):
        self._soap_client.transport.session.proxies = {"https": request_proxy_url}
        resp = self._soap_client.ServiceE(
            body
        )
        return resp
    
    def methodD():
        resp = self._soap_client.ServiceC(
            body
        )
        return resp

    def methodE():
        resp = self._soap_client.ServiceD(
            body
        )
        return resp

client = MySoapClient()
client.methodA()
client.methodB()
client.methodC("https://example.com")  <---- after this call it modifies the '_soap_client' attribute
client.methodD()  
client.methodE()

这就是为什么 methodD()methodE() 因为添加 self._soap_client.transport.session.proxies 而受到影响,实际上我只需要为 methodC() 设置代理 URL 但是由于单例它将更新的属性值传播到 mothodD()methodE()。最后使我的 methodD()methodE() 失败,因为这些方法中的 SOAP 调用不需要使用代理。

您可以将您的 methodC 重写为:

    def methodC(request_proxy_url):
        original_proxies = self._soap_client.transport.session.proxies
        self._soap_client.transport.session.proxies = {"https": request_proxy_url}
        resp = self._soap_client.ServiceE(
            body
        )
        self._soap_client.transport.session.proxies = original_proxies
        return resp

(对于需要在发出请求之前修改 self._soap_client 实例的设置的任何其他方法执行类似操作)

我有点怀疑是否强制执行单例模式,而不是仅仅在模块中创建一个全局变量并从任何地方导入它...但这只是个人品味,与您的问题无关。

了解 SOAP API 的性质,我预计 zeep.Client 实例是一个相当重的对象,因此如果可以避免,尽量避免拥有多个实例是完全有意义的。

如果您使用多线程平台(例如 Python 和 gevent),那么您必须小心避免全局变量改变它们的共享状态,就像 MySoapClient 现在所做的那样。

另一种方法是维护少量不同的 zeep.Client 实例,并为每个 methodA、methodC 等使用适当的 _soap_client 实例。类似于:

class MySoapClient(metaclass=Singleton):
    """MyFancySoapClient"""

    def __init__(
        self,
        user: str = "username",
        password: str = "password",
        wsdl: str = "wsdl_url_goes_here",
        request_proxy_url: str = "default value",
    ):
        self._history = HistoryPlugin()
        self._soap_client = zeep.Client(wsdl, plugins=[self._history])
        self._soap_client_proxied = zeep.Client(wsdl, plugins=[self._history])
        self._soap_client_proxied.transport.session.proxies = {"https": request_proxy_url}

    def methodB():
        resp = self._soap_client.ServiceB(
            body
        )
        return resp

    def methodC(request_proxy_url):
request_proxy_url}
        resp = self._soap_client_proxied.ServiceE(
            body
        )
        return resp

   # etc