在包的__init__.py模块中使用通用抽象类算是符合Python风格吗?
我正在为我的公司开发一个相当复杂的应用程序,使用的是Python3的面向对象模型。这个应用程序包含了几个包和子包,每个包里当然都有一个__init__.py模块。
我主要利用这些__init__.py模块来声明一些通用的类,这些类是为了在各自的包中作为抽象模板使用的。
我现在的问题是:这样使用__init__.py模块算不算是一个“好”的、正确的或者说符合Python风格的做法?还是说我应该把我的通用类放在其他地方呢?
举个例子,假设有一个包叫做mypkg
:
mypkg.__init__.py
:
class Foo(object):
__some_attr = None
def __init__(self, some_attr):
self.__some_attr = some_attr
@property
def some_attr(self):
return self.__some_attr
@some_attr.setter
def some_attr(self, val):
self.__some_attr = val
mypkg.myfoo.py
:
from . import Foo
class MyFoo(Foo):
def __init__(self):
super().__init__("this is my implemented value")
def printme(self):
print(self.some_attr)
4 个回答
千万不要把 __init__.py
用来做其他事情,除了定义 __all__
。如果你能避免这样做,真的能省下很多麻烦。
原因:开发者常常会查看包和模块,但有时候会遇到一个问题。如果你有一个包,你会认为里面有模块和代码。但你很少会把 __init__.py
当成一个模块,因为说实话,大多数情况下它只是为了让目录里的模块可以被导入。
package
__init__.py
mod1.py
humans.py
cats.py
dogs.py
cars.py
commons.py
那类 Family
应该放在哪里呢?这是一个常见的类,它依赖于其他类,因为我们可以创建人类、狗和猫的家庭,甚至是汽车的家庭!
按照我的逻辑,还有我朋友的逻辑,它应该放在一个单独的文件里,但我会试着在 commons
找,接着在 humans
找,然后……我会很尴尬,因为我真的不知道它在哪里!
听起来有点傻吧?但这说明了一个问题。
确实可以使用 __init__.py
来初始化特定的模块,但我从来没见过有人用它来定义新函数。我知道的唯一“正确”的用法是讨论的内容在 这个话题里...。
不过,如果你的应用比较复杂,你可以使用一个临时文件,在里面定义你需要的所有函数,而不是直接在 __init__.py
模块中定义。这样做会让代码更易读,后续修改也更简单。
你可以把所有东西都放在一个源文件里。但把复杂的代码分成不同的模块或包,是为了把相关的东西和不相关的东西分开。分开的部分应该尽量独立,不受其他部分的影响。这种做法适用于任何层面:数据结构、函数、类、模块、包、应用程序。
在模块内部或包内部也应该遵循相同的规则。我同意Bakuriu的观点,__init__.py
应该与包的结构紧密相关,但不一定要和模块的功能直接相关。我个人认为,__init__.py
应该尽量简单。原因有两个:首先,一切都应该尽可能简单,但不能更简单。其次,阅读你包代码的人通常会这样想。他们可能不期待在__init__.py
中看到意外的功能。可能更好的是在包内创建一个generic.py
来实现这个目的。这样更容易为模块的目的写文档(比如通过它的顶部文档字符串)。
从一开始就做好分离,未来独立的功能组合起来会更好。这样你会得到更好的灵活性——无论是在包内使用模块,还是将来的修改。
这要看你想提供什么样的API了。举个例子,标准库里的collections
模块就定义了所有在__init__.py
里的类。标准库应该是比较“python风格”的,虽然这个“python风格”是什么意思可能有点模糊。
不过它主要提供的是一种“模块式”的接口。你很少会看到:
import collections.abc
如果你已经有了子包,可能更好的是引入一个新的子包。如果目前这个包的使用并不依赖于子包,你可以考虑把代码放在__init__.py
里。或者把代码放在某个私有模块里,然后在__init__.py
中简单地导入这些名字(这也是我个人的偏好)。
如果你只是想知道把抽象基类放在哪里比较好,如上面所示(collections.abc
包含了collections
包的抽象基类),你可以从标准库的abc
模块看到,通常会定义一个abc.py
的子模块来包含它们。你可以考虑直接从__init__.py
中暴露它们,方法是:
from .abc import *
from . import abc
# ...
__all__ = list_of_names_to_export + abc.__all__
在你的__init__.py
里。
1 不过,实际使用的实现是用C语言写的:_collectionsmodule.c