Python是强类型的吗?

我遇到过一些链接,上面说Python是一种强类型语言。

然而,我认为在强类型语言中你不能这样做:

bob = 1
bob = "bob"

我认为强类型语言在运行时不接受类型更改。也许我对强/弱类型的定义是错误的(或过于简单)。

那么,Python是强类型语言还是弱类型语言呢?

196482 次浏览

你混淆了“强类型”“动态”

我不能通过添加字符串'12'来改变1的类型,但我可以选择在变量中存储什么类型,并在程序运行时更改它。

与动态类型相对的是静态类型;变量类型的声明在程序的生命周期内不会改变。强类型的反面是弱类型;的类型可以在程序的生命周期中改变。

根据这篇wiki Python文章,Python是动态类型和强类型的(也提供了一个很好的解释)。

也许你正在考虑静态类型语言,其中类型不能在程序执行期间更改,并且在编译时进行类型检查以检测可能的错误。

这个SO问题可能会感兴趣:动态类型语言与静态类型语言和关于类型系统的维基百科文章提供了更多信息

Python是强动态类型的。

  • 强大的类型意味着值的类型不会以意外的方式改变。只包含数字的字符串不会像Perl中那样神奇地变成数字。每次类型更改都需要显式转换。
  • 动态类型意味着运行时对象(值)有类型,而静态类型中变量有类型。

至于你的例子

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完全可以添加类型为intfloat的对象,并返回类型为float的对象(例如,int(42) + float(1)返回43.0)。另一方面,由于类型之间的不匹配,如果有人尝试下面的(42 :: Integer) + (1 :: Float), Haskell会抱怨。这使得Haskell成为一种严格的类型语言,其中类型是完全不相连的,并且只能通过类型类进行受控的重载。

这个问题已经回答过几次了,但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

还有稍微相关但更高级的:

http://effbot.org/zone/call-by-object.htm

Python变量存储对表示该值的目标对象的无类型引用。

任何赋值操作都意味着将未类型化的引用赋值给已赋值的对象——即对象通过原始引用和新引用(已计数)共享。

值类型绑定到目标对象,而不是引用值。(强)类型检查是在执行带有该值的操作(运行时)时执行的。

换句话说,变量(技术上)没有类型——如果想要精确,那么按照变量类型来考虑是没有意义的。但是引用是自动解除引用的我们实际上是根据目标对象的类型来考虑的。

我认为,这个简单的例子可以解释强类型和动态类型之间的区别:

>>> 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
}
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....

在一个大型系统中,上述情况会在很长一段时间内造成代码不可维护的噩梦。随便你怎么称呼它,但“动态”改变变量类型的能力只是一个坏主意……

有一些重要的问题,我认为所有现有的答案都忽略了。


弱类型意味着允许访问底层表示。在C语言中,我可以创建一个指向字符的指针,然后告诉编译器我想把它用作指向整数的指针:

char sz[] = "abcdefg";
int *i = (int *)sz;

在一个32位整数的小端平台上,这将使i变成一个由数字0x646362610x00676665组成的数组。事实上,你甚至可以将指针本身转换为整数(大小合适):

intptr_t i = (intptr_t)&sz;

当然,这意味着我可以覆盖系统中任何位置的内存

char *spam = (char *)0x12345678
spam[0] = 0;

*当然,现代操作系统使用虚拟内存和页面保护,所以我只能覆盖我自己的进程内存,但C本身没有提供这样的保护,任何曾经在经典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)是正确的。我认为在Perl或JavaScript中2是字符串类型的实例没有任何意义,尽管在Tcl中,它实际上是,因为everything是字符串的实例。


最后,还有另一个完全正交的“强”和“强”的定义。“弱”类型,其中“强”意味着强大/灵活/富有表现力。

例如,Haskell允许您定义一种类型,即数字、字符串、该类型的列表或从字符串到该类型的映射,这是表示可以从JSON解码的任何内容的完美方式。在Java中没有办法定义这样的类型。但至少Java有参数(泛型)类型,所以你可以写一个函数,接受一个List of T,并且知道元素是类型T;其他语言,如早期的Java,强迫你使用对象列表和向下转换。但至少Java允许你用它们自己的方法创建新类型;C语言只允许创建结构。而BCPL连这个都没有。以此类推,直到集合,唯一的类型是不同的位长。

因此,从这个意义上说,Haskell的类型系统比现代Java的类型系统更强,现代Java的类型系统比早期Java的类型系统更强,早期Java的类型系统比C的类型系统更强,C的类型系统比BCPL的类型系统更强。

那么,Python在这个范围中处于什么位置呢?这有点棘手。在许多情况下,duck typing允许你模拟在Haskell中可以做的所有事情,甚至一些你不能做的事情;当然,错误是在运行时而不是编译时捕获的,但它们仍然会被捕获。然而,在某些情况下,鸭子类型是不够的。例如,在Haskell中,你可以判断一个int型的空列表是一个int型列表,所以你可以决定在该列表上简化+应该返回0*;在Python中,空列表就是空列表;没有类型信息来帮助你决定减少+应该做什么。

*事实上,Haskell不允许你这样做;如果在空列表上调用不接受起始值的reduce函数,则会得到一个错误。但是它的类型系统足够强大,你可以使它工作,而Python的不能。

术语“强类型”没有明确的定义。

因此,这个词的使用取决于你和谁说话。

我不认为任何没有显式声明变量类型或没有静态类型的语言是强类型的。

强类型不只是排除转换(例如,“自动”将整数转换为字符串)。它排除了赋值(即改变变量的类型)。

如果以下代码编译(解释),则该语言不是强类型的:

Foo = 1 Foo = "1"

在强类型语言中,程序员可以“依赖”类型。

例如,如果程序员看到声明,

UINT64 kZarkCount;

并且他或她知道20行之后,kZarkCount仍然是UINT64(只要它出现在同一个块中)——而不必检查中间的代码。

TLDR;

Python类型是动态,所以你可以改变一个字符串变量为int(在静态语言中你不能)

x = 'somestring'
x = 50

Python类型是强大的,所以你不能合并类型:

'foo' + 3 --> TypeError: cannot concatenate 'str' and 'int' objects

在弱类型Javascript中,这种情况会发生……

 'foo'+3 = 'foo3'

关于类型推断

有些语言(如Java)强制显式声明对象类型

int x = 50;

另一些像芬兰湾的科特林只是从值本身推断它是int

x = 50

但是因为这两种语言都使用静态类型,所以x不能从int更改。这两种语言都不允许像动态这样的变化

x = 50
x = 'now a string'

我刚刚发现了一个极好的、简洁的记忆方法:

动态/静态类型表达式;强/弱类型值。

现有的答案大多同意Python是一种强类型语言,因为它不会隐式地将值从一种类型转换为另一种类型。这些答案提到了将字符串添加到整数的情况来支持这种说法;"foo" + 3在Python中引发TypeError,而在Javascript(通常被认为是弱类型语言)中,数字3隐式转换为字符串,然后连接,因此结果是字符串"foo3"

但在其他一些情况下,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

因此,“强类型”并没有严格的二分法。和“;weakly-typed"我们可以说Python比Javascript更强类型,但没有f#强类型。

Python是强类型的,因为它没有无节制的动态类型错误。换句话说,您不能违反它的类型系统。

定义

  • 类型:值集。它部分地定义了其值的预期用法(行为)。可以用枚举作为扩展,也可以用谓词作为内涵。
  • 类型系统:系统检查值是否按预期使用,部分避免未定义的行为。应避免未定义的行为,因为它可能导致后期程序崩溃或无声的数据丢失和产生不正确的结果。
  • 带有类型系统的类型语言:语言。
  • 可由类型系统检查的错误类型:程序错误。
  • 表达式:表示值的程序文本。
  • 静态/动态类型:表达式的编译时/运行时类型。表达式的运行时类型是它所表示的值的类型。
  • 静态/动态类型系统:类型系统检查静态/动态类型。
  • 静态/动态类型语言:语言带有静态/动态类型系统。
  • 可由静态/动态类型系统检查的静态/动态类型错误:程序错误。
  • 弱/强类型语言:语言带有/没有未检查的动态类型错误。静态类型、动态类型或两者都意味着强类型。
  • 具有单个动态类型/多个动态类型的单型的/多态表达式:表达式。单态表达式有一个预期的用法,而多态表达式有多个预期的用法。
  • 通用/特设多态表达式:实/虚多态表达式。实多态表达式表示具有多种类型的单个值,而虚多态表达式表示具有单一类型的多个值。
  • 基于类型的泛型类型/子类型的参数化/包含多态表达:通用多态表达式(例如,c++表达式&表示单个T* (T&)操作符值,其中T是泛型类型/ c++表达式std::exception表示单个S类值,其中Sstd::exception的泛型子类型)。
  • 重载/强制多态表达式:基于表达式/值转换的临时多态表达式(例如,c++表达式+表示int (int&, int&)float (float&, float&)操作符值/ c++表达式3.5表示floatbool值)。

参考

Cardelli (Luca), Wegner (Peter),“关于理解类型,数据抽象和多态性”,计算调查,卷17,第4期,1985,第471-523页,DOI: https://doi.org/10.1145/6041.6042