Python入门教程,Java开发者必须了解这些!

本文转自阿里云前端

摘要:编者按:在Java文章频道里,咱们大部分人应该对该语言都很是的了解,并且在该生态圈内至少已经呆了好几年了。这让咱们有常规和专业的知识,可是也同时也让咱们一些井蛙之见。 在Outside-In Java系列文章中,一些非Java开发人员会给咱们讲讲他们对于咱们这个生态圈的见解。java


v2-a51000be38ad0255d6b10ab0417bc1f2_b.jp




从哲学的角度来说,Python几乎是与Java截然相反。它抛弃了静态类型和刚性结构,而是使用了一个松散的沙盒,在这里面你能够自由的作任何你想作的事情。也许Python是关于你可以作什么,而Java则是关于你能够作什么。python

然而,两种语言都从C语言里吸收了大量的灵感。他们都是命令式语言,拥有块、循环、方法、赋值以及中缀算术(infix math)。二者都大量使用了类、对象、继承以及多态性。二者的功能异常都至关优秀。 二者都能自动管理内存。它们都是编译成能够运行在某种虚拟机上的字节码,尽管Python是透明的进行编译。 Python甚至从Java汲取了一些养分:好比基本库的 logging 和 unittest 模块分别是受到了log4j 和JUnit的启发。程序员

鉴于以上的技术重叠,我认为Java开发人员在使用Python时理应感到宾至如归。 因此我来给你一些简单的Python介绍。 我会能够告诉你什么使得Python与Java不一样,以及为何我以为这些差别有吸引力。 至少,您可能会发现一些有趣的想法使您回到Java生态系统。数据库

(若是你想要一个Python教程,Python文档是一个很好的选择,并且这是从Python 3的角度编写的,Python 2的使用仍是很常见的,它与Python 3有一些语法上的差别。编程

对于正在学习java但不知道学习方法,不知道该如何找到工做的朋友,我仍是要推荐下我本身建的前端学习群:240448376,首先你要是学前端的,其次无论你是小白仍是大牛,小编都挺欢迎,群里天天都会分享前端相关干货,包括我本身(一名工做六年的前端老司机)最近熬夜花了一星期整理出的一份适合2017年自学的最新java前端资料,都送给你们,欢迎初学和进阶中的小伙伴。缓存

语法网络

咱们先把语法弄明白。下面是 hello world入门程序:session

print("Hello, world!")数据结构

嗯, 并非颇有启发性。 好吧,再来看一个函数,看看如何在一个文件中找到最多见的10个单词。在这里我取巧使用了标准库的 Counter 类型,可是它就是这么的好用。

fromcollections import Counter

def count_words(path):

words = Counter()

withopen(path)asf:

forlineinf:

forwordinline.strip().split():

words[word] += 1

forword,countinwords.most_common(10):

print(f"{word} x{count}")

Python由空格分隔。人们常常对此有强烈的意见。当我第一次看到它的时候,我 甚至认为这是异端邪说。如今,十多年过去了,这种写法彷佛天然到我很难再回到大括号式的写法。若是你所以逃避,我甚至怀疑我能够说服你,不过我劝你至少暂时忽略一下它;实际上它并无形成任何严重的问题,反而消除了一大堆的干扰。此外,Python开发人员历来没必要争论{应该放在哪里。

除了审美上的差别以外,其余方面应该看起来很熟悉。咱们有一些数字,一些赋值和一些方法调用。import 语句的工做方式有些不一样,但它具备相同的“使这些内容可用”的通常含义。 Python的for循环与Java的for-each循环很是类似,只是少了点标点符号。函数自己使用def而不是类型进行分隔,但它正是按照你指望的方式工做:您可使用参数调用它,而后返回一个值(尽管某些函数不返回值)。

只有两件事情是很不寻常的。 一个是 with 块,很是相似于Java 7的“try-with-resources” – 它保证文件在块的结尾处关闭,即便会抛出一个异常。 另外一个是f“…”语法,这是一个至关新的功能,容许将表达式直接插入到字符串中。

就是这样! 你已经读了一些Python的内容。 至少,它不是来自一个彻底不一样的星球的语言。

动态类型

看这个例子可能很明显,可是Python代码里没有太多的类型声明。 变量声明上没有,参数或返回类型上没有,对象上也没有。 任何值在任什么时候候均可以是任何类型的。 我尚未显示一个类定义,因此这里只是一个简单的定义。

class Point:

def __init__(self, x, y):

self.x = x

self.y = y

def magnitude(self):

return(self.x ** 2 + self.y ** 2) ** 0.5

point = Point(3, 4)

print(point.x)  # 3

print(point.magnitude())  # 5.0

尽管x和y有并无定义为属性,它们的存在是由于构造器中建立了它们。没有谁强制我必须传个整型参数,我也能够传小数和分数。

若是你之前只用过静态语言,这可能看起来一片混乱。类型是温暖的懒惰的以及使人满意的。他们保证···(好吧,或许不会)代码实际能工做(虽然有人不一样意)。可是当你都不知道什么是正确类型的时候,你又怎么能依靠代码呢?

可是等等 – Java也没有这样的保证! 毕竟任何对象可能都是null,对吧? 实际上几乎历来没有一个正确类型的对象。

您可能会将动态类型视为对null问题的完全放弃。 若是咱们必须处理它,咱们不妨拥抱它,并让它为咱们工做 – 经过将一切 延迟到运行时。 类型错误变成正常的逻辑错误,您能够以相同的方式处理它们。

(对于相反的方法,请参阅 Rust,它没有空值 – 或者异常,我仍是宁愿写Python,可是我很欣赏Rust的类型系统并不老是对我说谎。)

在个人magnitude方法中,self.x是int或float或任何其余类型,这都不重要。 它只须要支持**运算符并返回支持+运算符的内容。 (Python支持操做符重载,因此这多是任何内容。)一样适用于普通方法调用:任何类型均可以接受,只要它实际上能够工做。

这意味着Python不须要泛型; 一切都是按照泛型工做的。 不须要接口; 一切都已是多态的。 没有向下转型(downcasts),没有向上转型(upcasts),在类型系统中没有逃生舱口(escape hatches)。 当它们能够和任意Iterable工做良好时,运行时API不须要List。

许多常见的模式变得更加容易。 您能够建立包装器对象和代理,而无需更改消费代码。 您可使用组合而不是继承来扩展第三方类型,而不须要为保留多态作任何特殊的操做。 灵活的API不须要将每一个类做为接口复制; 一切都已经做为一个隐式接口了。

动态类型哲学

使用静态类型,不管谁编写 某些代码来选择这些类型,编译器都会检查它们是否正常工做。 使用动态类型,不管谁使用 某些代码来选择这些类型,运行时都会尝试一下。 这就是对立的哲学在起做用:类型系统着重于你能够 作什么,而不是你可能 作什么。

这样使用动态类型有时被称为 “鸭子类型”(duck typing),这是基于这种思想: “若是它走起来像鸭子,并且叫起来也像鸭子,那么它就是一个鸭子。” 换言之,就是说,若是你想要的是能像鸭子同样呱呱叫的东西的话,你不用强制你的代码必须接收一个鸭子对象,相反的你能够接收任何给你的东西,而且让它可以呱呱叫便可。若是它能够达成你的目标的话,那么它就跟鸭子同样好用。(不然若是它没法如你所愿,会抛出AttributeError的错误,可是这也没什么大不了的。)

同时也要注意Python是强类型的。这个词有点模糊,它一般意味着变量的值在运行时会一直保持其类型不变。一个典型的例子是,Python不会让你把一个字符串赋值给一个数字类型的变量,而像弱类型语言,如JavaScript 则能够将一种类型静默的转换成另外一种,这里使用了优先级的规则,跟你的预期会有所不一样。

与大多数动态语言不一样的是,Python 在运行以前就能够捕获错误信息。例如读取一个不存在的变量会产生一个异常,包括从一个字典对象(如 Map)中读取一个不存在的键时也会报错。在 JavaScript 、Lua 和相似语言中,上面两种状况是返回 null 值。(Java 也是返回 null 值)。若是想在值不存在时返回默认值,字典类提供了更加明确的方法调用。

这里绝对是一个权衡的结果,是否值得取决于不一样的项目和人。对我来讲,至少很是适合用来设计一个更加可靠的系统,觉得我能够看到它实际执行的状况。可是静态类型的语言都期待有一个预先的设计。静态类型很难尝试各类不一样的想法,更难发挥。

你确实拥有了更少的静态检查保证,但根据个人经验,大多数的类型错误能够正确被捕获……由于我写完代码的第一件事就是尝试去运行它!其它任何错误应该能够被你的测试所捕获—测试应该在全部语言中都有,而且python语言写测试相对来讲更容易。

一个混合的范例

Python 和 Java 都是命令式和对象导向的:它们的工做方式是执行指令,它们把每件事物塑造为对象。

在最近的发行版本中,Java增长了一些函数式语言特征,我认为这是一件好事。Python也有函数式语言特征,可是实现的方式不太同样。它提供了一些内置的函数如map和reduce,可是它并非基于串联许多小函数的思想来设计的。

相反,Python混合了其它东西。我不知道Python采用的方法的通用名称。我认为它把“函数链”的思想分为两个:做用于序列上和使函数自身更加有用。

序列

序列和迭代在Python有重要的地位。序列是最基础的数据结构,做为于之上的工具很是有价值。我认为Python对于函数式编程的实现以下:Python首先使得使用命令式代码来操做序列很是容易,而不是使得结合许多小函数而后应用于序列很是容易。

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

forword,countinwords.most_common(10):

for循环对咱们来讲很熟悉,可是这行代码一次迭代了两个变量。 实际发生的是,列表most_common中的每一个元素返回一个元组,它们是一组按顺序区分的值。 真正发生的是元组能够经过将它们分配给元组变量名来解包。 元组一般用于在Python中返回多个值,但它们偶尔也可用于特殊结构。 在Java中,您须要一个完整的类和几行分配值的代码。

任何能够迭代的东西一样能够解包。 解包支持任意嵌套,因此a,(b,c)= …按照它看起来的样子解包。 对于未知长度的序列,一个*leftovers元素能够出如今任何地方,而且将根据须要获取尽量多的元素。 也许你真的会喜欢LISP?

values= [5, 7, 9]

head, *tail =values

print(head)  # 5

print(tail)  # (7, 9)

Python还具备经过简单表达式建立列表的语法 – 所谓的“列表解析” – 这比函数方法如map更为常见。 存在相似的语法用于建立分组和集合。 整个循环能够减小到一个单一的表达式,只突出你真正感兴趣的部分。

values= [3, 4, 5]

values2 = [val * 2forvalinvaluesif val != 4]

print(values2) # [6, 10]

标准库还在itertools模块中包含了许多有趣的迭代,组合器和用法。

最后,Python具备用于生成命令行代码的延迟序列的生成器。 包含yield关键字的函数在被调用时不当即执行; 而是返回一个生成器对象。 当生成器迭代结束时,该函数运行直到它遇到一个yield,此时它暂停; 生成的值将成为下一个迭代值。

def odd_numbers():

n = 1

whileTrue:

yield n

n += 2

forxinodd_numbers():

print(x)

if x > 4:

break

# 1

# 3

# 5

由于生成器延迟运行,它们能够产生无限序列或在中途中断。 他们能够产生大量的大型对象,不会由于让它们所有存活而消耗一大堆内存。 它们也能够做为“链式”函数编程风格的通常替代。 您能够编写熟悉的命令行代码,而不是组合map和filter。

# Thisisthe pathlib.Path APIfromthe standard library

def iter_child_filenames(dirpath):

forchildindirpath.iterdir():

if child.is_file():

yield child.name

要在Java中表示彻底任意的惰性迭代器,您须要编写一个手动跟踪其状态的Iterator。除了最简单的状况以外,这一切都会变得至关棘手。 Python也有一个迭代接口,因此您仍然可使用这种方法,可是生成器很是容易使用,以致于大多数自定义迭代都是用它们编写的。

并且由于生成器能够本身暂停,因此它们在其余一些上下文中是有用的。 经过手动调用生成器(而不是仅用一个for循环来一次性所有迭代),就能够在一段时间内运行一个功能,让它在某一点中止,并在恢复该函数以前运行其余代码。 Python充分利用这一点来添加对异步I/O(不使用线程的非阻塞网络)的支持,尽管如今它具备专用的async 和await 语法。

函数

乍一看,Python的函数看上去很是面熟。你可使用参数来调用它们。传递风格与Java彻底相同—Python既没有引用也没有隐式复制。 Python甚至有“docstrings”,相似于Javadoc注释,但它是内置的语法而且在运行时可见。

def foo(a, b, c):

"""Print out the arguments. Not a very useful function, really."""

print("I got", a, b, c)

foo(1, 2, 3) # I got 1 2 3

Java具备args …语法的可变函数; Python使用* args能够实现相似的功能。 (用于解包的*leftovers语法灵感来源于函数语法。)可是,Python还有一些技巧。任何参数均可以有一个默认值,使其成为可选项。任何参数也能够经过名称 给出 – 我以前使用Point(x = 3,y = 4)演示了这点。在调用 任何函数时,可使用* args语法,它将传递一个序列,就像它是单独的参数同样,而且有一个等价的** kwargs将命名参数做为一个dict接受或传递。一个参数能够做为“仅关键字(keyword-only)”,因此它必须 经过名称传递,这对于可选的布尔值是很是好的。

Python固然没有 函数重载,可是你使用它实现的功能大部分均可以被鸭子类型(duck typing)和可选参数替换。

这是现阶段Python最强大的功能之一。 与动态类型同样,您能够经过包装器或代理透明地替换对象,* args和** kwargs容许任何函数 被透明地包装。

def log_calls(old_function):

def new_function(*args, **kwargs):

print("i'm being called!", args, kwargs)

returnold_function(*args, **kwargs)

returnnew_function

@log_calls

def foo(a, b, c=3):

print(f"a = {a}, b = {b}, c = {c}")

foo(1, b=2)

# i'm being called! (1,) {'b': 2}

# a = 1, b = 2, c = 3

这有点难以理解,对不起。 不用担忧它到底是如何工做的; 要点是,foo被一个new_function替换,它将全部的参数转发到foo。 foo和调用者都不须要知道哪些事情有哪些不一样。

我不能低估它有多么强大。 它可用于记录,调试,管理资源,缓存,访问控制,验证等。 它与其余元编程功能工做良好,一样地,它可让您分解结构,而不只仅是代码。



v2-9ce28c0b04fce1b18a04985ff6cfe311_b.jp



对象和动态运行时

动态运行时是一种在背后驱动语言核心部分的东西 — 它能够在运行时被执行。像 C 或者 C++ 这类的语言毫不会具备动态运行时;它们源码的结构被“烘焙”成编译输出,而且后续没有明显的方法来改变它的行为。从另外一方面,Java的确具备动态运行时!它甚至带有一整个专门用于反射的包。

Python 固然也有反射。不少简单的函数经过反射被构建,用来联机检查或修改对象的属性,这对调试以及偶尔的欺骗很是有用。

但 Python 对此更深刻一点。 由于一切都在运行时完成,Python 暴露了不少扩展点来自定义它的语义。 你不能改变语法,代码依然看起来像 Python,但你能够分解结构 — 这在一种更死板的语言是很是难以作到的。

举个极端的例子,看下 pytest, 它聪明的处理了 Python 的 assert 声明。 一般, assert x == 1 为 false 时只会简单的抛出一个 AssertionError 异常,致使你不知道错误是什么或者哪里出错了。这就是为何 Python 内置的单元测试模块 — 像 JUnit 和不少其余的测试工具 — 提供不少专门的工具函数,好比 assertEquals。不幸的是,这些工具函数使得测试初看到时,更加复杂更难以读懂。但在 pytest 中, assert x == 1 是好用的。若是失败,pytest 将告诉你 x 是… 或者两个列表哪里有分歧,或者两个集合哪些元素不一样, 或者其余的。全部的这些都是基于比较完成和运算对象的类型自动发生的。

pytest是如何工做的呢? 你确实不想知道。你不须要知道如何用pytest写测试 — 这令人很开心。

这就是动态运行时的真正优点所在。 就本身而言,可能没有使用这些功能。可是你能从这些库中收获巨大的好处,你使用这些库并不须要关心它们如何运行。 甚至 Python 自己依靠使用本身的扩展点实现了不少额外的特性 — 这些不须要改变语法或者解释器。

对象

属性(在C#中通常翻译成特性)存取是我喜欢举的一个简单例子。在 Java 中,一个 Point 类可能选择用 getX() 和 setX() 方法而不是一个简单直白的 x 属性。缘由是若是你须要改变 x 的读或写,你不须要破坏接口。在 Python 中,你不须要担忧前面的这些, 由于必要时你能解释属性存取。

class Point:

def __init__(self, x, y):

self._x = x

self._y = y

@property

def x(self):

returnself._x

# ... samefory ...

point = Point(3, 4)

print(point.x)  # 3

有趣的 @property 语法是一种修饰,看来就像 Java 的注解,但它能够更直接地修改函数或类。这是彻底透明的调用代码——和读其它属性没什么区别——可是对象能够根据本身的须要干预或处理它。与 Java 不一样,属性访问是类 API 的一部分,能够自由的定义。(注意这个示例让 x 成为只读的,由于我没有指定写方法!可写属性的语法有点滑稽,这里暂时无论它如何工做。但你能够具体规定只有奇数能够赋值给 point.x。)

这个特性也存在于其它静态语言中,好比 C#,因此这并非什么大不了的东西。关于 Python 真正有趣的部分是,属性并没什么特别。它是一个正常的内建类型,一段纯粹并且不满一屏的 Python 程序。它的工做原理是 Python 类能够自定义其属性访问,包括通常的和按属性的。包装、代理和组合很容易实现:你能够将全部访问调用转发到底层对象,而没必要知道它有什么方法。

相同的钩子属性可用于懒加载属性或者自动持有弱引用的属性——这对调用代码彻底透明,经过纯 Python 就能实现。

你可能已经注意到个人代码没有使用 public 或 private 修饰符。事实上,Python 中不存在这个概念。按照惯例,用一个下划线开头表示“私有”——或者更准备地说,“不打算成为稳定公开 API 的一部分”。但这并无语言上的意义,Phthon 自己不会阻止侦查或修改这样的属性(若是是方法的话,则是调用)。一样,也没有final 、static或const。

这是一样的工做原理:核心 Python 一般不会妨碍你作任何事情。若是你须要,它会很是有用。我已经经过启动时调用或重写,甚至从新定义私有方法来修补第三方库的 BUG。这样我不须要从新作一个项目的本地分支,能够省很多事。并且一量官方修复了 BUG,可很容易就能删掉本身的补丁代码。

一样,你能够轻松地编写依赖外部状态的测试代码——好比当前时间。若是重构不可行,你能够在测试时把 time.time() 替换为模拟函数。库函数只是模块模块的属性(就像 Java 的包),并且 Python 模块是种对象,和其它对象同样,因此它们能够以一样的方式侦查到或被修改。

Java 的类由 Class 对象支持,但两者并不有彻底互换。好比 Foo 类的 Class 对象是 Foo.class。我不认为 Foo 能够被它本身使用,由于它命名了一个类型,Java 在处理类型和值的时候会有一些微妙的区别。

Python 中,类是对象,是类型的实例(它自己就是对象,是它本身的实例,想起来颇有趣。)类能够看成其它值同样使用:做为参数、保存一某个更大的数据结构中、检查、操做。有时候把类做为字典的键特别有用。并且由于类是实例化的,能够简单地调用它们——Python 没有 new 关键字 —— 不少状况下 new 和简单的函数调用能够互换。这样一来,一些常见的模式,好比工厂模式,就太简单了,几乎不须要。

# 等等,Vehicle 是一个类仍是一个工厂函数?谁管呢!

# 就算它在类或工厂函数之间互换,也不会破坏这段代码。

car = Vehicle(wheels=4, doors=4)

最近我好几回把函数甚至常规代码放在顶层,不在任何类里面。这样作不会有问题,可是其含义有点微妙。Python 中甚至 class 和 def 都是常规代码,在运行的时候执行。Python 文件从上往下执行,class 和 def 并不特别。它们只是有特殊的语法,用来建立特定类型的对象:类和函数。

如下是真正酷的部分。类是对象,它们的类型是type,所以你能够子类化并改变它的运行。而后,你能够生成你的子类的实例。

第一次接触仔细想一想会感受有点奇怪。可是再想下,你获益于不须要知道它是如何运行的。好比,Python没有enum块,但它确实有 enum module:

class Animal(Enum):

cat = 0

dog = 1

mouse = 2

snake = 3

print(Animal.cat)           #

print(Animal.cat.value)     # 0

print(Animal(2))            #

print(Animal['dog'])        #

class 语句建立了一个对象,这意味着它在某处调用了构造函数,并且能够重写该构造函数来改变类的构造方式。这里Enum建立了一个固定的实例集,而不是类属性。全部这些都是用普通的 Python 代码的常规的 Python 语法实现的。

实体库也是基于这个思路来构建的。你讨厌在构造函数中单调的干 self.foo = foo 这种事情吗?而后纯手工定义相等性比较、哈希和克隆和开发可读的列表?Java 须要编译器支持,这个支持可能来自 Amber 项目。Python 很是灵活,因此社区中有 attrs 库解决了这个问题。

import attr

@attr.s

class Point:

x = attr.ib()

y = attr.ib()

p = Point(3, 4)

q = Point(x=3, y=4)

p == q  #True, which it wouldn't have been before!

print(p)  # Point(x=3, y=4)

或者采用 SQLAlchemy 这个功能强大的 Python 数据库封装库。它包含一个灵感来自 Hibernate 的 ORM,但不须要在配置文件里定义表结构或者经过其余冗长的注解,你可直接在类里编写数据库映射代码:

classOrder(Table):

id =Column(Integer, primary_key=True)

order_number =Column(Integer,index=True)

status =Column(Enum('pending','complete'),default='pending')

...

它的基本思想类同Enum,但SQLAlchemy也使用和property一样的钩子,天然而然地,你能够修改栏位值。

order.order_number = 5

session.commit()

最后,类自己能够在运行中被建立。 这有点好处,但是 thriftpy 建立的整个 module, 里面全是基于 Thrift 定义文件的类。 在Java中,你得须要代码生成,这就增长了全新的编译步骤,从而致使不一样步。

全部这些示例依赖于Python现存的语法,但也在里面吸取了新的含义。它能作的,没有你在Java或者其余语言中不能作的。但它删减告终构性的重复 — 正是这些使得编码更易写、易读,以及产生更少的bug。

结语

Python 有不少和Java相同的基本概念,但处理方向很是不一样,它加入了一些全新的思想。Java关注于稳定性和可靠性,而Python关注于可表达性和灵活性。它是一种彻底不一样的思想方式来思考命令式编程。

我有理由相信Python将让你在Java所擅长的领域替换Java。Python可能不会赢在速度竞赛上,好比(Pypy,一种即时编译的Python)。Java拥有对线程的原生支持,而Python界大都回避了这些问题。规模庞大复杂又有不少死角的软件更喜欢静态类型提供的合理性检查(好比mypy,一种Python静态类型检查器)。

也许某些 Java 并不擅长的领域恰好是 Python 的强项。例如大量的软件对性能的要求并不高,也不须要并行执行,咱们只须要考虑具体实现业务的问题。我发现使用 Python 开始一个项目很是简单并且快速。没有单独的编译过程,编码而后运行的速度很是快。代码很简短,这也意味着代码更容易被理解。尝试不一样的架构方法看起来更加容易。并且有时候尝试一些愚蠢的想法是如此的有趣,就好像 使用库实现 goto 功能。

我但愿你能试试 Python ,自从我用了它以后,已经从中得到不少乐趣,我相信你也同样。至少不用刻意的去拒绝它。

最坏的状况,还有个 Pyjnius 可让你这样作:

fromjnius import autoclass

System = autoclass('java.lang.System')

System.out.println('Hello, world!')


最后,每一位读到这里的网友,感谢大家能耐心地看完。以为对你有帮助能够给个喜欢!但愿在成为一名更优秀的Java程序员的道路上,咱们能够一块儿学习、一块儿进步

相关文章
相关标签/搜索