访问脚本主模块中定义的python类变量
我有一个使用Django的项目,它用Celery来处理异步任务。我使用的是Python 2.7。
在我的Django项目中,有一个模块叫做 client.py
,里面有一个类:
# client.py
class Client:
def __init__(self):
# code for opening a persistent connection and saving the connection client in a class variable
...
self.client = <connection client>
def get_connection_client(self):
return self.client
def send_message(self, message):
# --- Not the exact code but this is the function I need to access to for which I need access to the client variable---
self.client.send(message)
# Other functions that use the above method to send messages
...
这个类只需要实例化一次,就能创建一个持久的连接到远程服务器。
我运行一个名为 connection.py
的脚本,这个脚本会一直运行:
# connection.py
from client import Client
if __name__ == '__main__':
clientobj = Client()
client = clientobj.get_connection_client()
# Blocking process
while True:
# waits for a message from the remote server
...
我需要从另一个模块 tasks.py
访问变量 client
(这是Celery需要的)。
# tasks.py
...
from client import Client
@app.task
def function():
# Need access to the client variable
# <??? How do I get an access to the client variable for the
# already established connection???>
message = "Message to send to the server using the established connection"
client.send_message(message)
这三个Python模块都在同一台机器上。 connection.py
是作为独立脚本执行的,并且是第一个执行的。 tasks.py
中的 function()
方法会在项目的其他模块中多次被调用,因此我不能在这个方法里实例化 Client
类。全局变量也不管用。
在Java中,我们可以创建全局静态变量,并在整个项目中访问它。那在Python中该怎么做呢?
我想到的一些方法,但不确定在Python中是否可行:
- 把这个变量保存在一个公共文件中,这样在我的项目其他模块中就能访问到?
- 把这个客户端作为设置保存在Django或Celery中,然后在需要的模块中访问这个设置?
- 根据Sebastian的建议,另一种方法是共享正在运行的进程之间的变量。我其实就是想这样做。那在Python中该怎么实现呢?
如果有人想知道为什么需要这样做,请 查看这个问题。它解释了完整的系统设计和涉及的各种组件。
我也欢迎任何需要改变代码结构的建议。
5 个回答
在从文件中读取数据时,使用 pickle
。
如果connection.py文件中引入了tasks.py文件,你可以在tasks.py中这样做:
import __main__ # connection.py
main_globals = __main__.__dict__ # this "is" what you getting in connection.py when you write globals()
client = main_globals["client"] # this client has the same id with client in connection.py
BaseManager也是一个解决方案,但它是在本地使用socket网络,这并不是访问变量的好方法,特别是如果你并没有使用多进程的话。我的意思是,如果你需要使用多进程,那就应该用BaseManager。但是如果你不需要多进程,使用多进程就不是一个好选择。我的代码只是从解释器中获取了connection.py里的“client”变量的指针。
另外,如果你想使用多进程,我的代码就不行了,因为不同进程中的解释器是不同的。
我没有使用Django的经验,但如果它们是从同一个脚本执行的,你可以把Client做成单例,或者在init.py里声明Client,然后在需要的地方导入它。
如果你选择单例模式,可以为此写一个装饰器:
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
然后你可以定义:
# client.py
@singleton
class Client:
def __init__(self):
# code for opening a persistent connection and saving the connection client in a class variable
...
self.client = <connection client>
def get_connection_client(self):
return self.client
这就是我能根据你提供的简单描述给出的建议。也许可以更详细地解释一下整个运行过程或者涉及的部分。
Python 有类属性(在多个实例之间共享的属性)和类方法(作用于类本身的方法)。这两者都可以在类和实例上读取。
# client.py
class Client(object):
_client = None
@classmethod
def connect(cls):
# dont do anything if already connected
if cls._client is None:
return
# code for opening a persistent connection and saving the connection client in a class variable
...
cls._client = <connection client>
@classmethod
def get_connection_client(cls):
return cls._client
def __init__(self):
# make sure we try to have a connection on initialisation
self.connect()
现在我不确定这是否是解决你问题的最佳方案。
multiprocessing
提供了你完成这个任务所需的所有工具。
connection.py
from multiprocessing.managers import BaseManager
from client import Client()
client = Client()
class ClientManager(BaseManager): pass
ClientManager.register('get_client', callable=lambda: client)
manager = ClientManager(address=('', 50000), authkey='abracadabra')
server = manager.get_server()
server.serve_forever()
tasks.py
from multiprocessing.managers import BaseManager
class ClientManager(BaseManager): pass
ClientManager.register('get_client')
manager = ClientManager(address=('localhost', 50000), authkey='abracadabra')
manager.connect()
client = manager.get_client()
@app.task
def function():
message = "Message to send to the server using the established connection"
client.send_message(message)