如何处理Python的静态类型?

11

我来自Java的世界,想知道除了编译代码时错过错误之外,Python中的动态类型有什么优点?

你喜欢Python的类型定义吗?你有大型项目中类型定义带来帮助的例子吗?它不会有一些容易出错的问题吗?


3
静态类型是过早的优化。(这并不是编程中最普遍被认同的观点。) - David Thornley
2
@Etam:看,主观和争议性(关于David的评论) - OscarRyz
2
加载的问题:“如何处理Python静态类型?”争论性的:“除了在编译代码时错过错误之外,有什么好处……”无意义的问题:“你喜欢用Python吗?” - Nick T
1
动态类型是我从Java转向Python的主要原因之一。为什么呢?学习Python才能充分欣赏它。这不是可以轻易向Java思维方式的人解释清楚的事情。 - MAK
3
动态类型:当在编译时发现错误已经不够刺激时。 - user1228
3
@Will:显然我的程序没有漏洞,编译器没有发现任何错误。 - Lie Ryan
7个回答

17

通常情况下,静态类型检查是不可判定的。这意味着有些程序是静态类型安全的,但类型检查器无法“证明”它们是静态类型安全的,因此类型检查器必须拒绝这些程序。

换句话说:存在类型安全的程序,但类型检查器不允许你编写它们。更简洁地说:静态类型检查防止你编写某些程序。

这适用于所有静态类型,而不仅仅是Java。

至于Java本身:它的类型系统相当糟糕。它的类型系统不足以表达甚至非常简单的属性。例如,在static void java.util.Arrays.sort(Object[] a)的类型中,哪里实际上说了结果必须是排序的?或者数组元素必须是部分有序的?

Java的另一个问题是其类型系统有如此之大的漏洞,以至于你可以通过驾驶一辆卡车来穿过它们:

String[] a = new String[1];
Object[] b = a;
b[0] = 1; // ArrayStoreException

在这个特定的情况中,问题出在协变数组上。数组既不能协变又不能保证类型安全。

Java将静态类型的所有麻烦都结合在了一起,但却没有任何优点。因此,你可以摆脱这些麻烦。

然而,请注意这并非普适。有其他语言拥有更好的类型系统,其中权衡更加清晰。

例如,在Python中,以下是有史以来最愚蠢的语言基准测试之一(斐波那契数列):

def fib(n):
    if n < 2: return n
    return fib(n-2) + fib(n-1)

还有Java:

int fib(int n) {
    if (n < 2) return n;
    return fib(n-2) + fib(n-1);
}

注意,那里有相当多的混乱,这与静态类型有关。为了使比较更加公平,让我们想象一种具有Python语法和Java语义的语言:

def fib(n: int) -> int:
    if n < 2: return n
    return fib(n-2) + fib(n-1)

[有趣的一点是:随着Python 3.x中可选的静态类型注释的添加,那段代码实际上也是有效的Python代码,尽管它显然仍然不是静态类型安全的,因为这些注释只是注释而已。它们实际上从未被检查过。]

在Haskell中,它看起来像这样:

fib n
  |     n < 2 = n
  | otherwise = fib (n-2) + fib (n-1)
与Python版本不同,这个版本是完全静态类型安全的,但没有任何与类型相关的杂乱代码。
在这种特定情况下,静态和动态类型之间的优点问题要少得多。
顺便说一下,更典型的Haskell版本可能看起来像这样:
fib 0 = 0
fib 1 = 1
fib n = fib (n-2) + fib (n-1)

或者这样:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

实际上,Java和Python之间更为重要的区别不是Java是静态类型而Python是动态类型,而是Java只是一个不太好的编程语言,而Python则是。所以,Java总是会输,不是因为它是静态类型的,而是因为它很糟糕。将BASIC与Haskell进行比较,Haskell显然胜出,但同样不是因为它是静态类型的,而是因为BASIC很糟糕。

一个更有意思的比较应该是Java vs BASIC或Python vs Haskell。


7
+1,我可以建议您在帖子中对整体反Java(甚至反BASIC)的主题保持适度吗?它提出了令人信服的论点,但使用“糟糕的”、“洞口大得可以开卡车通过”、“不是一个好的编程语言”等表达方式并没有为已经很好的答案增添任何东西。 - MAK
1
+1 让我们不要假装我们可以在没有强类型的情况下进行静态类型化。 - John La Rooy
据我所知,Java 与 Haskell 或 Python 的区别在于显式类型与隐式类型。动态类型仍然更灵活,但隐式静态类型是一个完美的折衷方案。(我是一位 Python 迷,经常利用动态类型,但我仍认为像 Java 和 C/C++ 这样的语言最大的缺陷是显式类型的冗余干扰)。 - ssokolow
3
是的,没错。需要注意的是,实际上 C# 3 和 C++0x 都通过类型推断提供了隐式类型,但在这两种情况下,你实际上必须使用 varauto 关键字来显式告诉它们使用隐式类型。 - Jörg W Mittag
你开始就有一个有趣的前提,可能会让我信服,即“有一些类型安全的程序,类型检查器不允许你编写”。但是后来你放弃了这个前提,并且陷入了无力的争论,认为静态类型不能捕获所有错误或需要更多文本。 - Aleksandr Dubinsky
显示剩余8条评论

13

我怀疑绝大多数非平凡的Java程序都包含动态类型。每次在Java中将Object类型转换为显式类型时,都会进行动态类型检查,这包括在1.5之前使用集合类的每个用法。实际上,即使是Java泛型仍然可以推迟某些类型检查直到运行时。

每次使用Java反射时,都会进行动态类型检查。这包括从文本文件中的类或方法名称映射到实际类或方法,例如每次使用Spring XML配置文件。

这是否使Java程序变得脆弱且容易出错?Java程序员是否花费大量时间跟踪和修复不正确的动态类型?可能不会,Python程序员也是如此。

动态类型的一些优点:

  • 大大减少对继承的依赖。我曾经见过具有庞大继承树的Java程序。 Python程序通常很少或不使用继承,而更喜欢使用鸭子类型。
  • 编写真正通用代码很容易。例如,min()和max()函数可以使用任何可比较类型的序列 - 整数,字符串,浮点数,具有适当比较方法的类,列表,元组等。
  • 少些代码。Java代码中有很大一部分对解决问题毫无帮助 - 它纯粹存在于保持类型系统的状态。如果Python程序是相应Java程序的五分之一大小,那么要编写、维护、阅读和理解的代码量就会减少五分之一。换句话说,Python具有更高的信噪比。
  • 更快的开发周期。这与少些代码相伴随-您花费更少的时间考虑类型和类,而更多地时间考虑解决当前工作中的问题。
  • 很少需要AOP。我认为Python中有面向方面的库,但我不知道有谁使用它们,因为对于99%的AOP需求,您可以使用装饰器和动态对象修改来完成。在Java世界中广泛使用AspectJ表明,在核心Java语言中存在必须用外部工具补偿的不足。

5

你喜欢用Python吗?

Python的一部分。在Python中喜欢它是愚蠢的。

你有一个大型项目中它帮助的例子吗?

有。每一天我都为此感到高兴,因为由于鸭子类型,我可以进行更改,并且这些更改相对局部化,通过所有单元测试、集成测试,并且没有影响其他地方。

如果使用Java,这些更改将需要无休止地重构以从类中提取接口,以便我可以引入仍然符合Java静态类型检查下允许的变化。

这不会有点容易出错吗?

不比静态类型检查更容易出错。简单的单元测试可以确认对象符合预期的特征。

在Java中很容易编写一个(a)通过编译时检查并(b)在运行时崩溃的类。强制转换是实现此目的的好方法。未满足类的意图是常见的事情,即类可能编译但仍然不能正常工作。


2
很多设计模式(例如 GoF 中的)在动态类型的函数式语言中是不必要的,或者可以用更少的工作量实现。实际上,很多模式已经“内置”到 Python 中,所以如果你编写简短且“Pythonic”的代码,你将免费获得所有好处。你不需要迭代器、观察者、策略、工厂方法、抽象工厂和许多其他在 Java 或 C++ 中常见的模式。
这意味着需要编写的代码更少,(更重要的是)需要阅读、理解和支持的代码更少。我认为这是像 Python 这样的语言的主要优点。在我看来,这远远超过了静态类型的缺失。与 Python 代码相比,类型相关的错误并不常见,并且可以通过简单的功能测试轻松捕捉(而这样的测试在 Python 中比 Java 更容易编写)。

我认为这有点误导人:迭代器和观察者模式仍在使用,工厂和策略只是函数引用,而工厂方法是__new__ - Score_Under

0

这是一种减轻负担的方式。你可以将颜色红色视为“Red”(一个常量),或者是“255, 0, 0”(一个元组),或者是“#FF0000”(一个字符串):三种不同的格式,需要三种不同的类型,或者在Java中进行复杂的查找和转换方法。

这使得代码更简单。


0
例如,您可以编写函数,可以将整数、字符串、列表、字典或其他任何内容作为参数传递,并且它将能够以透明的方式处理所有这些内容(或者如果无法处理该类型,则抛出异常)。在其他语言中也可以做到这样的事情,但通常需要使用指针、引用或类型转换等技巧,这会导致程序错误,并且很难看。

http://en.wikipedia.org/wiki/Tagged_union - kennytm

0
作为来自Java世界的你,显而易见的答案是,不必强制写那些你不得不写的东西,只是为了让Java的类型系统满意,这真是太棒了。
当然,还有其他静态类型检查的语言,它们不会强迫你写那些Java强制要求你写的东西。
即使C#也对局部方法变量进行类型推断!
还有其他静态类型检查的语言,提供比Java更多的编译时错误检查。
(关于Python动态类型有什么好处,不太明显的答案可能需要更多对Python的理解才能理解。)

这不仅仅涉及类型推断(也可以是静态类型),而是类型在运行时已知的事实。即在Python中,“hello(self,object):....”中的对象类型直到运行时才确定。 - OscarRyz
@OscarRyz >> 类型直到运行时才知道 << 你的意思是在运行时之前不知道类型吗? - igouy
@OscarRyz >> 这也可以是静态类型的 << 这就是我写的“还有其他静态类型检查的语言,它们不像Java那样强制你写所有这些东西”和“即使C#也会为本地方法变量执行类型推断!” - igouy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接