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

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

bob = 1
bob = "bob"

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

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


当前回答

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

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

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

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

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

Foo = 1 Foo = "1"

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

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

UINT64 kZarkCount;

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

其他回答

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

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

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

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

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

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

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


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

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

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

intptr_t i = (intptr_t)&sz;

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

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

*当然,现代操作系统使用虚拟内存和页面保护,所以我只能覆盖我自己的进程内存,但C本身没有提供这样的保护,任何曾经在经典Mac OS或Win16上编码的人都可以告诉你。

传统的Lisp允许类似的黑客行为;在某些平台上,双字浮点和cons单元格是同一类型的,你可以将其中一个传递给一个函数,期待另一个,它就会“工作”。

今天的大多数语言都不像C和Lisp那么弱,但其中许多仍然存在漏洞。例如,任何有未检查的“向下转换”的OO语言*都是类型泄漏:你实际上是在告诉编译器“我知道我没有给你足够的信息来知道这是安全的,但我非常确定它是安全的”,而类型系统的全部意义在于编译器总是有足够的信息来知道什么是安全的。

*被检查的向下转换不会因为将检查移到运行时而使语言的类型系统变得更弱。如果是这样,那么子类型多态性(又名虚拟或全动态函数调用)将同样违反类型系统,我认为没有人愿意这样说。

Very few "scripting" languages are weak in this sense. Even in Perl or Tcl, you can't take a string and just interpret its bytes as an integer.* But it's worth noting that in CPython (and similarly for many other interpreters for many languages), if you're really persistent, you can use ctypes to load up libpython, cast an object's id to a POINTER(Py_Object), and force the type system to leak. Whether this makes the type system weak or not depends on your use cases—if you're trying to implement an in-language restricted execution sandbox to ensure security, you do have to deal with these kinds of escapes…

你可以使用struct这样的函数。unpack来读取字节,并根据“C如何表示这些字节”构建一个新的int,但这显然没有漏洞;甚至Haskell也承认这一点。


与此同时,隐式转换与弱的或有漏洞的类型系统确实是两码事。

Every language, even Haskell, has functions to, say, convert an integer to a string or a float. But some languages will do some of those conversions for you automatically—e.g., in C, if you call a function that wants a float, and you pass it in int, it gets converted for you. This can definitely lead to bugs with, e.g., unexpected overflows, but they're not the same kinds of bugs you get from a weak type system. And C isn't really being any weaker here; you can add an int and a float in Haskell, or even concatenate a float to a string, you just have to do it more explicitly.

And with dynamic languages, this is pretty murky. There's no such thing as "a function that wants a float" in Python or Perl. But there are overloaded functions that do different things with different types, and there's a strong intuitive sense that, e.g., adding a string to something else is "a function that wants a string". In that sense, Perl, Tcl, and JavaScript appear to do a lot of implicit conversions ("a" + 1 gives you "a1"), while Python does a lot fewer ("a" + 1 raises an exception, but 1.0 + 1 does give you 2.0*). It's just hard to put that sense into formal terms—why shouldn't there be a + that takes a string and an int, when there are obviously other functions, like indexing, that do?

*实际上,在现代Python中,这可以用OO子类型来解释,因为isinstance(2, numbers.Real)为真。我不认为在Perl或JavaScript中,2是字符串类型的实例……尽管在Tcl中,它实际上是,因为所有东西都是字符串的实例。


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

For example, Haskell lets you define a type that's a number, a string, a list of this type, or a map from strings to this type, which is a perfectly way to represent anything that can be decoded from JSON. There's no way to define such a type in Java. But at least Java has parametric (generic) types, so you can write a function that takes a List of T and know that the elements are of type T; other languages, like early Java, forced you to use a List of Object and downcast. But at least Java lets you create new types with their own methods; C only lets you create structures. And BCPL didn't even have that. And so on down to assembly, where the only types are different bit lengths.

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

So, where does Python fit into that spectrum? That's a bit tricky. In many cases, duck typing allows you to simulate everything you can do in Haskell, and even some things you can't; sure, errors are caught at runtime instead of compile time, but they're still caught. However, there are cases where duck typing isn't sufficient. For example, in Haskell, you can tell that an empty list of ints is a list of ints, so you can decide that reducing + over that list should return 0*; in Python, an empty list is an empty list; there's no type information to help you decide what reducing + over it should do.

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

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

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

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

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