在Python中的同一文件中可以有多个类吗?


18

经过数年的Java和PHP生涯,我才刚刚进入Python世界。尽管语言本身非常简单明了,但我仍在努力解决一些我无法解决的“小问题”,并且到目前为止,我在众多文档和教程中都找不到答案。

对于有经验的Python从业者来说,这个问题似乎很愚蠢,但是我确实想要一个答案,因此我可以继续使用该语言:

在Java和PHP中(虽然不是严格要求),但您最好将每个class文件写在自己的文件中,class最佳做法是使用文件名。

但是在Python,至少在我检查的教程,这是确定要在同一个文件中的多类。

该规则是在生产型,可部署的代码中保留吗?还是为了简洁起见,仅在具有教育意义的代码中执行?

Answers:


13

在Python中的同一文件中可以有多个类吗?

是。无论是从哲学角度还是从实践角度。

在Python中,模块是一个存在于内存中的命名空间。

假设我们具有以下假设的目录结构,每个文件定义一个类:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

所有这些类都在collections模块中可用,(实际上总共有25个)在标准库模块中定义。_collections_abc.py

我认为这里有几个问题要_collections_abc.py优于替代假设的目录结构。

  • 这些文件按字母顺序排序。您可以用其他方式对它们进行排序,但是我不知道有一种功能可以按语义依赖性对文件进行排序。_collections_abc模块源按依赖关系进行组织。
  • 在非病理情况下,模块和类定义都是单例的,在内存中各出现一次。模块与类之间存在双射映射-使模块冗余。
  • 文件数量的增加使随意阅读这些类变得不那么方便(除非您拥有一个使它变得简单的IDE),这使没有工具的人无法访问它。

从命名空间和组织角度来看,您是否希望将类分组分成不同的模块?

没有。

来自PythonZen,它反映了其发展和演变的哲学和原则:

命名空间是一个很棒的主意-让我们做更多这些吧!

但请记住,它也表示:

扁平比嵌套更好。

Python非常干净而且易于阅读。它鼓励您阅读。将每个单独的类放在单独的文件中不鼓励阅读。这违反了Python的核心理念。看一下标准库的结构,绝大多数模块是单文件模块,而不是软件包。我会向您提交的是,惯用的Python代码是以与CPython标准库相同的样式编写的。

这是抽象基类模块中的实际代码。我喜欢将其用作语言中各种抽象类型表示的参考。

您会说这些类中的每一个都需要一个单独的文件吗?

class Hashable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __hash__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Hashable:
            try:
                for B in C.__mro__:
                    if "__hash__" in B.__dict__:
                        if B.__dict__["__hash__"]:
                            return True
                        break
            except AttributeError:
                # Old-style class
                if getattr(C, "__hash__", None):
                    return True
        return NotImplemented


class Iterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            if _hasattr(C, "__iter__"):
                return True
        return NotImplemented

Iterable.register(str)


class Iterator(Iterable):

    @abstractmethod
    def next(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterator:
            if _hasattr(C, "next") and _hasattr(C, "__iter__"):
                return True
        return NotImplemented


class Sized:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if _hasattr(C, "__len__"):
                return True
        return NotImplemented


class Container:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if _hasattr(C, "__contains__"):
                return True
        return NotImplemented


class Callable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __call__(self, *args, **kwds):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Callable:
            if _hasattr(C, "__call__"):
                return True
        return NotImplemented

那么他们每个人都应该有自己的文件吗?

我希望不是。

这些文件不仅是代码,而是有关Python语义的文档。

他们平均可能是10到20行。为什么我必须转到一个完全独立的文件才能看到另外10行代码?那将是非常不切实际的。此外,每个文件上导入的样板文件几乎相同,从而增加了更多的冗余代码行。

我发现知道有一个模块可以在其中找到所有这些抽象基类,这非常有用,而不必查看模块列表。在彼此的上下文中查看它们可以使我更好地理解它们。当我看到Iterator 是一个 Iterable时,可以快速浏览一下Iterable的组成。

有时我有时会参加几个非常短的课程。即使它们需要随着时间增长而增大,它们仍保留在文件中。有时,成熟的模块具有超过1000行代码。但是ctrl-f很容易,并且某些IDE使查看文件轮廓变得容易-因此,无论文件多大,您都可以快速转到要查找的任何对象或方法。

结论

在Python的上下文中,我的方向是希望将相关的和语义相似的类定义保留在同一文件中。如果文件太大而变得笨拙,请考虑进行重组。


1
好吧,据我所知,由于您提交的代码可以在同一个文件中包含多个类,因此我认为该参数没有说服力。例如,在PHP中,通常只有一个类似于以下代码的文件即可:class SomeException extends \Exception {}
Olivier Malki,2016年

3
不同的社区有不同的编码标准。Java人看着python说“为什么它允许每个文件多个类!?”。Python的人们看着Java,说“为什么它要求每个类都有自己的文件!?”。最好是跟随你工作社区的风格。
戈特机器人

我也很困惑 显然,我的答案误解了一些关于Python的知识。但是,有人会应用“扁平比嵌套更好”来向类中添加尽可能多的方法吗?总的来说,我认为凝聚力和SRP的原理仍然适用于支持模块的模块,这些模块提供的类在功能上彼此密切相关(尽管可能比一个类好得多,因为一个模块比单个类建模更粗糙的包装概念) ),尤其是因为任何模块范围的变量(尽管希望通常避免使用)的范围都会增加。

1
Python的禅宗是一系列相互矛盾的原则。一个人可能会同意您的观点:“稀疏胜于密集”。-紧随其后的是,“扁平比嵌套更好”。Python的禅宗中的各行很容易被误用并极端化,但是从整体上看,它可以帮助进行编码,并找到合理的人可能不同意的共同点。我认为人们不会认为我的代码示例过于密集,但是您所描述的内容对我来说却非常密集。
亚伦·霍尔

谢谢杰弗里·艾伯森(Jeffrey Albertson)/漫画书人。:)大多数Python用户不应该使用(双下划线)特殊方法,但是他们确实允许核心设计者/架构师从事元编程,以自定义使用运算符,比较器,下标表示法,上下文等。语言功能。只要他们不违反最小惊奇原则,我认为对价值比率的危害就很小。
亚伦·霍尔

4

使用Python构建应用程序时,您需要考虑软件包和模块的问题。

模块与您正在谈论的文件有关。在同一模块中有很多类是很好的。目的是同一模块内的所有类都应具有相同的目的/逻辑。如果模块时间太长,则可以考虑通过重新设计逻辑来细分它。

不要忘记不时阅读有关Python增强建议索引的信息。


2

真正的答案是通用的,而不取决于所使用的语言:文件中应包含的内容主要不取决于其定义的类数。它取决于逻辑连接性和复杂性。期。

因此,如果您有几个非常紧密相关的小类,则应将它们捆绑到同一文件中。如果某个班级与另一个班级的联系不紧密,或者太复杂而无法包含在另一个班级中,则应该拆分一个班级。

也就是说,“每文件一类”规则通常是一种很好的启发式方法。但是,有一些重要的例外情况:一个小的助手类实际上只是其唯一用户类的实现细节,通常应该捆绑到该用户类的文件中。同样,如果您具有,和的三个类vector2,则几乎没有理由在单独的文件中实现它们。vector3vector4


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.