为什么你应该学 Python ?

2017 年 9 月 29 日 Python开发者 伯乐专栏/逆旅

(点击上方蓝字,快速关注我们)


编译:伯乐在线 - 逆旅 

如有好文章投稿,请点击 → 这里了解详情


引言


第一次接触 Python 是在一节编程入门课上。其实,在此之前了解过它,所以在上课之前我对它的语法已经很熟悉了,但在上课之前我没有用它做过真正的项目。尽管对它没有太大兴趣,但我认为把它介绍给人们去学习编程还是很好的。我对它不是不喜欢,而是一种“无所谓”的态度。原因很简单:它里面有太多“魔法”。 C 和 Java 这些语言,对底层的行为描述的很清晰,Python 则完全相反。


另外,Python 结构松散:写大型复杂程序时,遇到规则严谨的程序结构体(比如每个文件一个公共类),比其他语言(比如 Java )要费些力气。但是,在这些方面 Python 给了你很大的自由。


另一件事是严格的编码风格和调试:因为Python 是解释型语言,查找问题不太容易:如果C 语言有语法错误,编译器会直接停止编译,但在解释型语言中,直到执行到问题行,问题才会被发现。试着在需要整数的时候传一个字符串?cc 会马上提醒你,Python 解释器却对此一点都不介意(虽然有工具可以发现这个问题,比如 mypy,但我讨论的是通用的Python)。我提到的这些问题是解释型语言的通病,并非 Python 独有,但这些是我不喜欢它的主要原因。


还有一个烦人的问题是强制缩进。我们老师(很优秀)认为这是好事情,因为“它强制我们形成简洁的代码风格”。确实如此,但还是有点烦,当代码没有按预期执行时,你分析代码想要找出 bug,它却无影无踪,过了很长时间之后你发现 if 语句那一行有一个多余的空格。


我曾经和同事聊过 Python,告诉他为什么我之前对这个语言不感冒,他笑着问我“问什么不喜欢Python呢?因为它读起来很像英语?”。是的。因为这个语言做了很多底层的工作,有时候会不清楚发生了什么。举个读文件的例子,假设你想一行一行读取文件内容并打印出来。C 会这么做:


#include <stdio>

 

int main(void) {

    FILE *fp;

    char buff[256]; // assuming a line won't contain more than 256 chars

    fp = fopen("hello.txt", "r");

 

    while(fgets(buff, 256, fp)) {

        printf("%s", buff);

    }

 

    fclose(fp);

    return 0;

}


python 这么做:


with open('hello.txt') as f:

    for line in f:

        print(line)


现在,很多人会认为这是 python 的优势,然而,第一个例子中,干了什么一目了然:


  • 获取一个文件指针

  • 从文件读取每一行数据到缓存中,打印缓存中的内容

  • 关闭文件流


python 的例子中看不到这些,它是一种 “魔法般的”过程。现在,有人认为这是好事,因为将程序员与底层实现细节隔离(我同意这个说法),但我想知道到底发生了什么。


有趣的是,我以上提到的缺点,我现在认为都是优点。为了公平起见,我强调,Python 里边没有魔法,如果你多了解一点,你会发现真的没有,有的只是语言解释代码的方式,从这点来看,我发现它挺有意思的。如果你也这么觉得,我建议你深入了解它的工作机制,如果有东西像魔法,就找出来到底发生了什么,事情就会变得清晰,魔法就变成了便利。


我的认识发生很大的变化,尤其是我决定使用 Python 后,事实上我现在是 Python 的死忠!现在你也许会想我将会在哪里说服你学 Python 是个好主意,不要担心,马上就到。作为引言的结尾,我想说明,这只是我对这个语言的个人感受,只是个人偏好。我没有试图以“如果你用 Python,你就不是真正的程序员(实际上,我不这么认为)”的理由劝说人们学 C。当有人问我他们的入门语言应该选哪个,我通常建议他们选 Python,基于我上边提到的“缺点”的原因。我的感觉来源于我的兴趣,我曾经在做一些很底层的东西,你能想到,Python 并不适用。


Python 语言精粹


在借用了JavaScript 畅销书 《JavaScript 语言精粹》作为本节标题后,我们开始讨论本文的主题:为什么你(没错,就是你!)应该学 Python。


1、通用脚本语言


这是我使用 Python 的主要原因。我曾经和很多人做过很多项目,不同的人用不同的系统。就我而言,我经常在windows系统和linux系统之间切换。举一个实际的例子,有一个项目,我写了项目的自动测试脚本,结果发现只有我能用,因为是用 PowerShell 写的,而我是项目中唯一使用 Windows 的。当时同事们自然认为 bash 是最好的,我还向他们解释 PowerShell 遵循一种不同的模式并且有它的强项(例如,它提供了 .NET 框架接口),它是面向对象的脚本语言,和 bash 完全不一样。现在我不想讨论哪个更好,因为这不是本文的重点。


那么这个问题怎么解决呢?嗯…现在,是否有一种脚本语言可以在所有主流平台上运行呢?你猜对了,它就是 Python。除了可以在主流平台上运行,它还是开箱即用的脚本语言。标准库包含不少实用程序,提供了独立于系统的常用接口。举一个简洁明了的例子,假设你想获取文件夹下所有文件的文件名,然后对其进行处理,在 UNIX下,你要这么做:


for f in *; do echo "Processing $f file..."; done


用 PowerShell 做类似的事情:


Get-ChildItem "." |

Foreach-Object {

    $name = $_.Name

    Write-Output "Processing $($name) file..."

}


An equivalent functionality in Python can be achieved with:


python 这么做:


from os import listdir

 

for f in listdir('.'):

    print('Processing {} file...'.format(f))


现在我认为,Python 除了可以跑在 Linux,MacOSX 和 Windows 上,它也很易读。上边例子中的脚本很简单,在复杂的例子中不同语言的易读性差异会更明显。


就像我之前提到的,Python 自带了许多强大的库用来取代 shell 脚本,你会发现,最有用的是:


  • os – 提供系统无关功能,比如文件目录和文件读写。

  • subprocess – 产生新进程、与输入输出流和返回代码交互。可以用它来启动系统已安装的程序,但请记住如果你担心脚本的可移植性,这不是最好的选择。

  • shutil – 提供对文件和文件集合的高级操作。

  • argparse – 解析命令行参数,构建命令行接口。


好了,假设你 get 到了重点,跨平台和易读性听起来挺不错的,但是你真的喜欢类 UNIX shell 类似的语法怎么办?告诉你个好消息,鱼和熊掌可以兼得!看看 Plumbum,它是一个 Python 模块,它的座右铭是“ 再也不写 shell 脚本”。它模仿了 shell 语法,同时保持了跨平台。


不要完全抛弃 shell 脚本


即使 Python 可以完全取代 shell 脚本,但也不是必须这么做,因为 Python 脚本天生适合 Unix 命令行理念,你要做的就是让它们从 sys.stdin (标准输入)读数据,向 sys.stdout(标准输出)写数据。举个例子,假设你有一个文件,每行有一个单词,你想知道每个单词在文中出现的次数。这种情况就没必要全部是用Python,我们可以使用 cat 命令和我们的脚本,称它为 namecount.py 一起来完成这个任务。


假设有一个文件,名为 names.txt ,内容如下:


cat

dog

mouse

bird

cat

cat

dog


现在使用我们的脚本:


$> cat names.txt | namecount.py


Powershell:


$> Get-Content names.txt | python namecount.py


期望的输出如下(顺序可能会变化):


bird 1

mouse 1

cat 3

dog 2


namecount.py 源码:


#!/usr/bin/env python3

import sys

 

def count_names():

    names = {}

    for name in sys.stdin.readlines():

        name = name.strip()

 

        if name in names:

            names[name] += 1

        else:

            names[name] = 1

            

    for name, count in names.items():

        sys.stdout.write("{0}\t{1}\n".format(name, count))

 

if __name__ == "__main__":

    count_names()


无序的信息可读性差,你可能想按单词出现的次数对其排序,让我们试试。我们要用管道输出文件内容供内建命令处理。按数字降序排序,我们要做的就是 $> cat names.txt | namecount.py | sort -rn 。如果使用PowerShell 应该这样:$> Get-Content names.txt | python namecount.py | Sort-Object { [int]$_.split()[-1] } -Descending (你可能听到了 Unixer 的吐槽声了,PowerShell 怎么这么繁琐)。


这回我们的输出是确定的,如下所示:


cat 3

dog 2

bird 1

mouse 1


(旁注:如果你用 PowerShell,cat 是 Get-Content 的别名,sort 是 Sort_object 的别名,所以以上命令可以写成:$> cat names.txt | python namecount.py 和 $> cat names.txt | python namecount.py | sort { [int]$_.split()[-1] } -Descending )


但愿我成功说服你 python 是你某些脚本的替代品,你不必完全抛弃 shell 脚本,因为你可以将 Python 融合到你现有的工作流和工具箱中,还可以从它跨平台,更好的可读性,还有丰富的库中获益(后面会讲)。


2、大量优秀的库


Python 有非常丰富的库。我的意思是,几乎任何事都有库(有趣的是:如果你在你的Python 解释器中输入 import antigravity,在浏览器中打开 xkdc 漫画的页面,是不是很酷?)。我不是很推崇堆叠模块式的编程,但你不必这样。因为有太多的库,不表示你都要使用。我也不喜欢堆叠模块(它有点像 CBSE),我在了解它们之后才使用。


例如,我决定研究马尔科夫链,我想了一个项目:抓取一个艺术家的所有歌词,建立一个马尔科夫链,然后从其中生成歌曲。这个项目的目的是生成的歌曲应该能反映出艺术家的风格。所以我到处找相关的东西,搞出了 lyricst 项目(这只是个样品,还不成熟,只是一个测试项目,如我所言,我只是随便搞了一下,没想深入。如果你想玩的话,它包含有命令行界面和示例的说明文档)。我认为,最好的找歌词的地方是 RAPGenius,因为它很活跃,经常更新。


为了获取艺术家所有的歌词,我必须从网站上爬,然后处理 HTML。幸运的是,Python 很适合做网络爬虫,它有强大的库像 BeautifulSoup 可以处理 HTML。所以我是这么做的,先使用 BeautifulSoup 从网页中抽取我需要的信息(就是歌词)然后用这些信息构建马尔科夫链。当然我曾经想用正则表达式构建自己的 HTML 解析器,但是这个库的存在让我更关注项目的最终目的:把玩马尔科夫链,让它更有趣,比方说,从文件中读取些内容出来。


3、用来做渗透测试很强大


如果你在作渗透测试或仅仅是喜欢玩玩,Python 是你的好帮手!由于Python 在所有 LInux 和 MAC OS 机器上都有安装,还有丰富的库,完善的语法,还是一门脚本语言,让它很适合干这个。


另一个我为什么决定使用 Python 的原因(除了我之前提到的)是我对安全很感兴趣,Python 是用来做渗透测试的完美选择。我在第一次进入领域是通过 Scapy(或 Scapy3k ,python3),我印象很深。Scapy 能够创建、监听、解析数据包。它的 API 很简单,文档也很完善。你可以很容易的创建不同层的数据(我指的是 OSI 模型)或者捕获它们对其进行分析或修改。你甚至可以导出 pcap 文件用 Wireshark 打开。虽然除了抓包还能做很多事情,还有很多其他的库也可以,但我在这里不会涉及,因为这不是本文的重点而且要展开讲的话需要一篇文章。


有人可能会说,“哦,太棒了,但我感兴趣的是 Windows 设备,里边不会自带 Python”。别当心,你可以用 py2exe 把你的脚本编译成 .exe 文件。文件可能会有点大(取决于你是用的库的数量),但这不是重点。


如果你很好奇,请参考 list of Python pentesting tools。 文末我还推荐了几本书。


4、黑客的语言


Python 是可塑性很强的语言。你可以用各种方法改造它。可参见《 altering the way imports work》和《messing with classes before they are created》这两篇文章。这只是一些例子。也让它成为强大的脚本语言(在第一节有说)适合做渗透测试(第三节),因为它给了你很大的自由。


我不想讲太多,但我会讲述它让我惊讶的地方。当时,我在做一个网络爬虫( Python 很适合干这个!),我用的其中一个工具是 BeautifulSoup。 这是我用来学习 Python 的项目之一。Beautifulsoup 处理 HTML 的语法清晰直观,原因是在自定义行为方面,Python 给了你很大的自由。了解一番 API 后,发现有 “魔法”。和这种情况类似:


from bs4 import BeautifulSoup

 

soup = BeautifulSoup('<p class="someclass">Hello</p>', 'html.parser')

soup.p


上面的代码利用第一个字符串参数创建了一个 BeautifulsSoup 实例,第二个参数表示我想使用 Python 自带的 HTML 解析器(BeautifulSoup 可以搭配多种解析器)。soup.p 返回一个 Tag(bs4.element.Tag) 对象,表示将作为第一个参数。


以上代码的输出是:


<p class="someclass">Hello</p>


现在你可能会想,你说的魔法在哪?马上就来。魔法在于上面的代码可以被修改为任何标签,甚至可以是自定义的。它意味着下面的代码也可以正常运行:


from bs4 import BeautifulSoup

soup = BeautifulSoup('<foobarfoo class="someclass">Hello</foobarfoo>', 'html.parser')

soup.foobarfoo


The output is the following:


输出如下:


<foobarfoo class="someclass">Hello</foobarfoo>


当我发现这样也能运行,我的反应是“怎么回事?”。因为,第一个例子很容易实现,我的意思是最直接的方法是为每一个 HTML 标签定义一个属性(实例变量),在解析过程中如果找到了,就赋值给它们。但是这对第二种情况不适用,不可能对所有的字符串定义属性。我想知道它是怎么实现的,所以我打开 BeautifulSoups 源代码开始寻找。 我没有发现任何命名为 p 的属性,这一点也不奇怪,解析函数没有对其赋值。谷歌一番后,我找到了答案:魔法方法。什么是魔法方法,为什么要叫这个名字?事实上,魔法方法是给你的类赋予魔法的方法。这种方法通常前后有两条下划线(例如 __init__()),在Python文档的 DataModel model section 有对它的说明。


真正让 BeautifulSoup 拥有这个功能的魔法方法是__getattr__(self, name)(self 在python 中指向实例,和 Java 中的this 类似)。如果去查看文档,你会发现第一段如下:


如果在属性常见地方找不到属性时,比如既不是实例属性,又没有在 self 类树中找到,则调用该方法(object.__getattr__(self, name))。参数 name 就是属性名这个方法应当返回(计算过的)属性值或抛出 AttributeError 异常。


当你尝试访问一个不存在的属性,对象的 __getattr__(self,name) 方法会被调用,将返回一个以name 作为名字的属性的字符串。


举个例子。假设你有一个 Person 类,拥有 first_name 属性。我们给使用者访问和 name 相同属性的内容的能力。下面是代码:


class Person(object):

    def __init__(self, first_name):

        self.first_name = first_name

 

    def __getattr__(self, name):

        if (name == 'name'):

            return self.first_name

        raise AttributeError('Person object has no attribute \'{}\''.format(name))


我们在终端运行代码:


person = Person('Jason')

>>> person.first_name

'Jason'

 

>>> person.name

'Jason'

 

>>> person.abc

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 7, in __getattr__

AttributeError: Person object has no attribute 'abc'


这意味着我们能凭空构造实例属性,是不是很棒?所以你可以偷偷的让你的 Dog 除了汪汪叫之外,还会喵喵叫:


class Dog(object):

    def bark(self):

        print('Ruff, ruff!')

    

    def __getattr__(self, name):

        if(name == 'meow'):

           return lambda:  print('Meeeeeeow')

        raise AttributeError('I don\'t know what you\'re talking about...')


>>> snoop = Dog()

 

>>> snoop.bark()

Ruff, ruff!

 

>>> snoop.meow()

Meeeeeeow


你可以在没有 reflection 的情况下,随意添加新属性。object.__dict__ 是(字典)[https://docs.python.org/3.5/library/stdtypes.html#typesmapping] 包含 object 的属性和它们的值(注意我说的是 object.dict, object 是一个实例,还有一个 class.dict,是类的属性的字典)。


意思是:


class Dog(object):

    

    def __init__(self):

        self.name = 'Doggy Dogg'


等价于:


class Dog(object):

    

    def __init__(self):

        self.__dict__['name'] = 'Doggy Dogg'


两者输出是一样的:


snoop = Dog()

 

>>> snoop.name

'Doggy Dogg'


到这里你会想,是挺好的,但是有什么用呢?答案很简单:magical APIs。你有没有用过一些 Python 库让你感觉像魔法?这是让它们变的有”魔法”的一种情况。虽然一旦你懂了底层发生的事情,就会发现没有魔法。


如果你还想了解更多,可以查看文档中的 Description Protocol。


Python 的面向对象


Python 的面向对象有点奇怪。例如,类中没有私有变量和方法。所以你想在类中创建一个实例变量或私有方法,你必须遵守规则:


  • 一个下划线 (_)表示私有变量和方法。

  • 两个下划线(__) 表示的变量和方法,它们的名字会被修改。


举个例子,假设你有如下类:


class Foo(object):

    def __init__(self):

        self.public = 'public'

        self._private = 'public'

        self.__secret = 'secret'


转到解释器:


>>> foo = Foo()

>>> foo.public

'public'

>>> foo._private

'public'

>>> foo.__secret

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

AttributeError: 'Foo' object has no attribute '__secret'


如你所见,你可以访问 _private 变量,但是最后一个例子发生了什么,它是否意味着有两个下划线的变量是真正的私有变量?答案是 NO,它们的名字被改变了,实际上,它被 Python替换成了 _Foo_secret 。如果你想访问的话,你仍然可以访问:


>>> foo._Foo__secret

'secret'


然而,PEP8 建议只在父类中使用双下划线来避免属性名冲突。“PEP”,表示 “Python Enhancement Proposal”,它用来描述 Python 特性或作用。如果你想要添加一个新特性,你可以创建一个 PEP,这样可以让整个社区可以看到并讨论。你可以在这里了解更多的 PEPs。


可见,Python 很信任程序员。


我不会再深入讲 OO 了,因为它需要单独一篇文章(甚至是一系列)来讲解。


我确实想给你提个醒,Python 的 OO 可不像 Java 语言那么自然,你需要慢慢适应,但你知道吗,它只是做事的方法不同而已。举个例子,它没有抽象类,你必须使用装饰器来实现这个行为。


结语


希望这篇文章,能够给你一个学习 Python 的理由。这篇文章来自一个为过去说了Python 的坏话而愧疚,如今在到处宣传 Python 的人。我先申明一点,这只是个人喜好问题,当有人问我先学哪门语言时,我通常推荐 Python。


如果你还没决定,那就给它一次机会!用上一两个小时,多读些关于它的东西。如果你喜欢从书上学习,我也会帮你,看看《Fluent Python》, 下节还有更多。


书籍推荐


我兑现了诺言,这一节推荐书籍。我会尽量保持简短一些,只包含一些我读过的书籍。


  • 《Fluent Python》 —— 一本讲 Python3 的好书。无论你是新手、熟手还是高手都值得一读。包含了 Python 的来龙去脉。

  • 《Web Scraping With Python》 —— 标题已经说明了一切,讲如何用Python 来做网络爬虫。你会探索如何爬网上的内容,解析 HTML 等。我觉得这本书对爬虫领域的新手和熟手很有帮助。即使你之前从没用过Python,你也可以看懂。它没有涉及任何高级主题。

  • 《Black Hat Python》 —— 这个有趣!你可以创建反弹 SSH shell,木马等等!如果你想知道 Python 如何做渗透测试,请一定要读它。注意它使用的是 Python 2,我有一个仓库,用的是 Python 3。

  • 《Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers》 ——比上面的主题要多,你会学到如何写一个常见的用于实战的渗透测试,取证分析和安全脚本。


看完本文有收获?请转发分享给更多人

关注「Python开发者」,提升Python技能

登录查看更多
0

相关内容

Python是一种面向对象的解释型计算机程序设计语言,在设计中注重代码的可读性,同时也是一种功能强大的通用型语言。
Python分布式计算,171页pdf,Distributed Computing with Python
专知会员服务
107+阅读 · 2020年5月3日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【新书】Python数据科学食谱(Python Data Science Cookbook)
专知会员服务
114+阅读 · 2020年1月1日
【书籍推荐】简洁的Python编程(Clean Python),附274页pdf
专知会员服务
179+阅读 · 2020年1月1日
【新书】Python中的经典计算机科学问题,224页PDF
专知会员服务
52+阅读 · 2019年12月31日
盘一盘 Python 系列特别篇 PyEcharts TreeMap
平均机器
17+阅读 · 2019年6月13日
Python用法速查网站
Python程序员
17+阅读 · 2018年12月16日
Python 杠上 Java、C/C++,赢面有几成?
CSDN
6+阅读 · 2018年4月12日
Python为啥这么牛?
Python程序员
3+阅读 · 2018年3月30日
快乐的迁移到 Python3
Python程序员
5+阅读 · 2018年3月25日
为什么 Python 更适合做 AI/机器学习?
计算机与网络安全
10+阅读 · 2018年3月18日
五位专家跟你讲讲为啥Python更适合做AI/机器学习
全球人工智能
3+阅读 · 2018年3月18日
Python3爬虫之入门和正则表达式
全球人工智能
7+阅读 · 2017年10月9日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
Arxiv
6+阅读 · 2020年2月15日
A Comprehensive Survey on Transfer Learning
Arxiv
121+阅读 · 2019年11月7日
A Comprehensive Survey on Graph Neural Networks
Arxiv
21+阅读 · 2019年1月3日
Arxiv
3+阅读 · 2018年2月24日
Arxiv
8+阅读 · 2018年1月19日
Arxiv
4+阅读 · 2016年12月29日
VIP会员
相关VIP内容
Python分布式计算,171页pdf,Distributed Computing with Python
专知会员服务
107+阅读 · 2020年5月3日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【新书】Python数据科学食谱(Python Data Science Cookbook)
专知会员服务
114+阅读 · 2020年1月1日
【书籍推荐】简洁的Python编程(Clean Python),附274页pdf
专知会员服务
179+阅读 · 2020年1月1日
【新书】Python中的经典计算机科学问题,224页PDF
专知会员服务
52+阅读 · 2019年12月31日
相关资讯
盘一盘 Python 系列特别篇 PyEcharts TreeMap
平均机器
17+阅读 · 2019年6月13日
Python用法速查网站
Python程序员
17+阅读 · 2018年12月16日
Python 杠上 Java、C/C++,赢面有几成?
CSDN
6+阅读 · 2018年4月12日
Python为啥这么牛?
Python程序员
3+阅读 · 2018年3月30日
快乐的迁移到 Python3
Python程序员
5+阅读 · 2018年3月25日
为什么 Python 更适合做 AI/机器学习?
计算机与网络安全
10+阅读 · 2018年3月18日
五位专家跟你讲讲为啥Python更适合做AI/机器学习
全球人工智能
3+阅读 · 2018年3月18日
Python3爬虫之入门和正则表达式
全球人工智能
7+阅读 · 2017年10月9日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
相关论文
Arxiv
6+阅读 · 2020年2月15日
A Comprehensive Survey on Transfer Learning
Arxiv
121+阅读 · 2019年11月7日
A Comprehensive Survey on Graph Neural Networks
Arxiv
21+阅读 · 2019年1月3日
Arxiv
3+阅读 · 2018年2月24日
Arxiv
8+阅读 · 2018年1月19日
Arxiv
4+阅读 · 2016年12月29日
Top
微信扫码咨询专知VIP会员