我来自 Java 世界,我想知道除了在编译代码时丢失错误之外,Python 中的动态类型还有什么好处?
你喜欢 Python 的打字吗?你有一个例子,它在一个大项目中有所帮助吗?是不是有点容易出错?
我来自 Java 世界,我想知道除了在编译代码时丢失错误之外,Python 中的动态类型还有什么好处?
你喜欢 Python 的打字吗?你有一个例子,它在一个大项目中有所帮助吗?是不是有点容易出错?
在一般情况下,静态类型检查是不可判定的。这意味着有些程序是静态类型安全的,但类型检查器无法证明它们是静态类型安全的,因此类型检查器必须拒绝这些程序。
换句话说:有类型检查器不允许您编写的类型安全程序。或者,更简洁地说:静态类型会阻止您编写某些程序。
这通常适用于所有静态类型,而不仅仅是 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 与 BASIC 或 Python 与 Haskell。
我怀疑绝大多数重要的 Java 程序都有动态类型。
每次在 Java 中从 Object 转换为显式类型时,您都在进行动态类型检查 - 这包括在 1.5 中引入泛型之前对集合类的每次使用。实际上,Java 泛型仍然可以将一些类型检查推迟到运行时。
每次使用 Java 反射时,您都在进行动态类型检查。这包括从文本文件中的类或方法名到真实类或方法的映射——例如每次使用 Spring XML 配置文件时。
这是否会使 Java 程序变得脆弱且容易出错?Java 程序员是否会花费大量时间来跟踪和修复错误动态类型的问题?可能不会——Python 程序员也不会。
动态类型的一些优点:
你喜欢 Python 吗?
它是 Python 的一部分。在Python 中 喜欢它是愚蠢的。
你有一个例子,它在一个大项目中有所帮助吗?
是的。每一天我都为我可以做出改变而高兴——因为 Duck 类型——它们被合理地本地化,通过了所有的单元测试,通过了所有的集成测试,并且在其他地方没有任何中断。
如果这是 Java,那么这些更改将需要无休止的重构以将接口从类中提取出来,以便我可以引入在 Java 的静态类型检查下仍然允许的变体。
是不是有点容易出错?
只不过是静态类型。一个简单的单元测试确认对象符合预期的特征。
It's easy to write a class in Java that (a) passes compile-time checks and (b) crashes horribly at run time. Casts are a good way to do this. Failing to meet the classes intent is a common thing -- a class may compile but still not work.
许多模式(例如来自 GoF)是不必要的,或者可以在具有函数风格的动态类型语言中以较少的努力实现。事实上,很多模式都是“内置”到 python 中的,所以如果你编写简短的“pythonic”代码,你将免费获得所有好处。您不需要 Iterator、Observer、Strategy、Factory Method、Abstract Factory 和一堆 Java 或 C++ 中常见的其他模式。
这意味着要编写的代码更少,而且(更重要的是)需要阅读、理解和支持的代码更少。我认为这是像 python 这样的语言的主要好处。在我看来,这大大超过了静态类型的缺失。与类型相关的错误在 python 代码中并不常见,它们很容易通过简单的功能测试来捕获(而且这样的测试在 python 中肯定比在 java 中更容易编写)。
这是你头脑中的一个负担。您可以将红色视为“Red”(常量)或“255, 0, 0”(元组)或“#FF0000”(字符串):三种不同的格式,需要三种不同的类型,或复杂的查找和Java中的转换方法。
它使代码更简单。
例如,您可以编写可以向其传递整数以及字符串、列表或字典或其他任何内容的函数,并且它将能够以适当的方式透明地处理所有这些(或者如果它抛出异常无法处理类型)。你也可以用其他语言来做类似的事情,但通常你不得不求助于(ab)使用指针、引用或类型转换之类的东西,这会为编程错误打开漏洞,而且很丑陋。
由于您来自 Java 世界,显而易见的答案是,最好不要被迫编写所有您被迫编写的东西,只是为了让 Java 的类型系统满意。
当然,还有其他静态类型检查语言不会强迫您编写 Java 强迫您编写的所有内容。
甚至 C# 也会对局部方法变量进行类型推断!
还有其他静态类型检查语言提供的编译时错误检查比 Java 提供的更多。
(不太明显的答案 - Python 中的动态类型有什么好处? - 可能需要更多地了解 Python 才能理解。)