区分类型别名和实际 类 的约定?

Convention to distinguish type aliases from actual classes?

类型模块允许将复杂的类型签名分配给 aliases,然后可以与实际类型互换使用。这似乎有一个问题,它将类型别名与实际的 classes 混淆,即在这样的定义中

def foo(obj: MyComplexObject):
    ...

无法判断 MyComplexObject 是在某处作为 class 实现还是只是一个类型别名。这对我来说似乎是一个不幸的混淆来源,特别是当别名被(重新)使用远离其原始定义时。这是否被普遍接受,或者是否有以某种方式区分类型别名与实际 classes 的约定?

没有区分这两者的官方惯例。

类型别名与其他语言中的 typedef 非常相似。但是,按照惯例,类型别名用于复杂的类型组合,例如 Message = Union[str, MyClass1, MyClass2, OtherClass],在这种情况下多次写出完整的联合会很麻烦。因此,单个名称注释应该是:一种类型,或者是具有别名的类型的某种组合。您必须转到该名称的定义才能将它们区分开来。

我认为类型别名和常规类型在设计上是无法区分的:类型别名意味着它们作为别名的任何类型的确切替代。

因此,使用您的示例,MyComplexObject 是实际的 class 还是类型别名并不一定重要:它们在运行时和静态类型检查时的行为相同。

并且因为类型检查器可以立即反馈您是否以类型安全的方式使用 obj,所以混淆的可能性很小:要么您将使用 obj 参数正确,否则您将立即得到反馈。 (类似地,理解 PEP 484 语义的 Pycharm 等 IDE 也将理解类型别名,并且会正确 auto-complete/flag 错误。)

还值得注意的是,在您专门为其他 class 起别名的情况下,别名和原始 class 在运行时几乎无法区分:它们都是引用相同的基础类型对象。 (所以可以说,实际上没有什么可混淆的:别名和原始类型都是 "same thing"。)

因此,几乎没有动力去寻找区分类型和类型别名的命名约定——这在实践中并没有真正成为问题。


也就是说,一些关于何时使用类型别名的隐含约定。例如,人们通常不会直接为 class 别名:正如 Ethan 所说,类型别名主要用于帮助简化在多个地方重复的较大复合类型。

这意味着类型别名主要用于当你有一堆非常难看的类型签名时。然后它变成了 "tradeoff" 之类的事情:reader 确实需要花时间查找别名的定义并将其缓存在他们的头脑中,但是一旦他们这样做了,阅读其余代码应该变得更愉快。

因此,如果您仍然发现类型别名可能会造成混淆,并且除非必要,否则宁愿避免使用它们,那也很好:这几乎是其他人所做的。唯一的区别归结为您何时越过从 "unnecessary" 到 "necessary" 的临界点。