1. 实际是静态语言vs动态语言
之前,一直使用C/C++做开发,最近开始尝试使用Python做一些小的工具,顺便学习一下。
包括在学生阶段,我也喜欢C/C++,有更强的控制感,程序的每一步,我都要尽可能的掌握,清楚它到底在做什么。也偶尔用过几次Java做些事情。总而言之,这都属于“静态语言”。与之相对的是“动态语言”,典型代表就是Python
。
-
静态语言,比如C/C++,源代码编写结束之后,需要进行编译才可以放在机器上运行。并且,程序内的变量都有确定的类型,即编译期就可以确定变量类型,那么可见,编译器可以对变量做一些检查,不同类型的变量之间能否运算,是否合法等。
-
动态语言,比如Python,源代码不需要编译,但是他们需要“解释”。以Python语言为例,python.exe就是解释器,编写好的main.py需要让它“解释”并且执行一下里边的内容。动态语言中的变量,一般没有固定的类型。一个变量x,它的具体类型需要到运行时才能知道,并且,也可以在运行期间改变,它的类型取决于被赋予了什么样的数据。例如,最开始
x = 123
,那它就是整型,后来,x = 'hello clay'
,那么此时它就是字符串类型了。
2. 用C++对比学习Python
习惯了静态语言,想尝试动态语言,还是有些不适应。从最近的一些实践来看,以下几点可能比较关键:
2.1 环境和包管理
工欲善其事必先利其器。
开发肯定离不开一个合适的编辑器,或者IDE,方便我们查看函数签名或者代码提示等,提高开发效率。在编写Python程序时,可以使用Pycharm进行开发。当然,也可以用宇宙最强编辑器VsCode,装一个Python插件就可以了。
C/C++开发,需要在编辑器中配置编译器,或者有些IDE(例如Visual Studio)直接就默认的msvc,你直接使用就好了。 在Python开发中,你需要配置一个解释器。
C/C++中,包管理我觉得做的很烂,各种版本问题,兼容性问题。。。。在Python中,省事得多,一般用pip就都可以解决了。还有,C/C++通常需要类似CMake的工具为项目配置可用的包(这些包可能散落在系统各处,有时候也可能需要手动指定路径)。这点,在Python中比较容易,你可以为每个工程单独创建一个Python的虚拟环境,如果你不加到系统环境变量中的话,那么各个虚拟环境是独立的,互不影响。可以用Anaconda来维护系统中的多个虚拟环境,也可以使用Python自带的venv模块来管理,当然也可以用virtualenv。如果是开源项目的话,一般会有一个requirements.txt,里边记录了该工程依赖的第三方库和相应的版本号,你只需要用pip install -r requirements.txt
安装即可。
2.2 变量
变量的差别我觉得是比较大的,一个静态,一个动态。
在Python中,你不需要关注变量的类型,你只管用它!为什么这样说,可以一会看下边的“鸭子类型”。
变量除了鸭子类型,还有一点是作用域的问题。
在C/C++中,变量作用域有:全局、类、局部、代码块。
这四种作用域用下边的代码说明:
int g_var = 0; // 全局作用域
class custom:
{
private:
int m_var; // 类作用域
public:
void func()
{
int f_var = 0; // 局部作用域
{
int b_var = 0; // 块作用域
}
}
}
在Python中,变量作用域有:Build-in,Global,Enclosing,Local
个人理解,Python中的变量作用域并不是对一个变量的直接描述,有时候也是相对的描述。
看下边代码:
# Build-in 一般好像也用不到
g_var = 100.0 # Global
def func_1():
f1_var = 1.0 # Local,相对于func_2中的代码,这是 Enclosing
if True:
f1_var = 1.1 # 使用的是上边的
print(f1_var)
def func_2():
# nonlocal f1_var # 开启后,下一行修改的是外层f1_var
f1_var = 88 # Local,并非外层的f1_var
print(f1_var)
func_2()
print(f1_var)
func_1()
记住一点,Python中if、else这种关键字下边的变量,不像C/C++中是一个新的作用域。Python中整个函数
是一个作用域,无论变量是不是在if、else这种关键字下边赋值创建的,都可以在整个函数内使用。
func_2
是func_1
内嵌的一个方法,func_2
中读写变量f1_var
是有区别的,如果只有读操作,那么默认就是使用func_1
中的那个对象,如果有写操作,分两种情况:如果声明了nonlocal f1_var
,那么后边再读写的话,都是读写func_1
中的那个对象,如果没声明,会在func_2
内部新建一个对象出来,读写这个对象。
2.3 鸭子类型
“鸭子类型”,不是一种类型,而是一种设计风格:只要这个“东西”像鸭子一样会叫,也像鸭子一样走路,我就认为它是鸭子。
具体到设计一个方法时,我不关心你传进来的是int
还是str
,又或者是什么自定义的类型,我只关心你这只“鸭子”的speak
方法,或者walk
方法。
如下,不用鸭子类型:
def do_speak(x):
if isinstance(x, duck):
x.speak()
if isinstance(x, dog):
x.speak()
else:
raise Exception("invalid type")
使用鸭子类型:
def do_speak(x):
x.speak()
如果x
没有speak
方法,那么它就不是鸭子!执行到这里Python会报错的。