【问题标题】:Changes to static class variable are not visible from another module从另一个模块看不到对静态类变量的更改
【发布时间】:2022-01-17 15:45:46
【问题描述】:

我正在为一些 REST API 编写 Python 包。为了更轻松地操作从 API 获得的对象的状态,我想存储对我的 API 客户端实例的静态引用。假设我有一个文件user.py:

class User:
    _client: Client = None

所以当客户端第一次成功认证时,我更新引用:

class Client:
    def authenticate(self):
        ...
        User._client = self

这发生在我的client.py 文件中。现在我可以从client.py 中声明的任何函数成功访问User._client,但是当我从另一个模块尝试同样的事情时,User._client 仍然是None

假设这是我的文件夹结构

└── my_api_client
    ├── __init__.py
    ├── client.py
    ├── search.py
    └── user.py

然后当我从search.py 中的函数调用User._client 时,我得到None 而不是对当前客户端实例的引用。

我在创建一个演示此问题的最小工作示例时遇到了麻烦,所以也许我只是在导入时弄乱了一些东西。我认为,如果我更好地理解如果我从另一个模块导入一个类以及对该类的静态属性的引用是否在所有模块之间共享,那将真的很有帮助。如果真是这样,那我做错了什么?

【问题讨论】:

  • 您能否发布一个 search.py​​ 的代码示例,看看您是如何调用的以及您的导入是什么?
  • 您是否有理由特别想动态更改静态类变量?从提供的行来看,我假设在连接时创建了一个使用客户端对象的User-object。因此,需要对 userobject._client 进行分配,而不是使用类变量。
  • 我没有设法在我正在开发的应用程序之外重现该问题,以上所有内容只是对我正在做的事情的简化抽象。我会很感激任何关于在使用类变量和模块导入时 Python 可能出错的提示
  • @JonathanWeine 我想要类而不是实例上的客户端引用,因此我不必每次创建 User 实例时都将客户端引用传递给构造函数。在这种情况下这可能是可行的,但我有层级的类会自动反序列化,并且每次创建实例时都注入客户端引用非常困难。
  • 我在这里有点摸不着头脑,但听起来你正在为用户实现类似单例模​​式的东西。如果是这种情况(这通常不是最佳选择,尤其是如果您想在某个时候并行执行操作),您可以使用global User 实例来代替修改静态类变量。

标签: python python-module class-variables


【解决方案1】:

我试图重现你的问题,但不能。

# user.py

print("defining class User")


class User:
    _client = None  # removed type annotation here to prevent circular import


print("right now, User._client is " + repr(User._client))
# client.py

import user


class Client:
    def authenticate(self):
        user.User._client = self


print("instantiating a Client")
client = Client()
print("before athenticating, the User._client is " + repr(so70744003_user.User._client))
client.authenticate()
print("before athenticating, the User._client is " + repr(so70744003_user.User._client))

并运行文件client.py

defining class User
right now, User._client is None
instantiating a Client
before athenticating, the User._client is None
before athenticating, the User._client is <__main__.Client object at 0x7fc1816a9b20>

我可以给你详细的分析:

  • Python 获得了文件client.py,因此开始运行文件中的代码
  • 它以import user 开头,因此Python 将搜索user 模块并加载它
    • Python 没有内置的user 模块,因此它会在其sys.path 目录中搜索它,并找到匹配的文件
    • Python 开始运行user.py 文件,该文件定义了一个User 类,因此它为其实例化了一个class 实例(静态成员_client 设置为None),并将其绑定到User 当前模块中的名称
    • 然后它打印出User 的静态成员_client 确实是None
    • 运行完user.py 文件后,Python 继续运行client.py 文件
  • 导入成功,因此 Python 有一个 module 对象并将其绑定到当前模块范围内的 user 名称 (client)
  • 继续运行该文件,遇到一个类定义(定义了一个方法)并绑定到当前模块中的Client 名称
  • 然后它实例化一个Client 并将其绑定到当前模块中的client 名称(也命名为client
  • 然后它调用authenticate,它设置user(指向从user.py加载的模块的变量的名称).User(模块中的类)._client(在类)到self
  • 最后打印出静态变量新值实际上是Client的一个实例

在您问题的 cmets 中,建议使用 global 变量。它不会改变任何事情,因为您的 User 类本质上已经是一个 singleton (只声明了一个),您只是碰巧访问了它的一个字段。单例的字段也可以被视为单例值。

我不知道你的实现有什么问题,你没有给我们足够的信息来找到错误。如果你想找到它,我建议尝试将其减少到Minimal Reproducible Example。通常在这个过程中,你会自己找到解决方案。

我担心这可能是由相互导入引起的,因此变量的状态取决于项目每个文件中导入语句的顺序。这是尝试打破循环或小心处理循环的原因。

【讨论】:

  • 感谢您的详细描述!这几乎就是我期望我的应用程序的行为方式,只是由于某种我不明白的原因它不会。不幸的是,我未能创建一个最小可重现示例,我无法在此处粘贴所有代码:/
  • 您可以做的是添加大量日志记录,或者使用调试器逐步运行。您的代码运行方式的mental model 是错误的,您必须找到差异。此外,rubber duck programming(或与人类一起)也可能有所帮助。
猜你喜欢
  • 1970-01-01
  • 2011-04-01
  • 2015-10-04
  • 2015-11-17
  • 2016-04-08
  • 1970-01-01
  • 2012-09-06
相关资源
最近更新 更多