编者注:在 Java 频道中,我们大多数人都非常了解该语言,并且已经在其生态系统中工作了至少几年。这给了我们常规和专业知识,但它也引起了一定程度的隧道视野。在Outside-In Java系列中,非 Java 主义者将向我们展示他们对生态系统的看法。

目录

  • 句法
  • 动态类型
  • 动态打字哲学
  • 混合范式
  • 序列
  • 职能
  • 对象和动态运行时
  • 对象
  • 课程
  • 包起来
  • 评论

从哲学上讲,Python 几乎与 Java 截然相反。它放弃了静态类型和刚性结构,取而代之的是一个松散的沙箱,您可以在其中自由地做任何您想做的事情。也许 Python 是关于你可以做什么,而 Java 是关于你可以做什么。

然而,这两种语言仍然有很多可以追溯到 C 的灵感。它们都是具有块、循环、函数、赋值和中缀数学的命令式语言。两者都大量使用类、对象、继承和多态性。两者都具有相当突出的异常。两者都自动处理内存管理。它们甚至都编译为在 VM 上运行的字节码,尽管 Python 为您透明地编译。Python 甚至从 Java 中汲取了一些线索——标准库logging和unittest模块分别受到 log4j 和 JUnit 的启发。

鉴于这种重叠,我认为Java 开发人员应该对 Python 感到宾至如归。所以我带着一些温和的 Python 宣传来找你。如果您给我一个机会,我可以向您展示 Python 与 Java 的不同之处,以及为什么我觉得这些差异很有吸引力。至少,您可能会发现一些有趣的想法可以带回 Java 生态系统。

(如果你想要 Python教程,Python 文档有一个很好的。另外,这是从 Python 3 的角度来看的!Python 2 在野外仍然相当普遍,它有一些语法差异。)

句法

让我们先解决这个问题。这是你好世界:

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">print<span style="color:#c792ea">(</span><span style="color:#c3e88d">"Hello, world!"</span><span style="color:#c792ea">)</span>
</code></span></span>

嗯,嗯,这不是很有启发性。好的,这是一个查找文件中最常用的十个单词的函数。我使用标准库的Counter类型有点作弊,但它真是太好了

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">from collections import Counter

def <span style="color:#82aaff">count_words</span><span style="color:#c792ea">(</span>path<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    words <span style="color:#89ddff">=</span> Counter<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span>
    with <span style="color:#82aaff">open</span><span style="color:#c792ea">(</span>path<span style="color:#c792ea">)</span> as f<span style="color:#c792ea">:</span>
        for line in f<span style="color:#c792ea">:</span>
            for word in line<span style="color:#c792ea">.</span>strip<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">.</span>split<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
                words<span style="color:#c792ea">[</span>word<span style="color:#c792ea">]</span> <span style="color:#89ddff">+=</span> <span style="color:#f78c6c">1</span>

    for word<span style="color:#c792ea">,</span> count in words<span style="color:#c792ea">.</span>most_common<span style="color:#c792ea">(</span><span style="color:#f78c6c">10</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        print<span style="color:#c792ea">(</span><span style="color:#c3e88d">f"</span><span style="color:#c792ea">{</span>word<span style="color:#c792ea">}</span><span style="color:#c3e88d"> x</span><span style="color:#c792ea">{</span>count<span style="color:#c792ea">}</span><span style="color:#c3e88d">"</span><span style="color:#c792ea">)</span>
</code></span></span>

Python 由空格分隔。人们经常对此有强烈的看法。当我第一次看到它时,我什至认为它是异端。现在,大约十年后,这似乎很自然,以至于我很难重新戴上牙套。如果您对此感到厌烦,我怀疑我无法说服您,但我敦促您至少暂时忽略它;它在实践中确实不会引起任何严重的问题,并且它消除了相当多的噪音。另外,Python 开发人员永远不必争论 a{应该去哪里。

如果对Python有兴趣,想了解更多的Python以及AIoT知识,解决测试问题,以及入门指导,帮你解决学习Python中遇到的困惑,我们这里有技术高手。如果你正在找工作或者刚刚学校出来,又或者已经工作但是经常觉得难点很多,觉得自己Python方面学的不够精想要继续学习的,想转行怕学不会的, 都可以加入我们,可领取最新Python大厂面试资料和Python爬虫、人工智能、学习资料!微信公众号【Python大本营】等你来玩奥~

除了这种美学差异之外,其中大部分应该看起来很熟悉。我们有一些数字、一些赋值和一些方法调用。该import声明的工作方式略有不同,但它具有相同的一般含义,即“使这个东西可用”。Python 的循环与 Java 的-each 循环for非常相似,只是标点符号少了一点。for函数本身是用def而不是类型分隔的,但它可以按照您的预期工作:可以使用参数调用它,然后返回一个值(尽管这个没有)。

这里只有两件事非常不寻常。一个是with块,与 Java 7 的“-with-resources”非常相似try——它保证文件将在块的末尾关闭,即使在其中引发异常也是如此。另一个是f"..."语法,这是一个相当新的功能,允许将表达式直接插入字符串。

就是这样!您已经阅读了一些 Python。至少,这不是来自完全不同星球的语言。

动态类型

从该示例中可能很明显,但 Python 代码并没有很多类型。不在变量声明上,不在参数或返回类型上,不在对象的布局上。任何东西在任何时候都可以是任何类型。我还没有显示一个类定义,所以这里是一个微不足道的定义。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">class <span style="color:#ffcb6b">Point</span><span style="color:#c792ea">:</span>
    def <span style="color:#82aaff">__init__</span><span style="color:#c792ea">(</span>self<span style="color:#c792ea">,</span> x<span style="color:#c792ea">,</span> y<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        self<span style="color:#c792ea">.</span>x <span style="color:#89ddff">=</span> x
        self<span style="color:#c792ea">.</span>y <span style="color:#89ddff">=</span> y

    def <span style="color:#82aaff">magnitude</span><span style="color:#c792ea">(</span>self<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        return <span style="color:#c792ea">(</span>self<span style="color:#c792ea">.</span>x <span style="color:#89ddff">**</span> <span style="color:#f78c6c">2</span> <span style="color:#89ddff">+</span> self<span style="color:#c792ea">.</span>y <span style="color:#89ddff">**</span> <span style="color:#f78c6c">2</span><span style="color:#c792ea">)</span> <span style="color:#89ddff">**</span> <span style="color:#f78c6c">0.5</span>

point <span style="color:#89ddff">=</span> Point<span style="color:#c792ea">(</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">)</span>
print<span style="color:#c792ea">(</span>point<span style="color:#c792ea">.</span>x<span style="color:#c792ea">)</span>  <span style="color:#697098"># 3</span>
print<span style="color:#c792ea">(</span>point<span style="color:#c792ea">.</span>magnitude<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">)</span>  <span style="color:#697098"># 5.0</span>
</code></span></span>

甚至xandy也没有被声明为属性;它们之所以存在,是因为构造函数创建了它们。没有什么强迫我传入整数。我本可以传入浮点数,或者可能是Decimals或Fractions。

如果您只使用静态语言,这可能听起来很混乱。类型温暖、舒适和舒适。他们保证......好吧,也许不是代码真的有效(尽管有些人不同意),但有些东西。当你甚至不知道任何东西是正确的类型时,你怎么能依赖代码呢?

但是等等——Java 也没有这样的保证!毕竟,任何物体都可能是null,对吧?这几乎从来都不是正确类型的对象。

您可能会认为动态类型是对问题的完全投降null。如果我们无论如何都必须处理它,我们不妨接受它并让它为我们工作——通过推迟一切运行时间。类型错误变成了正常的逻辑错误,你以同样的方式处理它们。

(对于相反的方法,请参阅Rust,它没有null价值或异常。我仍然更愿意编写 Python,但我很欣赏 Rust 的类型系统并不总是悄悄地对我撒谎。)

在我的方法中, int 或 float 或任何类型的数字都magnitude无关紧要。self.x它只需要支持**运营商,并返回支持+运营商的东西。(Python 支持运算符重载,所以这可能是任何东西。)这同样适用于普通的方法调用:任何类型都是可接受的,只要它在实践中有效。

这意味着 Python 不需要泛型。一切都已经正常工作。无需接口;一切都已经是多态的。在类型系统中没有向下转换,没有向上转换,没有逃生舱口。不会遇到需要List何时可以与任何Iterable.

许多常见的模式变得容易得多。您可以创建包装对象和代理,而无需更改使用代码。你可以使用组合而不是继承来扩展第三方类型——不需要做任何特殊的事情来保持多态性。灵活的 API 不需要将每个类都复制为接口;一切都已经充当隐式接口。

动态打字哲学

使用静态类型,任何编写代码的人都可以选择类型,编译器会检查它们是否可以工作。使用动态类型,任何使用一些代码的人都可以选择类型,运行时会试一试。这就是相反的哲学:类型系统关注的是你能做什么,而不是你可以做什么。

以这种方式使用动态类型有时被称为“鸭子类型”,意思是“如果它像鸭子一样走路并且它像鸭子一样嘎嘎叫,那就是鸭子”。这个想法是,如果你想要的只是嘎嘎声,那么不要静态地强制你的代码必须接收鸭子,你可以拿走你得到的任何东西并让它嘎嘎。如果是这样,无论如何,这就是你所关心的,所以它和鸭子一样好。(如果不能,你会得到一个AttributeError,但这不是很有力。)

请注意,Python 仍然是类型的。这个术语有点模糊,但它通常意味着值在运行时保留它们的类型。典型的例子是 Python 不允许您将字符串添加到数字中,而像 JavaScript 这样的弱类型语言会使用可能不符合您期望的优先级规则默默地将一种类型转换为另一种类型。

与许多动态语言不同,Python 倾向于尽早发现错误——无论如何,在运行时。例如,从尚不存在的变量读取Map将引发异常,从 dict (如 a )读取不存在的键也会引发异常。在 JavaScript、Lua 和类似的语言中,你会null在这两种情况下默默地得到一个值。(即使是 Java 也会null因为缺少Map键而返回!)如果你回退到默认值,dicts 有方法可以更明确地表达它。

这里肯定有一个权衡,是否值得因项目和个人而异。至少对我来说,在我看到一个系统的实际设计后,它更容易确定一个系统的设计,但是静态类型的语言需要预先设计。静态类型使得尝试很多不同的想法变得更难,更难

你确实有较少的静态保证,但根据我的经验,大多数类型错误都会立即被捕获......因为我在编写一些代码后做的第一件事就是尝试运行它!其他任何东西都应该被你的测试捕捉到——你应该用任何语言编写,而 Python 使得它相对容易。

混合范式

Python 和 Java 都是命令式的和面向对象的:它们通过执行指令来工作,并将一切都建模为对象。

在最近的版本中,Java 已经添加了一些功能特性,我认为这非常欢呼。Python 也有相当多的功能特性,但是……方法有些不同。map它提供了一些像and这样的令牌内置reduce函数,但它并不是真正围绕将许多小函数链接在一起的想法而设计的。

相反,Python 混入了……别的东西。我不知道 Python 采用的方法有什么通用名称。我想它将“链接函数”的想法分为两部分:使用序列,并使函数本身更强大。

序列

序列和迭代在 Python 中扮演着重要的角色。序列可以说是最基本的数据结构,因此使用它们的工具非常好。我将此解释为 Python 对函数式编程的替代方案:Python 不是更容易组合许多小函数,然后将它们应用于序列,而是首先使用命令式代码更容易操作序列。

回到开始的时候,我随便掉进了这行:

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">    for word<span style="color:#c792ea">,</span> count in words<span style="color:#c792ea">.</span>most_common<span style="color:#c792ea">(</span><span style="color:#f78c6c">10</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
</code></span></span>

循环很熟悉,但这段代码一次for迭代两个变量。实际发生的是列表中的每个元素都most_common返回一个元组,一组按顺序区分的值。元组可以通过将它们分配给变量名称的元组来解包,这就是这里真正发生的事情。元组通常用于在 Python 中返回多个值,但它们有时在临时结构中也很有用。在 Java 中,您需要一个完整的类和几行分配内容。

任何可以迭代的东西也可以被解包。解包支持任意嵌套,a, (b, c) = ...它的样子也是如此。对于未知长度的序列,一个*leftovers元素可以出现在任何地方,并且会根据需要吸收尽可能多的元素。也许你真的很喜欢 LISP?

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">values <span style="color:#89ddff">=</span> <span style="color:#c792ea">[</span><span style="color:#f78c6c">5</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">7</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">9</span><span style="color:#c792ea">]</span>
head<span style="color:#c792ea">,</span> <span style="color:#89ddff">*</span>tail <span style="color:#89ddff">=</span> values
print<span style="color:#c792ea">(</span>head<span style="color:#c792ea">)</span>  <span style="color:#697098"># 5</span>
print<span style="color:#c792ea">(</span>tail<span style="color:#c792ea">)</span>  <span style="color:#697098"># (7, 9)</span>
</code></span></span>

Python 还具有从简单表达式创建列表的语法——所谓的“列表推导式”——这比函数式方法(如map. 存在用于创建 dicts 和 set 的类似语法。整个循环可以简化为一个强调您真正感兴趣的内容的表达式。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">values <span style="color:#89ddff">=</span> <span style="color:#c792ea">[</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">5</span><span style="color:#c792ea">]</span>
values2 <span style="color:#89ddff">=</span> <span style="color:#c792ea">[</span>val <span style="color:#89ddff">*</span> <span style="color:#f78c6c">2</span> for val in values if val <span style="color:#89ddff">!=</span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">]</span>
print<span style="color:#c792ea">(</span>values2<span style="color:#c792ea">)</span>  <span style="color:#697098"># [6, 10]</span>
</code></span></span>

标准库还在itertools模块中包含许多有趣的迭代器、组合器和配方。

最后,Python 具有使用命令式代码生成惰性序列的生成器。包含yield关键字的函数在调用时不会立即执行;相反,它返回一个生成器对象。当生成器被迭代时,函数会一直运行,直到遇到 a yield,此时它会暂停; 产生的值成为下一个迭代值。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">def <span style="color:#82aaff">odd_numbers</span><span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    n <span style="color:#89ddff">=</span> <span style="color:#f78c6c">1</span>
    while <span style="color:#ff5874">True</span><span style="color:#c792ea">:</span>
        yield n
        n <span style="color:#89ddff">+=</span> <span style="color:#f78c6c">2</span>

for x in odd_numbers<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    print<span style="color:#c792ea">(</span>x<span style="color:#c792ea">)</span>
    if x <span style="color:#89ddff">></span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">:</span>
        break
<span style="color:#697098"># 1</span>
<span style="color:#697098"># 3</span>
<span style="color:#697098"># 5</span>
</code></span></span>

由于生成器运行延迟,它们可以产生无限序列或中途中断。它们可以同时生成大量大型对象,而不会消耗大量内存。它们还可以作为“链式”函数式编程的一般替代方案。您可以编写熟悉的命令式代码,而不是组合映射和过滤器。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python"><span style="color:#697098"># This is the pathlib.Path API from the standard library</span>
def <span style="color:#82aaff">iter_child_filenames</span><span style="color:#c792ea">(</span>dirpath<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    for child in dirpath<span style="color:#c792ea">.</span>iterdir<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        if child<span style="color:#c792ea">.</span>is_file<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
            yield child<span style="color:#c792ea">.</span>name
</code></span></span>

要在 Java 中表达一个完全任意的惰性迭代器,您需要编写一个Iterator手动跟踪其状态的迭代器。对于除了最简单的情况之外的所有情况,这可能会变得非常棘手。Python 也有一个迭代接口,所以你仍然可以使用这种方法,但是生成器非常易于使用,大多数自定义迭代都是用它们编写的。

由于生成器可以自行暂停,因此它们在其他一些情况下也很有用。通过手动推进生成器(而不是仅仅通过循环一次迭代for),可以在中途运行一个函数,让它在某个点停止,并在恢复函数之前运行其他代码。Python 利用这一点纯粹作为一个库添加了对异步 I/O(没有线程的非阻塞网络)的支持,尽管现在它具有专用asyncawait语法。

职能

乍一看,Python 函数非常熟悉。您可以使用参数调用它们。传递风格与 Java 中的完全一样——Python 既没有引用也没有隐式复制。Python 甚至有“文档字符串”,类似于 Javadoc 注释,但内置在语法中并且在运行时可读。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">def <span style="color:#82aaff">foo</span><span style="color:#c792ea">(</span>a<span style="color:#c792ea">,</span> b<span style="color:#c792ea">,</span> c<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    <span style="color:#c3e88d">"""Print out the arguments.  Not a very useful function, really."""</span>
    print<span style="color:#c792ea">(</span><span style="color:#c3e88d">"I got"</span><span style="color:#c792ea">,</span> a<span style="color:#c792ea">,</span> b<span style="color:#c792ea">,</span> c<span style="color:#c792ea">)</span>

foo<span style="color:#c792ea">(</span><span style="color:#f78c6c">1</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">2</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">3</span><span style="color:#c792ea">)</span>  <span style="color:#697098"># I got 1 2 3</span>
</code></span></span>

Java 有带args...语法的可变参数函数;Python 使用*args. (*leftovers解包的语法受到函数语法的启发。)但是 Python 还有一些技巧。任何参数都可以有一个默认值,使其成为可选的。任何参数也可以通过名称给出——我之前用Point(x=3, y=4)调用*args任何函数时都可以使用该语法来传递序列,就好像它是单独的参数一样,并且有一个等价物可以接受或传递命名参数作为 dict。可以将参数设置为“仅关键字”,因此必须按名称传递,这对于可选布尔值非常好。**kwargs

当然, Python 没有函数重载,但您使用它的大部分内容都可以替换为鸭子类型和可选参数。

现在已经为 Python 最强大的功能之一做好了准备。与动态类型非常相似,您可以通过包装器或代理透明地替换对象,*args**kwargs允许透明地包装任何函数

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">def <span style="color:#82aaff">log_calls</span><span style="color:#c792ea">(</span>old_function<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    def <span style="color:#82aaff">new_function</span><span style="color:#c792ea">(</span><span style="color:#89ddff">*</span>args<span style="color:#c792ea">,</span> <span style="color:#89ddff">**</span>kwargs<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        print<span style="color:#c792ea">(</span><span style="color:#c3e88d">"i'm being called!"</span><span style="color:#c792ea">,</span> args<span style="color:#c792ea">,</span> kwargs<span style="color:#c792ea">)</span>
        return old_function<span style="color:#c792ea">(</span><span style="color:#89ddff">*</span>args<span style="color:#c792ea">,</span> <span style="color:#89ddff">**</span>kwargs<span style="color:#c792ea">)</span>

    return new_function

<span style="color:#c792ea">@log_calls</span>
def <span style="color:#82aaff">foo</span><span style="color:#c792ea">(</span>a<span style="color:#c792ea">,</span> b<span style="color:#c792ea">,</span> c<span style="color:#89ddff">=</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    print<span style="color:#c792ea">(</span><span style="color:#c3e88d">f"a = </span><span style="color:#c792ea">{</span>a<span style="color:#c792ea">}</span><span style="color:#c3e88d">, b = </span><span style="color:#c792ea">{</span>b<span style="color:#c792ea">}</span><span style="color:#c3e88d">, c = </span><span style="color:#c792ea">{</span>c<span style="color:#c792ea">}</span><span style="color:#c3e88d">"</span><span style="color:#c792ea">)</span>

foo<span style="color:#c792ea">(</span><span style="color:#f78c6c">1</span><span style="color:#c792ea">,</span> b<span style="color:#89ddff">=</span><span style="color:#f78c6c">2</span><span style="color:#c792ea">)</span>
<span style="color:#697098"># i'm being called! (1,) {'b': 2}</span>
<span style="color:#697098"># a = 1, b = 2, c = 3</span>
</code></span></span>

有点浓,不好意思。不要太担心它是如何工作的;要点是它foo被替换为 a new_function,它将所有参数转发到foo。调用者和调用者都不foo需要知道有什么不同。

我不能低估这是多么强大。它可用于记录、调试、管理资源、缓存、访问控制、验证等。它与其他元编程功能配合得非常好,并且以类似的方式,它可以让您分解结构而不仅仅是代码。

对象和动态运行时

动态运行时是一种运行时——支持语言核心部分的幕后东西——可以运行时使用。像 C 或 C++ 这样的语言非常没有动态运行时;源代码的结构被“烘焙”到编译输出中,以后没有明智的方法来改变它的行为。另一方面,Java确实具有动态运行时!它甚至还附带了一个专门用于反射的包。

当然,Python 也有反射。内置了许多简单的函数,用于动态检查或修改对象的属性,这对于调试和偶尔的恶作剧非常有用。

但是 Python 更进一步。由于无论如何一切都是在运行时完成的,Python 公开了许多扩展点来自定义其语义。您无法更改语法,因此代码仍然看起来像 Python,但您通常可以分解结构——这在更严格的语言中很难做到。

举个极端的例子,看看pytest,它用 Python 的assert语句做了非常聪明的事情。通常,写作assert x == 1只会AssertionError在错误时抛出一个错误,让您没有关于哪里出错或哪里出错的上下文。这就是为什么 Python 的内置unittest模块(如 JUnit 和许多其他测试工具)提供了一堆特定的实用功能,如assertEquals. 不幸的是,这些使测试变得更加冗长且难以一目了然。但是使用 pytestassert x == 1就可以了。如果失败,pytest 会告诉你什么x是……或者两个列表在哪里分歧,或者两个集合之间有什么不同的元素,或者你有什么。所有这些都是根据正在进行的比较和操作数的类型自动发生的。

pytest 是如何工作的?你真的不想知道。而且您不必知道如何使用 pytest 编写测试——并且可以尽情享受。

这是动态运行时的真正优势。您个人可能无法使用这些功能。但是你可以从使用它们的库中获得巨大的好处,而不用关心它们是如何工作的。甚至 Python 本身也使用自己的扩展点实现了许多额外的功能——不需要对语法或解释器进行任何更改。

对象

我最喜欢的简单示例是属性访问。在 Java 中,Point类可能会选择getX()setX()方法而不是普通x属性。理由是,如果您需要更改x读取或写入的方式,您可以在不破坏界面的情况下这样做。在 Python 中,您无需预先担心这一点,因为您可以在必要时拦截属性访问。

如果对Python有兴趣,想了解更多的Python以及AIoT知识,解决测试问题,以及入门指导,帮你解决学习Python中遇到的困惑,我们这里有技术高手。如果你正在找工作或者刚刚学校出来,又或者已经工作但是经常觉得难点很多,觉得自己Python方面学的不够精想要继续学习的,想转行怕学不会的, 都可以加入我们,可领取最新Python大厂面试资料和Python爬虫、人工智能、学习资料!微信公众号【Python大本营】等你来玩奥~

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">class <span style="color:#ffcb6b">Point</span><span style="color:#c792ea">:</span>
    def <span style="color:#82aaff">__init__</span><span style="color:#c792ea">(</span>self<span style="color:#c792ea">,</span> x<span style="color:#c792ea">,</span> y<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        self<span style="color:#c792ea">.</span>_x <span style="color:#89ddff">=</span> x
        self<span style="color:#c792ea">.</span>_y <span style="color:#89ddff">=</span> y

    <span style="color:#c792ea">@property</span>
    def <span style="color:#82aaff">x</span><span style="color:#c792ea">(</span>self<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
        return self<span style="color:#c792ea">.</span>_x

    <span style="color:#697098"># ... same for y ...</span>

point <span style="color:#89ddff">=</span> Point<span style="color:#c792ea">(</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">)</span>
print<span style="color:#c792ea">(</span>point<span style="color:#c792ea">.</span>x<span style="color:#c792ea">)</span>  <span style="color:#697098"># 3</span>
</code></span></span>

有趣的@property语法是装饰器,它看起来像 Java 注释,但可以更直接地修改函数或类。

Readingpoint.x现在调用一个函数并计算它的返回值。这对调用代码是完全透明的——并且与读取的任何其他属性没有区别——但是对象可以随意干预和处理它。与 Java 不同,属性访问是类 API 的一部分,可以自由定制。(请注意,这个例子也是x只读的,因为我没有指定如何写入它!可写的语法property看起来有点滑稽,它的工作方式在这里并不重要。但你可以微不足道,比如说,强制只能将奇数分配给point.x.)

类似的特性存在于其他静态语言(如 C#)中,所以这可能不是那么令人印象深刻。关于 Python 真正有趣的部分是它property一点也不特别。它是一种普通的内置类型,可以用不到一屏纯 Python 编写。它之所以有效,是因为 Python 类可以自定义其自己的属性访问,包括一般属性访问和每个属性访问。包装器、代理和组合很容易实现:您可以将所有方法调用转发到底层对象,而不必知道它有什么方法。

相同的钩子property可以用于延迟加载属性或自动保存弱引用的属性——对调用代码完全透明,并且全部来自纯 Python。

您现在可能已经注意到我的代码没有publicorprivate修饰符,而且 Python 确实没有这样的概念。按照惯例,单个前导下划线用于表示“私有”——或者更准确地说,“不打算作为稳定公共 API 的一部分”。但这没有语义意义,Python 本身不会阻止任何人检查或更改这样的属性(或调用它,如果它是一个方法)。不finalstaticconst, 要么。

这与工作原理相同:核心 Python 通常不会阻止您做任何事情。当你需要它时,它非常有用。我通过在启动时调用或覆盖甚至彻底重新定义私有方法来修补第三方库中的错误。它使我不必创建项目的整个本地分支,一旦上游修复了错误,我只需删除我的补丁代码。

类似地,您可以轻松地为依赖于外部状态(例如当前时间)的代码编写测试。如果重构不切实际,您可以time.time()在测试期间用虚拟函数替换。库函数只是模块的属性(如 Java 包),而 Python 模块与其他任何东西一样都是对象,因此可以以相同的方式检查和修改它们。

课程

Java 类由一个Class对象支持,但两者并不是完全可以互换的。对于一个类Foo,类对象是Foo.class. 我不认为Foo可以单独使用它,因为它命名了一个type,而 Java 在类型和值之间做了一些微妙的区分。

在 Python 中,一个类是一个对象,一个实例type(它本身就是一个对象,因此它本身就是一个实例,这很有趣。)因此可以像对待任何其他值一样对待类:作为参数传递,存储在更大的结构,检查或操纵。制作键是类的字典的能力有时特别有用。而且因为类是通过调用它们来实例化的——Python 没有new关键字——在很多情况下它们可以与简单的函数互换。像工厂这样的一些常见模式非常简单,几乎消失了。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python"><span style="color:#697098"># Wait, is Vehicle a class or a factory function?  Who cares!</span>
<span style="color:#697098"># It could even be changed from one to the other without breaking this code.</span>
car <span style="color:#89ddff">=</span> Vehicle<span style="color:#c792ea">(</span>wheels<span style="color:#89ddff">=</span><span style="color:#f78c6c">4</span><span style="color:#c792ea">,</span> doors<span style="color:#89ddff">=</span><span style="color:#f78c6c">4</span><span style="color:#c792ea">)</span>
</code></span></span>

现在有好几次,我将函数甚至常规代码放在任何类之外的顶层。这是允许的,但含义有点微妙。在 Python 中,甚至classand语句都是在运行时def执行的常规代码。Python 文件自上而下执行,并且在这方面并不特殊。它们只是用于创建某些类型的对象的特殊语法:类和函数。classdef

这是非常酷的部分。因为类是对象,并且它们的类型是type,所以您可以子类 type化并更改它的工作方式。然后,您可以创建作为子类实例的类。

一开始把你的头绕起来有点奇怪。但同样,你不需要知道它是如何从中受益的。例如,Python 没有enum块,但它确实有一个enum 模块:

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">class <span style="color:#ffcb6b">Animal</span><span style="color:#c792ea">(</span>Enum<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    cat <span style="color:#89ddff">=</span> <span style="color:#f78c6c">0</span>
    dog <span style="color:#89ddff">=</span> <span style="color:#f78c6c">1</span>
    mouse <span style="color:#89ddff">=</span> <span style="color:#f78c6c">2</span>
    snake <span style="color:#89ddff">=</span> <span style="color:#f78c6c">3</span>

print<span style="color:#c792ea">(</span>Animal<span style="color:#c792ea">.</span>cat<span style="color:#c792ea">)</span>           <span style="color:#697098"># <Animal.cat: 0></span>
print<span style="color:#c792ea">(</span>Animal<span style="color:#c792ea">.</span>cat<span style="color:#c792ea">.</span>value<span style="color:#c792ea">)</span>     <span style="color:#697098"># 0</span>
print<span style="color:#c792ea">(</span>Animal<span style="color:#c792ea">(</span><span style="color:#f78c6c">2</span><span style="color:#c792ea">)</span><span style="color:#c792ea">)</span>            <span style="color:#697098"># <Animal.mouse: 2></span>
print<span style="color:#c792ea">(</span>Animal<span style="color:#c792ea">[</span><span style="color:#c3e88d">'dog'</span><span style="color:#c792ea">]</span><span style="color:#c792ea">)</span>        <span style="color:#697098"># <Animal.dog: 1></span>
</code></span></span>

class语句创建了一个对象,这意味着它在某个地方调用了一个构造函数,并且可以重写该构造函数以更改类的构建方式。在这里,Enum创建一组固定的实例而不是类属性。所有这些都是用纯 Python 代码和普通 Python 语法实现的。

整个图书馆都建立在这些想法之上。您是否讨厌self.foo = foo为构造函数中的每个属性键入繁琐的内容?然后手动定义相等、散列和克隆以及开发人员可读的表示?Java 需要编译器支持,这可能与Project Amber一起提供。Python 足够灵活,社区用attrs库解决了这个问题。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">import attr

<span style="color:#c792ea">@attr<span style="color:#c792ea">.</span>s</span>
class <span style="color:#ffcb6b">Point</span><span style="color:#c792ea">:</span>
    x <span style="color:#89ddff">=</span> attr<span style="color:#c792ea">.</span>ib<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span>
    y <span style="color:#89ddff">=</span> attr<span style="color:#c792ea">.</span>ib<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span>

p <span style="color:#89ddff">=</span> Point<span style="color:#c792ea">(</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">,</span> <span style="color:#f78c6c">4</span><span style="color:#c792ea">)</span>
q <span style="color:#89ddff">=</span> Point<span style="color:#c792ea">(</span>x<span style="color:#89ddff">=</span><span style="color:#f78c6c">3</span><span style="color:#c792ea">,</span> y<span style="color:#89ddff">=</span><span style="color:#f78c6c">4</span><span style="color:#c792ea">)</span>
p <span style="color:#89ddff">==</span> q  <span style="color:#697098"># True, which it wouldn't have been before!</span>
print<span style="color:#c792ea">(</span>p<span style="color:#c792ea">)</span>  <span style="color:#697098"># Point(x=3, y=4)</span>
</code></span></span>

或者使用SQLAlchemy,这是一个用于 Python 的功能强大的数据库库。它包含一个受 Java 的Hibernate启发的 ORM ,但不是在配置文件中或通过一些冗长的注释声明表的模式,您可以直接将其编写为一个类:

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">class <span style="color:#ffcb6b">Order</span><span style="color:#c792ea">(</span>Table<span style="color:#c792ea">)</span><span style="color:#c792ea">:</span>
    <span style="color:#82aaff">id</span> <span style="color:#89ddff">=</span> Column<span style="color:#c792ea">(</span>Integer<span style="color:#c792ea">,</span> primary_key<span style="color:#89ddff">=</span><span style="color:#ff5874">True</span><span style="color:#c792ea">)</span>
    order_number <span style="color:#89ddff">=</span> Column<span style="color:#c792ea">(</span>Integer<span style="color:#c792ea">,</span> index<span style="color:#89ddff">=</span><span style="color:#ff5874">True</span><span style="color:#c792ea">)</span>
    status <span style="color:#89ddff">=</span> Column<span style="color:#c792ea">(</span>Enum<span style="color:#c792ea">(</span><span style="color:#c3e88d">'pending'</span><span style="color:#c792ea">,</span> <span style="color:#c3e88d">'complete'</span><span style="color:#c792ea">)</span><span style="color:#c792ea">,</span> default<span style="color:#89ddff">=</span><span style="color:#c3e88d">'pending'</span><span style="color:#c792ea">)</span>
    <span style="color:#c792ea">.</span><span style="color:#c792ea">.</span><span style="color:#c792ea">.</span>
</code></span></span>

这与 基本思想相同Enum,但 SQLAlchemy 也使用相同的钩子,property因此您可以自然地修改列值。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">order<span style="color:#c792ea">.</span>order_number <span style="color:#89ddff">=</span> <span style="color:#f78c6c">5</span>
session<span style="color:#c792ea">.</span>commit<span style="color:#c792ea">(</span><span style="color:#c792ea">)</span>
</code></span></span>

最后,类本身可以在运行时创建。它有点小众,但thriftpy创建了一个完整的模块,其中包含基于Thrift定义文件的类。在 Java 中,您需要代码生成,这会添加一个可能会不同步的全新编译步骤。

所有这些示例都依赖于 Python 现有的语法,但却为其注入了新的意义。它们都没有任何你用 Java 或任何其他语言做不到的事情,但它们减少了结构重复——这使得代码更容易编写、更容易阅读并且不易出错。如果对Python有兴趣,想了解更多的Python以及AIoT知识,解决测试问题,以及入门指导,帮你解决学习Python中遇到的困惑,我们这里有技术高手。如果你正在找工作或者刚刚学校出来,又或者已经工作但是经常觉得难点很多,觉得自己Python方面学的不够精想要继续学习的,想转行怕学不会的, 都可以加入我们,可领取最新Python大厂面试资料和Python爬虫、人工智能、学习资料!微信公众号【Python大本营】等你来玩奥~

包起来

Python 有许多与 Java 相同的基本概念,但将它们引向了一个非常不同的方向,并添加了一些全新的想法。Java 侧重于稳定性和可靠性,而 Python 侧重于表现力和灵活性。这是考虑命令式编程的一种完全不同的方式。

我怀疑 Python 会在 Java 擅长的领域取代 Java。例如,Python 可能不会赢得任何速度竞赛(但请参阅PyPy,一个 JITted Python)。Java 对线程有原生支持,而 Python 社区在很大程度上避开了它们。带有很多尘土飞扬的角落的大型复杂软件可能更喜欢静态类型提供的健全性检查(但请参阅mypy,Python 的静态类型检查器)。

但也许 Python 会在 Java 没有的领域大放异彩。大量软件不需要特别快或并行,然后其他问题就会浮出水面。我发现用 Python 开始一个项目非常快速和容易。没有单独的编译步骤,写/运行循环要快得多。代码更短,这通常意味着更容易理解。尝试不同的架构方法感觉更便宜。有时尝试一些愚蠢的想法很有趣,比如用库实现 goto。

我希望你能尝试一下 Python。我玩得很开心,我想你也会的。只是尽量不要把它当作 Java 来对待,所有类型都对你隐藏。

最坏的情况,总是有Pyjnius,它可以让你做到这一点。

<span style="background-color:#292d3e"><span style="color:#bfc7d5"><code class="language-python">from jnius import autoclass

System <span style="color:#89ddff">=</span> autoclass<span style="color:#c792ea">(</span><span style="color:#c3e88d">'java.lang.System'</span><span style="color:#c792ea">)</span>
System<span style="color:#c792ea">.</span>out<span style="color:#c792ea">.</span>println<span style="color:#c792ea">(</span><span style="color:#c3e88d">'Hello, world!'</span><span style="color:#c792ea">)</span></code></span></span>

更多推荐

面向 Java 人员的 Python