先从一个低级错误说起
上周,在项目中使用SQLAchemy操作数据库时,代码中出现了数据修改操作无法写入数据库的bug。为简化问题,就不贴原本的代码了,代码逻辑结构大致如下:
try:
results = db.session.query(all_record).all()
for result in results:
result.nmae = 'new name'
print(result.nmae)
db.session.commit()
except Exception as e:
print(e)
执行结果:
new name
大家知道,SQLAchemy是Python的一个用于实现ORM库(Object Relational Mapping,即对象关系映射),我们可以藉由它通过编程语言对多种类型的数据库实现数据表定义创建和对数据的增删改查。它将数据库中的关系转化为程序语言中的对象,因此所有对数据库的操作都体现为对对象属性的修改。
然而这段代码执行完后,并没有将数据写入数据表的对应字段,这种情况一般来说是没有执行commit提交事务,导致数据修改的结果并没有真正写入数据库。
但是在这里,我是明明写了commit()语句并且用断点的方式检查却是执行了的,而且也没有抛出异常。
排除了许多可能后,我才猛然发现,犯了一个写错变量名的低级错误。比如,这里代码操作的属性是 result.nmae ,在数据库中定义的本应该是name,在对象所对应的类里的属性自然也是name,故而在写入的时候没有相应字段可以写入,所以我去数据库查看name依然是以前的值。可是它也没有抛出异常,这个大概就是SQLAchemy的坑了。
但是,问题就来了,既然类中没有nmae这个属性,为什么可以被定义和赋值?
这似乎颠覆了我对面向对象的认知。经某大牛同学Talk is cheap ,Show me the code式的点播,我才猛然认识到这是Python作为一门动态语言的特性。
大牛code如下:
In [16]: class test:
...: def __init__(self):
...: self.A = 1
...:
...:
In [17]: t = test()
In [18]: t.A
Out[18]: 1
In [19]: t.B = 2
In [20]: t.__dict__
Out[20]: {'A': 1, 'B': 2}
In [21]: t.B
Out[21]: 2
Python类由类变量和实例变量、方法以及可以看作特殊变量的数据成员(类变量或者实例变量, 用于处理类及其实例对象的相关的数据)构成。其中,类变量为所有类产生实例所共用,比如一个类对应的实例个数的计数器。想要修改类变量,必须通过类内部特定的修改方法来修改。而作为实例变量,直接像上面那样,在实例化后赋值即可,甚至也可以定义一个类中不存在的新的对象属性。作为一个从JAVA入门面向对象的程序员来说,却是有些难以理解,因为JAVA并没有这种常规操作,javassit这种非常规技术就不说了。
造成这种差异和误解的根本原因是JAVA是一门静态语言,而Python是一门动态语言。而恰好,JAVA又是一门编译型的语言,而Python是一个用解释器执行的解释型语言。于是有人告诉我,编译型语言就是静态语言,而解释型语言就是动态语言。
事实果真如此么?我们一起来梳理下面几个概念!
编译型语言
需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。一般需经过编译(compile)、链接(linker)这两个步骤。编译是把源代码编译成机器码,链接是把各个模块的机器码和依赖库串连起来生成可执行文件。
- 优点:一次编译,重复可用,不需要每次执行时再编译一遍,由于编译后生成的是能直接被机器执行的机器码,所以通常情况下执行效率会更高。
- 缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件(比如Windows上的.exe文件,CentOS上的.rpm文件)。
- 代表语言:C、C++、Pascal、Object-C以及最近很火的苹果新语言swift
解释型语言
解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译为机器码来给机器执行。
- 优点:可以在任何安装了解释器(虚拟机)的机器上运行,不用关心用了什么操作系统。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
- 缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
- 代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby
混合型语言
既然编译型和解释型各有缺点就会有人想到把两种类型整合起来,取其精华去其糟粕。就出现了半编译型语言。比如C#,C#在编译的时候不是直接编译成机器码而是中间码,.NET平台提供了中间语言运行库运行中间码,中间语言运行库类似于Java虚拟机。.net在编译成IL代码后,保存在dll中,首次运行时由JIT在编译成机器码缓存在内存中,下次直接执行。Java先生成字节码再在Java虚拟机中解释执行。严格来说混合型语言属于解释型语言。C#更接近编译型语言。
动态语言和静态语言
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
走出误区
很多人认为解释型语言都是动态语言,这个观点是错的!Java是解释型语言但是不是动态语言,Java不能在运行的时候改变自己结构。反之成立吗?动态语言都是解释型语言。也是错的!Object-C是编译型语言,但是他是动态语言。得益于特有的run time机制(准确说run time不是语法特性是运行时环境,这里不展开)OC代码是可以在运行的时候插入、替换方法的。
静态类型语言和动态类型语言
为了不混淆动态语言和动态类型语言,下面还补充说明以下几个概念
动态类型语言
很多网上资料把动态类型语言和动态语言混为一谈,简直是误人子弟。动态类型语言和动态语言是完全不同的两个概念。动态类型语言是指在运行期间才去做数据类型检查的语言,说的是数据类型,动态语言说的是运行是改变结构,说的是代码结构。
动态类型语言的数据类型不是在编译阶段决定的,而是把类型绑定延后到了运行阶段。
主要语言:Python、Ruby、Erlang、JavaScript、swift、PHP、Perl。
静态类型语言
静态语言的数据类型是在编译其间确定的或者说运行之前确定的,编写代码的时候要明确确定变量的数据类型。
主要语言:C、C++、C#、Java、Object-C。
注意
解释型语言并不都是动态类型语言,编译型语言也不全是静态类型语言。swift是编译型语言但是它也是动态类型语言。C#和Java是解释型语言也是静态类型语言,虽然它们看作解释型语言,但是它们也有编译过程,会在编译过程中做数据类型的检查
所以,动态类型语言是数据类型检查的动态(运行时检查),而不是像动态语言一样代码逻辑结构的在运行时可变更,静态则相反。
强类型语言和弱类型语言
说到数据类型,就不得不提从数据类型是否严格定义的角度产生的另外两个概念:强类型语言和弱类型语言
强类型语言:
强类型语言,一旦一个变量被指定了某个数据类型,如果不经过强制类型转换,那么它就永远是这个数据类型。你不能把一个整形变量当成一个字符串来处理。
主要语言:Java、C#、Python、Object-C、Ruby
弱类型语言:
数据类型可以被忽略,一个变量可以赋不同数据类型的值。一旦给一个整型变量a赋一个字符串值,那么a就变成字符类型。
主要语言:JavaScript、PHP
3、注意:
一个语言是不是强类型语言和是不是动态类型语言也没有必然联系。Python是动态类型语言,是强类型语言。JavaScript是动态类型语言,是弱类型语言。Java是静态类型语言,是强类型语言。
附录概念对照表
概念 | 划分角度 | 特点 |
---|---|---|
编译型语言 | 执行方式 | 将源代码编译为机器码给机器执行 |
解释型语言 | 执行方式 | 将源代码交由解释器解释为机器码执行 |
混合型语言 | 执行方式 | 将源代码编译为“中间码”,再交由解释器(虚拟机)解释为机器码执行 |
动态语言 | 代码逻辑结构是否可以在运行时改变 | 可以改变 |
静态语言 | 代码逻辑结构是否可以在运行时改变 | 不可改变 |
动态类型语言 | 代码的数据类型何时检查 | 运行时检查 |
静态类型语言 | 代码的数据类型何时检查 | 运行时不检查 |
强类型语言 | 变量定义后是否可以随意改变数据类型 | 不可以 |
弱类型语言 | 变量定义后是否可以随意改变数据类型 | 可以 |
网友评论