Python Tornado:实例变量无法在方法间保存值的问题
这个问题的核心原因其实很简单:Tornado会为每个HTTP请求创建一个独立的RequestHandler子类实例。也就是说,你发送PUT请求时,会生成一个ServerHandler实例,在这个实例里修改self.needed_file;而当你发送GET请求时,Tornado会创建一个全新的ServerHandler实例,它的self.needed_file是在initialize方法里初始化的空列表,自然拿不到之前PUT请求里修改的值。
下面给你几种可行的解决方案,你可以根据自己的应用场景选择:
方案1:使用类变量替代实例变量
把needed_file定义为类的属性,而不是实例属性,这样所有实例都会共享这个变量。
修改后的代码示例:
class ServerHandler(tornado.web.RequestHandler): # 将needed_file定义为类变量,所有实例共享 needed_file = [] def initialize(self): # 这里不再需要初始化实例变量self.needed_file pass def put(self): try: if something: ServerHandler.needed_file.append('Ninja file') ServerHandler.needed_file.append(dico_data[0][1].decode('utf-8')) print(ServerHandler.needed_file) except IndexError: pass def get(self): print('//' + str(ServerHandler.needed_file) + '//')
⚠️ 注意:如果你的Tornado应用是多进程模式运行的,类变量无法在不同进程间共享,这时候这个方案就不适用了。另外,单进程下如果有并发请求(虽然Tornado是单线程,但如果用了异步IO可能有并发操作),也要注意线程安全,建议用threading.Lock来保护对类变量的读写操作。
方案2:使用外部存储(适合多进程/分布式场景)
如果你的应用是多进程部署或者需要分布式运行,最好把共享数据存在外部存储中,比如Redis、数据库等。以Redis为例:
import redis import tornado.web class ServerHandler(tornado.web.RequestHandler): def initialize(self): # 初始化Redis连接 self.redis_client = redis.Redis(host='localhost', port=6379, db=0) def put(self): try: if something: # 向Redis列表中添加元素 self.redis_client.rpush('needed_file', 'Ninja file') self.redis_client.rpush('needed_file', dico_data[0][1].decode('utf-8')) # 打印当前列表内容 print(self.redis_client.lrange('needed_file', 0, -1)) except IndexError: pass def get(self): # 从Redis获取列表所有元素并解码 needed_files = self.redis_client.lrange('needed_file', 0, -1) needed_files_str = [item.decode('utf-8') for item in needed_files] print('//' + str(needed_files_str) + '//')
这个方案的优势是天然支持多进程、分布式场景,而且Redis本身也提供了原子操作,不需要额外处理线程安全问题。
方案3:使用应用级别的全局存储
你可以在Tornado应用初始化时,把共享数据放到应用的settings里,或者单独的全局对象中,让所有RequestHandler实例都能访问到。
示例代码:
import tornado.web # 定义全局存储对象 global_shared_data = {'needed_file': []} class ServerHandler(tornado.web.RequestHandler): def initialize(self): # 从应用settings中获取全局数据 self.shared_data = self.application.settings['shared_data'] def put(self): try: if something: self.shared_data['needed_file'].append('Ninja file') self.shared_data['needed_file'].append(dico_data[0][1].decode('utf-8')) print(self.shared_data['needed_file']) except IndexError: pass def get(self): print('//' + str(self.shared_data['needed_file']) + '//') # 创建Tornado应用时传入全局数据 app = tornado.web.Application( [(r'/', ServerHandler)], shared_data=global_shared_data )
同样要注意:如果你的应用是多进程模式,这个全局数据也无法跨进程共享;单进程下如果有并发操作,需要加锁保护(比如threading.Lock)。
内容的提问来源于stack exchange,提问作者Sam The Sid

