专栏名称: Python程序员
最专业的Python社区,有每日推送,免费电子书,真人辅导,资源下载,各类工具。我已委托“维权骑士”(rightknights.com)为我的文章进行维权行动
目录
相关文章推荐
Python爱好者社区  ·  模仿一下领导说话的样子 ·  15 小时前  
Python爱好者社区  ·  DeepSeek又爆了。。。 ·  15 小时前  
Python爱好者社区  ·  今年程序员这薪资是认真的吗? ·  昨天  
Python爱好者社区  ·  35个爬虫实例 ·  3 天前  
Python中文社区  ·  马斯克一条推特,股价狂飙?AI量化模型揭秘背 ... ·  4 天前  
51好读  ›  专栏  ›  Python程序员

写给Java开发者的Python入门

Python程序员  · 公众号  · Python  · 2017-06-24 12:51

正文

Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

编者注: 这是一篇来自Java社区的文章,作者的观点是从Java开发者的角度进行讲解的。但是....为了部落的荣耀,人生苦短,我用Python!

从哲学的角度来看,Python和Java是全然相反的。它放弃了静态类型和刚性结构,并以松散的沙盒结构取而代之,在这样的沙盒里你基本上可以做任何你想做的事情。也许Python这门语言关注的是你能做什么,而Java则更关注你可以做些什么。

尽管如此,两门语言都从C语言中汲取了大量灵感。它们都是使用块、循环、函数、赋值、中缀算术的命令式语言。二者都大量使用了类、对象、继承和多态。二者对异常的处理都非常优秀。两门语言都能自动进行内存管理。它们甚至都需要编译成字节码然后在虚拟机上运行,尽管Python的编译过程对用户是隐式的。Python有部分功能也借鉴了Java,例如Python标准库中的logging和unittest模块分别受到Java的log4j和JUnit的启发。

基于以上的技术重叠,我觉得Java开发者理应对Python产生家人一般的感觉。所以我给你们带来了Python的一些简单介绍。有机会的话,我会给大家讲一下是什么让Python和Java有所不同,以及这些不同吸引我的原因。至少,你有可能会把一些有意思的想法带回到Java的生态系统中。

(如果需要Python教程,Python部落的 视频教程 是一个不错的选择。另外需要注意的是,这是从Python 3的视角来写的!目前,Python 2 仍然活跃在很多领域中,它和Python 3 有一些语法上的不同。)

语法

那么,就从hello world 开始吧:

呃,好吧,看起来好像没什么启发性。好吧,接下来是一个函数,用来统计文件中出现频次最高的十个单词。这里我是用了Python标准库中的Counter类取了个巧,没办法它实在太好用了。

Python 使用空格作为分隔。人们对此颇有怨言。我之前甚至将其视为异端。但是十多年后的今天,这看上去竟如此正常以至于再让我用回花括号我还得花一段时间来适应。如果你是因为这个而迟迟没有开始,那么我不保证能说服你,但我强烈推介你暂时忽略它;在实际工作中,这真的不会带来太大的问题,反而会减少代码中的一些干扰。除此之外,Python开发者从来不用在诸如这个花括号应该放在哪里之类的问题产生争论。

除了这些美学上的区别外,其他看起来都很熟悉。我们得到一些数值,做了赋值操作,调用了模块。Python的import语句使用起来有一点不同,但是很明显它的作用是“使后面这些东西变得可用”。除了一些标点符号外,Python的for循环和Java的for循环非常像。函数使用def分隔而不是Java中的class,但是它运行的过程并无太大差异:函数可以使用参数调用,然后返回值(即使上述例子中的函数并没有返回值。)

只有两个地方有比较明显的差别。第一个是with代码块,这和Java 7 的“try-with-resources”很像—这保证了区块中的代码运行结束后文件的正常关闭,即使代码块中抛出了异常。另外一个是f”…”语法,这是相当新的一个特性,它允许把表达式直接写入字符串中。

大概就是这样了!你已经读了一些Python代码。至少,它看上去并不像一门完全来自外星的语言了吧。

动态类型

从上面的列子我们可以看到,Python代码没有太多的类型声明。变量声明的时候没有,函数的参数和返回值没有,对象上也没有。任何事在任何时间可以是任何类型。我还没有介绍过类的定义,所以这里就有一个:

甚至x,y都没有声明为属性;它们的存在是因为构造器中创建了它们。没有什么限制我传入整数,我也可以传入浮点数, 小数或者分数等。

如果你只用过静态语言,这听起来简直要乱成一团。类型是温暖的懒惰的以及令人满意的。类型保证了…好吧,也许不一定能保证代码能顺利工作(尽管有人会不同意),但总是有好处的。当你连正确的类型都没搞清楚的时候你怎么可能顺利地运行代码。

稍等片刻- Java也不一定能保证这些呀。毕竟,所有的对象都可以是null,对吧?这样看来,实际上几乎从来没有一个类型正确的对象。

你可能会把动态类型看做对null问题的完全屈服。如果我们必须去处理它,我们也可以试着去拥抱它,然后让它来为我们工作—通过把所有问题都放在运行过程中解决。类型错误在Python中变成很普通的逻辑错误,你可以用相同的方式处理。

(另一种相反的方法,请查看Rust,这是一门没有null值或者异常的语言。我仍然更愿意使用Python,尽管我对Rust从不说谎的类型系统赞赏有加。)

在我的magnitude模块中,self.x的值是整型、浮点型还是其他任何类型的值都不重要。它只要支持**操作,然后返回支持+操作的值就够了。(Python支持运算符重载,所以它可能是任何类型。)正常的方法调用同样适用:只要在实践中能运行,那么任何类型都是可以接受的。

这意味着Python不需要泛型;一切都是按照泛型工作的。Python也不需要接口;一切都是多态的。没有向下转型,没有向上转型,在类型系统中也没有逃生舱口。当它们可以和任何可迭代对象在一起运行正常时,API也不需要必须是列表。

许多常见模式将变得更加简单。你可以创建一个封装和代理而不用去修改你的消费者代码。你可以使用组合来替代集成扩展第三方类,而不用做任何特殊的工作来保证多态。灵活的API不需要将每个类作为接口重复复制;一切都早已经是隐式的接口了。

动态类型哲学

使用静态类型,无论谁编写一些代码来选择类型,编译器都要确定其能运行。而使用动态类型,无论谁使用某些代码来选择类型,都会在运行过程中尝试一下。这就是前文讲到的两门语言对立哲学在实际中的体现:类型系统关注的是你可以做什么,而不是你可能要做什么。

这样使用动态类型被称为“鸭子类型”,这是基于思想“如果它走起路来像一只鸭子,叫起来也像一只鸭子,那么它就是一只鸭子”。简单来说,意思就是如果所有你想做的只是像鸭子一样叫,那么不用像静态语言那样限制你的代码必须接受一只鸭子,你可以接受任何给你的东西然后想办法让它像鸭子一样呱呱叫就行。如果它能做到,那么它就和鸭子一样好用了。(如果它做不到,你可能会得到一个类型错误,但这也不是多大的问题。)

同时要注意的是,Python仍然是强类型的。这个术语可能有点含糊,总体上来说它的意思就是值在运行过程中始终拥有他们的类型。典型的例子就是Python不会允许你把一个字符串和一个数字相加,而像JavaScript这样的弱类型语言将会隐式地把一个类型转换成另外一种,,这里使用的优先级规则和你想到的可能有所不同。

不像大多数的动态语言,Python在运行之前就可以捕捉错误信息。例如,从一个不存在的变量中读取信息会产生一个异常,包括从字典(类似Java中的Map)中读取一个不存在的键。在JavaScript、Lua和类似的语言中,上述操作会返回null值(对Map中不存在的键取值,Java也会返回null!)如果想在键不存在时返回默认值,Python的字典类提供了更加明确的方法调用。

这样的操作当然是有所取舍的,到底值不值得要因人而异、因项目而异。对我来说,至少非常适合用来设计一个更加可靠的系统,在我看到它运行之后;而静态语言则需要预先进行设计。静态类型使大量尝试不同想法变得更加困难。你确实少了一些静态检查保证,但在我的实践中,大多数的类型错误都会立刻被捕捉到...因为我写完代码的第一件事情就是运行一下。另外一些会在我们的测试中被捕捉到—这个测试你可以用任何语言来写,当然Python写起来要相对容易一些。


混合范式


Python 和Java都是命令式和面向对象的;它们都通过执行命令来运行,同时都遵循一切皆对象的理念。


在最近的发行版中,Java新增了一些函数类的特性,我认为这是一个好事。Python也有一定比例的函数式特性,但是二者使用的方法还是有些不同。Python提供了内置的函数如map和reduce,但它们实际上并不是设计用来把一系列小的函数串联起来的。


相反,Python把挺多东西混合起来了。我不太清楚Python使用的方法的通用名称。我觉得它应该是把函数链的概念拆分成两部分了:序列运行,使函数本身更加强大。


序列


序列和迭代在Python中扮演了非常重要的角色。序列是最重要的数据结构之一,所以操作序列的工作都很容易获取。我认为Python对于函数式编程的实现如下:Python首先使得使用命令式代码来操作序列非常容易,而不是使得结合许多小函数然后应用于序列非常容易。


回到本文开始的地方,我曾写下这么一行代码:

for 循环很常见,但这行代码中同时对两个变量进行迭代。真正发生的事most_common这个列表中的每个元素都返回一个元祖,一组有序唯一值。元组可以通过解包赋值给一组变量,这就是for循环中发生的事情。Python中的元组通常作为返回一组值,但它们有时在ad-hoc数据结构中也很有用。而在Java中,你需要一个完整的类以及很多行的赋值操作。


所有可迭代对象都支持解包。解包也支持嵌套结构,所以a, (b,c) = … 这种形式的可以顺利解包。对不定长的序列来说,* leftovers 元素可以出现在任何位置,并且将根据需要获取尽可能多的元素。也许你真的比较喜欢LISP?

Python 同样也有创建列表的简单表达—被称为列表生成式—这比map之类的函数式方法要更常见。类似的方法也支持创建字典和元组。整个循环都被压缩到你真正感兴趣的一个表达式上。

标准库还在itertools模块中包含了一些有趣的迭代,组合器。


最后,Python使用生成器来生成命令代码的延迟序列。包含yield关键字的函数被调用时,不会立即执行,而是产生一个生成器对象。当生成器进行迭代时,代码运行到yield关键字处即停止;返回的值将作为下一次迭代的值。

由于生成器运行是有延迟的,它可以用来生成无限的序列或者在中途中断。它们可以用来生成大量的大型对象,而不需要为了保持存活而一次性消耗大量内存。它们同样被用来作为链式风格的函数式编程的一般替代。你可以编写熟悉的命令行代码,而不用考虑maps 和filters的组合。

在Java中实现一个完全任意延迟的迭代器,你可能需要在迭代器中增加一些代码来跟踪其状态。除了最简单的情况之外,这将变得有些棘手。Python也有迭代的接口,所以你仍然可以使用这种方式来实现,但生成器非常好用以至于大多数常用的迭代都是用它实现的。


另外,由于生成器可以自动停止,所以它们在另外一些场景下也是非常有用的。通过手动调用生成器(而不是用一次for循环来实现一次性全部迭代),你可以先运行一个函数,使其在某处停止,然后在函数运行过程中执行其他代码。Python依靠这些特性新增支持了 异步 I/O (不使用线程的非阻塞网络),即使现在它已经有专用的async 和 await 语法。


函数


Python 的函数看上去似曾相似。你可以使用参数调用它。参数传递的方式也和Java一模一样 – Python既不是引用也不是隐式复制。Pyhton甚至也有描述符,和Java的注释类似,但是Python的是一种语法而且在运行过程中是可读的。

Java 有使用args…的可变参数函数;相应的Python对应的函数使用*args。(支持 *leftovers语法的解包操作正是受函数这种语法的启发。)但Python在这基础上还提升了一些技巧。任何参数都可以设置默认值,使其成为可选参数。另外,参数也可以设置名称-之前我曾经这样做过Point(x=3, y=4)。*args语法用来在函数调用时接收一个列表作为参数;**kwargs用来接受或者传递字典类型的带名称参数。参数可以是“仅关键字keyword-only”的,所以参数传递必须通过名字,这对可选的布尔参数来说非常友好。


当然,Python没有函数重载的概念,不过大多数你需要它的时候都可以用鸭子类型和可选参数来取代。


这是现阶段Python最强大的功能之一。就像动态语言允许你使用封装或者代理透明的替换一个对象一样,* args和**kwargs允许任何函数被透明封装起来。

不好意思,这看上去有点难以理解。不过别太担心它是怎么工作的;需要知道的就是函数foo被一个新函数所代替了,它将所有的参数都转发给foo。不管是foo还是调用都和之前完全一样。


我还无法想象这个功能有多么强大。它可以用在日志、调试、资源管理、缓存、访问控制,验证等等。它在串联其他元编程特性上表现的非常不错,换句话说,它让你结构化思考而不只是单单的编程。


对象和动态运行时


运行时是一种在幕后驱动语言核心部分的东西,它可以在运行时被执行。C和C++之类的语言没有动态运行时;它们源码的结构被“烘焙”进编译器输出,之后就没有能修改的机会了。另一方面,Java确实有动态运行时,Java甚至还有一整套用来进行自省(反射)。


当然,Python也有自省(反射)。Python有许多简单的通过自省构建的函数可以用于监控、修改对象的属性,这对调试和偶发性的异常来说非常有用。

Python 还将这个功能进行了升级。因为一切都在运行时完成,Python使用了很多扩展来自定义其语义。你无法修改语法,所以代码看上去仍然很像Python,但你可以分解出一些结构,这在死板的语言来说是非常困难的。

举一个极端点的例子,pytest使用Python的assert语句很智能的完成了很多事情。通常,assert x == 1 这样的代码 当判定为假时, 只会简单的抛出一个属性异常,这让你对异常产生的位置和时间毫无概念。这也正是Python内置的unittest模块(和JUnit以及其他语言的单元测试库类似)提供了一堆诸如assertEquals之类的函数的原因。不幸的是,这也让测试在第一眼看上去显得更加复杂难懂。但在pytest中,assert x == 1是可用的。如果结果为false,pytest将会告诉你x是什么…或者两个列表哪里出现偏离,或者两个集合有什么不同,以及其他事情。基于进行的比较和操作数的类型,所有的这一切都是自动产生的。


那pytest又是怎么运行的呢?这些你并不需要知道。你也不需要搞清楚使用pytest来写测试-这总是让人很开心的。


这正是动态运行时的优势所在。你个人来说可能不会使用这些特性。但你将从使用这些功能的库中获益良多而不需要去弄明白它们是如何工作的。甚至Python本身也通过使用自己的扩展拥有了很多特性-这些不需要改变语法和解释器。


对象


我最喜欢的例子是属性的获取。在Java中,一个point类可能会有getX()和setX()方法而不是直接操作x属性。原因是在不破坏接口的前提下,你可以修改x的读或写。在Python中,你不用担心前面的这些问题,因为必要时你能拦截属性的存取。

有趣的@property语法是一个装饰器,和Java的注解看上去很像,但是可以更直接的修改函数和类。


读取point.x现在调用了一个函数并以函数的返回值作为输出。这对调用代码来说是完全透明的-和读取其他属性并没太大区别-但对象可以根据自己的需要干预和处理它。不像Java,读取属性是类API的一部分,你可以自由得进行自定义。(注意这个例子使得x是只读的,因为我并没有指定如何对其进行修改。可写属性的语法看上去可能有点滑稽,这里暂时不管它如何工作。但你可以规定只有__init__可以赋值给point.x。)


同样的特性也存在于C#之类的静态语言中,所以这可能并不会让人们多么印象深刻。关于Python真正有意思的部分是属性本身并没什么特别。它这是一种使用纯Python语言几行代码就能写下来的内置类型。它能运行主要还是因为Python类可以自定义它的属性访问,包括一般的和暗属性的。封装、代理和组合是很容易实现的;你可以将所有访问调用转发到底层对象,而不必知道它有什么方法。


同样的钩子属性可用于 惰性加载属性 或者 自动持有弱引用 的属性,对调用代码来说完全透明,全部使用纯Python就能实现。


你可能已经注意到至今为止我的代码都没有public或者private修饰符。事实上,Python并没有这些概念。按照惯例,用一个下划线开头标识私有,或者更确切地说—“不打算作为稳定公开API的一部分”。但这其实并没有语法上的意义,Pyhton本身并不会阻止任何人查看或者修改这样的属性(调用方法)。同样,也没有final、static、const这些概念。


这又是同样的哲学在起作用:核心Python通常不会阻碍你做任何事情。当我们需要的时候,它将会非常有用。我已经通过启动时调用或重写、甚至重新定义私有方法来修改第三方包的BUG。这样我就不需要重新做一个项目的本地分支,可以省不少事情。而且一旦官方修复了BUG,我可以很容易得删掉自己的补丁代码。







请到「今天看啥」查看全文


推荐文章
Python爱好者社区  ·  模仿一下领导说话的样子
15 小时前
Python爱好者社区  ·  DeepSeek又爆了。。。
15 小时前
Python爱好者社区  ·  今年程序员这薪资是认真的吗?
昨天
Python爱好者社区  ·  35个爬虫实例
3 天前