访问脚本主模块中定义的python类变量

2 投票
5 回答
1777 浏览
提问于 2025-04-19 05:31

我有一个使用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 个回答

-1

在从文件中读取数据时,使用 pickle

0

如果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”变量的指针。

另外,如果你想使用多进程,我的代码就不行了,因为不同进程中的解释器是不同的。

0

我没有使用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

这就是我能根据你提供的简单描述给出的建议。也许可以更详细地解释一下整个运行过程或者涉及的部分。

0

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()

现在我不确定这是否是解决你问题的最佳方案。

2

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)

撰写回答