在包的__init__.py模块中使用通用抽象类算是符合Python风格吗?

12 投票
4 回答
5029 浏览
提问于 2025-04-18 10:05

我正在为我的公司开发一个相当复杂的应用程序,使用的是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 个回答

0

千万不要把 __init__.py 用来做其他事情,除了定义 __all__。如果你能避免这样做,真的能省下很多麻烦。

原因:开发者常常会查看包和模块,但有时候会遇到一个问题。如果你有一个包,你会认为里面有模块和代码。但你很少会把 __init__.py 当成一个模块,因为说实话,大多数情况下它只是为了让目录里的模块可以被导入。

package
    __init__.py
    mod1.py
    humans.py
    cats.py
    dogs.py
    cars.py
    commons.py

那类 Family 应该放在哪里呢?这是一个常见的类,它依赖于其他类,因为我们可以创建人类、狗和猫的家庭,甚至是汽车的家庭!

按照我的逻辑,还有我朋友的逻辑,它应该放在一个单独的文件里,但我会试着在 commons 找,接着在 humans 找,然后……我会很尴尬,因为我真的不知道它在哪里!

听起来有点傻吧?但这说明了一个问题。

0

确实可以使用 __init__.py 来初始化特定的模块,但我从来没见过有人用它来定义新函数。我知道的唯一“正确”的用法是讨论的内容在 这个话题里...

不过,如果你的应用比较复杂,你可以使用一个临时文件,在里面定义你需要的所有函数,而不是直接在 __init__.py 模块中定义。这样做会让代码更易读,后续修改也更简单。

4

你可以把所有东西都放在一个源文件里。但把复杂的代码分成不同的模块或包,是为了把相关的东西和不相关的东西分开。分开的部分应该尽量独立,不受其他部分的影响。这种做法适用于任何层面:数据结构、函数、类、模块、包、应用程序。

在模块内部或包内部也应该遵循相同的规则。我同意Bakuriu的观点,__init__.py应该与包的结构紧密相关,但不一定要和模块的功能直接相关。我个人认为,__init__.py应该尽量简单。原因有两个:首先,一切都应该尽可能简单,但不能更简单。其次,阅读你包代码的人通常会这样想。他们可能不期待在__init__.py中看到意外的功能。可能更好的是在包内创建一个generic.py来实现这个目的。这样更容易为模块的目的写文档(比如通过它的顶部文档字符串)。

从一开始就做好分离,未来独立的功能组合起来会更好。这样你会得到更好的灵活性——无论是在包内使用模块,还是将来的修改。

4

这要看你想提供什么样的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

撰写回答