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_2func_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会报错的。