tl;dr:你说得对,Python 的语义本质上是 Java 的语义,没有任何原始类型。
“通过赋值传递”实际上与您所询问的区别不同。1 这个想法是传递给函数(和其他可调用对象)的参数的工作方式与赋值完全相同有效。
考虑:
def f(x):
pass
a = 3
b = a
f(a)
b = a 表示目标b,在本例中为全局命名空间中的名称,成为对a 引用的任何值的引用。
f(a) 表示目标x,在这种情况下是为执行f 而构建的框架的本地命名空间中的名称,成为对a 引用的任何值的引用。
语义相同。每当一个值被分配给一个目标时(这并不总是一个简单的名称——例如,认为lst[0] = a 或spam.eggs = a),它遵循相同的赋值规则集——无论是赋值语句、函数调用还是as 子句,或者循环迭代变量,只有一组规则。
但总的来说,您认为 Python 与 Java 类似但只有引用类型的直观想法是准确的:您总是“按值传递引用”。
争论这算作“按引用传递”还是“按值传递”是没有意义的。试图为它想出一个没有人会争论的新的明确名称是更没有意义的。 Liskov 在 30 年前发明了 "call by object" 这个词,如果它从未流行起来,那么今天有人想出的任何东西都不太可能做得更好。
您了解实际的语义,这才是最重要的。
是的,这意味着没有复制。在 Java 中,只复制原始值,而 Python 没有原始值,因此不会复制任何内容。
唯一的区别是“原始类型”(例如,数字)不会被复制,而只是被视为对象
最好将其视为“唯一的区别是没有没有'原始类型'(甚至不是简单的数字)”,正如您在开始时所说的那样。
还值得一问,为什么 Python 没有原始类型——或者为什么 Java 有。2
将所有内容“装箱”可能会非常缓慢。在 Python 中添加 2 + 3 意味着取消引用 2 和 3 对象,从中获取本机值,将它们相加,并将结果包装在新的 5 对象中(或在表中查找)因为您已经有一个现有的5 对象)。这比仅仅添加两个整数要多得多。3
虽然像 Hotspot 或 Python 的 PyPy 这样的良好 JIT 通常可以自动执行这些优化,但有时“经常”还不够好。这就是 Java 具有原生类型的原因:让您在这些情况下手动优化事物。
相反,Python 依赖于 Numpy 等第三方库,它让您只需为整个数组支付一次装箱成本,而不是每个元素一次。这使语言更简单,但代价是需要 Numpy。4
1。据我所知,“通过分配”在常见问题解答中出现了几次,但实际上并未在参考文档或词汇表中使用。参考文档已经倾向于直观而不是严谨,但常见问题解答和教程一样,朝着这个方向走得更远。因此,询问常见问题解答中的一个术语是什么意思,超出了它试图传达的直观概念,首先可能不是一个有意义的问题。
2。我将在这里忽略 Java 缺少运算符重载的问题。没有理由他们不能为少数核心类包含特殊的语言规则,即使他们不允许你对自己的类做同样的事情——例如,Go 对 range 和人们做的事情就是这样很少抱怨。
3。 ……甚至不是循环两个 30 位数字的数组,这正是 Python 实际所做的。与装箱的成本相比,处理无限大小的“bigint”的成本微乎其微,因此 Python 总是支付额外的、几乎不引人注意的成本。 Python 2 确实像 Java 一样具有单独的固定类型和大整型类型,但几十年的经验表明,它并没有从额外的复杂性中获得任何性能优势。
4. Numpy 的实现当然远非简单。但是使用它非常简单,需要使用 Numpy 的人比需要编写 Numpy 的人多得多,因此这是一个相当不错的权衡。