Python导入包报NameError:无法找到__init__.py定义的Singleton元类
问题根因
这个报错和元类特性、__init__.py的特殊限制没有任何关系,本质是Python模块的命名空间隔离规则+导入时序问题:
- 每个
.py文件都是独立的模块,拥有自己的全局命名空间。代码里用到的任何名字(包括元类参数里的类名),必须在当前模块内提前定义、或者显式导入,不会自动从同包其他模块(哪怕是包的__init__.py)中获取。 this_one_works.py运行正常,和元类场景无关:它用到的PuckerMeta是同文件内提前定义的,属于当前模块命名空间内的名字,自然可以正常访问。- 你当前代码的执行时序直接触发了报错:
- 运行
main.py执行import foobar,Python开始加载foobar包的__init__.py __init__.py从上到下执行,先完成Singleton类的定义,接着执行导入语句加载this_one_works.py,该模块无外部依赖导入成功,返回__init__.py继续执行- 接下来
__init__.py执行from foobar.csend import Csend,开始加载csend.py csend.py第一行定义类时传入metaclass=Singleton,Python需要在csend.py自己的全局命名空间里查找Singleton这个名字,但你从未在csend.py中导入过Singleton——这个名字仅存在于foobar/__init__.py的命名空间中,csend.py无权直接访问,因此抛出NameError。
- 运行
__init__.py本质就是一个普通模块,只是导入包时会优先执行它的代码,它内部定义的类、变量没有任何使用限制,完全可以作为元类使用,不存在特殊约束。
修复方案
你可以任选以下一种方案修复问题,第二种更适合工程化项目:
- 方案1:在使用
Singleton的模块中显式导入
注意当前导入流程中foobar包还未完成初始化,不要用绝对导入从foobar顶层导,用相对导入避免循环导入问题:# foobar/csend.py from . import Singleton class Csend(metaclass=Singleton): def __init__(self): pass - 方案2(推荐):拆分公共定义到独立模块
不要把跨模块共用的类、工具函数直接写在__init__.py中,单独拆到独立模块规避导入时序问题:- 新建
foobar/singleton.py,把Singleton类的完整定义移到该文件中 - 在需要使用单例元类的模块(比如
csend.py)中显式导入:# foobar/csend.py from .singleton import Singleton class Csend(metaclass=Singleton): def __init__(self): pass - 如果需要在包对外暴露
Singleton,可以在__init__.py中从独立模块导入即可,不会再出现时序问题。
- 新建
注:报错栈中显示
class Csend(Singleton):是Python traceback的简化展示,实际你写的metaclass=Singleton参数会在类定义阶段立即求值,找不到对应名字时就会抛出错误,和类继承时找不到父类的报错表现一致。
内容的提问来源于stack exchange,提问作者Omar and Lorraine
相关产品推荐
相关产品推荐

