2018年9月

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中国 荣誉推出

cmd/compile 包含构成 Go 编译器主要的包。编译器在逻辑上可以被分为四个阶段,我们将简要介绍这几个阶段以及包含相应代码的包的列表。

在谈到编译器时,有时可能会听到 前端 front-end 后端 back-end 这两个术语。粗略地说,这些对应于我们将在此列出的前两个和后两个阶段。第三个术语 中间端 middle-end 通常指的是第二阶段执行的大部分工作。

请注意,go/parsergo/typesgo/* 系列的包与编译器无关。由于编译器最初是用 C 编写的,所以这些 go/* 包被开发出来以便于能够写出和 Go 代码一起工作的工具,例如 gofmtvet

需要澄清的是,名称 “gc” 代表 “ Go 编译器 Go compiler ”,与大写 GC 无关,后者代表 垃圾收集 garbage collection

1、解析

  • cmd/compile/internal/syntax 词法分析器 lexer 解析器 parser 语法树 syntax tree

在编译的第一阶段,源代码被标记化(词法分析)、解析(语法分析),并为每个源文件构造语法树(LCTT 译注:这里标记指 token,它是一组预定义的、能够识别的字符串,通常由名字和值构成,其中名字一般是词法的类别,如标识符、关键字、分隔符、操作符、文字和注释等;语法树,以及下文提到的 抽象语法树 Abstract Syntax Tree (AST),是指用树来表达程序设计语言的语法结构,通常叶子节点是操作数,其它节点是操作码)。

每个语法树都是相应源文件的确切表示,其中节点对应于源文件的各种元素,例如表达式、声明和语句。语法树还包括位置信息,用于错误报告和创建调试信息。

2、类型检查和 AST 变换

  • cmd/compile/internal/gc(创建编译器 AST, 类型检查 type-checking AST 变换 AST transformation

gc 包中包含一个继承自(早期)C 语言实现的版本的 AST 定义。所有代码都是基于它编写的,所以 gc 包必须做的第一件事就是将 syntax 包(定义)的语法树转换为编译器的 AST 表示法。这个额外步骤可能会在将来重构。

然后对 AST 进行类型检查。第一步是名字解析和类型推断,它们确定哪个对象属于哪个标识符,以及每个表达式具有的类型。类型检查包括特定的额外检查,例如“声明但未使用”以及确定函数是否会终止。

特定变换也基于 AST 完成。一些节点被基于类型信息而细化,例如把字符串加法从算术加法的节点类型中拆分出来。其它一些例子是 死代码消除 dead code elimination 函数调用内联 function call inlining 逃逸分析 escape analysis (LCTT 译注:逃逸分析是一种分析指针有效范围的方法)。

3、通用 SSA

  • cmd/compile/internal/gc(转换成 SSA)
  • cmd/compile/internal/ssa(SSA 相关的 环节 pass 和规则)

(LCTT 译注:许多常见高级语言的编译器无法通过一次扫描源代码或 AST 就完成所有编译工作,取而代之的做法是多次扫描,每次完成一部分工作,并将输出结果作为下次扫描的输入,直到最终产生目标代码。这里每次扫描称作一个 环节 pass ;最后一个环节之前所有的环节得到的结果都可称作中间表示法,本文中 AST、SSA 等都属于中间表示法。SSA,静态单赋值形式,是中间表示法的一种性质,它要求每个变量只被赋值一次且在使用前被定义)。

在此阶段,AST 将被转换为 静态单赋值 Static Single Assignment (SSA)形式,这是一种具有特定属性的低级 中间表示法 intermediate representation ,可以更轻松地实现优化并最终从它生成机器码。

在这个转换过程中,将完成 内置函数 function intrinsics 的处理。这些是特殊的函数,编译器被告知逐个分析这些函数并决定是否用深度优化的代码替换它们(LCTT 译注:内置函数指由语言本身定义的函数,通常编译器的处理方式是使用相应实现函数的指令序列代替对函数的调用指令,有点类似内联函数)。

在 AST 转化成 SSA 的过程中,特定节点也被低级化为更简单的组件,以便于剩余的编译阶段可以基于它们工作。例如,内建的拷贝被替换为内存移动,range 循环被改写为 for 循环。由于历史原因,目前这里面有些在转化到 SSA 之前发生,但长期计划则是把它们都移到这里(转化 SSA)。

然后,一系列机器无关的规则和编译环节会被执行。这些并不考虑特定计算机体系结构,因此对所有 GOARCH 变量的值都会运行。

这类通用的编译环节的一些例子包括,死代码消除、移除不必要的空值检查,以及移除无用的分支等。通用改写规则主要考虑表达式,例如将一些表达式替换为常量,优化乘法和浮点操作。

4、生成机器码

  • cmd/compile/internal/ssa(SSA 低级化和架构特定的环节)
  • cmd/internal/obj(机器码生成)

编译器中机器相关的阶段开始于“低级”的编译环节,该阶段将通用变量改写为它们的特定的机器码形式。例如,在 amd64 架构中操作数可以在内存中操作,这样许多 加载-存储 load-store 操作就可以被合并。

注意低级的编译环节运行所有机器特定的重写规则,因此当前它也应用了大量优化。

一旦 SSA 被“低级化”并且更具体地针对目标体系结构,就要运行最终代码优化的编译环节了。这包含了另外一个死代码消除的环节,它将变量移动到更靠近它们使用的地方,移除从来没有被读过的局部变量,以及 寄存器 register 分配。

本步骤中完成的其它重要工作包括 堆栈布局 stack frame layout ,它将堆栈偏移位置分配给局部变量,以及 指针活性分析 pointer liveness analysis ,后者计算每个垃圾收集安全点上的哪些堆栈上的指针仍然是活动的。

在 SSA 生成阶段结束时,Go 函数已被转换为一系列 obj.Prog 指令。它们被传递给汇编程序(cmd/internal/obj),后者将它们转换为机器码并输出最终的目标文件。目标文件还将包含反射数据,导出数据和调试信息。

扩展阅读

要深入了解 SSA 包的工作方式,包括它的环节和规则,请转到 cmd/compile/internal/ssa/README.md


via: https://github.com/golang/go/blob/master/src/cmd/compile/README.md

作者:mvdan 译者:stephenxs 校对:pityonline, wxy

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

如果你在寻找一种基于日出和日落时间自动更改 Gtk 主题的简单方法,请尝试一下 AutomaThemely

AutomaThemely 是一个 Python 程序,它可以根据光亮和黑暗时间自动更改 Gnome 主题,如果你想在夜间使用黑暗的 Gtk 主题并在白天使用明亮的 Gtk 主题,那么它非常有用。

虽然该程序是为 Gnome 桌面制作的,但它也适用于 Unity。AutomaThemely 不支持不使用 org.gnome.desktop.interface Gsettings 的桌面环境,如 Cinnamon,的 Gtk 主题,或者更改图标主题,至少现在还不行。它也不支持设置 Gnome Shell 主题。

除了自动更改 Gtk3 主题外,AutomaThemely 还可以自动切换 Atom 编辑器和 VSCode 的明暗主题,以及 Atom 编辑器的明暗语法高亮。这显然也是基于一天中的时间完成的。

AutomaThemely Atom 和 VSCode 主题/语法设置

程序使用你的 IP 地址来确定你的位置,以便检索日出和日落时间,并且需要有可用的 Internet 连接。但是,你可以从程序用户界面禁用自动定位,并手动输入你的位置。

在 AutomaThemely 用户界面中,你还可以输入日出和日落时间的偏移(以分钟为单位),并启用或禁用主题更改的通知。

下载/安装 AutomaThemely

Ubuntu 18.04:使用上面的链接,下载包含依赖项的 Python 3.6 DEB(python3.6-automathemely_1.2_all.deb)。

Ubuntu 16.04:你需要下载并安装 AutomaThemely Python 3.5 DEB,它不包含依赖项(python3.5-no_deps-automathemely_1.2_all.deb),并使用 PIP3 分别安装依赖项(requestsastralpytztzlocalschedule):

sudo apt install python3-pip
python3 -m pip install --user requests astral pytz tzlocal schedule

AutomaThemely 下载页面还包含 Python 3.5 或 3.6 的 RPM 包,有包含和不包含依赖项两种。安装适合你的 Python 版本的软件包。如果你下载了包含依赖项的包但无法在你的系统上使用,请下载 “no\_deps” 包并如上所述使用 PIP3 安装 Python3 依赖项。

使用 AutomaThemely 根据太阳时间更改明亮/黑暗 Gtk 主题

安装完成后,运行 AutomaThemely 一次以生成配置文件。单击 AutomaThemely 菜单条目或在终端中运行:

automathemely

这不会运行任何 GUI,它只生成配置文件。

使用 AutomaThemely 有点反直觉。你将在菜单中看到 AutomaThemely 图标,但单击它不会打开任何窗口/GUI。如果你使用支持列表跳转/快捷列表的 Gnome 或其他基于 Gnome 的桌面,你可以右键单击菜单中的 AutomaThemely 图标(或者你可以将其固定为 Dash/dock 并在那里右键单击它)并选择 Manage Settings 启动GUI:

你还可以使用以下命令从命令行启动 AutomaThemely GUI:

automathemely --manage

配置要使用的主题后,你需要更新太阳的时间并重新启动 AutomaThemely 调度器。你可以通过右键单击 AutomaThemely 图标(应该在 Unity/Gnome 中可用)并选择 “Update sun times” 来更新太阳时间,然后选择 “Restart the scheduler” 来重启调度器完成此操作。你也可以使用以下命令从终端执行此操作:

automathemely --update
automathemely --restart

via: https://www.linuxuprising.com/2018/08/automatically-switch-to-light-dark-gtk.html

作者:Logix 选题:lujun9972 译者:geekpi 校对:wxy

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

迄今我因 BSD 是 自由及开源软件 Free and Open Source Software (FOSS)已经写了数篇关于它的文章。但总有人会问:“为什么要纠结于 BSD?”。我认为最好的办法是写一篇关于这个话题的文章。

为什么用 BSD 取代 Linux?

为了准备这篇文章,我与几位 BSD 的用户聊了聊,其中有人使用了多年 Linux 而后转入 BSD。因而这篇文章的观点都来源于真实的 BSD 用户。本文希望提出一个不同的观点。

why use bsd over linux

1、BSD 不仅仅是一个内核

几个人都指出 BSD 提供的操作系统对于终端用户来说就是一个巨大而统一的软件包。他们指出所谓 “Linux” 仅仅说的是内核。一个 Linux 发行版由上述的内核与许多由发行者所选取的不同的应用与软件包组成。有时候安装新的软件包所导致的不兼容会使系统产生崩溃。

一个典型的 BSD 由内核和许多必要的软件包组成。这些包里的大多数是通过活跃的项目所开发,因此其具备高集成度与高响应度的特点。

2、软件包更值得信赖

说起软件包,BSD 用户提出的另一点是软件包的可信度。在 Linux 上,软件包可以从一堆不同的源上获得,一些是发行版的开发者提供的,另一些是第三方。Ubuntu其他发行版就遇到了在第三方应用里隐藏了恶意软件的问题。

在 BSD 上,所有的软件包由“集中式软件包/ ports 系统”所提供,“每个软件包都是单一仓库的一部分,并且每一步都设有安全系统”。这就确保了黑客不能将恶意软件潜入到看似稳定的应用程序中,保障了 BSD 的长期稳定性。

3、更新缓慢 = 更好的长期稳定性

如果更新是一场竞赛,那么 Linux 就是兔子,BSD 就是乌龟。甚至最慢的 Linux 发行版每年至少发布一个新版本(当然,除了 Debian)。在 BSD 的世界里,重大版本的发布需要更长时间。这就意味着可以更关注于将事情做完善之后再将它推送给用户。

这也意味着操作系统的变化会随着时间的推移而发生。Linux 世界经历了数次快速而重大的变化,我们至今仍感觉如此(咳咳, systemD,咳咳)。就像 Debian 那样,长时间的开发周期可以帮助 BSD 去测试新的想法,保证在它在永久改变之前正常工作。它也有助于生产出不太可能出现问题的代码。

4、Linux 太乱了

没有一个 BSD 用户直截了当地指出这一点,但这是他们许多经验所显示出的情况。很多用户从一个 Linux 发行版跳到另一个发行版去寻找适合他的版本。很多情况下,他们无法使所有的软件或硬件正常工作。这时,他们决定尝试使用 BSD,接着,所有的东西都正常工作了。

当考虑到如何选择 BSD 时,一切就变得相当简单。目前只有六个 BSD 发行版在积极开发。这些 BSD 中的每一个都有特定的用途。“OpenBSD 更安全,FreeBSD 适用于桌面或服务器,NetBSD 无所不包,DragonFlyBSD 精简高效”。与此同时,充斥着 Linux 世界的许多发行版仅仅是在现有的发行版上增加了主题或者图标而已。BSD 项目数量之少意味着它重复性低并且更加专注。

5、ZFS 支持

一个 BSD 用户说到他选择 BSD 最主要的原因是 ZFS。事实上,几乎所有我谈过的人都提到 BSD 支持 ZFS 是他们没有返回 Linux 的原因。

这一点是 Linux 从一开始就处于下风的地方。虽然在一些 Linux 发行版上可以使用 OpenZFS,但是 ZFS 已经内置在了 BSD 的内核中。这意味着 ZFS 在 BSD 上将会有更好地性能。尽管有过将 ZFS 加入到 Linux 内核中的数次尝试,但许可证问题依旧无法解决。

6、许可证

就许可证而言也有不同的看法。大多数人所持有的想法是,GPL 不是真正的自由,因为它限制了如何使用软件。一些人也认为 GPL “太庞大而复杂而难于理解,如果在开发过程中不仔细检查许可证会导致法律问题。”

另一方面,BSD 协议只有 3 条,并且允许任何人“使用软件、进行修改、做任何事,并且对开发者提供了保护”。

总结

这些仅仅只是一小部分人们使用 BSD 而不使用 Linux 的原因。如果你感兴趣,你可以在这阅读其他人的评论。如果你是 BSD 用户并且觉得我错过什么重要的地方,请在评论里说出你的想法。

如果你觉得这篇文章有意思,请在社交媒体上、技术资讯或者 Reddit 上分享它。


via: https://itsfoss.com/why-use-bsd/

作者:John Paul 选题:lujun9972 译者:LuuMing 校对: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中国 荣誉推出