标签 python 下的文章

在 Python 中有许多库可以很容易地测试、转换和读取日期和时间信息。

这篇文章是与 Jeff Triplett 一起合写的。

曾几何时,我们中的一个人(Lacey)盯了一个多小时的 Python 文档中描述日期和时间格式化字符串的表格。当我试图编写从 API 中将日期时间字符串转换为 Python datetime 对象时,我很难理解其中的特定部分,因此我决定请求帮助。

有人问道:“为什么你不使用 dateutil 呢?”

读者,如果你没有从这个月的 Python 专栏中获得任何东西,只是学习到有比 datetime 的 strptime 更容易地将 datetime 字符串转换为 datetime 对象的方法,那么我们觉得就已经成功了。

但是,除了将字符串转换为更有用的 Python 对象之外,还有许多库都有一些有用的方法和工具,可以让您更轻松地进行时间测试、将时间转换为不同的时区、以人类可读的格式传递时间信息,等等。如果这是你在 Python 中第一次接触日期和时间,请暂停并阅读 如何使用 Python的日期和时间 。要理解为什么在编程中处理日期和时间是困难的,请阅读 愚蠢的程序员相信时间

这篇文章将会向你介绍以下库:

随意跳过那些你已经熟悉的库,专注于那些对你而言是新的库。

内建的 datetime 模块

在跳转到其他库之前,让我们回顾一下如何使用 datetime 模块将日期字符串转换为 Python datetime 对象。

假设我们从 API 接受到一个日期字符串,并且需要它作为 Python datetime 对象存在:

2018-04-29T17:45:25Z

这个字符串包括:

  • 日期是 YYYY-MM-DD 格式的
  • 字母 T 表示时间即将到来
  • 时间是 HH:II:SS 格式的
  • 表示此时间的时区指示符 Z 采用 UTC (详细了解日期时间字符格式

要使用 datetime 模块将此字符串转换为 Python datetime 对象,你应该从 strptime 开始。 datetime.strptime 接受日期字符串和格式化字符并返回一个 Python datetime 对象。

我们必须手动将日期时间字符串的每个部分转换为 Python 的 datetime.strptime 可以理解的合适的格式化字符串。四位数年份由 %Y 表示,两位数月份是 %m,两位数的日期是 %d。在 24 小时制中,小时是 %H,分钟是 %M,秒是 %S

为了得出这些结论,需要在Python 文档的表格中多加注意。

由于字符串中的 Z 表示此日期时间字符串采用 UTC,所以我们可以在格式中忽略此项。(现在,我们不会担心时区。)

转换的代码是这样的:

$ from datetime import datetime 
$ datetime.strptime('2018-04-29T17:45:25Z', '%Y-%m-%dT%H:%M:%SZ')
datetime.datetime(2018, 4, 29, 17, 45, 25)

格式字符串很难阅读和理解。我必须手动计算原始字符串中的字母 T 和 “Z”的位置,以及标点符号和格式化字符串,如 %S%m。有些不太了解 datetime 的人阅读我的代码可能会发现它很难理解,尽管其含义已有文档记载,但它仍然很难阅读。

让我们看看其他库是如何处理这种转换的。

Dateutil

dateutil 模块datetime 模块做了一些扩展。

继续使用上面的解析示例,使用 dateutil 实现相同的结果要简单得多:

$ from dateutil.parser import parse
$ parse('2018-04-29T17:45:25Z')
datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=tzutc())

如果字符串包含时区,那么 dateutil 解析器会自动返回字符串的时区。由于我们在 UTC 时区,你可以看到返回来一个 datetime 对象。如果你想解析完全忽略时区信息并返回原生的 datetime 对象,你可以传递 ignoretz=True 来解析,如下所示:

$ from dateutil.parser import parse
$ parse('2018-04-29T17:45:25Z', ignoretz=True)
datetime.datetime(2018, 4, 29, 17, 45, 25)

dateutil 还可以解析其他人类可读的日期字符串:

$ parse('April 29th, 2018 at 5:45 pm')
datetime.datetime(2018, 4, 29, 17, 45)

dateutil 还提供了像 relativedelta 的工具,它用于计算两个日期时间之间的时间差或向日期时间添加或删除时间,rrule 创建重复日期时间,tz 用于解决时区以及其他工具。

Arrow

Arrow 是另一个库,其目标是操作、格式化,以及处理对人类更友好的日期和时间。它包含 dateutil,根据其文档,它旨在“帮助你使用更少的包导入和更少的代码来处理日期和时间”。

要返回我们的解析示例,下面介绍如何使用 Arrow 将日期字符串转换为 Arrow 的 datetime 类的实例:

$ import arrow 
$ arrow.get('2018-04-29T17:45:25Z')
<Arrow [2018-04-29T17:45:25+00:00]>

你也可以在 get() 的第二个参数中指定格式,就像使用 strptime 一样,但是 Arrow 会尽力解析你给出的字符串,get() 返回 Arrow 的 datetime 类的一个实例。要使用 Arrow 来获取 Python datetime 对象,按照如下所示链式 datetime:

$ arrow.get('2018-04-29T17:45:25Z').datetime
datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=tzutc())

通过 Arrow datetime 类的实例,你可以访问 Arrow 的其他有用方法。例如,它的 humanize() 方法将日期时间翻译成人类可读的短语,就像这样:

$ import arrow
$ utc = arrow.utcnow()
$ utc.humanize()
'seconds ago'

在 Arrow 的文档中阅读更多关于其有用方法的信息。

Moment

Moment 的作者认为它是“内部测试版”,但即使它处于早期阶段,它也是非常受欢迎的,我们想来讨论它。

Moment 的方法将字符转换为其他更有用的东西很简单,类似于我们之前提到的库:

$ import moment
$ moment.date('2018-04-29T17:45:25Z')
<Moment(2018-04-29T17:45:25)>

就像其他库一样,它最初返回它自己的 datetime 类的实例,要返回 Python datetime 对象,添加额外的 date() 调用即可。

$ moment.date('2018-04-29T17:45:25Z').date
datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=<StaticTzInfo 'Z'>)

这将 Moment datetime 类转换为 Python datetime 对象。

Moment 还提供了使用人类可读的语言创建新日期的方法。例如创建一个明天的日期:

$ moment.date("tomorrow")
<Moment(2018-04-06T11:24:42)>

它的 add()subtract() 命令使用关键字参数来简化日期的操作。为了获得后天,Moment 会使用下面的代码:

$ moment.date("tomorrow").add(days=1)
<Moment(2018-04-07T11:26:48)>

Maya

Maya 包含了 Python 中其他流行处理日期时间的库,包括 Humanize、 pytz 和 pendulum 等等。这个项目旨在让人们更容易处理日期。

Maya 的 README 包含几个有用的实例。以下是如何使用 Maya 来重新处理以前的解析示例:

$ import maya
$ maya.parse('2018-04-29T17:45:25Z').datetime()
datetime.datetime(2018, 4, 29, 17, 45, 25, tzinfo=<UTC>)

注意我们必须在 maya.parse() 之后调用 datetime()。如果我们跳过这一步,Maya 将会返回一个 MayaDT 类的示例:<MayaDT epoch=1525023925.0>

由于 Maya 与 datetime 库中很多有用的方法重叠,因此它可以使用 MayaDT 类的实例执行诸如使用 slang_time() 方法将时间偏移量转换为纯文本语言,并将日期时间间隔保存在单个类的实例中。以下是如何使用 Maya 将日期时间表示为人类可读的短语:

$ import maya
$ maya.parse('2018-04-29T17:45:25Z').slang_time()
'23 days from now

显然,slang_time() 的输出将根据距离 datetime 对象相对较近或较远的距离而变化。

Delorean

Delorean,以 《返回未来》 电影中的时间旅行汽车命名,它对于操纵日期时间特别有用,包括将日期时间转换为其他时区并添加或减去时间。

Delorean 需要有效的 Python datetime 对象才能工作,所以如果你需要使用时间字符串,最好将其与上述库中的一个配合使用。例如,将 Maya 与 Delorean 一起使用:

$ import maya 
$ d_t = maya.parse('2018-04-29T17:45:25Z').datetime()

现在,你有了一个 datetime 对象 d\_t,你可以使用 Delorean 来做一些事情,例如将日期时间转换为美国东部时区:

$ from delorean import Delorean
$ d = Delorean(d_t)
$ d
Delorean(datetime=datetime.datetime(2018, 4, 29, 17, 45, 25), timezone='UTC')
$ d.shift('US/Eastern')
Delorean(datetime=datetime.datetime(2018, 4, 29, 13, 45, 25), timezone='US/Eastern')

看到小时是怎样从 17 变成 13 了吗?

你也可以使用自然语言方法来操作 datetime 对象。获取 2018 年 4 月 29 日之后的下个星期五(我们现在使用的):

$ d.next_friday()
Delorean(datetime=datetime.datetime(2018, 5, 4, 13, 45, 25), timezone='US/Eastern')

在 Delorean 的文档中阅读更多关于其的用法。

Freezegun

Freezegun 是一个可以帮助你在 Python 代码中测试特定日期的库。使用 @freeze_time 装饰器,你可以为测试用例设置特定的日期和时间,并且所有对 datetime.datetime.now()datetime.datetime.utcnow() 等的调用都将返回你指定的日期和时间。例如:

from freezegun import freeze_time
import datetime

@freeze_time("2017-04-14")
def test(): 
    assert datetime.datetime.now() == datetime.datetime(2017, 4, 14)

要跨时区进行测试,你可以将 tz_offset 参数传递给装饰器。freeze_time 装饰器也接受更简单的口语化日期,例如 @freeze_time('April 4, 2017')


上面提到的每个库都提供了一组不同的特性和功能,也许很难决定哪一个最适合你的需要。Maya 的作者, Kenneth Reitz 说到:“所有这些项目相辅相成,它们都是我们的朋友”。

这些库共享一些功能,但不是全部。有些擅长时间操作,有些擅长解析,但它们都有共同的目标,即让你对日期和时间的工作更轻松。下次你发现自己对 Python 的内置 datetime 模块感到沮丧,我们希望你可以选择其中的一个库进行试验。


via: https://opensource.com/article/18/4/python-datetime-libraries

作者: Lacey Williams Hensche 选题: lujun9972 译者: MjSeven 校对: wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Python 编程语言已经成为 IT 中使用的最流行的语言之一。成功的一个原因是它可以用来解决各种问题。从网站开发到数据科学、机器学习到任务自动化,Python 生态系统有丰富的框架和库。本文将介绍 Fedora 软件包集合中提供的一些有用的 Python shell 来简化开发。

Python Shell

Python Shell 让你以交互模式使用解释器。这在测试代码或尝试新库时非常有用。在 Fedora 中,你可以通过在终端会话中输入 python3 来调用默认的 shell。虽然 Fedora 提供了一些更高级和增强的 shell。

IPython

IPython 为 Python shell 提供了许多有用的增强功能。例如包括 tab 补全,对象内省,系统 shell 访问和命令历史检索。许多功能也被 Jupyter Notebook 使用,因为它底层使用 IPython。

安装和运行 IPython

dnf install ipython3
ipython3

使用 tab 补全会提示你可能的选择。当你使用不熟悉的库时,此功能会派上用场。

如果你需要更多信息,输入 ? 命令来查看文档。对此的更多详细信息,你可以使用 ?? 命令。

另一个很酷的功能是使用 ! 字符执行系统 shell 命令的能力。然后可以在 IPython shell 中引用该命令的结果。

IPython 完整的功能列表可在官方文档中找到。

bpython

bpython 并不能像 IPython 做那么多,但它却在一个简单的轻量级包中提供了一系列有用功能。除其他功能之外,bpython 提供:

  • 内嵌语法高亮显示
  • 在你输入时提供自动补全建议
  • 可预期的参数列表
  • 能够将代码发送或保存到 pastebin 服务或文件中

安装和运行 bpython

dnf install bpython3
bpython3

在你输入的时候,bpython 为你提供了选择来自动补全你的代码。

当你调用函数或方法时,会自动显示需要的参数和文档字符串。

另一个很好的功能是可以使用功能键 F7 在外部编辑器(默认为 Vim)中打开当前的 bpython 会话。这在测试更复杂的程序时非常有用。

有关配置和功能的更多细节,请参考 bpython 文档

总结

使用增强的 Python shell 是提高生产力的好方法。它为你提供增强的功能来编写快速原型或尝试新库。你在使用增强的 Python shell 吗?请随意在评论区留言。

图片由 David ClodeUnsplash 上发布


via: https://fedoramagazine.org/enhance-python-interactive-shell/

作者:Clément Verna 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

全局变量

在 Python 中,在函数之外或在全局范围内声明的变量被称为全局变量。 这意味着,全局变量可以在函数内部或外部访问。

我们来看一个关于如何在 Python 中创建一个全局变量的示例。

示例 1:创建全局变量

x = "global"

def foo():
    print("x inside :", x)

foo()
    print("x outside:", x)

当我们运行代码时,将会输出:

x inside : global
x outside: global

在上面的代码中,我们创建了 x 作为全局变量,并定义了一个 foo() 来打印全局变量 x。 最后,我们调用 foo() 来打印x的值。

倘若你想改变一个函数内的 x 的值该怎么办?

x = "global"

def foo():
    x = x * 2
    print(x)
foo()

当我们运行代码时,将会输出:

UnboundLocalError: local variable 'x' referenced before assignment

输出显示一个错误,因为 Python 将 x 视为局部变量,而 x 没有在 foo() 内部定义。

为了运行正常,我们使用 global 关键字,查看 PythonGlobal 关键字以便了解更多。

局部变量

在函数体内或局部作用域内声明的变量称为局部变量。

示例 2:访问作用域外的局部变量

def foo():
    y = "local"

foo()
print(y)

当我们运行代码时,将会输出:

NameError: name 'y' is not defined

输出显示了一个错误,因为我们试图在全局范围内访问局部变量 y,而局部变量只能在 foo() 函数内部或局部作用域内有效。

我们来看一个关于如何在 Python 中创建一个局部变量的例子。

示例 3:创建一个局部变量

通常,我们在函数内声明一个变量来创建一个局部变量。

def foo():
    y = "local"
    print(y)

foo()

当我们运行代码时,将会输出:

local

让我们来看看前面的问题,其中x是一个全局变量,我们想修改 foo() 内部的 x

全局变量和局部变量

在这里,我们将展示如何在同一份代码中使用全局变量和局部变量。

示例 4:在同一份代码中使用全局变量和局部变量

x = "global"

def foo():
    global x
    y = "local"
    x = x * 2
    print(x)
    print(y)

foo()

当我们运行代码时,将会输出(LCTT 译注:原文中输出结果的两个 global 有空格,正确的是没有空格):

globalglobal
local

在上面的代码中,我们将 x 声明为全局变量,将 y 声明为 foo() 中的局部变量。 然后,我们使用乘法运算符 * 来修改全局变量 x,并打印 xy

在调用 foo() 之后,x 的值变成 globalglobal了(LCTT 译注:原文同样有空格,正确的是没有空格),因为我们使用 x * 2 打印两次 global。 之后,我们打印局部变量y的值,即 local

示例 5:具有相同名称的全局变量和局部变量

x = 5

def foo():
    x = 10
    print("local x:", x)

foo()
print("global x:", x)

当我们运行代码时,将会输出:

local x: 10
global x: 5

在上面的代码中,我们对全局变量和局部变量使用了相同的名称 x。 当我们打印相同的变量时却得到了不同的结果,因为这两个作用域内都声明了变量,即 foo() 内部的局部作用域和 foo() 外面的全局作用域。

当我们在 foo() 内部打印变量时,它输出 local x: 10,这被称为变量的局部作用域。

同样,当我们在 foo() 外部打印变量时,它输出 global x: 5,这被称为变量的全局作用域。

非局部变量

非局部变量用于局部作用域未定义的嵌套函数。 这意味着,变量既不能在局部也不能在全局范围内。

我们来看一个关于如何在 Python 中创建一个非局部变量的例子。(LCTT 译者注:原文为创建全局变量,疑为笔误)

我们使用 nonlocal 关键字来创建非局部变量。

示例 6:创建一个非局部变量

def outer():
    x = "local"

    def inner():
        nonlocal x
        x = "nonlocal"
        print("inner:", x)

    inner()
    print("outer:", x)

outer()

当我们运行代码时,将会输出:

inner: nonlocal
outer: nonlocal

在上面的代码中有一个嵌套函数 inner()。 我们使用 nonlocal 关键字来创建非局部变量。inner() 函数是在另一个函数 outer() 的作用域中定义的。

注意:如果我们改变非局部变量的值,那么变化就会出现在局部变量中。


via: https://www.programiz.com/python-programming/global-local-nonlocal-variables

作者:programiz 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

从理论上来说,可以。Zed Shaw 说过一句著名的话,如果不行,那么 Python 3 一定不是图灵完备的。但在实践中,这是不现实的,我将通过给你们举几个例子来说明原因。

对于字典(dict)来说,这意味着什么?

让我们来想象一台拥有 Python 6 的虚拟机,它可以读取 Python 3.6 编写的 module3.py。但是在这个模块中,它可以导入 Python 2.7 编写的 module2.py,并成功使用它,没有问题。这显然是实验代码,但假设 module2.py 包含以下的功能:

def update_config_from_dict(config_dict):
    items = config_dict.items()
    while items:
        k, v = items.pop()
        memcache.set(k, v)

def config_to_dict():
    result = {}
    for k, v in memcache.getall():
        result[k] = v
    return result

def update_in_place(config_dict):
    for k, v in config_dict.items():
        new_value = memcache.get(k)
        if new_value is None:
            del config_dict[k]
        elif new_value != v:
            config_dict[k] = v

现在,当我们想从 module3 中调用这些函数时,我们遇到了一个问题:Python 3.6 中的字典类型与 Python 2.7 中的字典类型不同。在 Python 2 中,字典是无序的,它们的 .keys(), .values(), .items() 方法返回了正确的序列,这意味着调用 .items() 会在字典中创建状态的副本。在 Python 3 中,这些方法返回字典当前状态的动态视图。

这意味着如果 module3 调用 module2.update_config_from_dict(some_dictionary),它将无法运行,因为 Python 3 中 dict.items() 返回的值不是一个列表,并且没有 .pop() 方法。反过来也是如此。如果 module3 调用 module2.config_to_dict(),它可能会返回一个 Python 2 的字典。现在调用 .items() 突然返回一个列表,所以这段代码无法正常工作(这对 Python 3 字典来说工作正常):

def main(cmdline_options):
    d = module2.config_to_dict()
    items = d.items()
    for k, v in items:
        print(f'Config from memcache: {k}={v}')
    for k, v in cmdline_options:
        d[k] = v
    for k, v in items:
        print(f'Config with cmdline overrides: {k}={v}')

最后,使用 module2.update_in_place() 会失败,因为 Python 3 中 .items() 的值现在不允许在迭代过程中改变。

对于字典来说,还有很多问题。Python 2 的字典在 Python 3 上使用 isinstance(d, dict) 应该返回 True 吗?如果是的话,这将是一个谎言。如果没有,代码将无法继续。

Python 应该神奇地知道类型并会自动转换!

为什么我们的 Python 6 的虚拟机无法识别 Python 3 的代码,在 Python 2 中调用 some_dict.keys() 时,我们还有别的意思吗?好吧,Python 不知道代码的作者在编写代码时,她所认为的 some_dict 应该是什么。代码中没有任何内容表明它是否是一个字典。在 Python 2 中没有类型注释,因为它们是可选的,即使在 Python 3 中,大多数代码也不会使用它们。

在运行时,当你调用 some_dict.keys() 的时候,Python 只是简单地在对象上查找一个属性,该属性恰好隐藏在 some_dict 名下,并试图在该属性上运行 __call__()。这里有一些关于方法绑定,描述符,slots 等技术问题,但这是它的核心。我们称这种行为为“鸭子类型”。

由于鸭子类型,Python 6 的虚拟机将无法做出编译时决定,以正确转换调用和属性查找。

好的,让我们在运行时做出这个决定

Python 6 的虚拟机可以标记每个属性,通过查找“来自 py2 的调用”或“来自 py3 的调用”的信息来实现这一点,并使对象发送正确的属性。这会让它变得很慢,并且使用更多的内存。这将要求我们在内存中保留两种版本的代码,并通过代理来使用它们。我们需要加倍付出努力,在用户背后同步这些对象的状态。毕竟,新字典的内存表示与 Python 2 不同。

如果你已经被字典问题绕晕了,那么再想想 Python 3 中的 Unicode 字符串和 Python 2 中的字节(byte)字符串的各种问题吧。

没有办法了吗?Python 3 根本就不能运行旧代码吗?

不会。每天都会有项目移植到 Python 3。将 Python 2 代码移植到两个版本的 Python 上推荐方法是在你的代码上运行 Python-Modernize。它会捕获那些在 Python 3 上不起作用的代码,并使用 six 库将其替换,以便它在 Python 2 和 Python 3 上运行。这是 2to3 的一个改编版本,用于生成仅针对 Python 3 代码。Modernize 是首选,因为它提供了更多的增量迁移路线。所有的这些在 Python 文档中的 Porting Python 2 Code to Python 3文档中都有很好的概述。

但是,等一等,你不是说 Python 6 的虚拟机不能自动执行此操作吗?对。Modernize 查看你的代码,并试图猜测哪些是安全的。它会做出一些不必要的改变,还会错过其他必要的改变。但是,它不会帮助你处理字符串。如果你的代码没有在“来自外部的二进制数据”和“流程中的文本数据”之间保持界限,那么这种转换就不会那么轻易。

因此,大项目的迁移不能自动完成,并且需要人类进行测试,发现问题并修复它们。它工作吗?是的,我曾帮助将一百万行代码迁移到 Python 3,并且这种切换没有造成事故。这一举措让我们重新获得了 1/3 的服务器内存,并使代码运行速度提高了 12%。那是在 Python 3.5 上,但是 Python 3.6 的速度要快得多,根据你的工作量,你甚至可以达到 4 倍加速

亲爱的 Zed

hi,伙计,我关注你已经超过 10 年了。我一直在观察,当你感到沮丧的时候,你对 Mongrel 没有任何信任,尽管 Rails 生态系统几乎全部都在上面运行。当你重新设计它并开始 Mongrel 2 项目时,我一直在观察。我一直在关注你使用 Fossil 这一令人惊讶的举动。随着你发布 “Rails 是一个贫民窟”的帖子,我看到你突然离开了 Ruby 社区。当你开始编写《笨方法学 Python》并且开始推荐它时,我感到非常兴奋。2013 年我在 DjangoCon Europe 见过你,我们谈了很多关于绘画,唱歌和倦怠的内容。你的这张照片是我在 Instagram 上的第一个帖子。

你几乎把另一个“贫民区”的行动与 “反对 Python 3” 案例 文章拉到一起。我认为你本意是好的,但是这篇文章引起了很多混淆,包括许多人觉得你认为 Python 3 不是图灵完整的。我花了好几个小时让人们相信,你是在开玩笑。但是,鉴于你对《笨方法学 Python》的重大贡献,我认为这是值得的。特别是你为 Python 3 更新了你的书。感谢你做这件事。如果我们社区中真的有人因你的帖子为由要求将你和你的书列入黑名单,而请他们出去。这是一个双输的局面,这是错误的。

说实话,没有一个核心 Python 开发人员认为 Python 2 到 Python 3 的转换过程会顺利而且计划得当,包括 Guido van Rossum。真的,可以看那个视频,这有点事后诸葛亮的意思了。从这个意义上说,我们实际上是积极地相互认同的。如果我们再做一次,它会看起来不一样。但在这一点上,在 2020 年 1 月 1 日,Python 2 将会到达终结。大多数第三方库已经支持 Python 3,甚至开始发布只支持 Python 3 的版本(参见 Django科学项目关于 Python 3 的声明)。

我们也积极地就另一件事达成一致。就像你于 Mongrel 一样,Python 核心开发人员是志愿者,他们的工作没有得到报酬。我们大多数人在这个项目上投入了大量的时间和精力,因此我们自然而然敏感于那些对他们的贡献不屑一顾和激烈的评论。特别是如果这个信息既攻击目前的事态,又要求更多的自由贡献。

我希望到 2018 年会让你忘记 2016 发布的帖子,有一堆好的反驳。我特别喜欢 eevee(LCTT 译注:eevee 是一个为 Blender 设计的渲染器)。它特别针对“一起运行 Python 2 和 Python 3 ”的场景,这是不现实的,就像在同一个虚拟机中运行 Ruby 1.8 和 Ruby 2.x 一样,或者像 Lua 5.3 和 Lua 5.1 同时运行一样。你甚至不能用 libc.so.6 运行针对 libc.so.5 编译的 C 二进制文件。然而,我发现最令人惊讶的是,你声称 Python 核心开发者是“有目的地”创造诸如 2to3 之类的破坏工具,这些由 Guido 创建,其最大利益就是让每个人尽可能顺利,快速地迁移。我很高兴你在之后的帖子中放弃了这个说法,但是你必须意识到你会激怒那些阅读了原始版本的人。对蓄意伤害的指控最好有强有力的证据支持。

但看起来你仍然会这样做。就在今天你说 Python 核心开发者“忽略”尝试解决 API 的问题,特别是 six。正如我上面写的那样,Python 文档中的官方移植指南涵盖了 six。更重要的是,six 是由 Python 2.7 的发布管理者 Benjamin Peterson 编写。很多人学会了编程,这要归功于你,而且由于你在网上有大量的粉丝,人们会阅读这样的推文,他们会相信它的价值,这是有害的。

我有一个建议,让我们把 “Python 3 管理不善”的争议搁置起来。Python 2 正在死亡,这个过程会很慢,并且它是丑陋而血腥的,但它是一条单行道。争论那些没有用。相反,让我们专注于我们现在可以做什么来使 Python 3.8 比其他任何 Python 版本更好。也许你更喜欢看外面的角色,但作为这个社区的成员,你会更有影响力。请说“我们”而不是“他们”。


via: http://lukasz.langa.pl/13/could-we-run-python-2-and-python-3-code-same-vm/

作者:Łukasz Langa 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在读这篇文章之前,确保你对 Python 全局、局部和非局部变量 有一定的基础。

global 关键字简介

在 Python 中,global 关键字允许你修改当前范围之外的变量。它用于创建全局变量并在本地上下文中更改变量。

global 关键字的规则

在 Python 中,有关 global 关键字基本规则如下:

  • 当我们在一个函数中创建一个变量时,默认情况下它是本地变量。
  • 当我们在一个函数之外定义一个变量时,默认情况下它是全局变量。你不必使用 global 关键字。
  • 我们使用 global 关键字在一个函数中来读写全局变量。
  • 在一个函数外使用 global 关键字没有效果。

使用 global 关键字(含示例)

我们来举个例子。

示例 1:从函数内部访问全局变量

c = 1 # 全局变量
def add():
    print(c)

add()

运行程序,输出为:

1

但是我们可能有一些场景需要从函数内部修改全局变量。

示例 2:在函数内部修改全局变量

c = 1 # 全局变量
def add():
    c = c + 2 # 将 c 增加 2
    print(c)
add()

运行程序,输出显示错误:

UnboundLocalError: local variable 'c' referenced before assignment

这是因为在函数中,我们只能访问全局变量但是不能修改它。

解决的办法是使用 global 关键字。

示例 3:使用 global 在函数中改变全局变量

c = 0 # global variable

def add():
    global c
    c = c + 2 # 将 c 增加 2
    print("Inside add():", c)

add()
print("In main:", c)

运行程序,输出为:

Inside add(): 2
In main: 2

在上面的程序中,我们在 add() 函数中定义了 c 将其作为全局关键字。

然后,我们给变量 c 增加 2,即 c = c + 2。之后,我们调用了 add() 函数。最后,打印全局变量 c

正如我们所看到的,在函数外的全局变量也发生了变化,c = 2

Python 模块中的全局变量

在 Python 中,我们创建一个单独的模块 config.py 来保存全局变量并在同一个程序中的 Python 模块之间共享信息。

以下是如何通过 Python 模块共享全局变量。

示例 4:在Python模块中共享全局变量

创建 config.py 文件来存储全局变量

a = 0
b = "empty"

创建 update.py 文件来改变全局变量

import config

config.a = 10
config.b = "alphabet"

创建 main.py 文件来测试其值的变化

import config
import update

print(config.a)
print(config.b)

运行 main.py,输出为:

10
alphabet

在上面,我们创建了三个文件: config.py, update.pymain.py

config.py 模块中保存了全局变量 ab。在 update.py 文件中,我们导入了 config.py 模块并改变了 ab 的值。同样,在 main.py 文件,我们导入了 config.pyupdate.py 模块。最后,我们打印并测试全局变量的值,无论它们是否被改变。

在嵌套函数中的全局变量

以下是如何在嵌套函数中使用全局变量。

示例 5:在嵌套函数中使用全局变量

def foo():
    x = 20
    def bar():
         global x
         x = 25

    print("Before calling bar: ", x)
    print("Calling bar now")
    bar()
    print("After calling bar: ", x)

foo()
print("x in main : ", x)

输出为:

Before calling bar: 20
Calling bar now
After calling bar: 20
x in main : 25

在上面的程序中,我们在一个嵌套函数 bar() 中声明了全局变量。在 foo() 函数中, 变量 x 没有全局关键字的作用。

调用 bar() 之前和之后, 变量 x 取本地变量的值,即 x = 20。在 foo() 函数之外,变量 x 会取在函数 bar() 中的值,即 x = 25。这是因为在 bar() 中,我们对 x 使用 global 关键字创建了一个全局变量(本地范围)。

如果我们在 bar() 函数内进行了任何修改,那么这些修改就会出现在本地范围之外,即 foo()


via: https://www.programiz.com/python-programming/global-keyword

作者:programiz 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

用一些简单的脚本,可以很容易地清理文档和其它大量的 HTML 文件。但是首先你需要解析它们。

作为 Scribus 文档团队的长期成员,我要随时了解最新的源代码更新,以便对文档进行更新和补充。 我最近在刚升级到 Fedora 27 系统的计算机上使用 Subversion 进行检出操作时,对于下载该文档所需要的时间我感到很惊讶,文档由 HTML 页面和相关图像组成。 我恐怕该项目的文档看起来比项目本身大得多,并且怀疑其中的一些内容是“僵尸”文档——不再使用的 HTML 文件以及 HTML 中无法访问到的图像。

我决定为自己创建一个项目来解决这个问题。 一种方法是搜索未使用的现有图像文件。 如果我可以扫描所有 HTML 文件中的图像引用,然后将该列表与实际图像文件进行比较,那么我可能会看到不匹配的文件。

这是一个典型的图像标签:

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我对 src= 之后的第一组引号之间的部分很感兴趣。 在寻找了一些解决方案后,我找到一个名为 BeautifulSoup 的 Python 模块。 脚本的核心部分如下所示:

soup = BeautifulSoup(all_text, 'html.parser')
match = soup.findAll("img")
if len(match) > 0:
    for m in match:
        imagelist.append(str(m))

我们可以使用这个 findAll 方法来挖出图片标签。 这是一小部分输出:

<img src="images/pdf-form-ht3.png"/><img src="images/pdf-form-ht4.png"/><img src="images/pdf-form-ht5.png"/><img src="images/pdf-form-ht6.png"/><img align="middle" alt="GSview - Advanced Options Panel" src="images/gsadv1.png" title="GSview - Advanced Options Panel"/><img align="middle" alt="Scribus External Tools Preferences" src="images/gsadv2.png" title="Scribus External Tools Preferences"/>

到现在为止还挺好。我原以为下一步就可以搞定了,但是当我在脚本中尝试了一些字符串方法时,它返回了有关标记的错误而不是字符串的错误。 我将输出保存到一个文件中,并在 KWrite 中进行编辑。 KWrite 的一个好处是你可以使用正则表达式(regex)来做“查找和替换”操作,所以我可以用 \n<img 替换 <img,这样可以看得更清楚。 KWrite 的另一个好处是,如果你用正则表达式做了一个不明智的选择,你还可以撤消。

但我认为,肯定有比这更好的东西,所以我转而使用正则表达式,或者更具体地说 Python 的 re 模块。 这个新脚本的相关部分如下所示:

match = re.findall(r'src="(.*)/>', all_text)
if len(match)>0:
    for m in match:
        imagelist.append(m)

它的一小部分输出如下所示:

images/cmcanvas.png" title="Context Menu for the document canvas" alt="Context Menu for the document canvas" /></td></tr></table><br images/eps-imp1.png" title="EPS preview in a file dialog" alt="EPS preview in a file dialog" images/eps-imp5.png" title="Colors imported from an EPS file" alt="Colors imported from an EPS file" images/eps-imp4.png" title="EPS font substitution" alt="EPS font substitution" images/eps-imp2.png" title="EPS import progress" alt="EPS import progress" images/eps-imp3.png" title="Bitmap conversion failure" alt="Bitmap conversion failure"

乍一看,它看起来与上面的输出类似,并且附带有去除图像的标签部分的好处,但是有令人费解的是还夹杂着表格标签和其他内容。 我认为这涉及到这个正则表达式 src="(.*)/>,这被称为贪婪,意味着它不一定停止在遇到 /> 的第一个实例。我应该补充一点,我也尝试过 src="(.*)",这真的没有什么更好的效果,我不是一个正则表达式专家(只是做了这个),找了各种方法来改进这一点但是并没什么用。

做了一系列的事情之后,甚至尝试了 Perl 的 HTML::Parser 模块,最终我试图将这与我为 Scribus 编写的一些脚本进行比较,这些脚本逐个字符的分析文本内容,然后采取一些行动。 为了最终目的,我终于想出了所有这些方法,并且完全不需要正则表达式或 HTML 解析器。 让我们回到展示的那个 img 标签的例子。

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

我决定回到 src= 这一块。 一种方法是等待 s 出现,然后看下一个字符是否是 r,下一个是 c,下一个是否 =。 如果是这样,那就匹配上了! 那么两个双引号之间的内容就是我所需要的。 这种方法的问题在于需要连续识别上面这样的结构。 一种查看代表一行 HTML 文本的字符串的方法是:

for c in all_text:

但是这个逻辑太乱了,以至于不能持续匹配到前面的 c,还有之前的字符,更之前的字符,更更之前的字符。

最后,我决定专注于 = 并使用索引方法,以便我可以轻松地引用字符串中的任何先前或将来的字符。 这里是搜索部分:

    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and (all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1

我用第四个字符开始搜索(索引从 0 开始),所以我在下面没有出现索引错误,并且实际上,在每一行的第四个字符之前不会有等号。 第一个测试是看字符串中是否出现了 =,如果没有,我们就会前进。 如果我们确实看到一个等号,那么我们会看前三个字符是否是 src。 如果全都匹配了,就调用函数 imagefound

def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return

我们给函数发送当前索引,它代表着 =。 我们知道下一个字符将会是 ",所以我们跳过两个字符,并开始向名为 newimage 的控制字符串添加字符,直到我们发现下一个 ",此时我们完成了一次匹配。 我们将字符串加一个换行符(\n)添加到列表 imagelist 中并返回(return),请记住,在剩余的这个 HTML 字符串中可能会有更多图片标签,所以我们马上回到搜索循环中。

以下是我们的输出现在的样子:

images/text-frame-link.png
images/text-frame-unlink.png
images/gimpoptions1.png
images/gimpoptions3.png
images/gimpoptions2.png
images/fontpref3.png
images/font-subst.png
images/fontpref2.png
images/fontpref1.png
images/dtp-studio.png

啊,干净多了,而这只花费几秒钟的时间。 我本可以将索引前移 7 步来剪切 images/ 部分,但我更愿意把这个部分保存下来,以确保我没有剪切掉图像文件名的第一个字母,这很容易用 KWrite 编辑成功 —— 你甚至不需要正则表达式。 做完这些并保存文件后,下一步就是运行我编写的另一个脚本 sortlist.py

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# sortlist.py

import os

imagelist = []
for line in open('/tmp/imagelist_parse4.txt').xreadlines():
    imagelist.append(line)

imagelist.sort()

outfile = open('/tmp/imagelist_parse4_sorted.txt', 'w')
outfile.writelines(imagelist)
outfile.close()

这会读取文件内容,并存储为列表,对其排序,然后另存为另一个文件。 之后,我可以做到以下几点:

ls /home/gregp/development/Scribus15x/doc/en/images/*.png > '/tmp/actual_images.txt'

然后我需要在该文件上运行 sortlist.py,因为 ls 方法的排序与 Python 不同。 我原本可以在这些文件上运行比较脚本,但我更愿意以可视方式进行操作。 最后,我成功找到了 42 个图像,这些图像没有来自文档的 HTML 引用。

这是我的完整解析脚本:

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# parseimg4.py

import os

def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return

htmlnames = []
imagelist = []
tempstring = ''
filenames = os.listdir('/home/gregp/development/Scribus15x/doc/en/')
for name in filenames:
    if name.endswith('.html'):
        htmlnames.append(name)
#print htmlnames
for htmlfile in htmlnames:
    all_text = open('/home/gregp/development/Scribus15x/doc/en/' + htmlfile).read()
    linelength = len(all_text)
    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and
(all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1

outfile = open('/tmp/imagelist_parse4.txt', 'w')
outfile.writelines(imagelist)
outfile.close()
imageno = len(imagelist)
print str(imageno) + " images were found and saved"

脚本名称为 parseimg4.py,这并不能真实反映我陆续编写的脚本数量(包括微调的和大改的以及丢弃并重新开始写的)。 请注意,我已经对这些目录和文件名进行了硬编码,但是很容易变得通用化,让用户输入这些信息。 同样,因为它们是工作脚本,所以我将输出发送到 /tmp 目录,所以一旦重新启动系统,它们就会消失。

这不是故事的结尾,因为下一个问题是:僵尸 HTML 文件怎么办? 任何未使用的文件都可能会引用图像,不能被前面的方法所找出。 我们有一个 menu.xml 文件作为联机手册的目录,但我还需要考虑 TOC(LCTT 译注:TOC 是 table of contents 的缩写)中列出的某些文件可能引用了不在 TOC 中的文件,是的,我确实找到了一些这样的文件。

最后我可以说,这是一个比图像搜索更简单的任务,而且开发的过程对我有很大的帮助。

关于作者

Greg Pittman 是 Kentucky 州 Louisville 市的一名退休的神经学家,从二十世纪六十年代的 Fortran IV 语言开始长期以来对计算机和编程有着浓厚的兴趣。 当 Linux 和开源软件出现的时候,Greg 深受启发,去学习更多知识,并实现最终贡献的承诺。 他是 Scribus 团队的成员。更多关于我


via: https://opensource.com/article/18/1/parsing-html-python

作者:Greg Pittman 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出