分类 软件开发 下的文章

Python 生态系统包含丰富的工具和库,可以让开发人员更加舒适。 例如,我们之前已经介绍了如何使用交互式 shell 增强 Python。本文重点介绍另一种可以节省时间并提高 Python 技能的工具:Python 调试器。

Python 调试器

Python 标准库提供了一个名为 pdb 的调试器。此调试器提供了调试所需的大多数功能,如断点、单行步进、堆栈帧的检查等等。

了解一些pdb 的基本知识很有用,因为它是标准库的一部分。 你可以在无法安装其他增强的调试器的环境中使用它。

运行 pdb

运行 pdb 的最简单方法是从命令行,将程序作为参数传递来调试。 看看以下脚本:

# pdb_test.py
#!/usr/bin/python3

from time import sleep

def countdown(number):
    for i in range(number, 0, -1):
        print(i)
        sleep(1)


if __name__ == "__main__":
    seconds = 10
    countdown(seconds)

你可以从命令行运行 pdb,如下所示:

$ python3 -m pdb pdb_test.py
> /tmp/pdb_test.py(1)<module>()
-> from time import sleep
(Pdb)

使用 pdb 的另一种方法是在程序中设置断点。为此,请导入 pdb 模块并使用set_trace 函数:

# pdb_test.py
#!/usr/bin/python3

from time import sleep


def countdown(number):
    for i in range(number, 0, -1):
        import pdb; pdb.set_trace()
        print(i)
        sleep(1)


if __name__ == "__main__":
    seconds = 10
    countdown(seconds)
$ python3 pdb_test.py
> /tmp/pdb_test.py(6)countdown()
-> print(i)
(Pdb)

脚本在断点处停止,pdb 显示脚本中的下一行。 你也可以在失败后执行调试器。 这称为 事后调试 postmortem debugging

穿行于执行堆栈

调试中的一个常见用例是在执行堆栈中穿行。 Python 调试器运行后,可以使用以下命令:

  • w(here):显示当前执行的行以及执行堆栈的位置。
$ python3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) w
/tmp/test_pdb.py(16)<module>()
-> countdown(seconds)
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb)
  • l(ist):显示当前位置周围更多的上下文(代码)。
$ python3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) l
5
6
7     def countdown(number):
8         for i in range(number, 0, -1):
9             import pdb; pdb.set_trace()
10  ->         print(i)
11             sleep(1)
12
13
14     if __name__ == "__main__":
15         seconds = 10
  • u(p)/d(own):向上或向下穿行调用堆栈。
$ py3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) up
> /tmp/test_pdb.py(16)<module>()
-> countdown(seconds)
(Pdb) down
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb)

单步执行程序

pdb提供以下命令来执行和单步执行代码:

  • n(ext):继续执行,直到达到当前函数中的下一行,或者返回
  • s(tep):执行当前行并在第一个可能的场合停止(在被调用的函数或当前函数中)
  • c(ontinue):继续执行,仅在断点处停止。
$ py3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) n
10
> /tmp/test_pdb.py(11)countdown()
-> sleep(1)
(Pdb) n
> /tmp/test_pdb.py(8)countdown()
-> for i in range(number, 0, -1):
(Pdb) n
> /tmp/test_pdb.py(9)countdown()
-> import pdb; pdb.set_trace()
(Pdb) s
--Call--
> /usr/lib64/python3.6/pdb.py(1584)set_trace()
-> def set_trace():
(Pdb) c
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) c
9
> /tmp/test_pdb.py(9)countdown()
-> import pdb; pdb.set_trace()
(Pdb)

该示例显示了 nextstep 之间的区别。 实际上,当使用 step 时,调试器会进入 pdb 模块源代码,而接下来就会执行 set_trace 函数。

检查变量内容

  • pdb 非常有用的地方是检查执行堆栈中存储的变量的内容。 例如,a(rgs) 命令打印当前函数的变量,如下所示:
py3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) where
/tmp/test_pdb.py(16)<module>()
-> countdown(seconds)
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) args
number = 10
(Pdb)

pdb 打印变量的值,在本例中是 10。

  • 可用于打印变量值的另一个命令是 p(rint)
$ py3 test_pdb.py
> /tmp/test_pdb.py(10)countdown()
-> print(i)
(Pdb) list
5
6
7     def countdown(number):
8         for i in range(number, 0, -1):
9             import pdb; pdb.set_trace()
10  ->         print(i)
11             sleep(1)
12
13
14     if __name__ == "__main__":
15         seconds = 10
(Pdb) print(seconds)
10
(Pdb) p i
10
(Pdb) p number - i
0
(Pdb)

如示例中最后的命令所示,print 可以在显示结果之前计算表达式。

Python 文档包含每个 pdb 命令的参考和示例。 对于开始使用 Python 调试器人来说,这是一个有用的读物。

增强的调试器

一些增强的调试器提供了更好的用户体验。 大多数为 pdb 添加了有用的额外功能,例如语法突出高亮、更好的回溯和自省。 流行的增强调试器包括 IPython 的 ipdbpdb++

这些示例显示如何在虚拟环境中安装这两个调试器。 这些示例使用新的虚拟环境,但在调试应用程序的情况下,应使用应用程序的虚拟环境。

安装 IPython 的 ipdb

要安装 IPython ipdb,请在虚拟环境中使用 pip

$ python3 -m venv .test_pdb
$ source .test_pdb/bin/activate
(test_pdb)$ pip install ipdb

要在脚本中调用 ipdb,必须使用以下命令。 请注意,该模块称为 ipdb 而不是 pdb:

import ipdb; ipdb.set_trace()

IPython 的 ipdb 也可以用 Fedora 包安装,所以你可以使用 Fedora 的包管理器 dnf 来安装它:

$ sudo dnf install python3-ipdb

安装 pdb++

你可以类似地安装 pdb++:

$ python3 -m venv .test_pdb
$ source .test_pdb/bin/activate
(test_pdb)$ pip install pdbp

pdb++ 重写了 pdb 模块,因此你可以使用相同的语法在程序中添加断点:

import pdb; pdb.set_trace()

总结

学习如何使用 Python 调试器可以节省你在排查应用程序问题时的时间。 对于了解应用程序或某些库的复杂部分如何工作也是有用的,从而提高 Python 开发人员的技能。


via: https://fedoramagazine.org/getting-started-python-debugger/

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

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

如果你想知道大家对某件事情的看法,Twitter 是最好的地方了。Twitter 是观点持续不断的涌现出来的地方,每秒钟大概有 6000 条新 Twitter 发送出来。因特网上的发展很快,如果你想与时俱进或者跟上潮流,Twitter 就是你要去的地方。

现在,我们生活在一个数据为王的时代,很多公司都善于运用 Twitter 上的数据。根据测量到的他们新产品的人气,尝试预测之后的市场趋势,分析 Twitter 上的数据有很多用处。通过数据,商人把产品卖给合适的用户,收集关于他们品牌和改进的反馈,或者获取他们产品或促销活动失败的原因。不仅仅是商人,很多政治和经济上的决定是在观察人们意见的基础上所作的。今天,我会试着让你感受下关于 Twitter 的简单 情感分析,判断这个 Twitter 是正能量、负能量还是中性的。这不会像专业人士所用的那么复杂,但至少,它会让你知道挖掘观念的想法。

我们将使用 NodeJs,因为 JavaScript 太常用了,而且它还是最容易入门的语言。

前置条件:

  • 安装了 NodeJs 和 NPM
  • 有 NodeJs 和 NPM 包的经验
  • 熟悉命令行。

好了,就是这样。开始吧。

开始

为了你的项目新建一个目录,进入这个目录下面。打开终端(或是命令行)。进入刚创建的目录下面,运行命令 npm init -y。这会在这个目录下创建一个 package.json 文件。现在我们可以安装需要的 npm 包了。只需要创建一个新文件,命名为 index.js 然后我们就完成了初始的编码。

获取推文

好了,我们想要分析 Twitter ,为了实现这个目的,我们需要以编程的方式访问 Twitter。为此,我们要用到 twit 包。因此,先用 npm i wit 命令安装它。我们还需要注册一个 App,以通过我们的账户来访问 Twitter 的 API。点击这个 链接,填写所有项目,从 “Keys and Access Token” 标签页中复制 “Consumer Key”、“Consumer Secret”、“Access token” 和 “Access Token Secret” 这几项到一个 .env 文件中,就像这样:

# .env
# replace the stars with values you copied
CONSUMER_KEY=************
CONSUMER_SECRET=************
ACCESS_TOKEN=************
ACCESS_TOKEN_SECRET=************

现在开始。

用你最喜欢的代码编辑器打开 index.js。我们需要用 npm i dotenv 命令安装 dotenv 包来读取 .env 文件。好了,创建一个 API 实例。

const Twit = require('twit');
const dotenv = require('dotenv');

dotenv.config();

const { CONSUMER_KEY
      , CONSUMER_SECRET
      , ACCESS_TOKEN
      , ACCESS_TOKEN_SECRET
      } = process.env;

const config_twitter = {
    consumer_key: CONSUMER_KEY,
    consumer_secret: CONSUMER_SECRET,
    access_token: ACCESS_TOKEN,
    access_token_secret: ACCESS_TOKEN_SECRET,
    timeout_ms: 60*1000
};

let api = new Twit(config_twitter);

这里已经用所需的配置文件建立了到 Twitter 上的连接。但我们什么事情都没做。先定义个获取推文的函数:

async function get_tweets(q, count) {
    let tweets = await api.get('search/tweets', {q, count, tweet_mode: 'extended'});
    return tweets.data.statuses.map(tweet => tweet.full_text);
}

这是个 async 函数,因为 api.get 函数返回一个 promise 对象,而不是 then 链,我想通过这种简单的方式获取推文。它接收两个参数 qcountq 是查询或者我们想要搜索的关键字,count 是让这个 api 返回的推文数量。

目前为止我们拥有了一个从 Twitter 上获取完整文本的简单方法。不过这里有个问题,现在我们要获取的文本中可能包含某些连接或者由于转推而被截断了。所以我们会编写另一个函数,拆解并返回推文的文本,即便是转发的推文,并且其中有链接的话就删除。

function get_text(tweet) {
    let txt = tweet.retweeted_status ? tweet.retweeted_status.full_text : tweet.full_text;
    return txt.split(/ |\n/).filter(v => !v.startsWith('http')).join(' ');
 }

async function get_tweets(q, count) {
    let tweets = await api.get('search/tweets', {q, count, 'tweet_mode': 'extended'});
    return tweets.data.statuses.map(get_text);
}

现在我们拿到了文本。下一步是从文本中获取情感。为此我们会使用 npm 中的另一个包 —— sentiment。让我们像安装其他包那样安装 sentiment,添加到脚本中。

const sentiment = require('sentiment')

sentiment 用起来很简单。我们只用把 sentiment 函数用在我们想要分析的文本上,它就能返回文本的相对分数。如果分数小于 0,它表达的就是消极情感,大于 0 的分数是积极情感,而 0,如你所料,表示中性的情感。基于此,我们将会把推文打印成不同的颜色 —— 绿色表示积极,红色表示消极,蓝色表示中性。为此,我们会用到 colors 包。先安装这个包,然后添加到脚本中。

const colors = require('colors/safe');

好了,现在把所有东西都整合到 main 函数中。

async function main() {
    let keyword = \* define the keyword that you want to search for *\;
    let count = \* define the count of tweets you want *\;
    let tweets = await get_tweets(keyword, count);
    for (tweet of tweets) {
        let score = sentiment(tweet).comparative;
        tweet = `${tweet}\n`;
        if (score > 0) {
            tweet = colors.green(tweet);
        } else if (score < 0) {
            tweet = colors.red(tweet);
        } else {
            tweet = colors.blue(tweet);
        }
        console.log(tweet);
    }
}

最后,执行 main 函数。

main();

就是这样,一个简单的分析推文中的基本情感的脚本。

\\ full script
const Twit = require('twit');
const dotenv = require('dotenv');
const sentiment = require('sentiment');
const colors = require('colors/safe');

dotenv.config();

const { CONSUMER_KEY
      , CONSUMER_SECRET
      , ACCESS_TOKEN
      , ACCESS_TOKEN_SECRET
      } = process.env;

const config_twitter = {
    consumer_key: CONSUMER_KEY,
    consumer_secret: CONSUMER_SECRET,
    access_token: ACCESS_TOKEN,
    access_token_secret: ACCESS_TOKEN_SECRET,
    timeout_ms: 60*1000
};

let api = new Twit(config_twitter);

function get_text(tweet) {
    let txt = tweet.retweeted_status ? tweet.retweeted_status.full_text : tweet.full_text;
    return txt.split(/ |\n/).filter(v => !v.startsWith('http')).join(' ');
 }

async function get_tweets(q, count) {
    let tweets = await api.get('search/tweets', {q, count, 'tweet_mode': 'extended'});
    return tweets.data.statuses.map(get_text);
}

async function main() {
    let keyword = 'avengers';
    let count = 100;
    let tweets = await get_tweets(keyword, count);
    for (tweet of tweets) {
        let score = sentiment(tweet).comparative;
        tweet = `${tweet}\n`;
        if (score > 0) {
            tweet = colors.green(tweet);
        } else if (score < 0) {
            tweet = colors.red(tweet);
        } else {
            tweet = colors.blue(tweet)
        }
        console.log(tweet)
    }
}

main();

via: https://boostlog.io/@anshulc95/twitter-sentiment-analysis-using-nodejs-5ad1331247018500491f3b6a

作者:Anshul Chauhan 译者:BriFuture 校对:wxy

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

如果你正在阅读本文,那么你已经意识到了 Python 3.7 以及它所包含的新特性。就我个人而言,我对 Dataclasses 感到非常兴奋,因为我等了它一段时间了。

本系列包含两部分:

  1. Dataclass 特点概述
  2. 在下一篇文章概述 Dataclass 的 fields

介绍

Dataclasses 是 Python 的类(LCTT 译注:更准确的说,它是一个模块),适用于存储数据对象。你可能会问什么是数据对象?下面是定义数据对象的一个不太详细的特性列表:

  • 它们存储数据并代表某种数据类型。例如:一个数字。对于熟悉 ORM 的人来说,模型实例就是一个数据对象。它代表一种特定的实体。它包含那些定义或表示实体的属性。
  • 它们可以与同一类型的其他对象进行比较。例如:一个数字可以是 greater than(大于)、less than(小于) 或 equal(等于) 另一个数字。

当然还有更多的特性,但是这个列表足以帮助你理解问题的关键。

为了理解 Dataclasses,我们将实现一个包含数字的简单类,并允许我们执行上面提到的操作。

首先,我们将使用普通类,然后我们再使用 Dataclasses 来实现相同的结果。

但在我们开始之前,先来谈谈 Dataclasses 的用法。

Python 3.7 提供了一个装饰器 dataclass,用于将类转换为 dataclass

你所要做的就是将类包在装饰器中:

from dataclasses import dataclass

@dataclass
class A:
 ...

现在,让我们深入了解一下 dataclass 带给我们的变化和用途。

初始化

通常是这样:

class Number:

    def __init__(self, val):
        self.val = val

>>> one = Number(1)
>>> one.val
>>> 1

dataclass 是这样:

@dataclass
class Number:
    val:int 

>>> one = Number(1)
>>> one.val
>>> 1

以下是 dataclass 装饰器带来的变化:

  1. 无需定义 __init__,然后将值赋给 selfdataclass 负责处理它(LCTT 译注:此处原文可能有误,提及一个不存在的 d
  2. 我们以更加易读的方式预先定义了成员属性,以及类型提示。我们现在立即能知道 valint 类型。这无疑比一般定义类成员的方式更具可读性。
Python 之禅: 可读性很重要

它也可以定义默认值:

@dataclass
class Number:
    val:int = 0

表示

对象表示指的是对象的一个有意义的字符串表示,它在调试时非常有用。

默认的 Python 对象表示不是很直观:

class Number:
    def __init__(self, val = 0):
    self.val = val

>>> a = Number(1)
>>> a
>>> <__main__.Number object at 0x7ff395b2ccc0>

这让我们无法知悉对象的作用,并且会导致糟糕的调试体验。

一个有意义的表示可以通过在类中定义一个 __repr__ 方法来实现。

def __repr__(self):
    return self.val

现在我们得到这个对象有意义的表示:

>>> a = Number(1)
>>> a
>>> 1

dataclass 会自动添加一个 __repr__ 函数,这样我们就不必手动实现它了。

@dataclass
class Number:
    val: int = 0
>>> a = Number(1)
>>> a
>>> Number(val = 1)

数据比较

通常,数据对象之间需要相互比较。

两个对象 ab 之间的比较通常包括以下操作:

  • a < b
  • a > b
  • a == b
  • a >= b
  • a <= b

在 Python 中,能够在可以执行上述操作的类中定义方法。为了简单起见,不让这篇文章过于冗长,我将只展示 ==< 的实现。

通常这样写:

class Number:
    def __init__( self, val = 0):
       self.val = val

    def __eq__(self, other):
        return self.val == other.val

    def __lt__(self, other):
        return self.val < other.val

使用 dataclass

@dataclass(order = True)
class Number:
    val: int = 0

是的,就是这样简单。

我们不需要定义 __eq____lt__ 方法,因为当 order = True 被调用时,dataclass 装饰器会自动将它们添加到我们的类定义中。

那么,它是如何做到的呢?

当你使用 dataclass 时,它会在类定义中添加函数 __eq____lt__ 。我们已经知道这点了。那么,这些函数是怎样知道如何检查相等并进行比较呢?

生成 __eq__ 函数的 dataclass 类会比较两个属性构成的元组,一个由自己属性构成的,另一个由同类的其他实例的属性构成。在我们的例子中,自动生成的 __eq__ 函数相当于:

def __eq__(self, other):
    return (self.val,) == (other.val,)

让我们来看一个更详细的例子:

我们会编写一个 dataclassPerson 来保存 nameage

@dataclass(order = True)
class Person:
    name: str
    age:int = 0

自动生成的 __eq__ 方法等同于:

def __eq__(self, other):
    return (self.name, self.age) == ( other.name, other.age)

请注意属性的顺序。它们总是按照你在 dataclass 类中定义的顺序生成。

同样,等效的 __le__ 函数类似于:

def __le__(self, other):
    return (self.name, self.age) <= (other.name, other.age)

当你需要对数据对象列表进行排序时,通常会出现像 __le__ 这样的函数的定义。Python 内置的 sorted 函数依赖于比较两个对象。

>>> import random

>>> a = [Number(random.randint(1,10)) for _ in range(10)] #generate list of random numbers

>>> a

>>> [Number(val=2), Number(val=7), Number(val=6), Number(val=5), Number(val=10), Number(val=9), Number(val=1), Number(val=10), Number(val=1), Number(val=7)]

>>> sorted_a = sorted(a) #Sort Numbers in ascending order

>>> [Number(val=1), Number(val=1), Number(val=2), Number(val=5), Number(val=6), Number(val=7), Number(val=7), Number(val=9), Number(val=10), Number(val=10)]

>>> reverse_sorted_a = sorted(a, reverse = True) #Sort Numbers in descending order 

>>> reverse_sorted_a

>>> [Number(val=10), Number(val=10), Number(val=9), Number(val=7), Number(val=7), Number(val=6), Number(val=5), Number(val=2), Number(val=1), Number(val=1)]

dataclass 作为一个可调用的装饰器

定义所有的 dunder(LCTT 译注:这是指双下划线方法,即魔法方法)方法并不总是值得的。你的用例可能只包括存储值和检查相等性。因此,你只需定义 __init____eq__ 方法。如果我们可以告诉装饰器不生成其他方法,那么它会减少一些开销,并且我们将在数据对象上有正确的操作。

幸运的是,这可以通过将 dataclass 装饰器作为可调用对象来实现。

从官方文档来看,装饰器可以用作具有如下参数的可调用对象:

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
 …
  1. init:默认将生成 __init__ 方法。如果传入 False,那么该类将不会有 __init__ 方法。
  2. repr__repr__ 方法默认生成。如果传入 False,那么该类将不会有 __repr__ 方法。
  3. eq:默认将生成 __eq__ 方法。如果传入 False,那么 __eq__ 方法将不会被 dataclass 添加,但默认为 object.__eq__
  4. order:默认将生成 __gt____ge____lt____le__ 方法。如果传入 False,则省略它们。

我们在接下来会讨论 frozen。由于 unsafe_hash 参数复杂的用例,它值得单独发布一篇文章。

现在回到我们的用例,以下是我们需要的:

  1. __init__

    1. __eq__

默认会生成这些函数,因此我们需要的是不生成其他函数。那么我们该怎么做呢?很简单,只需将相关参数作为 false 传入给生成器即可。

@dataclass(repr = False) # order, unsafe_hash and frozen are False
class Number:
    val: int = 0


>>> a = Number(1)

>>> a

>>> <__main__.Number object at 0x7ff395afe898>

>>> b = Number(2)

>>> c = Number(1)

>>> a == b

>>> False

>>> a < b

>>> Traceback (most recent call last):
 File “<stdin>”, line 1, in <module>
TypeError: ‘<’ not supported between instances of ‘Number’ and ‘Number’

Frozen(不可变) 实例

Frozen 实例是在初始化对象后无法修改其属性的对象。

无法创建真正不可变的 Python 对象

在 Python 中创建对象的不可变属性是一项艰巨的任务,我将不会在本篇文章中深入探讨。

以下是我们期望不可变对象能够做到的:

>>> a = Number(10) #Assuming Number class is immutable

>>> a.val = 10 # Raises Error

有了 dataclass,就可以通过使用 dataclass 装饰器作为可调用对象配合参数 frozen=True 来定义一个 frozen 对象。

当实例化一个 frozen 对象时,任何企图修改对象属性的行为都会引发 FrozenInstanceError

@dataclass(frozen = True)
class Number:
    val: int = 0

>>> a = Number(1)

>>> a.val

>>> 1

>>> a.val = 2

>>> Traceback (most recent call last):
 File “<stdin>”, line 1, in <module>
 File “<string>”, line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field ‘val’

因此,一个 frozen 实例是一种很好方式来存储:

  • 常数
  • 设置

这些通常不会在应用程序的生命周期内发生变化,任何企图修改它们的行为都应该被禁止。

后期初始化处理

有了 dataclass,需要定义一个 __init__ 方法来将变量赋给 self 这种初始化操作已经得到了处理。但是我们失去了在变量被赋值之后立即需要的函数调用或处理的灵活性。

让我们来讨论一个用例,在这个用例中,我们定义一个 Float 类来包含浮点数,然后在初始化之后立即计算整数和小数部分。

通常是这样:

import math

class Float:
    def __init__(self, val = 0):
        self.val = val
        self.process()

    def process(self):
        self.decimal, self.integer = math.modf(self.val)

>>> a = Float( 2.2)

>>> a.decimal

>>> 0.2000

>>> a.integer

>>> 2.0

幸运的是,使用 post\_init 方法已经能够处理后期初始化操作。

生成的 __init__ 方法在返回之前调用 __post_init__ 返回。因此,可以在函数中进行任何处理。

import math

@dataclass
class FloatNumber:
    val: float = 0.0

    def __post_init__(self):
        self.decimal, self.integer = math.modf(self.val)

>>> a = Number(2.2)

>>> a.val

>>> 2.2

>>> a.integer

>>> 2.0

>>> a.decimal

>>> 0.2

多么方便!

继承

Dataclasses 支持继承,就像普通的 Python 类一样。

因此,父类中定义的属性将在子类中可用。

@dataclass
class Person:
    age: int = 0
    name: str

@dataclass
class Student(Person):
    grade: int

>>> s = Student(20, "John Doe", 12)

>>> s.age

>>> 20

>>> s.name

>>> "John Doe"

>>> s.grade

>>> 12

请注意,Student 的参数是在类中定义的字段的顺序。

继承过程中 __post_init__ 的行为是怎样的?

由于 __post_init__ 只是另一个函数,因此必须以传统方式调用它:

@dataclass
class A:
    a: int

    def __post_init__(self):
        print("A")

@dataclass
class B(A):
    b: int

    def __post_init__(self):
        print("B")

>>> a = B(1,2)

>>> B

在上面的例子中,只有 B__post_init__ 被调用,那么我们如何调用 A__post_init__ 呢?

因为它是父类的函数,所以可以用 super 来调用它。

@dataclass
class B(A):
    b: int

    def __post_init__(self):
        super().__post_init__() # 调用 A 的 post init
        print("B")

>>> a = B(1,2)

>>> A
    B

结论

因此,以上是 dataclass 使 Python 开发人员变得更轻松的几种方法。

我试着彻底覆盖大部分的用例,但是,没有人是完美的。如果你发现了错误,或者想让我注意相关的用例,请联系我。

我将在另一篇文章中介绍 dataclasses.fieldunsafe_hash

GithubTwitter 关注我。

更新:dataclasses.field 的文章可以在这里找到。


via: https://medium.com/mindorks/understanding-python-dataclasses-part-1-c3ccd4355c34

作者:Shikhar Chauhan 译者:MjSeven 校对:wxy

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

对大多数开发者来说,与 RxJS 的初次接触是通过库的形式,就像 Angular。一些函数会返回 stream ,要使用它们就得把注意力放在操作符上。

有些时候,混用响应式和非响应式代码似乎很有用。然后大家就开始热衷流的创造。不论是在编写异步代码或者是数据处理时,流都是一个不错的方案。

RxJS 提供很多方式来创建流。不管你遇到的是什么情况,都会有一个完美的创建流的方式。你可能根本用不上它们,但了解它们可以节省你的时间,让你少码一些代码。

我把所有可能的方法,按它们的主要目的,放在四个分类当中:

  • 流式化现有数据
  • 生成数据
  • 使用现有 API 进行交互
  • 选择现有的流,并结合起来

注意:示例用的是 RxJS 6,可能会以前的版本有所不同。已知的区别是你导入函数的方式不同了。

RxJS 6

import {of, from} from 'rxjs';

of(...);
from(...);

RxJS < 6

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/from';

Observable.of(...);
Observable.from(...);

//或

import { of } from 'rxjs/observable/of';
import { from } from 'rxjs/observable/from';

of(...);
from(...);

流的图示中的标记:

  • | 表示流结束了
  • X 表示流出现错误并被终结
  • ... 表示流的走向不定

流式化已有数据

你有一些数据,想把它们放到流中。有三种方式,并且都允许你把调度器当作最后一个参数传入(你如果想深入了解调度器,可以看看我的 上一篇文章)。这些生成的流都是静态的。

of

如果只有一个或者一些不同的元素,使用 of

of(1,2,3)
  .subscribe();
// 结果
// 1 2 3 |

from

如果有一个数组或者 可迭代的对象 ,而且你想要其中的所有元素发送到流中,使用 from。你也可以用它来把一个 promise 对象变成可观测的。

const foo = [1,2,3];

from(foo)
  .subscribe();
// 结果
// 1 2 3 |

pairs

流式化一个对象的键/值对。用这个对象表示字典时特别有用。

const foo = { a: 1, b: 2};

pairs(foo)
  .subscribe();
// 结果
// [a,1] [b,2] |

那么其他的数据结构呢?

也许你的数据存储在自定义的结构中,而它又没有实现 可迭代的对象 接口,又或者说你的结构是递归的、树状的。也许下面某种选择适合这些情况:

  1. 先将数据提取到数组里
  2. 使用下一节将会讲到的 generate 函数,遍历所有数据
  3. 创建一个自定义流(见下一节)
  4. 创建一个迭代器

稍后会讲到选项 2 和 3 ,因此这里的重点是创建一个迭代器。我们可以对一个 可迭代的对象 调用 from 创建一个流。 可迭代的对象 是一个对象,可以产生一个迭代器(如果你对细节感兴趣,参考 这篇 mdn 文章)。

创建一个迭代器的简单方式是 生成函数 generator function 。当你调用一个生成函数时,它返回一个对象,该对象同时遵循 可迭代的对象 接口和 迭代器 接口。

// 自定义的数据结构
class List {
  add(element) ...
  get(index) ...
  get size() ...
  ...
}

function* listIterator(list) {
  for (let i = 0; i<list.size; i++) {
    yield list.get(i);
  }
}

const myList = new List();
myList.add(1);
myList.add(3);

from(listIterator(myList))
  .subscribe(console.log);
// 结果
// 1 3 |    

调用 listIterator 函数时,返回值是一个 可迭代的对象 / 迭代器 。函数里面的代码在调用 subscribe 前不会执行。

生成数据

你知道要发送哪些数据,但想(或者必须)动态生成它。所有函数的最后一个参数都可以用来接收一个调度器。他们产生静态的流。

范围(range

从初始值开始,发送一系列数字,直到完成了指定次数的迭代。

range(10, 2)  // 从 10 开始,发送两个值
  .subscribe();
// 结果
// 10 11 |

间隔(interval) / 定时器(timer

有点像范围,但定时器是周期性的发送累加的数字(就是说,不是立即发送)。两者的区别在于在于定时器允许你为第一个元素设定一个延迟。也可以只产生一个值,只要不指定周期。

interval(1000) // 每 1000ms = 1 秒 发送数据
  .subscribe()
// 结果
// 0  1  2  3  4 ...
delay(5000, 1000) // 和上面相同,在开始前先等待 5000ms

delay(5000)
.subscribe(i => console.log("foo");
// 5 秒后打印 foo

大多数定时器将会用来周期性的处理数据:

interval(10000).pipe(
  flatMap(i => fetch("https://server/stockTicker")
).subscribe(updateChart)

这段代码每 10 秒获取一次数据,更新屏幕。

生成(generate

这是个更加复杂的函数,允许你发送一系列任意类型的对象。它有一些重载,这里你看到的是最有意思的部分:

generate(
  0,           // 从这个值开始
  x => x < 10, // 条件:只要值小于 10,就一直发送
  x => x*2     // 迭代:前一个值加倍
).subscribe();
// 结果
// 1 2 4 8 |

你也可以用它来迭代值,如果一个结构没有实现 可迭代的对象 接口。我们用前面的列表例子来进行演示:

const myList = new List();
myList.add(1);
myList.add(3);

generate(
  0,                  // 从这个值开始
  i => i < list.size, // 条件:发送数据,直到遍历完整个列表
  i => ++i,           // 迭代:获取下一个索引
  i => list.get(i)    // 选择器:从列表中取值
).subscribe();
// 结果
// 1 3 |

如你所见,我添加了另一个参数:选择器。它和 map 操作符作用类似,将生成的值转换为更有用的东西。

空的流

有时候你要传递或返回一个不用发送任何数据的流。有三个函数分别用于不同的情况。你可以给这三个函数传递调度器。emptythrowError 接收一个调度器参数。

empty

创建一个空的流,一个值也不发送。

empty()
  .subscribe();
// 结果
// |

never

创建一个永远不会结束的流,仍然不发送值。

never()
  .subscribe();
// 结果
// ...

throwError

创建一个流,流出现错误,不发送数据。

throwError('error')
  .subscribe();
// 结果
// X

挂钩已有的 API

不是所有的库和所有你之前写的代码使用或者支持流。幸运的是 RxJS 提供函数用来桥接非响应式和响应式代码。这一节仅仅讨论 RxJS 为桥接代码提供的模版。

你可能还对这篇出自 Ben Lesh全面的文章 感兴趣,这篇文章讲了几乎所有能与 promises 交互操作的方式。

from

我们已经用过它,把它列在这里是因为,它可以封装一个含有 observable 对象的 promise 对象。

from(new Promise(resolve => resolve(1)))
  .subscribe();
// 结果
// 1 |

fromEvent

fromEvent 为 DOM 元素添加一个事件监听器,我确定你知道这个。但你可能不知道的是,也可以通过其它类型来添加事件监听器,例如,一个 jQuery 对象。

const element = $('#fooButton'); // 从 DOM 元素中创建一个 jQuery 对象

from(element, 'click')
  .subscribe();
// 结果
// clickEvent ...

fromEventPattern

要理解为什么有 fromEvent 了还需要 fromEventPattern,我们得先理解 fromEvent 是如何工作的。看这段代码:

from(document, 'click')
  .subscribe();

这告诉 RxJS 我们想要监听 document 中的点击事件。在提交过程中,RxJS 发现 document 是一个 EventTarget 类型,因此它可以调用它的 addEventListener 方法。如果我们传入的是一个 jQuery 对象而非 document,那么 RxJs 知道它得调用 on 方法。

这个例子用的是 fromEventPattern ,和 fromEvent 的工作基本上一样:

function addClickHandler(handler) {
  document.addEventListener('click', handler);
}

function removeClickHandler(handler) {
  document.removeEventListener('click', handler);
}

fromEventPattern(
  addClickHandler,
  removeClickHandler,
)
.subscribe(console.log);

// 等效于
fromEvent(document, 'click')

RxJS 自动创建实际的监听器( handler )你的工作是添加或者移除监听器。fromEventPattern 的目的基本上是告诉 RxJS 如何注册和移除事件监听器。

现在想象一下你使用了一个库,你可以调用一个叫做 registerListener 的方法。我们不能再用 fromEvent,因为它并不知道该怎么处理这个对象。

const listeners = [];

class Foo {
  registerListener(listener) {
    listeners.push(listener);
  }

  emit(value) {
    listeners.forEach(listener => listener(value));
  }
}

const foo = new Foo();

fromEventPattern(listener => foo.registerListener(listener))
  .subscribe();

foo.emit(1);
// 结果
// 1 ...

当我们调用 foo.emit(1) 时,RxJS 中的监听器将被调用,然后它就能把值发送到流中。

你也可以用它来监听多个事件类型,或者结合所有可以通过回调进行通讯的 API,例如,WebWorker API:

const myWorker = new Worker('worker.js');

fromEventPattern(
  handler => { myWorker.onmessage = handler },
  handler => { myWorker.onmessage = undefined }
)
.subscribe();
// 结果
// workerMessage ...

bindCallback

它和 fromEventPattern 相似,但它能用于单个值。就在回调函数被调用时,流就结束了。用法当然也不一样 —— 你可以用 bindCallBack 封装函数,然后它就会在调用时魔术般的返回一个流:

function foo(value, callback) {
  callback(value);
}

// 没有流
foo(1, console.log); //prints 1 in the console

// 有流
const reactiveFoo = bindCallback(foo); 
// 当我们调用 reactiveFoo 时,它返回一个 observable 对象

reactiveFoo(1)
  .subscribe(console.log); // 在控制台打印 1
// 结果
// 1 |

websocket

是的,你完全可以创建一个 websocket 连接然后把它暴露给流:

import { webSocket } from 'rxjs/webSocket'; 

let socket$ = webSocket('ws://localhost:8081');

// 接收消息
socket$.subscribe(
  (msg) => console.log('message received: ' + msg),
  (err) => console.log(err),
  () => console.log('complete') * );

// 发送消息
socket$.next(JSON.stringify({ op: 'hello' }));

把 websocket 功能添加到你的应用中真的很简单。websocket 创建一个 subject。这意味着你可以订阅它,通过调用 next 来获得消息和发送消息。

ajax

如你所知:类似于 websocket,提供 AJAX 查询的功能。你可能用了一个带有 AJAX 功能的库或者框架。或者你没有用,那么我建议使用 fetch(或者必要的话用 polyfill),把返回的 promise 封装到一个 observable 对象中(参考稍后会讲到的 defer 函数)。

定制流

有时候已有的函数用起来并不是足够灵活。或者你需要对订阅有更强的控制。

主题(Subject

Subject 是一个特殊的对象,它使得你的能够把数据发送到流中,并且能够控制数据。Subject 本身就是一个可观察对象,但如果你想要把流暴露给其它代码,建议你使用 asObservable 方法。这样你就不能意外调用原始方法。

const subject = new Subject();
const observable = subject.asObservable();

observable.subscribe();

subject.next(1);
subject.next(2);
subject.complete();
// 结果
// 1 2 |

注意在订阅前发送的值将会“丢失”:

const subject = new Subject();
const observable = subject.asObservable();

subject.next(1);

observable.subscribe(console.log);

subject.next(2);
subject.complete();
// 结果
// 2

除了常规的 Subject,RxJS 还提供了三种特殊的版本。

AsyncSubject 在结束后只发送最后的一个值。

const subject = new AsyncSubject();
const observable = subject.asObservable();

observable.subscribe(console.log);

subject.next(1);
subject.next(2);
subject.complete();
// 输出
// 2

BehaviorSubject 使得你能够提供一个(默认的)值,如果当前没有其它值发送的话,这个值会被发送给每个订阅者。否则订阅者收到最后一个发送的值。

const subject = new BehaviorSubject(1);
const observable = subject.asObservable();

const subscription1 = observable.subscribe(console.log);

subject.next(2);
subscription1.unsubscribe();
// 输出
// 1
// 2
const subscription2 = observable.subscribe(console.log);

// 输出
// 2

ReplaySubject 存储一定数量、或一定时间或所有的发送过的值。所有新的订阅者将会获得所有存储了的值。

const subject = new ReplaySubject();
const observable = subject.asObservable();

subject.next(1);

observable.subscribe(console.log);

subject.next(2);
subject.complete();
// 输出
// 1
// 2

你可以在 ReactiveX 文档(它提供了一些其它的连接) 里面找到更多关于 Subject 的信息。Ben LeshOn The Subject Of Subjects 上面提供了一些关于 Subject 的理解,Nicholas Jamiesonin RxJS: Understanding Subjects 上也提供了一些理解。

可观察对象

你可以简单地用 new 操作符创建一个可观察对象。通过你传入的函数,你可以控制流,只要有人订阅了或者它接收到一个可以当成 Subject 使用的观察者,这个函数就会被调用,比如,调用 nextcompleterror

让我们回顾一下列表示例:

const myList = new List();
myList.add(1);
myList.add(3);

new Observable(observer => {
  for (let i = 0; i<list.size; i++) {
    observer.next(list.get(i));
  }

  observer.complete();
})
.subscribe();
// 结果
// 1 3 |

这个函数可以返回一个 unsubcribe 函数,当有订阅者取消订阅时这个函数就会被调用。你可以用它来清楚或者执行一些收尾操作。

new Observable(observer => {
  // 流式化

  return () => {
                 //clean up
               };
})
.subscribe();

继承可观察对象

在有可用的操作符前,这是一种实现自定义操作符的方式。RxJS 在内部扩展了 可观察对象Subject 就是一个例子,另一个是 publisher 操作符。它返回一个 ConnectableObservable 对象,该对象提供额外的方法 connect

实现 Subscribable 接口

有时候你已经用一个对象来保存状态,并且能够发送值。如果你实现了 Subscribable 接口,你可以把它转换成一个可观察对象。Subscribable 接口中只有一个 subscribe 方法。

interface Subscribable<T> {  subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void), error?: (error: any) => void, complete?: () => void): Unsubscribable}

结合和选择现有的流

知道怎么创建一个独立的流还不够。有时候你有好几个流但其实只需要一个。有些函数也可作为操作符,所以我不打算在这里深入展开。推荐看看 Max NgWizard K 所写的一篇 文章,它还包含一些有趣的动画。

还有一个建议:你可以通过拖拽元素的方式交互式的使用结合操作,参考 RxMarbles

ObservableInput 类型

期望接收流的操作符和函数通常不单独和可观察对象一起工作。相反,它们实际上期望的参数类型是 ObservableInput,定义如下:

type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>;

这意味着你可以传递一个 promises 或者数组却不需要事先把他们转换成可观察对象。

defer

主要的目的是把一个 observable 对象的创建延迟(defer)到有人想要订阅的时间。在以下情况,这很有用:

  • 创建可观察对象的开销较大
  • 你想要给每个订阅者新的可观察对象
  • 你想要在订阅时候选择不同的可观察对象
  • 有些代码必须在订阅之后执行

最后一点包含了一个并不起眼的用例:Promises(defer 也可以返回一个 promise 对象)。看看这个用到了 fetch API 的例子:

function getUser(id) {
  console.log("fetching data");
  return fetch(`https://server/user/${id}`);
}

const userPromise = getUser(1);
console.log("I don't want that request now");

// 其它地方
userPromise.then(response => console.log("done");
// 输出
// fetching data
// I don't want that request now
// done

只要流在你订阅的时候执行了,promise 就会立即执行。我们调用 getUser 的瞬间,就发送了一个请求,哪怕我们这个时候不想发送请求。当然,我们可以使用 from 来把一个 promise 对象转换成可观察对象,但我们传递的 promise 对象已经创建或执行了。defer 让我们能够等到订阅才发送这个请求:

const user$ = defer(() => getUser(1));

console.log("I don't want that request now");

// 其它地方
user$.subscribe(response => console.log("done");
// 输出
// I don't want that request now
// fetching data
// done

iif

iif 包含了一个关于 defer 的特殊用例:在订阅时选择两个流中的一个:

iif(
  () => new Date().getHours() < 12,
  of("AM"),
  of("PM")
)
.subscribe();
// 结果
// AM before noon, PM afterwards

引用该文档:

实际上 iif 能够轻松地用 defer 实现,它仅仅是出于方便和可读性的目的。

onErrorResumeNext

开启第一个流并且在失败的时候继续进行下一个流。错误被忽略掉。

const stream1$ = of(1, 2).pipe(
  tap(i => { if(i>1) throw 'error'}) //fail after first element
);

const stream2$ = of(3,4);

onErrorResumeNext(stream1$, stream2$)
  .subscribe(console.log);
// 结果
// 1 3 4 |

如果你有多个 web 服务,这就很有用了。万一主服务器开启失败,那么备份的服务就能自动调用。

forkJoin

它让流并行运行,当流结束时发送存在数组中的最后的值。由于每个流只有最后一个值被发送,它一般用在只发送一个元素的流的情况,就像 HTTP 请求。你让请求并行运行,在所有流收到响应时执行某些任务。

function handleResponses([user, account]) {
  // 执行某些任务
}

forkJoin(
  fetch("https://server/user/1"),
  fetch("https://server/account/1")
)
.subscribe(handleResponses);

merge / concat

发送每一个从可观察对象源中发出的值。

merge 接收一个参数,让你定义有多少流能被同时订阅。默认是无限制的。设为 1 就意味着监听一个源流,在它结束的时候订阅下一个。由于这是一个常见的场景,RxJS 为你提供了一个显示的函数:concat

merge(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2)),
  timer(0, 1000).pipe(mapTo("Stream 3"), take(2)),
  2 //two concurrent streams
)
.subscribe();

// 只订阅流 1 和流 2

// 输出
// Stream 1 -> after 1000ms
// Stream 2 -> after 1200ms
// Stream 1 -> after 2000ms

// 流 1 结束后,开始订阅流 3

// 输出
// Stream 3 -> after 0 ms
// Stream 2 -> after 400 ms (2400ms from beginning)
// Stream 3 -> after 1000ms

merge(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2))
  1
)
// 等效于
concat(
  interval(1000).pipe(mapTo("Stream 1"), take(2)),
  interval(1200).pipe(mapTo("Stream 2"), take(2))
)

// 输出
// Stream 1 -> after 1000ms
// Stream 1 -> after 2000ms
// Stream 2 -> after 3200ms
// Stream 2 -> after 4400ms

zip / combineLatest

mergeconcat 一个接一个的发送所有从源流中读到的值,而 zipcombineLatest 是把每个流中的一个值结合起来一起发送。zip 结合所有源流中发送的第一个值。如果流的内容相关联,那么这就很有用。

zip(
  interval(1000),
  interval(1200),
)
.subscribe();
// 结果
// [0, 0] [1, 1] [2, 2] ...

combineLatest 与之类似,但结合的是源流中发送的最后一个值。直到所有源流至少发送一个值之后才会触发事件。这之后每次源流发送一个值,它都会把这个值与其他流发送的最后一个值结合起来。

combineLatest(
  interval(1000),
  interval(1200),
)
.subscribe();
// 结果
// [0, 0] [1, 0] [1, 1] [2, 1] ...

两个函数都让允许传递一个选择器函数,把元素结合成其它对象而不是数组:

zip(
  interval(1000),
  interval(1200),
  (e1, e2) -> e1 + e2
)
.subscribe();
// 结果
// 0 2 4 6 ...

race

选择第一个发送数据的流。产生的流基本是最快的。

race(
  interval(1000),
  of("foo")
)
.subscribe();
// 结果
// foo |

由于 of 立即产生一个值,因此它是最快的流,然而这个流就被选中了。

总结

已经有很多创建可观察对象的方式了。如果你想要创造响应式的 API 或者想用响应式的 API 结合传统 API,那么了解这些方法很重要。

我已经向你展示了所有可用的方法,但它们其实还有很多内容可以讲。如果你想更加深入地了解,我极力推荐你查阅 文档 或者阅读相关文章。

RxViz 是另一种值得了解的有意思的方式。你编写 RxJS 代码,产生的流可以用图形或动画进行显示。


via: https://blog.angularindepth.com/the-extensive-guide-to-creating-streams-in-rxjs-aaa02baaff9a

作者:Oliver Flaggl 译者:BriFuture 校对:wxy

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

得到我的 awk 秘籍。

我管理着一个个人网站,用手工编辑网站上的网页。由于网站上的页面并不多,这种方法对我很适合,可以让我对网站代码的细节一清二楚。

最近我升级了网站的设计样式,我决定把所有的普通引号都转换成“花引号”,即在打印材料中使用的那种引号:用 “” 来代替 ""。

手工修改所有的引号太耗时了,因此我决定将这个转换所有 HTML 文件中引号的过程自动化。不过通过程序或脚本来实现该功能需要费点劲。这个脚本需要知道何时将普通引号转换成花引号,并决定使用哪种引号(LCTT 译注:左引号还是右引号,单引号还是双引号)。

有多种方法可以转换引号。Greg Pittman 写过一个 Python 脚本 来修正文本中的花引号。而我自己使用 GNU awk (gawk) 来实现。

下载我的 awk 秘籍。免费下载

开始之前,我写了一个简单的 gawk 函数来评估单个字符。若该字符是一个引号,这该函数判断是输出普通引号还是花引号。函数查看前一个字符;若前一个字符是空格,则函数输出左花引号。否则函数输出右花引号。脚本对单引号的处理方式也一样。

function smartquote (char, prevchar) {
        # print smart quotes depending on the previous character
        # otherwise just print the character as-is

        if (prevchar ~ /\s/) {
                # prev char is a space
                if (char == "'") {
                        printf("&lsquo;");
                }
                else if (char == "\"") {
                        printf("&ldquo;");
                }
                else {
                        printf("%c", char);
                }
        }
        else {
                # prev char is not a space
                if (char == "'") {
                        printf("&rsquo;");
                }
                else if (char == "\"") {
                        printf("&rdquo;");
                }
                else {
                        printf("%c", char);
                }
        }
}

这个 gawk 脚本的主体部分通过该函数处理 HTML 输入文件的一个个字符。该脚本在 HTML 标签内部逐字原样输出所有内容(比如,<html lang="en">)。在 HTML 标签外,脚本使用 smartquote() 函数来输出文本。smartquote() 函数来评估是输出普通引号还是花引号。

function smartquote (char, prevchar) {
        ...
}

BEGIN {htmltag = 0}

{
        # for each line, scan one letter at a time:

        linelen = length($0);

        prev = "\n";

        for (i = 1; i <= linelen; i++) {
                char = substr($0, i, 1);

                if (char == "<") {
                        htmltag = 1;
                }

                if (htmltag == 1) {
                        printf("%c", char);
                }
                else {
                        smartquote(char, prev);
                        prev = char;
                }

                if (char == ">") {
                        htmltag = 0;
                }
        }

        # add trailing newline at end of each line
        printf ("\n");
}

下面是一个例子:

gawk -f quotes.awk test.html > test2.html

其输入为:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Test page</title>
  <link rel="stylesheet" type="text/css" href="/test.css" />
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width" />
</head>
<body>
  <h1><a href="/"><img src="logo.png" alt="Website logo" /></a></h1>
  <p>"Hi there!"</p>
  <p>It's and its.</p>
</body>
</html>

其输出为:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Test page</title>
  <link rel="stylesheet" type="text/css" href="/test.css" />
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width" />
</head>
<body>
  <h1><a href="/"><img src="logo.png" alt="Website logo" /></a></h1>
  <p>&ldquo;Hi there!&rdquo;</p>
  <p>It&rsquo;s and its.</p>
</body>
</html>

via: https://opensource.com/article/18/8/gawk-script-convert-smart-quotes

作者:Jim Hall 选题:lujun9972 译者:lujun9972 校对:wxy

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

从事电子邮件营销, 准入 opt-in 邮箱列表是必不可少的。你可能已经有了准入列表,同时还使用电子邮件客户端软件。如果你能从电子邮件客户端中导出准入列表,那这份列表想必是极好的。

我使用一些代码来将 outlook 配置中的所有邮件写入一个临时文件中,现在让我来尝试解释一下这些代码。

首先你需要导入 win32com.client,为此你需要安装 pywin32:

pip install pywin32

我们需要通过 MAPI 协议连接 Outlok:

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")

然后从 outlook 配置中获取所有的账户:

accounts= win32com.client.Dispatch("Outlook.Application").Session.Accounts;

在然后需要从名为 emaileri\_al 的收件箱中获取邮件:

def emailleri_al(folder):
    messages = folder.Items
    a=len(messages)
    if a>0:
        for message2 in messages:
             try:
                sender = message2.SenderEmailAddress
                if sender != "":
                    print(sender, file=f)
             except:
                print("Ben hatayım")
                print(account.DeliveryStore.DisplayName)
                pass

             try:
                message2.Save
                message2.Close(0)
             except:
                 pass

你需要进入所有账户的所有收件箱中获取电子邮件:

for account in accounts:
    global inbox
    inbox = outlook.Folders(account.DeliveryStore.DisplayName)
    print("****Account Name**********************************",file=f)
    print(account.DisplayName,file=f)
    print(account.DisplayName)
    print("***************************************************",file=f)
    folders = inbox.Folders

    for folder in folders:
        print("****Folder Name**********************************", file=f)
        print(folder, file=f)
        print("*************************************************", file=f)
        emailleri_al(folder)
        a = len(folder.folders)

        if a>0 :
            global z
            z = outlook.Folders(account.DeliveryStore.DisplayName).Folders(folder.name)
            x = z.Folders
            for y in x:
                emailleri_al(y)
                print("****Folder Name**********************************", file=f)
                print("..."+y.name,file=f)
                print("*************************************************", file=

下面是完整的代码:

import win32com.client
import win32com
import os
import sys

f = open("testfile.txt","w+")

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
accounts= win32com.client.Dispatch("Outlook.Application").Session.Accounts;

def emailleri_al(folder):
    messages = folder.Items
    a=len(messages)
    if a>0:
        for message2 in messages:
             try:
                sender = message2.SenderEmailAddress
                if sender != "":
                    print(sender, file=f)
             except:
                print("Error")
                print(account.DeliveryStore.DisplayName)
                pass

             try:
                message2.Save
                message2.Close(0)
             except:
                 pass



for account in accounts:
    global inbox
    inbox = outlook.Folders(account.DeliveryStore.DisplayName)
    print("****Account Name**********************************",file=f)
    print(account.DisplayName,file=f)
    print(account.DisplayName)
    print("***************************************************",file=f)
    folders = inbox.Folders

    for folder in folders:
        print("****Folder Name**********************************", file=f)
        print(folder, file=f)
        print("*************************************************", file=f)
        emailleri_al(folder)
        a = len(folder.folders)

        if a>0 :
            global z
            z = outlook.Folders(account.DeliveryStore.DisplayName).Folders(folder.name)
            x = z.Folders
            for y in x:
                emailleri_al(y)
                print("****Folder Name**********************************", file=f)
                print("..."+y.name,file=f)
                print("*************************************************", file=f)



print("Finished Succesfully")

via: https://www.codementor.io/aliacetrefli/how-to-read-outlook-emails-by-python-jkp2ksk95

作者:A.A. Cetrefli 选题:lujun9972 译者:lujun9972 校对:wxy

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