【发布时间】:2012-07-04 22:45:00
【问题描述】:
我遇到过说 Python 是一种强类型语言的链接。
但是,我认为在强类型语言中你不能这样做:
bob = 1
bob = "bob"
我认为强类型语言在运行时不接受类型更改。也许我对强/弱类型的定义有误(或过于简单化)。
那么,Python 是强类型语言还是弱类型语言?
【问题讨论】:
标签: python strong-typing weak-typing
我遇到过说 Python 是一种强类型语言的链接。
但是,我认为在强类型语言中你不能这样做:
bob = 1
bob = "bob"
我认为强类型语言在运行时不接受类型更改。也许我对强/弱类型的定义有误(或过于简单化)。
那么,Python 是强类型语言还是弱类型语言?
【问题讨论】:
标签: python strong-typing weak-typing
现有的答案大多同意 Python 是一种强类型语言,因为它不会将值从一种类型隐式转换为另一种类型。这些答案提到了将字符串添加到整数以支持此主张的情况; "foo" + 3 在 Python 中引发 TypeError,而在 Javascript(通常被认为是一种弱类型语言)中,数字 3 被隐式转换为字符串然后连接起来,因此结果是字符串 @ 987654326@.
但在其他一些情况下,Python 会进行隐式类型转换:
# implicit conversion from int to float
1 + 1.0
# implicit conversion from list to bool
if []: pass
相比之下,F#(通常被认为是一种强类型语言)不允许这两种语言:
1 + 1.0;;
----^^^
error FS0001: The type 'float' does not match the type 'int'
if [] then 1 else 2;;
---^^
error FS0001: This expression was expected to have type bool but here has type 'a list
所以实际上并没有严格的“强类型”和“弱类型”语言的二分法,而是我们可以说 Python 比 Javascript 的强类型更强,但不如 F# 强类型。
【讨论】:
我刚刚发现了一种非常简洁的记忆方法:
动态/静态类型表达式;强/弱类型值。
【讨论】:
Python 类型是动态,因此您可以将字符串变量更改为 int(在 静态 语言中您不能)
x = 'somestring'
x = 50
Python 类型是强,所以你不能合并类型:
'foo' + 3 --> TypeError: cannot concatenate 'str' and 'int' objects
在弱类型 Javascript 中会发生这种情况...
'foo'+3 = 'foo3'
Java 强制您显式声明对象类型
int x = 50;
Kotlin 使用推理来实现它是int
x = 50
但由于两种语言都使用 static 类型,x 不能从 int 更改。两种语言都不允许像
x = 50
x = 'now a string'
【讨论】:
'x' + 3可能是operator+重载并在后台做类型转换?
Python 是强动态类型的。
至于你的例子
bob = 1
bob = "bob"
这是有效的,因为变量没有类型;它可以命名任何对象。在bob=1 之后,你会发现type(bob) 返回int,但是在bob="bob" 之后,它返回str。 (注意type 是一个常规函数,所以它会计算它的参数,然后返回值的类型。)
将此与旧的 C 方言进行对比,后者是弱静态类型的,因此指针和整数几乎可以互换。 (现代 ISO C 在很多情况下都需要转换,但我的编译器默认对此仍然宽容。)
我必须补充一点,强类型与弱类型更像是一个连续体,而不是布尔选择。 C++ 的类型比 C 强(需要更多的转换),但类型系统可以通过使用指针转换来颠覆。
在 Python 等动态语言中,类型系统的强度实际上取决于其原语和库函数对不同类型的响应方式。例如,+ 被重载,因此它可以处理两个数字或两个字符串,但不能处理一个字符串和一个数字。这是在实现+ 时做出的设计选择,但从语言的语义来看,这并不是真正的必需品。实际上,当您在自定义类型上重载 + 时,您可以使其隐式地将任何内容转换为数字:
def to_number(x):
"""Try to convert function argument to float-type object."""
try:
return float(x)
except (TypeError, ValueError):
return 0
class Foo:
def __init__(self, number):
self.number = number
def __add__(self, other):
return self.number + to_number(other)
Foo 类的实例可以添加到其他对象:
>>> a = Foo(42)
>>> a + "1"
43.0
>>> a + Foo
42
>>> a + 1
43.0
>>> a + None
42
请注意,即使强类型 Python 完全可以添加 int 和 float 类型的对象并返回 float 类型的对象(例如,int(42) + float(1) 返回 43.0)。另一方面,由于类型之间的不匹配,如果尝试以下(42 :: Integer) + (1 :: Float),Haskell 会抱怨。这使得 Haskell 成为一种严格类型化的语言,其中类型完全不相交,并且只能通过类型类实现受控形式的重载。
【讨论】:
True 或False。但是号码促销呢? 1.0 + 2 在 Python 中的效果与在 Perl 或 C 中一样好,尽管 "1.0" + 2 没有。我同意@jbrendel 的观点,这并不是真正的隐式转换,它只是重载——但在同样的意义上,Perl 也没有进行任何隐式转换。如果函数没有声明参数类型,则无处可进行隐式转换。
if isValid(value) - 1 可能会泄漏。布尔值被强制转换为整数,然后将其评估为真值。 False - 1 变为真,True - 1 变为假,导致难以调试的两层逐一错误。从这个意义上说,python大部分是强类型的;类型强制通常不会导致逻辑错误。
“强类型”一词没有明确的定义。
因此,该术语的使用取决于您与谁交谈。
我不认为任何语言,其中变量的类型既没有显式声明,也没有静态类型化为强类型。
强类型不只是排除转换(例如,“自动”从整数转换为字符串)。它排除了赋值(即更改变量的类型)。
如果以下代码编译(解释),则语言不是强类型:
Foo = 1 Foo = "1"
在强类型语言中,程序员可以“依赖”一种类型。
例如,如果程序员看到声明,
UINT64 kZarkCount;
并且他或她知道 20 行之后,kZarkCount 仍然是 UINT64(只要它出现在同一个块中) - 无需检查中间代码。
【讨论】:
我认为所有现有答案都遗漏了一些重要问题。
弱类型意味着允许访问底层表示。在 C 中,我可以创建一个指向字符的指针,然后告诉编译器我想将它用作指向整数的指针:
char sz[] = "abcdefg";
int *i = (int *)sz;
在具有 32 位整数的 little-endian 平台上,这会将 i 变成由数字 0x64636261 和 0x00676665 组成的数组。事实上,您甚至可以将指针本身转换为整数(大小合适):
intptr_t i = (intptr_t)&sz;
当然这意味着我可以覆盖系统中任何地方的内存。*
char *spam = (char *)0x12345678
spam[0] = 0;
* 当然,现代操作系统使用虚拟内存和页面保护,所以我只能覆盖我自己的进程的内存,但是 C 本身并没有提供这样的保护,就像任何曾经在 Classic Mac OS 上编码的人一样或者Win16可以告诉你。
传统的 Lisp 允许类似的黑客行为;在某些平台上,双字浮点数和 cons 单元格是同一类型,您只需将一个传递给期望另一个的函数,它就会“工作”。
今天的大多数语言并不像 C 和 Lisp 那样弱,但其中许多仍然有些漏洞。例如,任何具有未经检查的“向下转换”* 的 OO 语言都是类型泄漏:您实际上是在告诉编译器“我知道我没有给您足够的信息来知道这是安全的,但我很确定它是,”当类型系统的全部意义在于编译器总是有足够的信息来知道什么是安全的。
* 检查的向下转换不会仅仅因为它将检查移至运行时而使语言的类型系统变得更弱。如果确实如此,那么子类型多态性(也称为虚拟或完全动态函数调用)将同样违反类型系统,我认为没有人愿意这么说。
很少有“脚本”语言在这个意义上是弱的。即使在 Perl 或 Tcl 中,您也不能获取字符串并将其字节解释为整数。* 但值得注意的是,在 CPython 中(对于许多语言的许多其他解释器也是如此),如果您真的坚持不懈,您可以使用ctypes 加载libpython,将对象的id 转换为POINTER(Py_Object),并强制类型系统泄漏。这是否会使类型系统变弱取决于您的用例——如果您尝试实现语言内受限的执行沙箱以确保安全,则必须处理这些类型的转义……
* 您可以使用像struct.unpack 这样的函数来读取字节并根据“C 如何表示这些字节”构建一个新的 int,但这显然不是泄漏的;甚至 Haskell 也允许这样做。
同时,隐式转换与弱或泄漏类型系统确实不同。
每种语言,甚至是 Haskell,都有函数可以将整数转换为字符串或浮点数。但是有些语言会自动为你做一些转换——例如,在 C 中,如果你调用一个需要 float 的函数,然后你将它传递给 int,它会为你转换。这肯定会导致错误,例如意外溢出,但它们与您从弱类型系统中获得的错误类型不同。而且C在这里并没有真正变弱。你可以在 Haskell 中添加一个 int 和一个 float,甚至将一个 float 连接到一个字符串,你只需要更明确地去做。
对于动态语言,这非常模糊。在 Python 或 Perl 中没有“需要浮点数的函数”之类的东西。但是有一些重载的函数用不同的类型做不同的事情,并且有一种强烈的直觉感觉,例如,将一个字符串添加到其他东西是“一个需要字符串的函数”。从这个意义上说,Perl、Tcl 和 JavaScript 似乎做了很多隐式转换("a" + 1 给你"a1"),而 Python 做的少得多("a" + 1 引发异常,但1.0 + 1 确实给你2.0*)。很难将这种意义用正式术语表达——为什么不应该有一个 + 接受一个字符串和一个 int,而显然还有其他函数,比如索引,这样做呢?
* 实际上,在现代 Python 中,这可以用 OO 子类型来解释,因为 isinstance(2, numbers.Real) 是真的。我认为2 是 Perl 或 JavaScript 中字符串类型的实例没有任何意义……尽管在 Tcl 中,它实际上是,因为 everything 是字符串的实例。子>
最后,“强”与“弱”类型还有另一个完全正交的定义,其中“强”意味着强大/灵活/富有表现力。
例如,Haskell 允许您定义数字、字符串、此类型的列表或从字符串到此类型的映射的类型,这是表示可以从 JSON 解码的任何内容的完美方式。没有办法在 Java 中定义这样的类型。但是至少 Java 有参数(泛型)类型,所以你可以编写一个函数,它接受一个 T 的 List 并知道元素是 T 类型;其他语言,例如早期的 Java,迫使您使用对象列表并向下转换。但至少 Java 允许您使用自己的方法创建新类型; C 只允许您创建结构。 BCPL 甚至没有。依此类推,直到组装,其中唯一的类型是不同的位长度。
所以,从这个意义上说,Haskell 的类型系统比现代 Java 强,现代 Java 比早期 Java 强,比 C 强,比 BCPL 强。
那么,Python 在哪个范围内适合?这有点棘手。在很多情况下,duck typing 允许你模拟你在 Haskell 中可以做的所有事情,甚至是一些你不能做的事情;当然,错误是在运行时而不是编译时捕获的,但它们仍然会被捕获。但是,在某些情况下,duck 打字是不够的。例如,在 Haskell 中,您可以判断一个空的整数列表是一个整数列表,因此您可以决定在该列表上减少 + 应该返回 0*;在 Python 中,空列表是一个空列表;没有类型信息可以帮助您决定减少 + 应该做什么。
* 事实上,Haskell 不允许你这样做;如果您调用的 reduce 函数在空列表中不采用起始值,则会出现错误。但它的类型系统足够强大,您可以完成这项工作,而 Python 则不行。
【讨论】:
char sz[] 不是指向 char 的指针,它是 char 数组,在赋值中它衰减为指针。
class testme(object):
''' A test object '''
def __init__(self):
self.y = 0
def f(aTestMe1, aTestMe2):
return aTestMe1.y + aTestMe2.y
c = testme #get a variable to the class
c.x = 10 #add an attribute x inital value 10
c.y = 4 #change the default attribute value of y to 4
t = testme() # declare t to be an instance object of testme
r = testme() # declare r to be an instance object of testme
t.y = 6 # set t.y to a number
r.y = 7 # set r.y to a number
print(f(r,t)) # call function designed to operate on testme objects
r.y = "I am r.y" # redefine r.y to be a string
print(f(r,t)) #POW!!!! not good....
上述情况会在很长一段时间内在大型系统中造成无法维护的代码的噩梦。随心所欲地调用它,但是“动态”更改变量类型的能力只是一个坏主意......
【讨论】:
我想,这个简单的例子你应该解释强类型和动态类型之间的区别:
>>> tup = ('1', 1, .1)
>>> for item in tup:
... type(item)
...
<type 'str'>
<type 'int'>
<type 'float'>
>>>
java:
public static void main(String[] args) {
int i = 1;
i = "1"; //will be error
i = '0.1'; // will be error
}
【讨论】:
Python 变量存储对表示值的目标对象的无类型引用。
任何赋值操作都意味着将无类型引用分配给已分配对象——即对象通过原始引用和新(计数)引用共享。
值类型绑定到目标对象,而不是引用值。 (强)类型检查是在执行具有值的操作时完成的(运行时)。
换句话说,变量(从技术上讲)没有类型——如果想要准确,从变量类型的角度来思考是没有意义的。但是引用是自动取消引用的,我们实际上是根据目标对象的类型来考虑的。
【讨论】:
已经回答过几次了,但是 Python 是一种强类型语言:
>>> x = 3
>>> y = '4'
>>> print(x+y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
JavaScript 中的以下内容:
var x = 3
var y = '4'
alert(x + y) //Produces "34"
这就是弱类型和强类型的区别。弱类型会根据上下文(例如 Perl)自动尝试从一种类型转换为另一种类型。强类型从不隐式转换。
您的困惑在于误解了 Python 如何将值绑定到名称(通常称为变量)。
在 Python 中,名称没有类型,因此您可以执行以下操作:
bob = 1
bob = "bob"
bob = "An Ex-Parrot!"
名字可以绑定到任何东西:
>>> def spam():
... print("Spam, spam, spam, spam")
...
>>> spam_on_eggs = spam
>>> spam_on_eggs()
Spam, spam, spam, spam
进一步阅读:
https://en.wikipedia.org/wiki/Dynamic_dispatch
以及稍微相关但更高级的:
【讨论】:
"3"*4。结果当然是"3333"。你不会说它正在转换任何一种东西。当然,这可能只是在争论语义。
float 来自 float 和 int 的组合,它正在隐式转换类型。 float 和 int 之间有一种自然的关系,事实上,type heirarchy 说明了这一点。我想你可能会争辩说 Javascript 认为 '3'+4 和 'e'+4 都是定义明确的操作,就像 Python 认为 3.0 + 4 是明确定义的一样,但是在那时真的没有这么强的东西或弱类型,只是(未)定义的操作。
根据这篇wiki Python 文章,Python 既是动态类型又是强类型(也提供了很好的解释)。
也许您正在考虑 statically typed 语言,其中类型在程序执行期间无法更改,并且在编译期间进行类型检查以检测可能的错误。
这个 SO 问题可能会引起您的兴趣:Dynamic type languages versus static type languages 和这篇关于 Type Systems 的维基百科文章提供了更多信息
【讨论】:
您将'strongly typed' 与'dynamically typed' 混淆了。
我无法通过添加字符串 '12' 来更改 1 的类型,但我可以选择存储在变量中的类型并在程序运行时更改它。
与动态类型相反的是静态类型; 变量类型的声明在程序的生命周期内不会改变。强类型的反面是弱类型; 值的类型可以在程序的生命周期内改变。
【讨论】: