1. 真的能用隐式类型转换做为强弱类型的判断标准吗?
最近有些学员问我,Python究竟是强类型语言,仍是弱类型语言。我就直接脱口而出:Python是弱类型语言。没想到有一些学员给我了一些文章,有中文的,有英文的,都说Python是强类型语言。我就很好奇,特地仔细研究了这些文章,例如,下面就是一篇老外写的文章:
https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic%20language%20and%20also%20a%20strongly%20typed%20language
其余中文的相关文章,你们能够去网上搜,一堆,这里就不一一列举了。
我先不说这些结论对不对,我先总结一下这些文章的核心观点。这些文章将编程语言分为强类型、弱类型、动态类型和静态类型。这4个概念的解释以下:
强类型:若是一门语言不对变量的类型作隐式转换,这种编程语言就被称为强类型语言 ;
弱类型:与强类型相反,若是一门语言对变量的类型作隐式转换,那咱们则称之为弱类型语言;
动态类型:若是一门语言能够在运行时改变变量的类型,那咱们称之为动态类型语言;
静态类型:与动态类型相反,若是一门语言不能够在运行时改变变量的类型,则称之为静态类型语言;

其实这些概念就涉及到编程语言的两个特性:隐式类型转换和类型固化。
所谓类型固化,就是指一旦变量在初始化时被肯定了某个数据类型(如整数类型),那么这个变量的数据类型将永远不会变化。
关于动态类型和静态类型,在本文的后面再来讨论,这里先探讨强类型和弱类型。
如今姑且认为这个结论没问题。强类型就是不容许作隐式类型转换。OK,咱们看看用这个隐式类型转换来判断强类型和弱类型是否合理。
在这些文章中,给出了不少例子做为证据来证明这个结论,其中最典型的例子是在Python语言中,int + string是不合法的,没错,确实不合法。如执行1 + 'abc'会抛出异常。固然,还有人给出了另外一个例子:string / int也是不合法的,如执行'666' / 20会抛出异常,没错,字符串与整数的确不能直接相除。那你怎么不用乘号举例呢?如'abc' * 10,这在Python中但是合法的哦,由于这个表达式会将'abc'复制10份。为什么不用我大乘号来举例,难道瞧不起我大乘号吗?这是运算符歧视?

另外,难道没据说过Python支持运算符重载吗?经过运算符重载,可让两个类型彻底不一样的变量或值在一块儿运算,如相加,看下面的例子:
class MyClass1: def __init__(self,value): self.value = value
class MyClass2: def __init__(self,value): self.value = valuemy1 = MyClass1(20)my2 = MyClass2(30)print( my1 + my2)
若是执行这段代码,100%会抛出异常,由于MyClass1和MyClass2确定不能相加,但若是按下面的方式修改代码,就没问题了。
class MyClass1: def __init__(self,value): self.value = value def __add__(self,my): return self.value + my.valueclass MyClass2: def __init__(self,value): self.value = value def __add__(self,my): return self.value + my.value
my1 = MyClass1(20)my2 = MyClass2(30)
print( my1 + my2)
这段代码对MyClass1和MyClass2进行了加法运算符重载,这样两个不一样类型的变量就能够直接相加了,从表面上看,好像是发生了类型转换,但实际上是运算符重载。
固然,运算符重载也可能会使用显式类型转换,以下面的代码容许不一样类型的值相加。
class MyClass1: def __init__(self,value): self.value = value def __add__(self,my): return str(self.value) + str(my.value)class MyClass2: def __init__(self,value): self.value = value def __add__(self,my): return str(self.value) + str(my.value)
my1 = MyClass1(20)my2 = MyClass2("xyz")
print( my1 + my2)
其实这段代码也就至关于int + string形式了,只是用MyClass1和MyClass2包装了一层。可能有不少同窗会说,这能同样吗?明显不是int + string的形式,ok,的确是不太同样。
惋惜目前Python还不支持内建类型(如int、str)的运算符重载,但不能保证之后不支持,若是之后Python要是支持内建类型运算符重载,那就意味着能够重载str类的__add__方法了,目前str类定义在builtins.py文件中,里面已经预约义了不少可能被重载的运算符。固然,目前Python是直接将这些运算符方法固化在解析器中了,例如,__add__方法是只读的,不能修改。以下面的Python代码至关于a + "ok"。
a = "abc"print( a.__add__("ok"))
但你不能用下面的代码覆盖掉str类的__add__方法。
def new_add(self, value): return str(self) + str(value)str.__add__ = new_add
执行这段代码会抛出以下图的异常,也就是说,目前Python的内建类型,如str,是不能动态为其添加新的成员或覆盖之前的成员的。

但如今不能,不表明之后不能。若是之后Python支持覆盖内建类型的运算符,那么int + string就可让其合法化。不过可能还会有同窗问,就算内建类型支持运算符重载,那不还须要使用显式类型转换吗?是的,没错,须要类型转换。
如今咱们先来谈谈类型转换,先用另一个被公认的弱类型编程语言JavaScript为例。在JS中,1 + 'abc'是合法的、'444'/20也是合法的,因此就有不少人认为js是弱类型语言,没错,js的确是弱类型语言。但弱类型确实是根据1 + 'abc'和'444'/20得出来的?
有不少人认为,JavaScript不作类型检查,就直接将1和'abc'相加了!你是当真的?若是不作类型检查,那么js怎么会知道如何将1和'abc'相加,为啥不将1当作1.0呢?其实无论是什么类型的编程语言,数据类型检测都是必须的,无论是js、仍是Python,或是Java,内部必定会作数据类型检测,只是检测的目的不一样而已。在Python中,进行数据类型检测后,发现不合规的状况,有时会自动处理(如int+float),有时干脆就抛出异常(如int + string)。而在Java中就更严格了,在编译时,发现不合规的状况,就直接抛出编译错误了。在js中,发现不合规的状况,就会按最大可能进行处理,在内部进行类型转换。对,不是无论数据类型了,而是在内部作的数据类型转换。那么这和经过Python的运算符重载在外部作类型转换有什么区别呢?只是一个由编译器(解析器)内部处理的,一个是在外部由程序员编写代码处理的!并且就算Python不会支持内建类型的运算符重载,那么也有可能直接支持int + string的形式。由于目前Python不支持,因此正确的Python代码不可能有int + string的形式。因此若是之后支持int + string的形式,也能够彻底作到代码向下兼容。就算Python将来不支持int + string形式,那么我本身作一个Python解析器(例如,咱们团队如今本身作的Ori语言,支持类型隐式转换,不过其实是生成了其余的编程语言,也就是语言之间的转换,这是否是表明Ori是弱类型语言呢?),彻底兼容Python的代码,只不过支持int+string形式,那么能不能说,个人这个Python版本是弱类型Python呢?这很正常,由于像C++这种语言也有不少种实现版本,Python一样也能够拥有,只不过目前没多少人作而已,但不等于没有可能。
若是Python真这么作了,那么能不能说Python又从强类型语言变成了弱类型语言呢?若是你们认为一种语言的类型强弱是能够随着时间变化的,那么我无话可说!
总之,须要用一种肯定不会变的特性来表示强弱类型才是最合适的。一般来说,某种语言的变量一旦数据类型肯定了,就不容许变化了,这种才能够称为强类型,强大到类型一言九鼎,类型一旦肯定,就不容许变了,而Python显然不是,x = 20; x = 'abc';一样是合法的,x前后分别是int和str类型。
PS:这里再给你们一个表,一般编程语言中肯定类型是否兼容,就是经过相似的表处理的。这个表主要用于内建类型,若是是自定义类型,须要经过接口(实现)和类(继承)类肯定类型是否兼容。
这个表只给出了3个数据类型:int、float和str。根据业务不一样,这个表能够有多种用途,例如,赋值,是否能够进行运算等。这里就只考虑进行加法运算。其中True表示容许进行加法运算,False表示不容许进行加法运算,很显然,若是是int + int形式,第1个操做数能够从第1列查找,第2个操做数能够从第1行查找,找到了(1,1)的位置,该位置是True,因此int + int是合法的,int + float,float + float、str + str的情形相似,若是遇到int + str,就会找到(1,3)或(3,1),这两个位置都是False,就代表int + str是不合法的。其实Python和JavaScript都进行到了这一步。只不过Python就直接抛出了异常,而JS则尝试进行类型转换,但都须要进行类型检测。由于类型转换须要肯定数据类型的优先级,优先级低的会转换为优先级高的类型,如str的优先级比int高,因此int会转换为str类型。float比int高,因此int会转换为float类型,这就涉及到另一个类型优先级表了。
根据这个表可知,编程语言只是在遇到类型不合规的状况下处理的方式不一样,这就是编译器(解析器)的业务逻辑了,这个业务逻辑随时可能变(一般不会影响程序的向下兼容),因此是不能用这一特性做为强弱语言标识的,不然强类型和弱类型语言就有可能会不断切换了,由于编程语言会不断进化的。
那么为何能够用类型固化做为强弱类型的标识呢?由于类型固化一般是不可变的,那么为何是不可变的呢?下面用Python来举例:
下面的Python代码是合法的。x从int变成了str,类型并无固化,全部Python是弱类型语言。
那么有没有可能Python之后对类型进行固化呢?从技术上来讲,彻底没问题,但从代码兼容性问题上,将会形成严重的后果。由于类型没固化属于宽松型,一旦类型固化,属于严格型。之前已经遗留了不少宽松的代码,一旦严格,那么就意味着x = 'abc'将会抛出异常,就会形成不少程序没法正常运行。因此若是Python这么作,就至关于一种新语言了,如PythonX,而不能再称为Python了。就像人类进化,不管从远古的尼安德特人,仍是智人,或是现代各个国家的人,不管怎么进化,都须要在主线上发展,例如,都有一个脑壳,两条腿,两个胳膊。固然,可能细节不一样,如黑眼睛,黄头发等。你不能进化出两个头,8条腿来,固然能够这么进化,但这个就不能再称为人了,就是另一种生物了。

如今再看一个相反的例子,若是一种编程语言(如Java)是强类型的,可否之后变成弱类型语言呢?
其实从技术上和兼容性上这么作是没问题的。但也会有不少其余问题,如编译器(或运行时)的处理方式彻底不一样,咱们知道,类型固化的程序要比类型不固化的程序运行效率高,由于类型不固化,须要不断去考虑类型转换的问题。并且在空间分配上更麻烦,有可能会不断分配新的内存空间。例如,对于一个数组来讲,js和python(就是列表)是能够动态扩容的,其实这个方式效率很低,须要用算法在合理的范围内不断分配新的内存空间,而Java不一样,数组一旦分配内存空间,是不可变的,也就是空间固化(相似于类型固化),这样的运行效率很是高。
因此一旦编程语言从类型固化变成类型不固化,尽管能够保证代码的兼容性,但编译器或运行时的内部实现机理将彻底改变,因此从本质上说,也是另一种编程语言了。就像人类的进化,尽管从表面上符合人类的全部特征。但内部已经变成生化人了,已经不是血肉之躯了,这也不能称为人类了。
因此不管往哪一个方向变化,都会造成另一种全新的编程语言,因此用类型固化来做为强弱类型标识是彻底没有问题的。
3. C++、Java、Kotlin是强类型语言,仍是弱类型语言
我看到网上有不少文章将C++归为弱类型语言。其实,这我是头一次据说C++有人认为是弱类型语言,是由于C++支持string+int的写法吗?没错,C++是支持这种写法,但直接这么写,语法没问题,但不会获得咱们指望的结果,以下面的代码:
std::cout << "Hello, World!" + 3 << std::endl;
这行代码并不会输出Hello,World!3,要想输出正常的结果,须要进行显式类型转换,代码以下:
std::cout << "Hello, World!" + std::to_string(3) << std::endl;
尽管C++编译器支持string+int的写法,但得不到咱们指望的结果,因此C++的string和int相加须要进行转换。所以,仅仅经过string+int或相似的不一样类型不能直接在一块儿运算来判断语言是不是强类型和弱类型的规则是站不住脚的。并且C++也支持运算符重载,也就意味着可让"abc" + 4变成不合法的。
那么Java是强类型仍是弱类型呢?Java是强类型语言,由于不少文章给出了下面的例子(或相似):
是的,这个表达式会出错,但你不要忘了,Java支持下面的表达式:
这行表达式输出了6664,为啥不用加号(+)举例呢?前面歧视Python的乘号,如今又歧视Java里的加号吗?其实这是由于前面描述的类型优先级问题,因为string的优先级高于int,所以4会转换为"4"。因此"666" / 4其实会也会发生隐式类型转换,变成"666"/"4",两个字符串天然不能相除了,而"666" + 4会变成"666" + "4",两个字符串固然能够相加了。这就是个语义的问题,和强弱类型有毛关系。

因此吗?Java是强类型语言没错,但判断依据错了。
Kotlin是强类型和弱类型呢?答案是Kotlin是强类型语言。不过Kotlin支持运算符重载,看下面的代码。
class MyClass(var value: Int) { operator fun plus(other: Int): Int { return value + other; }}fun main() { var my: MyClass = MyClass(200); print(my + 20); }
咱们都知道,Kotlin也是JVM上的一种编程语言(尽管能够生成js,但须要用Kotlin专有API),而Java是不支持运算符重载的,在同一个运行时(JVM)上,有的语言支持运算符重载,有的语言不支持运算符重载。从这一点就能够看出,运算符来处理两侧的操做数,只不过是个语法糖而已。想让他支持什么样的运算均可以,如,"abcd" / "cd",其实也可让他合法化,例如,语义就表示去掉分子以分母为后缀的子字符串,若是没有该后缀,分子保持不变,因此,"abcd"/"cd"的结果就是"ab",而"abcd"/"xy"的结果仍是"abcd",语法糖而已,与强弱类型没有半毛钱关系。
如今来讲说静态语言和动态语言。有人说能够用是否实时(在运行时)改变变量类型判别是静态语言仍是动态语言,没错,变量类型的实时改变确实是动态语言的特征之一,但并非所有。动态语言的另外一些特征是能够随时随地为类【或其余相似的语法元素】(主要是指自定义的类,有一些语言可能不支持对内建类型和系统类进行扩展)添加成员(包括方法、属性等)。
例如,下面的JavaScript代码动态为MyClass类添加了一个静态方法(method1)和一个成员方法(method2)。
class MyClass {
}MyClass.method1 = function () { console.log('static method');}
MyClass.method1() var my = new MyClass();my.method2 = function () { console.log('common method')}my.method2()
Python动态添加成员的方式与JavaScript相似,代码以下:
class MyClass: pass
def method1(): print('static method') MyClass.method1 = method1
MyClass.method1() my = MyClass()
def method2(): print('common method')my.method2 = method2my.method2()
还有就是数组的动态扩容(根据必定的算法,并非每一次调用push方法都会增长内存空间),如JavaScript的代码:
a = []a.push("hello")a.push(20)a.push("world")console.log(a)
a = []a.append('world')a.append(20)a.append("hello")print(a)
这些特性在静态语言(如Java、C++)中是没法作到的。在静态语言中,一个类一旦定义完,就不能再为类动态添加任何成员和移除任何成员,除非修改类的源代码。
因此说,静态和动态其实涵盖了多个方面,如类型固化,动态扩展、数组扩容等。而强类型和弱类型的特性其实只能算静态和动态的特性之一。也就是说,说一种语言是静态语言,其实已经包含了这种语言的变量类型一旦肯定不可改变的事实,也就是静态语言必定是强类型的编程语言。
这句话看起来没毛病,也能看懂,但实际上是有语病的。由于前面已经说了这我的是一个男人了,后面就不必强调是男演员了,而只须要按下面说的便可:
应该用固定不变的特性来标识一种语言的特性。而语言是否支持隐式类型转换,这只是编译器或运行时的内部业务逻辑,至关于语法糖而已,是随时能够改变的。而类型固化,动态扩展、数组扩容,这些涉及到编程语言的根本,一旦改变,就变成了另一种语言了,因此一般用这些特性标识语言的特性。一般来说,静态语言的效率会高于动态语言。由于,这些动态特性会让程序有更大负担,如类型不固定,就意味着可能会为新的类型分配新的内存空间,动态扩展和数组扩容也意味着不断进行边界检测和分配新的内存空间(或回收旧的内存空间)。这就是为何C++、Java、C#等编程语言的性能要高于js、Python的主要缘由。
其实过分强调静态、动态、强类型、弱类型,意义并不大。觉得编程语言之后的发展方向是静态语言动态化,弱类型强类型化。都是互相渗透了,若是之后出现一种编程语言,同时拥有静态和动态的特性,其实并不稀奇。例如,尽管变量类型不容许改变,但容许动态为对象添加成员。就和光同样,既是光子(粒子),又是电磁波,也就是说光拥有波粒二象性! 编程语言也同样,也会同时拥有静动态二象性!

对本文感兴趣,能够加李宁老师微信公众号(unitymarvel):

关注 「极客起源」 公众号,得到更多免费技术视频和文章。
