1.要解决的问题
Python在语义中存在着包、模块、类(当然还有函数)这几个概念。
在编写Python代码时,我们需要管理代码的文件目录结构。
这时候会遇到这样一种情况:
1.由于Python一个文件算一个模块,一个带__init__.py的目录算一个包。
2.而为了控制代码文件不要过大,我们需要的是一个类(几个类或加些许函数)分配一个文件。
3.这时候会出现类似这样的语句:
1 2 3 4 5 6 7 8 9 |
#第一种 import package_a.class_a_file as caf a = caf.ClassA() #第二种 form package_a.class_a_file import * a = ClassA() #另外几种就不意义列举了... |
以上除了直观上可以看出import过长外,隐藏的另一点是我们是希望一个类用一个文件,在使用多个相关类的时候就必须写很多import。(注:我们可不想一堆代码扎堆,弄出一个超大代码文件。)
2.解决方案要达到的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#文件目录结构 #|--demo.py #|--package_a # |--__init__.py # |--class_a.py #类ClassA # |--class_b.py #类ClassB和函数func_b() #demo.py中可以直接使用 from package_a import * a = ClassA() b = ClassB() func_b() |
由于Python里一个带__init__.py的目录算一个包,所以利用这一机制,把类文件放在包里,用包来管理类。
注:在Python里“包是模块,而模块不是包”。用system.modules可以取到的名字是包和模块都有的,而用__package__却能很好的区分包和模块。也就是“包其实是一种特殊的模块”。
3.解决方案
这就是解决方案的文件base.py,代码很短:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import sys _packet_ = {} #它是个装饰器,item是类,或者函数 def export(item): #获取item的模块对象 module = sys.modules[item.__module__] #由模块对象得到包对象 package = sys.modules[module.__package__] #把item添加到包的__dict__里 package.__dict__[item.__name__] = item #生成所有使用该解决方案的包的__all__变量,并把导出的item添加进去 if not package.__name__ in _packet_: _packet_[package.__name__] = [] _packet_[package.__name__].append(item.__name__) #原封不动地把item返回 return item #它是个函数,在包__init__.py里用于获取__all__ def packet(name): if not name in _packet_: _packet_[name] = [] return _packet_[name] |
代码用意我写在注释里了,就是以装饰器来把类添加到包的__dict__和__all__里。__all__需要利用packet在包里生成,不这么做只会使得from package_name import * 后不能找到类,需要写具体的类名from package_name import ClassA。
4.使用解决方案
先来看下使用解决方案后的目录结构:
1 2 3 4 5 6 7 |
#文件目录结构(使用后结构只多了base.py) #|--base.py #|--demo.py #|--package_a # |--__init__.py # |--class_a.py #类ClassA # |--class_b.py #类ClassB和函数func_b() |
代码处就需要做到以下几点:
1.关于被导出的类文件里应该怎么做,这里以class_b.py为例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# ./package_a/class_b.py #1.需要导入base import base #2.使用export装饰器,装饰要导出的类或函数 @base.export class ClassB:pass #2.同样的export可以导出函数 @base.export def func_b(): print('func_b') |
2.使用了导出功能的包要做什么,这里以package_a包为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ./package_a/__init__.py #1.导入base import base #2.导入将要导出的子模块,需要具体模块名字的形式,from . import * 不可用 from . import class_a,class_b #3.用packet初始化__all__,这个可选,主要是看要不要支持 from 的用 * 导入 __all__ = base.packet(__name__) #4.这个是可选的,因为如果用了__all__会影响from *。可以用export把__init__.py里的项,加入__all__ @base.export def pafunc(): print('pafunc') |
5.总结
使用该解决方案可以归纳为两点:
1.用@base.export标记要导出的类或函数
2.在包__init__.py里初始化__all__ = base.packet(__name__)
3.(说好的只有两点呢?)其实第2点是可选的,不过最好加上。而在包的__init__.py里导入子模块才是真正的第2点。不然子模块不会被载入,也谈不上导出了。
最后,demo.py里可以这么写,和预期的效果一样:)
1 2 3 4 5 6 7 8 |
# ./demo.py from package_a import * a = ClassA() # 上面的实例没有给出,不过假设有ClassA在class_a.py里的 b = ClassB() func_b() |