Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为什么俺推荐 Python #289

Open
yanyue404 opened this issue Dec 3, 2024 · 5 comments
Open

为什么俺推荐 Python #289

yanyue404 opened this issue Dec 3, 2024 · 5 comments

Comments

@yanyue404
Copy link
Owner

为什么俺推荐 Python[0]:概述

当初博客开张的时候,原计划在编程语言方面聊聊 C++、Java 和 Python。谁曾想半年多过去了,尚未写过 Python 的帖子。实在是说不过去啊!所以今天赶紧写一篇来凑数。

其实俺接触 Python 的时间,也不算太早(大概是 2003 年那会儿)。当时看到  Eric Raymond  关于几大编程语言的评价(原文在"这里",翻译在"这里"),之后又看了他写的《Why Python?》。感觉这位大牛蛮器重 Python 滴,于是俺也就跟风学习了一番。从此一发不可收拾------现在 Python 已然成为俺最常使用的脚本语言了。
  接下来,俺就从几个不同的角度来鼓吹一下 Python。假如你从来没有听说过或者从来没用过此语言,听了俺的忽悠之后,或许会有所心动 :-) 假如你觉得俺只不过是 Python 阵营的枪手,发发广告帖,那你就不用再浪费宝贵的时间去看后续的帖子了。
  按照俺先前的习惯,会把接下来的内容拆成几个帖子来写......

为了方便阅读,把本系列帖子的目录整理如下(需翻墙):
1. 作为脚本语言的 Python
2. 作为动态语言的 Python
3. 作为面向对象语言的 Python
4. 作为函数式编程语言的 Python
5. 作为瑞士军刀的 Python------顺便分享俺整理的 Python 开源库
6. (未完待续)

@yanyue404
Copy link
Owner Author

为什么俺推荐 Python[1]:作为脚本语言的 Python

文章目录

★ 脚本语言好在哪?

★ 脚本语言有啥缺点?

★Python 和其它脚本语言的比较

★ 总结

俺窃以为,Python 的所有特征中,作为脚本语言("脚本语言"的定义参见"这里")是它的首要特征。因此,在本系列帖子中,俺首先来忽悠一下 Python 作为脚本语言,有些啥好处?

★ 脚本语言好在哪? ---------

要聊 Python 作为脚本语言的好处,首先得说说脚本语言自身有哪些优点。一般来说,当我们提及"脚本语言",都是强调其解释执行的特性(虽然有些脚本语言也支持编译)。所以,后面陈述的这些优点,大都是拿编译型语言来进行对比。

◇ 更高层次的抽象和封装

大部分脚本语言都提供了(内置了)比较高层次的抽象和封装。像很多脚本语言都内置了字符串处理能力以及正则表达式(典型代表就是 Perl)。还有很多脚本语言都内置了高级的数据结构。比如 Python 在语言层面支持了链表(Python 的术语叫 List)、映射(Python 的术语叫 Dict)、元组(Python 的术语叫 Tuple)。

有了这些高层次的封装,你写起代码来,就特别滴简单、特别滴爽。比如,在 Python 中要把一个 List 翻倍,只需这么写:

[1,2,3] * 2

就得到

[1,2,3,1,2,3]

◇ 更少的代码量

得益于高层次的封装,在完成相同功能的前提下,脚本语言的代码量会比编译型语言少很多。

比如说,要打印出某个文本文件的内容,如果用 Java 实现,正常的写法大概要七、八行代码(把所有代码硬挤到一行的,不予讨论);用 Python 也就三、五行。

再比如说,抓取某个网址对应的 web 网页,用 Python 自带的标准库实现,大概 3 到 5 行代码;但如果用 C++ 实现,代码量会增加许多(具体要写多少代码,取决于你用了哪个 http 的库)。

代码量少了之后,至少你看代码的时候(无论是看自个儿滴还是看别人滴),能少敲很多次翻页键,大大延长了键盘的寿命,顺便降低了手指头的劳损。

◇ 更好的可读性

当然啦,延长键盘寿命还不是关键,关键在于------代码量少了之后,(通常情况下)会有助于提高可读性。而可读性恰恰是 Python 的强项之一。

比方说:Python 在【语法层面】强制约定了作用域缩进(这是俺很喜欢 Python 的地方之一)。如此一来,即便是新手程序猿写出的 Python 代码,缩进风格也很统一。反观 C/C++/Java 的新手,写出的代码就没有这么整齐了。

◇ 更平缓的学习曲线

通常,脚本语言的语法都比较简单、傻瓜化。因此,入门也就容易很多。稍微有一些编程基础的人,就能够在短时间内上手。

比如俺手下的 C++、Java 程序员,以及某些测试人员,都可以在一周内(程序员用不着一周,一般就 1、2 天)掌握 Python 的语法并用来写一些辅助的小工具。大大节约了俺培训的口水。

◇ 支持"交互式"

很多脚本语言的 IDE 支持【交互式写代码】。也就是说,你每写完一行代码,解释器就执行一把。这样能很快发现输入错误,而且还可以立即看到执行结果。

★ 脚本语言有啥缺点? ----------

前面说了那么多优点,那脚本语言有啥缺点捏?

主要的缺点就是【性能差】。这是他们为上述优点所付出的沉重代价。所幸当今的计算机硬件突飞猛进,性能差的缺点已经越来越不明显了。

★Python 和其它脚本语言的比较 ------------------

有同学可能要问了,脚本语言有很多,为啥俺独独青睐 Python 捏?

为了回答这个问题,下面俺拿 Python 和一些常见的脚本语言作一些【肤浅的】比较。

鉴于后面的内容极易引发语言的口水战,俺特此声明:虽然接下来会提及某些语言相对于 python 的缺点,但俺绝无贬低这些语言的企图,也无意证明 python 比这些语言优秀!俺只是陈述一下:当年是如何在几种脚本语言中进行取舍的?

除了 Python 之外,常见的脚本语言还有很多,比如:PHP、JavaScript(以下简称 JS)、Perl、VBScript(以下简称 VBS)、Ruby、Bash、Lua、Tcl(可别误以为是某家电厂商)......Python 是如何从这些脚本语言中脱颖而出的捏?俺挑选的时候,主要考虑了如下几点:

◇ 通用性(跨领域)

因为俺懒得学太多编程语言。所以,俺希望熟悉一门脚本语言之后,能够尽量多帮俺搞定不同领域的事儿。从这点来看,俺就不会选择 PHP(太偏重于 Web 服务端)、JS(太偏重于 Web 客户端)、诸如 Bash 之类的 Shell 脚本(太偏重于系统管理)。

而 Python 则属于通用的脚本语言,覆盖范围很广。比如 Web 开发、桌面 GUI 应用、系统管理、网络应用、科学计算等许多领域,Python 都可以轻易搞定。

◇ 人气够旺

关于"人气"的重要性,俺在《如何选择开源项目》一文中,有介绍过。人气越高、越流行,就意味着更多的资源(包括文档、相关软件),当你碰到问题需要解决,也有更多的人可以咨询。

关于编程语言的流行程度,可以大致参考  TIOBE  的排名(虽然 TIOBE 不能全面反映流行程度,但至少可作为某种参考)。

像 Tcl、PowerShell、Groovy、JavaFX 等都排在30名之外(截至写本文的 2009 年 8 月),感觉用的人太少了,俺暂时不予考虑。而 Python 最近几年的排名则一路上升(请看"这里"),截止到 2009 年 8 月,已经高踞排行榜第6位。Perl 虽然也身居高位,但是最近几年的排名一路下滑(请看"这里")。俺个人认为,其人气不容乐观。

◇ 功能够强、库够丰富

另一个俺很看重的地方是功能是否够强大。在这点上,Python 和 Perl 都算是比较强悍的。关于 Python 如何强悍,俺会在本系列的第 5 篇帖子《作为瑞士军刀的 Python》中加以介绍。

反观 JS、Ruby、Tcl 等语言,则稍显不足(当然,也有可能俺孤陋寡闻)。

◇ 跨平台

由于俺平时会使用不同的操作系统,再加上俺负责的产品也是跨平台的。所以,俺对脚本语言有跨平台的要求。说到跨平台,诸如 VBS、Bash 之流就不予考虑了。

其实,很多脚本语言都支持跨平台。而 Python 在这方面,更为出众。不光支持主流的操作系统,还支持一些冷门的(比如古老的 DOS),还支持手持设备(比如智能手机和平板)。

◇ 和其它语言的整合、交互

最后这一点,估计大多数同学不会太关心。俺因为要在公司的产品中引入脚本技术,所以俺还得考虑该脚本语言和其它语言的整合能力。整合能力强的脚本语言,可以作为复杂系统中的胶水,用来把不同模块粘合在一起(关于 Glue Language,可以参见"这里")。

在这方面,Python 和 Ruby 的表现都不错:

它们和 Java 的整合有JythonJRuby

和 dotNet 平台的整合有IronPythonIronRuby

至于俺常用的 C++,Python 整合得比 Ruby 好。比如 C++ 社区大名鼎鼎的 Boost 库里面,就内置了一个 Boost.Python 的子库(参见"这里")。

关于 Python 如何用作胶水,俺会在后续的帖子"作为胶合语言的 Python"中会详细介绍。

★ 总结

基于上述几个方面的考虑,俺最终选择了 Python 作为日常使用的脚本工具,并把它引入到公司的产品中,作为模块之间的胶合剂。

啰嗦完 Python 作为脚本语言的方方面面,下一个帖子,咱来聊一下它作为动态语言的那些事儿。

@yanyue404
Copy link
Owner Author

为什么俺推荐 Python[2]:作为动态语言的 Python

文章目录

★ 动态语言扫盲

★ 为啥要学习动态语言?

★ 为什么是 Python?

★ 总结

上一篇帖子介绍了脚本语言的优缺点,然后又拿 Python 和其它脚本语言 PK 了一下。今天主要是忽悠一下动态语言,捎带忽悠一下 Python。如果你看完本贴,觉得动态语言不错,那俺建议你从 Python 开始入手。

★ 动态语言扫盲

考虑到还有很多同学对动态语言了解不深入,有必要先来普及一下它的基本常识。已经了解的同学,请略过本节。

通俗地说:能够在运行时修改自身程序结构的语言,就属于动态语言。那怎样才算是"运行时修改自身程序结构"捏?比如下面这几个例子都 算:在【运行时】给某个类增加成员函数及成员变量;在【运行时】改变某个类的父类;在【运行时】创建出某个函数......

从这些例子,你应该对动态语言有一个初步的感觉了吧?毕竟传统的静态语言(比如 C、C++、Java),是很难达到这些效果滴。

另外,有个误区需要澄清一下。很多同学以为脚本语言也就是动态语言。其实两者是不等价滴------虽然两者有很大的交集。比如 C# 在 4.0 之后,就可以算是动态语言了,但它不能算是脚本语言;另外,有很多 Shell 脚本语言(比如 DOS & Windows 下的 bat),不能算是动态语言。

关于动态语言更深入的介绍,大伙儿可以看"这里"。

★ 为啥要学习动态语言?

扫盲之后,就该来说一下,学习动态语言的动机了。搞明白动机,学起来才有干劲嘛  :-)

◇ 顺应大趋势

假如你经常关注  TIOBE  的排名,那你应该能察觉出来,动态语言近两年的发展势头比较迅猛(在 Top10 里面,至少占了半壁江山)。这能从某个侧面反映出动态语言的影响力在扩大。

假使你不相信  TIOBE  的排名,俺再举一个例子。两大开发阵营(Java 和 dotNet)最近几年也加大了对动态语言的支持力度。比如,dotNet 的 CLR 加入了对IronPython  和  IronRuby  的支持;Sun 当然也不甘示弱,JVM 也开始支持  GroovyJRuby  等语言。

俺费了这许多口水,列位看官应该明白动态语言是大势所趋吧。在这动态语言大行其道的日子里,你如果连一门动态语言都没搞懂,那出门都不好意思跟人打招呼。

不过,话又说回来,静态语言也是不会消亡滴。毕竟,静态语言有自己的优势(比如严谨、性能)。长期来说,必定是动态语言和静态语言并存。各自弥补对方的缺点。

◇ 了解新思维、新理念

学习一门动态语言还有一个好处:有很多时候,多学习一门语言,并不一定是为了在工作中用它,而是为了学习新的思维方式、体会新的理念。比如俺就曾经花时间去看  Prolog,但是俺在工作中,从来不需要用到它。(以后有空的话,俺会介绍一下这玩意儿)

由于动态语言可以在运行时修改自身结构,因此就会产生很多静态语言所没有编程范式和手法(比如  evalMixin)。如果你以前只使用静态语言,那你在学习了动态语言之后,多半会从它身上领略到很多新的思想和理念。

(关于 eval 的招数,俺后来写了一篇《再举几个动态语言 eval 手法的例子》)

◇ 能够化繁为简

可能有些同学觉得,前面说的都有些务虚,那咱再来说点具体实在的。大牛  Edsger Dijkstra(图灵奖得主)曾经说过:编程的艺术就是处理复杂性的艺术。咱们来看看,动态语言是如何处理复杂问题滴。

假设要你实现一个函数,用来完成两个数的"某种运算",具体的运算类型作为函数的参数传入,然后该函数返回运算结果。比如:

Foo("+", 2, 4) # 返回 6
Foo("*", 3, 5) # 返回 15

对于上述需求,你会如何实现捏?

请先暗自盘算一柱香的功夫,然后再往下看。

......

Thinking

......

如果你用静态语言(比如 C、C++、Java)来实现,你可能会在函数内使用一个 switch,根据不同的运算符,进行计算,然后返回计算结果。

对于某些比较 OO 的语言(例如 C++、Java),你或许还会抽象出一个运算的接口类(纯虚类),然后分别派生出若干个不同的计算类(比如加法类、乘法类),看起来似乎比 switch 要优雅一些。

当然,用静态语言还有其它一些玩法,但是代码量都不会少。具体详情可以看很早以前的一个老故事:《4 个程序员的一天》。(其实俺这个例子的灵感就是从那个老故事剽窃滴)

现在,咱们来看看 Python 是如何【优雅地】实现该需求滴。用 Python 只需要【两行代码】即可。请看:

def Foo(op, n1, n2) :
    return eval( "%d %s %d" % (n1, op, n2) )

不懂 Python 的同学可能要问了,这两行代码是啥子意思呀?

其实,第一行代码只不过是定义了一个函数头,第 2 行代码才是精华。这里面利用了动态语言常见的  eval  手法(具体参见"这里")。在 python 里面,内置的  eval  函数可以把某个字符串当成一个表达式,并对其求值。而语句  "%d %s %d" % (n1, op, n2)  只不过格式化出一个表达式的字符串。

顺便再插一句,Python 还有一个  exec  的内置函数,可以把一段 Python 源代码作为字符串参数传递给它,让该函数去执行。两个函数结合起来,就能玩出很多花样。具体的花样可以参见"这篇博文"。

★ 为什么是 Python?

说了动态语言的种种好话,有同学会问了,动态语言有很多种,为啥非要学习 Python 捏?

首先,俺在本系列第 1 篇帖子,已经对比过 Python 和另外几种脚本语言。那几种"脚本语言"碰巧也是知名的"动态语言"。Python 相对于他们的优势,此处就不再重复啰嗦了。

其次,单就语法本身而言,Python 的语法对动态性的支持是很优雅、很简洁滴。通过刚才那个  eval  小例子,大伙应该已经看出来了。为了更形象一点,咱拿前面提到的  Mixin  来 Show 一下 Python 的语法是如何的简洁。

通俗地说,Mixin  手法需要在【运行时】给某个类增加基类(也就是父类)。对于 Python 而言,每一个类都有一个内置属性  **bases**,里面包含这个类【当前】的所有基类。假如要在【运行时】增加基类,只需操作  **bases**  这个属性即可。

比如有一个类 A 和类 B。如果要在运行时把 B 加为 A 的父类,可以用如下语句:

A.__bases__ += (B,)

是不是也很简洁,而且可读性也不差?相比而言,有些动态语言(比如 JavaScript),要实现类似的效果,代码就相对复杂了。

由于 Mixin 不是今天的重点,就不再深入展开了。

★ 总结

最后,来个总结发言:如果你之前没有接触过动态语言,建议去学习一下;如果你已经打定主意要学,Python 是比较好的候选者。

好了,今天就聊到这里。下一个帖子,咱们来讲讲 Python 作为一个纯粹的面向对象语言,有些啥特色。

@yanyue404
Copy link
Owner Author

为啥俺推荐 Python[3]:作为面向对象语言的 Python

文章目录

★ 抽象(Abstraction)

★ 封装(Encapsulation)

★ 继承(Inheritance)

★ 多态(Polymorphism)

★ 结尾

本系列已经中断了很长时间 :( 直到最近一个读者来信问俺,为啥不继续写,俺才突然想起这个被遗忘的系列,实在是抱歉!前一个帖子介绍了作为动态语言的 Python,今天来聊一聊 Python 在面向对象编程(OOP)方面的特色。

本文主要针对那些熟悉 OOP,但还不熟悉 Python 的同学。为了让大伙儿有一个直观的认识,俺会拿 C++/Java 来进行语法上的对比。(这俩语言的名气够大,且号称支持 OO,也算有些可比性)

强调一下:本文虽然拿了某些语言来作对比,但丝毫没有贬低这些语言的意思,请这些语言的粉丝们,不要对号入座 :)

★ 抽象(Abstraction)

但凡介绍 OOP,自然会提到抽象。因为抽象,是 OO 的第一要素,也是其它要素的基础。而提到抽象,又不免提到对象(Object)。所以,俺首先来聊一下,Python 语言是如何体现"对象"这个思想的。

◇Python 的对象

如果要问俺,什么是 Python 中的对象,还真不好下一个严密又通俗易懂的定义。为了敷衍大伙儿,俺只好用一句话来概括,那就是 Python 语言中,【一切皆对象】。这句话该如何理解捏?简单来说,就是你在 Python 语言中涉及到的各种东东,都是"对象"。比如,函数是对象、各种数值(比如整数值、浮点数值、布尔值)是对象、模块(类似于 Java 的 package)是对象、None(类似于 Java 的空引用 null、C++ 的空指针 NULL)也是对象、甚至连类(class)也是对象......

对比一下 C++ 和 Java 的语法:只有【类的实例】才能算得上是对象。这 2 个语言的基本类型(比如"int、char、float"等)不是对象,至于函数,就更算不上了。

既然是一切皆对象,俺有必要稍微总结一下,Python 对象的共性,否则初学 Python 的同学还是会一头雾水。

◇ 对象的属性

首先,所有的 Python 的对象,都具有若干个属性。你可以通过内置的 dir() 函数进行反射,从而了解到某个对象分别都包含哪些属性。熟悉 Java 的同学,应该明白啥是"反射"。光懂 C/C++ 的同学,如果理解上有困难,可以先参考"维基百科的解释"。

另外,Python 还提供了若干内置的函数,用于在【运行时】操作指定对象的属性。具体如下:

hasattr(obj, name)  #判断obj对象是否具有名为name的属性
setattr(obj, name, value)  #设置obj对象的名为name的属性值为value
getattr(obj, name)  #获取obj对象的名为name的属性值
delattr(obj, name)  #删除obj对象的名为name的属性

◇ 对象的类型

所有的 Python 对象,都可以通过内置的 type() 函数获取该对象的类型。这实际上就是 Python 的  RTTI  机制的体现。懂 C++ 的同学,可以回忆一下 C++ 的 typeid 关键字;懂 Java 的同学,可以想一想 Java 的 instanceof 关键字。

◇ 对象的标示

所有的 Python 对象,都可以通过内置的 id() 函数获取该对象的【唯一】标示。而且当一个对象创建之后,这个唯一标示就会始终保持不变。对于学过 C/C++ 的同学,不妨把这个"唯一标示"想象成该对象在内存的地址。这或许有助于你的理解 :)

Python 对象还有其它一些共性,考虑到本文的扫盲性质,就不再费口水了。有兴趣的同学,可以自己找些入门书研读一番。

◇【一切皆对象】的好处?

可能有同学会问,"一切皆对象"有啥好处捏?俺窃以为:当一切皆为对象,就可以把很多概念、操作、惯用手法统一起来,在语法层面体现出美感。

下面俺举几个例子,并拿 Java 来对比一下。

在 Java 里面,由于基本类型不是继承自 Object 类,引出不少麻烦。当初 Java 它爹刚开始设计容器类(比如 Vector、ArrayList ...)的时候,颇费了一番功夫。因为容器里面放置的东东必须是 Object,为了让容器能适应基本类型,只好给每一种基本类型分别对应一个派生自 Object 的包装类(Integer 类对应 int、Float 类对应 float ...);后来又平添了自动装箱/拆箱的概念;搞来稿去,产生了 N 多复杂性。

而 Python 就没有这方面的困扰。

再拿刚才提及的"反射"来说事儿。虽然 Java 语言支持对象的反射,但是 Java 的 package 不是 Object,所以也就无法对 package 进行反射。反观 Python,任何一个 module(相当于 Java 的 package)import 之后,都可以直接通过前面提到的 dir() 函数进行反射,得知该 module 包含了哪些东东。仅仅需要 2 行代码:

import xxx
dir(xxx)

★ 封装(Encapsulation)

为了避免歧义,首先要明确一下:什么是"封装"?为了叙述方便,俺把【OOP 的封装】,分为"狭义"和"广义"两种。(关于"封装"的深入讨论,可以参见"另一篇博文")

◇ 广义封装

OOP 很强调以数据为中心。所以 OOP 的广义封装,就是把数据和操作数据的行为,打包到一起。比如 C++/Java 里的 class,可以同时包含数据成员和函数成员,就算是满足"广义的封装"了。对于 Python 而言,其 class 关键字类似于 C++ 和 Java,也已经具有"广义的封装性"了。

◇ 狭义封装

而 OOP 的狭义封装,则更进一步,增加了信息隐藏(Information Hiding)。比如 C++ 和 Java 的"public、protected、private"等关键字,就是通过访问控制来达到信息隐藏的效果。Python 虽然没有针对访问控制的关键字来修饰类成员,但是 Python 采用了另外一套机制------根据命名来约定。在 Python 的对象中,如果某个属性以双下划线开头来命名(比如  __name),就能起到类似于 C++/Java 的  private  关键字的效果。

◇ 对访问控制的偏见

俺曾经在某技术论坛看到有人质疑 Python 的访问控制机制,说 Python 的私有属性,可以通过反射机制绕过,因此形同虚设。在此,俺想举 C++ 和 Java 来进行反驳。

在 Java 中,同样可以通过反射机制,来访问类的私有成员。至于 C++,得益于指针的强大,只要能访问某个对象(的 this 指针),通过计算该对象成员变量在内存中的偏移,一样可轻易对私有成员变量进行读写。虽然这么干挺变态滴,但技术上是可行滴。

★ 继承(Inheritance)

紧接着,咱再来说一下继承的话题。

◇Python 的继承

Python 没有像 Java 那样,区分出"类继承"(OO 的术语中也叫"实现继承")和"接口继承";也没有像 C++ 那样,区分出"公有继承、私有继承、保护继承"这么花哨的玩意儿。Python 就只有一种继承方式。

◇ 继承的语法

Python 的继承语法,相比 C++/Java 而言,更加简洁。比如子类 Child 需要继承父类 Parent,代码只需如下:

class Child(Parent) :
    # xxxx

如果是多继承,代码大同小异:

class Child(Parent1, Parent2, Parent3) :
    # xxxx

假如你想知道某个类有哪些父类(基类),只需要通过  Child.bases  便可知晓。

◇ 继承的动态性

其实上一个帖子已经介绍了动态改变继承关系的例子。但是考虑到上一个帖子年代久远(距本文将近 1 年),想必很多同学没看过或者看过又忘了。俺不妨再啰嗦一下:作为一种动态语言,Python 可以在【运行时】修改类的继承关系。这个特性比较酷,是 C++/Java 所望尘莫及滴。请看下面的例子:

class Parent1 :
    def dump(self) :
        print("parent1")

class Parent2 :
    def dump(self) :
        print("parent2")

class Child :
    def dump(self) :
        print("child")

print(Child.__bases__)
Child.__bases__ += (Parent1, Parent2)  # 动态追加了2个父类
print(Child.__bases__)  # 此处打印出的父类信息中,已经包含 Parent1、Parent2

★ 多态(Polymorphism)

至于 Python 的多态,和传统的 OO 语言差不多,似乎没有太多值得说道的地方。俺简单举个代码作例子。为了省打字,直接复用上述的 3 个类,并增加一个 test() 函数如下:

def test(obj) :
    obj.dump()

然后对 test() 函数分别传入不同的类型的对象,输出结果俺就无需多说了吧?

c = Child()
test(c)  # 打印出 child
p1 = Parent1()
test(p1)  # 打印出 parent1

★ 结尾

今天的话题,主要是让不熟悉 Python 的网友,对 Python 在面向对象方面的特性,有一个粗浅、感性的认识。聊完了 OOP,下一个帖子会聊一下关于 FP(函数式编程)的话题

@yanyue404
Copy link
Owner Author

为啥俺推荐 Python[4]:作为函数式编程语言的 Python

文章目录

★ 什么是函数式编程

★FP 的特点

★FP 的优点

★Python 的函数语法

★ 和 FP 相关的内置函数

★ 消除控制流

★List Comprehension

★ 闭包

★ 结尾

春节前看到热心读者留言,提醒俺:Python 系列好久没更新了(不知不觉又过了一年多)。俺当时回复说:春节假期补上后一篇。昨天听到鞭炮声才发觉元宵已经到了,赶忙写出本文。

前一个帖子介绍了 Python 作为"面向对象编程"(以下简称 OOP)语言的特点,今天来聊一聊 Python 作为"函数式编程"(以下简称 FP)语言的特点。考虑到本系列面向的是 Python 的门外汉或刚入门的新手,故本文仅介绍若干浅显的 FP 特性。

★ 什么是函数式编程

说实话,"函数式编程语言"是一个很大的话题。由于篇幅有限,本文不可能对这个话题做全面介绍。俺干脆偷一下懒,只简单说说。

从字面上看,所谓的函数式编程,就是以"函数"为中心的"编程范式"。估计有同学又会问了,啥是"编程范式"捏?哎呦,这又是一个很大的话题。通俗来讲,"编程范式"就是指编程的套路。比方说大家很熟悉的 OOP,就是一种"编程范式"。FP 跟 OOP 一样,都是一种编程的套路。做个简单类比:OOP 以"对象/类"作为程序设计的核心,而 FP 以"函数"作为程序设计的核心。

★FP 的特点

既然说到 FP,自然要稍微说一下 FP 的特色。

◇ 函数很牛 X

刚才说了,FP 是以函数为中心。既然如此,在支持 FP 的语言中,函数的功能自然十分牛 X。通俗地说,OOP 语言中,类/对象能干的事情,FP 语言中的函数也能干。下面做一些对比,以加深大伙儿的印象。

OOP 中,对象可以互相赋值;FP 中,函数可以互相赋值。

OOP 中,对象可以作为函数的参数/返回值,FP 中,函数可以作为函数的参数/返回值。

某些 OOP 中,类可以嵌套定义;FP 中,函数可以嵌套定义。

某些 OOP 中,可以有匿名类;FP 中,可以有匿名函数。

◇ 避免副作用

在 FP 中,特别强调函数不要有"副作用"(洋文叫"side effect")。没有副作用的函数,又称之为纯函数(pure function)。其输出完全依赖于输入。换句话说,只要输入一样,输出就一样。

要成为纯函数,函数内部不能读写函数外部变量、不能有设备 I/O(比如读写文件)......

无副作用是 FP 的重要特性。FP 的很多优点来自于此特性。

◇ 避免控制流

在 FP 中,尽量避免用控制流语句(循环语句、判断语句)。对于控制流语句,FP 有另外的替代方式。比如:常用递归或高阶函数来替代循环。如此一来,FP 的代码会显得更简洁,更可读。

◇ 多态

大部分支持 FP 的语言,也都支持多态。函数参数支持多态化,便可实现非常灵活的功能。

说了这么多,不知道大伙儿明白了没?还是没整明白的同学,请看维基百科的英文词条(中文词条太简单,看不明白滴)。

洋文实在看不下去吗?那不妨看看 IT 大牛 Joel 写的《你的编程语言能这样做吗?》(中文版在"这里")。此文以 JavaScript 来阐述 FP 的妙处。

★FP 的优点

再稍微说一下 FP 的好处,以强化大伙儿学习的动力。

◇ 模块化

在 FP 的思想中,函数最好是"纯"的,而且最好只完成"单一"的任务。在这种指导思想下,函数的模块化程度自然就高。

◇ 可复用性

模块化程度高,直接的好处就是可复用性好。

◇ 可读性

刚才说了,FP 的思想强调函数又纯又小。这样的函数,代码的可读性自然好,修改起来也方便。

◇ 易于调试

前面提到了纯函数。如果你的程序中大部分函数都是纯函数,则调试 Bug 会容易很多。像 OOP 中,类的多个成员函数都可以修改类的成员变量,有时候会导致调试极其困难。而纯函数没有此问题。

另外,多线程是调试的一大噩梦。当年俺还专门写过帖子,介绍 C++ 多线程的注意事项(在"这里")。而纯函数由于没有副作用,不必担心各种"互斥"、"死锁"等问题。

◇ 易于测试

除了易于调试,纯函数的输出仅仅依赖于输入,这一特点注定了它很容易进行单元测试。

◇ 适合并行

在 FP 中,由于纯函数无副作用,很适合编写并行处理的代码。最典型并且在工业界获得巨大成功的例子就是 Erlang。

◇ 其它

当然啦,FP 的好处远不止上述这些(比如还有:利于形式化证明)。限于篇幅,俺就不展开了。

★Python 的函数语法

Python 中,常见的函数定义和函数调用,想必各位都晓得了。下面说几种不太常见的,且跟 FP 有关的语法。

◇ 函数赋值

Python 可以把函数直接赋值给一个变量。举例如下:

def square(n) :  # 这是一个计算平方的小函数,后面会反复用它举例
    return n ** 2

f = square  # 此处赋值给变量 f
f(10)  # 此处返回100。注意:对该变量使用小括号,等同于调用函数

◇ 匿名定义

Python 可以用 lambda 关键字定义【单行】的匿名函数。套用刚才的例子

square = lambda x : x**2  # 定义一个单参数的匿名函数,并把该函数赋值给变量
square(10)  # 此处返回 100

◇ 嵌套定义

Python 支持函数的嵌套定义(请看如下例子)。这种语法,在"闭包"中经常出现(后面会具体介绍"闭包")。

def outer() :  # 外层函数
    s = "hello"
    def inner() :  # 内层函数
        print(s)  # 此处引用的是外层作用域的变量

    inner()  # 输出 hello
    s = "world"
    inner()  # 输出 world

★ 和 FP 相关的内置函数

Python 内置了一大坨用于 FP 的函数,以方便程序猿写出简洁的代码。在接下去聊之前,俺有必要先介绍其中的 2 个。

◇map(func, iter)

为了省事,俺只介绍 2 参数的 map(正宗的 map 支持 N 参数)。

参数 func 是个函数,参数 iter 是个迭代器(也可以理解为集合)

map() 会把 iter 的每个元素传给 func,并把每次调用的结果保存到一个 list 中,然后返回此 list。

举例:

挨个计算整数 list 的平方:

map(square, [1, 2, 3])  # 返回 [1, 4, 9]

◇filter(func, iter)

参数含义同 map

filter() 会把 iter 的每个元素传给 func,如果 func 返回结果为 True,就把元素保存在一个 list 中,最后返回此 list。

举例:

要过滤出所有奇数,代码如下:

def odd(n) :
    return (n%2) == 1

filter(odd, [1, 2, 3])  # 返回[1, 3]

此处可以用上 lambda,把代码简化为一行:

filter(lambda n: (n%2)==1, lst)

★ 消除控制流

为了让大伙儿更深刻体会 FP 风格同传统风格的差别,俺把刚才两个例子组合一下------要求返回整数 list 中所有奇数的平方。

传统的写法(有控制流):

def func1(old_lst) :
    new_lst = []
    for n in old_lst :
        if odd(n) :
            new_lst.append(square(n))
    return new_lst

FP 的写法(无控制流):

def func2(lst) :
    return map(square, filter(odd, lst))

怎么样?是不是更简洁?连 for / if 这两个关键字都不需要了。

★List Comprehension

这个洋文比较难翻译。有人叫做"列表推导",也有人称为"列表展开"或"列表解析"。(俺比较喜欢头一个翻译------不禁让人联想到"推倒":)

在 Python 中,这是一个很好吃的语法糖------可以让你写出很简洁、很优雅的代码。

举例 1:

还拿刚才过滤奇数的例子。

filter(lambda n: (n%2)==1, lst)

上述写法可以等价替换为列表推导:

[n for n in lst if (n%2)==1]

举例 2:

再来一个稍微复杂的例子。假设有两个整数 list,分别存储矩形的宽度和高度。现在想把所有的宽度和高度进行两两组合,把大于 10 的面积打印出来。

传统的写法(2 层循环,4 行代码)

for w in width :
    for h in height :
        if w*h > 10 :
            print(w*h)

FP 的写法(无循环,1 行代码,多精致啊)

print( [w*h for w in width for h in height if w*h > 10] )

除了列表推导,Python 中还有字典推导、集合推导等等。为了省点口水,暂且打住。

★ 闭包

闭包,洋文叫"closure",解释在"这里"。它是 FP 的常见手法。那闭包到底有啥用捏?俺举一个微积分中,函数求导的例子。(不懂微积分或者对高数有心理阴影的同学,别担心,请把注意力集中在代码上)

def d(f) :
    def calc(x) :
        dx = 0.000001  # 表示无穷小的Δx
        return (f(x+dx) - f(x)) / dx  # 计算斜率。注意,此处引用了外层作用域的变量 f
    return calc  # 此处用函数作为返回值(也就是函数 f 的导数)

现在,假设要计算二次函数 f(x) = x^2^ + x + 1 的导数,只需如下代码:

f = lambda x : x**2 + x + 1  # 先把二次函数用代码表达出来
f1 = d(f)  # 这个f1 就是 f 的一阶导数啦。注意,导数依然是个函数

有了一阶导数,就可以很容易地计算该函数在某点的斜率

比如要计算 x=3 的斜率,只需:

f1(3)

如果要想得到二阶导数(导数的导数),只需依样画葫芦(瞧这代码写得多优雅)

f2 = d(f1)

看到这里,大伙儿不妨设想一下:如果不用 FP,改用 OOP,上述需求该如何实现?俺觉得吧,用 OOP 来求导,这代码写起来多半是又丑又臭。

★ 结尾

今天聊了不少 FP 的语法特性,可惜还是没聊完。由于俺比较懒,而且怕写得太长没人看,所以一些高级话题(比如:迭代器、生成器、等),今天就不介绍了。假如列位看官对那些玩意儿感兴趣,再抽空单独写一帖。

@yanyue404
Copy link
Owner Author

为啥俺推荐 Python[5]:作为瑞士军刀的 Python------顺便分享俺整理的 Python 开源库

文章目录

★ 短小精悍

★ 功能强大之 1------跟非常多的语言整合

★ 功能强大之 2------既可以跨平台,又可以跟操作系统深度整合

★ 功能强大之 3------具有很丰富的第三方模块

本系列几乎变成年度系列了------上一篇帖子是去年元宵节发的。这几天正值春节假期,赶紧抽空补上本系列新的一篇,免得大伙儿以为本系列"烂尾"了。

"瑞士军刀",大伙儿应该很熟悉,俺就不解释了。拿"瑞士军刀"来比喻 Python 就是想说明:Python 不但短小精悍,而且功能强大。今天就来介绍一下 Python 的这两个特点(尤其是后者)。

★ 短小精悍

◇ 轻量级的的代码

要吹嘘 Python 的轻量级,首先要吹嘘的,自然是它简洁的代码。为了让大伙儿有个初步的印象,举几个例子。

举例 1

要得到 100-200 之间所有奇数的 3 次方,只需如下一行

[x**3 for x in range(100, 200) if (x%2)==1]

举例 2

如果要把上述结果拼成一个逗号分隔的字符串,还是只要一行

','.join([str(x**3) for x in range(100, 200) if (x%2)==1])

举例 3

可能有人觉得上面 2 个例子是小儿科,再来举一个稍微复杂点的。只需一行代码,即可实现网络扫描(用的是第三方的  Scapy  工具)。

# 传统的 ping 扫描,基于 ICMP 协议
ans,unans = sr(IP(dst="192.168.1.1-254")/ICMP())
# 局域网扫描,基于 ARP 协议
ans,unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"), timeout=2)

◇ 轻量级的安装环境

比如 Python 的安装包只有十几兆,而 Java 的 JDK 安装包动辄接近 100 兆。

◇ 轻量级的运行环境

同样是运行一个"Hello World",用 Python 写的进程,占用的内存会更小(相比大多数其它编程语言)。而且 Python 的运行环境(Python 虚拟机)可以很方便地嵌入到 C/C++ 程序中,只需额外带一个动态库即可。

★ 功能强大之 1------跟非常多的语言整合

很多程序员喜欢"在一棵树上吊死"------始终只用一种编程语言。俺非常反对这种倾向。要想成为一个优秀的程序员,有必要掌握【不同风格/不同范式】的编程语言。编程语言多样化的好处在于,你可以利用不同语言的特色,以取长补短。

而 Python 在跨语言整合方面,堪称佼佼者------你很难找到哪个语言,能像 Python 这样,跟如此多的语言进行整合。

下面简单举几个例子:

◇ 跟 C/C++ 整合

咱们常用的 Python 运行环境也称为 CPython,是用 C 语言写的。所以 Python 天生就具有跟 C/C++ 整合的能力。而且这种整合是双向的------也就是说,既可以在 Python 模块调用 C/C++ 模块,也可以在 C/C++ 模块调用 Python 模块。

Python 跟 C/C++ 整合,可以有如下三个优点:

1 . 丰富的第三方资源

因为 C/C++ 具有非常丰富的第三方模块(包括开源和闭源),其丰富程度超过 Java。所以 Python 可以利用这种整合能力,直接调用各种 C/C++ 的模块,来丰富自己的功能。

举个例子

比如 cURL 是一个 C 语言编写的应用层网络工具,功能非常强。而 Python 可以通过整合 cURL 直接获得 cURL 的强大功能。

2 . 性能的提升

除了第三方模块丰富,C/C++ 开发的模块还有一个优点------性能好。所以,Python 整合了 C/C++ 模块之后,还能趁机获得性能提升。

举个例子

比方说你要写一个 3D 游戏,其中的 3D 渲染引擎是性能瓶颈。那么你可以用 Python 整合一个 C/C++ 的 3D 渲染引擎。而 Python 只用来实现跟游戏高层应用逻辑相关的部分。这样可以一举两得:性能不差,代码不多。

顺便说一下:如今有不少游戏都走这个开发思路------底层引擎用【重型的】C 或 C++ 实现,以确保高性能;而高层的业务逻辑则用【轻型的】脚本语言来开发。

3 . 跟操作系统整合

地球上的操作系统,只要是有点名气的,内核都是 C 语言写的;除了操作系统内核,大部分操作系统的运行库也都是 C/C++ 编写的。所以,Python 跟 C/C++ 整合,可以带来第三个好处------跟操作系统的整合。关于这点,待会儿俺还会专门聊。

◇ 跟 JVM(Java) 整合

通过  Jython  这个开源项目,Python 可以无缝整合到 JVM 环境中。如此一来,Python 能直接调用所有 JVM 标准库(比如 JDBC)和第三方库(比如 Lucene),还可以跟各种 JVM 语言(比如"Java、Scale、Groovy"等)进行互操作。

◇ 跟 dotNet 整合

通过  IronPython  这个开源项目,Python 可以无缝整合到 dotNet 环境中。如此一来,Python 能直接调用所有 dotNet 的标准库(比如 ADO.NET)和第三方库,还可以跟各种 dotNet 语言(比如"C#、F#"等)进行互操作。

顺便说一下:Jython 跟 IronPython 貌似同一个作者,牛人啊!

★ 功能强大之 2------既可以跨平台,又可以跟操作系统深度整合

◇Python 语言如何体现"跨平台性"?

说到跨平台这个话题,俺给编程语言分一下类:

1 . 可以跨平台,但无法跟操作系统深度耦合(比如 JavaScript、PHP)

2 . 可以跟操作系统深度耦合,但无法跨平台(比如 VB、VBScript)

3 . 既可以跨平台,又可以跟操作系统深度耦合(比如 C C++ Python)

为啥第三类语言能做到两者兼得?因为这类语言把选择权留给了程序猿------

一方面,如果程序猿遵循跨平台的开发规范,那么写出来的代码就是跨平台的;

另一方面,程序猿也可以写出跟系统耦合很紧密的代码------这样的代码虽然不能跨平台,但可以发挥特定平台的强项。

显然俺喜欢第三类编程语言,因为这类语言给了程序猿自由。

◇Python 语言如何"与操作系统深度整合"?

Python 的跨平台特性,懂 Python 的同学估计都晓得了。所以俺单独说一下 Python 如何跟操作系统深度整合。为了照顾到大多数人,俺拿 Windows 来说事儿。

在 Python 社区有一个很有名气的库,叫做  PyWin32。通过这个库,可以让 Python 代码很容易地调用 Windows 的 API 以及 COM 组件。

比方说,可以用 Python 代码直接操作 Windows 注册表

比方说,可以用 Python 代码直接读写 Windows 的系统日志

比方说,可以用 Python 代码直接操作某个窗口句柄

比方说,可以利用 COM 组件,直接调用 Word 来处理 doc 格式的文档

......

★ 功能强大之 3------具有很丰富的第三方模块

最后一个方面,也是最重要的一个方面,就是 Python 社区具有非常非常丰富的资源(第三方库),而且几乎都是开源的。形形色色的编程领域几乎都可以看到 Python 的身影。

为了让众多程序员读者领略 Python 的丰富资源,俺特地整理了一个 wiki 页面(请用鼠标猛击"这里"),放上 Python 在各个编程领域的常用模块。为了显示出 Python 代码的简洁,某些模块还放上示例代码。

最后感叹一下:

写这篇博文只花了不到一小时,而整理这个 wiki 页面累计超过 10 个小时。不过这时间没白花------整理的时候顺便对某些领域有了更多的了解。这也就是写博客的好处,既可以帮助别人,又可以帮助自己。

大伙儿如果觉得俺整理的清单有遗漏,欢迎到本页面留言补充。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant