接触 Python 后的一点感受记录

四火 2018-03-25 07:19

python最近因为工作的关系开始学习 Python 了。以前从不曾正儿八经地学过,如果说工作学习经验带来改变的话,那么编程语言的学习就是个很好的例子。如果在十年前,我要学习 Python 的话大概会买本系统介绍的 Python 教程,然后一页一页慢慢看,估计能够啃完大半本,跳过一些自认为次要的特性。等到在项目中使用已经得是一两个月之后了吧。但是如今我显然不太会做一样的事情,我现在会拿着我那些熟悉的编程语言来比较,不同的特性上面,Python 是怎样的,是先进还是落后,适合解决什么问题,在哪些领域可以大行其道,但在遇到哪些问题的时候事倍功半。

想起以前接触过的编程语言里,事实上有一半都不能算系统地学过,大致上只是零散地入了门,就开始在项目中使用了。自然,也不可能花很多时间琢磨透了再使用,靠的是时间和经历一点点地积累。这个世界变化太快,已经没有时间把每一步都踩扎实。这就是现实,有时候未必也不是件坏事。很多人觉得大学里对编程语言的学习不值一提,因为象牙塔和工业界完全是不同的概念。但是我倒觉得,如果只考虑同等长度的时间范畴,大学里的学习经历,才是更有影响力的那一个。工作快十年了,变化的东西太多,不变的也不少——看到一群程序员聚在一起进行语言的圣战,在几年前我可能会表达“不同的工具有不同的应用场景”这样看起来中立而不带无脑倾向的废话。不过现在我可能什么都不会说,但是饶有兴致地驻足观看这样的圣战,有时候是骂战。

首先,拿 Python 和以前学过的语言比较来看。我觉得和 Groovy 比较接近,特别是语言的动态性。我确信它的学习曲线也是比较简单的。在 Linux 平台上作为脚本语言很容易使用,往往不需要准备什么预装什么,这和其他脚本语言比起来,便有着不可替代的优势。单纯就语言层面上,Python 和 Bash 比起来它的代码组织性更好,和 Perl 比起来则没有那么多啰嗦的语法,代码更容易理解。仅由目前所掌握的这一点点知识来看,特别喜欢其中 yield 关键字的设计,让一些本来需要反复在不同方法上下文中切换的面条代码变得清晰和优雅,这也是我在其它语言中没有见过的。

下面的内容是作为一个初学者的一点点记录,觉得 Python 中有趣的几点,以及项目中使用的一些感受。

首先,关于缩进。这大概是第一个缩进具备实际意义的编程语言了,缩进不仅仅是用于帮助理解和美观需要了。对于痛恨满天遍地括号的程序员来说,似乎是一种解脱。

Tuple。Tuple 的最直接好处是不可变,它时常也被作为容器不同格式之间转换和过渡的媒介,再一个,正因为有了 Tuple,让函数“看起来”具备多个返回值也就变成了可能,以前在 Scala 中也使用过。印象中传统语言里面没有这样原生的东西,比如 Java 的 final 关键字除了不可变以外并不具备 Tuple 其它的特性。

可变参数:在 Java 中可变参数其实就是一个数组的语法糖,二者本质上没有什么区别。所以可以传一个数组给变参方法,数组会被准确解析成变参;反过来转换也一样——在变参方法中,变参一拿到手就已经是一个数组了,Python 为了变参更舒服地调用而设计了展开符号——*。**则是关键字参数,和*被解析成 [] 不同的是,**被解析成 {},这个我在别的语言里面似乎没有见到类似的。如果不是说方法定义,而是说方法调用,那么它似乎和 JavaScript ECMA 6 中的…类似,可以传递若干键值对(命名参数)。

尾递归优化(Tail Recursion Elimination, TRE)。这其实可以作为语言分类上的一个重要特性,Python 不支持尾递归优化,后续有机会再单写一篇来详细谈谈。

List Comprehension。我第一次接触它是在 Haskell 里(曾经写过一点点东西涉及到它)。它让代码的简洁程度到达一个新的级别,比如下面这样的两个例子:

[x * x for x in range(1, 100) if x % 2 == 1]
[m + n for m in ‘123' for n in 'abc']

从 yield 到 generator。yield 这个关键字不仅仅可以让函数多次返回,虽说这已经是一个颇为有趣的事情了,我在其他语言中我没有见到过。它还可以让函数变成 generator——其中一种简单的定义方法是,如果函数定义中包含 yield,那么它就成为了一个 generator,用于不断生成结果并返回。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数,在每次调用 next() 的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。看这个例子:

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

def m_of_three():
    iter = _odd_iter()
    while True:
        n = next(iter)
        if n%3 == 0:
            yield n

for n in m_of_three():
    if n < 100:
        print(n)
    else:
        break

例子中第一个 generator _odd_iter 用来返回从 1 开始的所有奇数,而 m_of_three 生成器用来过滤并返回其中 3 的倍数,最后一部分则是打印逻辑,打印 100 以内的结果。这个例子体现了 generator 的使用,也体现了它对无限序列的支持。

模块的灵活性和自解释能力。这也是我相当喜欢的一个特性。在 Python 中有这样的规约,一个下划线开头表示私有变量,两个下划线开头表示 Python 自身预定义的一些变量,这样的规约即保留了违背规约访问和使用这些变量的灵活性,也提供了很多预定义变量的可能。其中一个是模块的__init__.py,类似于 Java 的 package.java,但是提供了更多的特性,用以模块的自解释,比如标准注释、依赖、编码、文档注释和立即执行的 main 方法等等。

而实际开发的时候,借助 pip 和 virtualenv,可以说依赖和环境准备比多数语言都简单多了。

最后是 decorator,这个其实最早我实在 Groovy 中见到的,现在很多语言都支持注解,但是原生来看似乎 Python 是使用它支持特性最丰富的,这里不展开了。

在新的团队中,我们的项目比较特别,大部分的服务端代码都是用 Python 写的。我可以看到 Python 在开发效率上和环境搭建上的优势,从开发、测试、部署到调试,Python 都很易于使用,Linux 和命令行亲和力高,不需要很多额外的工具。总体来说,这一点在快速开发的场景下很吸引人。另一方面,Python 灵活和强调规约的特性,以及提供的元编程上丰富的支持,我以及好多次感到,在写代码的时候,可以用很简洁的代码,写出看起来有点复杂的特性。现在我接触的 Python 开源库还不多,随着学习的深入后续再慢慢研究和记录,但是有了 pip 等等通用的统一的包管理工具,这一切看起来似乎要比传统的 Java 和 C++要简单很多。

不过,Python 代码容易产生的问题不可谓不少。老实说,随着代码规模的增加,我更倾向于一个约束更强,类型系统严谨并且代码代码模板清楚统一的编程语言。Python 有些“太过灵活”,无论是易读性还是分层/模块化,我都看到代码中有很多需要改善的地方。最常见的一个模式是,用 Python 写一堆有关联的类和方法,放在一个 py 文件里面,在开始的时候很不错,也避免了过度臃肿的类文件,但是很快,随着代码规模的增加,这个文件需要不断被拆分和重构,但是重构这件事情却不是那么容易推动的。程序员都知道,当我们说“未来再扩展”或者“以后再改进”的时候,这件事情多半是不会发生了——于是代码过度耦合就变成了一个问题。以往使用 Java 写代码的时候,项目组仿佛有约定俗成的理念,无论是分层还是类文件设计,往往在开始的时候都显得有些“过度”,多层次的包、类设计,看起来似乎有些臃肿,但是随着代码规模的增加,好处就体现出来了,代码的单一职责这一要求维护得明显更好,在做某些修改的时候,需要评估的调查范围就小,代码修改也更清楚和易于评审者理解,修改者心里也更有谱。

显然这不能说孰好孰坏的问题,但是我对 Python 在中大规模项目(50 万行+)上的使用是有疑虑的。当然也许随着经验的积累我会有不同的看法。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》

[返回] [原文链接]