1. 真的能用隐式类型转换做为强弱类型的判断标准吗?
近期有些学员问我,Python到底是强类型语言,还是弱类型语言。我就直接脱口而出:Python是弱类型语言。没想到有有些学员给我了有些文案,有中文的,有英文的,都说Python是强类型语言。其它中文的关联文案,大众能够去网上搜,一堆,这儿就不一一列举了。
我先不说这些结论对不对,我先总结一下这些文案的核心观点。这些文案将编程语言分为强类型、弱类型、动态类型和静态类型。这4个概念的解释如下:
强类型:倘若一门语言不对变量的类型做隐式转换,这种编程语言就被叫作为强类型语言 ;
弱类型:与强类型相反,倘若一门语言对变量的类型做隐式转换,那咱们则叫作之为弱类型语言;
动态类型:倘若一门语言能够在运行时改变变量的类型,那咱们叫作之为动态类型语言;
静态类型:与动态类型相反,倘若一门语言不能够在运行时改变变量的类型,则叫作之为静态类型语言;
其实这些概念就触及到编程语言的两个特性:隐式类型转换和类型固化。
所说类型固化,便是指一旦变量在初始化时被确定了某个数据类型(如整数类型),那样这个变量的数据类型将永远不会变化。
关于动态类型和静态类型,在本文的后面再来讨论,这儿先探讨强类型和弱类型。
此刻姑且认为这个结论没问题。强类型便是不准许做隐式类型转换。OK,咱们瞧瞧用这个隐式类型转换来判断强类型和弱类型是不是恰当。
在这些文案中,给出了非常多例子做为证据来证实这个结论,其中最典型的例子是在Python语言中,int + string是不合法的,没错,确实不合法。如执行1 + abc会抛出反常。当然,还有人给出了另一个例子:string / int亦是不合法的,如执行666 / 20会抛出反常,没错,字符串与整数的确不可直接相除。那你怎么不消乘号举例呢?如abc * 10,这在Python中可是合法的哦,由于这个表达式会将abc复制10份。为么不消我大乘号来举例,难道瞧不起我大乘号吗? 这是运算符卑视?
PS:虽然abc * 10无做类型转换,但这儿说的是乘号(*),尽管日前Python不支持abc * 10的操作,但已有亦有可能会支持abc * 10,亦便是将10转换为10,这就出现了类型转换。
另一,难道没听说过Python支持运算符重载吗?经过运算符重载,能够让两个类型完全区别的变量或值在一块运算,如相加,看下面的例子: class MyClass1:
def __init__(self,value):
self.value = value
class MyClass2:
def __init__(self,value):
self.value = value
my1 = 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.value
class 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类型,这就触及到另一一个类型优先级表了。
按照这个表可知,编程语言只是在遇到类型不合规的状况下处理的方式区别,这便是编译器(解析器)的业务规律了,这个业务规律随时可能变(一般不会影响程序的向下兼容),因此是不可用这一特性做为强弱语言标识的,否则强类型和弱类型语言就有可能会持续切换了,由于编程语言会持续进化的。
2. 为何应该用类型固化做为强弱类型的标识
那样为何能够用类型固化做为强弱类型的标识呢?由于类型固化一般是不可变的,那样为何是不可变的呢?下面用Python来举例:
下面的Python代码是合法的。x从int变成为了str,类型并无固化,所有Python是弱类型语言。 x = 20
x = abc
那样有无可能Python以后对类型进行固化呢?从技术上来讲,完全没问题,但从代码兼容性问题上,将会导致严重的后果。由于类型没固化属于宽松型,一旦类型固化,属于严格型。以前已然遗留了非常多宽松的代码,一旦严格,那样就寓意着x = abc将会抛出反常,就会导致非常多程序没法正常运行。因此倘若Python这么做,就相当于一种新语言了,如PythonX,而不可再叫作为Python了。就像人类进化,无论从远古的尼安德特人,还是智人,或是现代各个国家的人,无论怎么进化,都必须在主线上发展,例如,都有一个脑袋,两条腿,两个胳膊。当然,可能细节不同,如黑眼睛,黄头发等。你不可进化出两个头,8条腿来,当然能够这么进化,但这个就不可再叫作为人了,便是另一一种生物了。
此刻再看一个相反的例子,倘若一种编程语言(如Java)是强类型的,能否以后变成弱类型语言呢?
看下面的Java代码: int x = 20;
x = "200"; // 出错
其实从技术上和兼容性上这么做是没问题的。但亦会有非常多其他问题,如编译器(或运行时)的处理方式完全区别,咱们晓得,类型固化的程序要比类型不固化的程序运行效率高,由于类型不固化,必须持续去思虑类型转换的问题。况且在空间分配上更麻烦,有可能会持续分配新的内存空间。例如,针对一个数组来讲,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是强类型语言,由于非常多文案给出了下面的例子(或类似):
"666" / 4;
是的,这个表达式会出错,但你不要忘了,Java支持下面的表达式:
"666" + 4;
这行表达式输出了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 {
returnvalue + other;
}
}fun main() {
var my: MyClass = MyClass(200);
print(my + 20); // 输出220
}咱们都晓得,Kotlin亦是JVM上的一种编程语言(尽管能够生成js,但必须用Kotlin专有API),而Java是不支持运算符重载的,在同一个运行时(JVM)上,有的语言支持运算符重载,有的语言不支持运算符重载。从这一点就能够看出,运算符来处理两侧的操作数,只不外是个语法糖罢了。想让他支持什么样的运算都能够,如,"abcd" / "cd",其实亦能够让它合法化,例如,语义就暗示去掉分子以分母为后缀的子字符串,倘若无该后缀,分子保持不变,因此,"abcd"/"cd"的结果便是"ab",而"abcd"/"xy"的结果还是"abcd",语法糖罢了,与强弱类型无半毛钱关系。
4. 静态语言和动态语言
此刻来讲说静态语言和动态语言。 有人说能够用是不是实时(在运行时)改变变量类型判别是静态语言还是动态语言,没错,变量类型的实时改变确实是动态语言的特征之一,但并不是所有。动态语言的另有些特征是能够随时随地为类【或其他类似的语法元素】(重点指的是自定义的类,有有些语言可能不支持对内建类型和系统类进行扩展)添加成员(包含办法、属性等)。
例如,下面的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 = method2
my.method2()还有便是数组的动态扩容(按照必定的算法,并不是每一次调用push办法都会增多内存空间),如JavaScript的代码: a = []
a.push("hello")
a.push(20)
a.push("world")
console.log(a)Python的数组(列表)扩容: a = []
a.append(world)
a.append(20)
a.append("hello")
print(a)当然,动态语言还有非常多特性,这儿就不一一介绍了。
这些特性在静态语言(如Java、C++)中是没法做到的。在静态语言中,一个类一旦定义完,就不可再为类动态添加任何成员和移除任何成员,除非修改类的源代码。
因此说,静态和动态其实涵盖了多个方面,如类型固化,动态扩展、数组扩容等。而强类型和弱类型的特性其实只能算静态和动态的特性之一。亦便是说,说一种语言是静态语言,其实已然包括了这种语言的变量类型一旦确定不可改变的事实,亦便是静态语言必定是强类型的编程语言。
倘若单独强调强类型,其实就相当于下面这句话:
这个人是一个男人,况且是一个男演员。
这句话看起来没毛病,亦能看懂,但其实是有语病的。由于前面已然说了这个人是一个男人了,后面就不必强调是男演员了,而只必须按下面说的就可:
这个人是一个男人,况且是一个演员。
此刻来总结一下:
应该用固定不变的特性来标识一种语言的特性。而语言是不是支持隐式类型转换,这只是编译器或运行时的内部业务规律,相当于语法糖罢了,是随时能够改变的。而类型固化,动态扩展、数组扩容,这些触及到编程语言的基本,一旦改变,就变成为了另一一种语言了,因此一般用这些特性标识语言的特性。一般来讲,静态语言的效率会高于动态语言。由于,这些动态特性会让程序有更大包袱,如类型不固定,就寓意着可能会为新的类型分配新的内存空间,动态扩展和数组扩容亦寓意着持续进行边界检测和分配新的内存空间(或回收旧的内存空间)。这便是为何C++、Java、C#等编程语言的性能要高于js、Python的重点原由。
其实过度强调静态、动态、强类型、弱类型,道理并不大。因为编程语言以后的发展方向是静态语言动态化,弱类型强类型化。都是互相渗透了,倘若以后显现一种编程语言,同期持有静态和动态的特性,其实并不稀奇。例如,尽管变量类型不准许改变,但准许动态为对象添加成员。就和光同样,既是光子(粒子),又是电磁波,亦便是说光持有波粒二象性! 编程语言亦同样,亦会同期持有静动态二象性!
最后,博主想说:我是一名python研发工程师,整理了一套最新的python系统学习教程,想要这些资料的能够关注私信博主“01”就可(免费分享哦)期盼能对你有所帮忙。
|