python 中的单例模式问题
Issue with singleton pattern in python
我正在使用 python class 和 Singleton 模式来获得不同类型的好处,例如
- 限制对共享资源的并发访问。
- 为资源创建全局访问点。
- 在程序的整个生命周期中只创建 class 的一个实例。
但现在也有一些问题,所以让我知道如何解决这个问题,同时获得上面提到的 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
我正在使用 python class 和 Singleton 模式来获得不同类型的好处,例如
- 限制对共享资源的并发访问。
- 为资源创建全局访问点。
- 在程序的整个生命周期中只创建 class 的一个实例。
但现在也有一些问题,所以让我知道如何解决这个问题,同时获得上面提到的 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