在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),这使没有工具的人无法访问它。
从命名空间和组织角度来看,您是否希望将类分组分成不同的模块?
没有。
来自Python的Zen,它反映了其发展和演变的哲学和原则:
命名空间是一个很棒的主意-让我们做更多这些吧!
但请记住,它也表示:
扁平比嵌套更好。
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的上下文中,我的方向是希望将相关的和语义相似的类定义保留在同一文件中。如果文件太大而变得笨拙,请考虑进行重组。
class SomeException extends \Exception {}