适合Java开发者学习的Python入门教程
作者:网络转载 发布时间:[ 2017/7/4 10:28:57 ] 推荐标签:测试开发技术 Java Python
在Java文章诸多里,我们大部分人应该对该语言都非常的了解,而且在该生态圈内至少已经呆了好几年了。这让我们有常规和专业的知识,但是也同时也让我们一些井蛙之见。 在Outside-In Java系列文章中,一些非Java开发人员会给我们讲讲他们对于我们这个生态圈的看法。
从哲学的角度来讲,Python几乎是与Java截然相反。它抛弃了静态类型和刚性结构,而是使用了一个松散的沙盒,在这里面你可以自由的做任何你想做的事情。也许Python是关于你能够做什么,而Java则是关于你可以做什么。
然而,两种语言都从C语言里吸取了大量的灵感。他们都是命令式语言,拥有块、循环、方法、赋值以及中缀算术(infix math)。两者都大量使用了类、对象、继承以及多态性。两者的功能异常都相当。 两者都能自动管理内存。它们都是编译成可以运行在某种虚拟机上的字节码,尽管Python是透明的进行编译。 Python甚至从Java汲取了一些营养:比如基本库的 logging 和 unittest 模块分别是受到了log4j 和JUnit的启发。
鉴于以上的技术重叠,我认为Java开发人员在使用Python时理应感到宾至如归。 所以我来给你一些简单的Python介绍。 我会可以告诉你什么使得Python与Java不同,以及为什么我觉得这些差异有吸引力。 至少,您可能会发现一些有趣的想法使您回到Java生态系统。
(如果你想要一个Python教程,Python文档是一个很好的选择,而且这是从Python 3的角度编写的,Python 2的使用还是很常见的,它与Python 3有一些语法上的差异。
语法
我们先把语法弄明白。下面是 hello world入门程序:
print("Hello, world!")
嗯, 并不是很有启发性。 好吧,再来看一个函数,看看如何在一个文件中找到常见的10个单词。在这里我取巧使用了标准库的 Counter 类型,但是它是这么的好用。
from collections import Counter
def count_words(path):
words = Counter()
with open(path) as f:
for line in f:
for word in line.strip().split():
words[word] += 1
for word, count in words.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首先使得使用命令式代码来操作序列非常容易,而不是使得结合许多小函数然后应用于序列非常容易。
回到本文开始的地方,我曾写下这一行代码:
for word, count in words.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 * 2 for val in values if val != 4]
print(values2) # [6, 10]
标准库还在itertools模块中包含了许多有趣的迭代,组合器和用法。
后,Python具有用于生成命令行代码的延迟序列的生成器。 包含yield关键字的函数在被调用时不立即执行; 而是返回一个生成器对象。 当生成器迭代结束时,该函数运行直到它遇到一个yield,此时它暂停; 生成的值将成为下一个迭代值。
def odd_numbers():
n = 1
while True:
yield n
n += 2
for x in odd_numbers():
print(x)
if x > 4:
break
# 1
# 3
# 5
因为生成器延迟运行,它们可以产生无限序列或在中途中断。 他们可以产生大量的大型对象,不会因为让它们全部存活而消耗一大堆内存。 它们也可以作为“链式”函数编程风格的一般替代。 您可以编写熟悉的命令行代码,而不是组合map和filter。
# This is the pathlib.Path API from the standard library
def iter_child_filenames(dirpath):
for child in dirpath.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允许任何函数 被透明地包装。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11