分类 软件开发 下的文章

这 12 个步骤能确保成功发布。

你写了一个 Python 库。自己觉着这太棒了!如果让人们能够轻松使用它不是很优雅么?这有一个需要考虑的清单,以及在开源 Python 库时要采取的具体步骤。

1、源码

将代码放在 GitHub 上,这里有很多开源项目,并且人们很容易提交拉取请求。

2、许可证

选择一个开源许可证。一般来说 MIT 许可证是一个挺好的宽容许可证。如果你有特定要求,Creative Common 的选择许可证可以指导你完成其它选择。最重要的是,在选择许可证时要记住三条规则:

  • 不要创建自己的许可证。
  • 不要创建自己的许可证。
  • 不要创建自己的许可证。

3、README

将一个名为 README.rst 的文件(使用 ReStructured Text 格式化)放在项目树的顶层。

GitHub 将像 Markdown 一样渲染 ReStructured Text,而 ReST 在 Python 的文档生态系统中的表现更好。

4、测试

写测试。这对你来说没有用处。但对于想要编写避免破坏相关功能的补丁的人来说,它非常有用。

测试可帮助协作者进行协作。

通常情况下,如果可以用 pytest 运行就最好了。还有其他测试工具 —— 但很少有理由去使用它们。

5、样式

使用 linter 制定样式:PyLint、Flake8 或者带上 --check 的 Black 。除非你使用 Black,否则请确保在一个文件中指定配置选项,并签入到版本控制系统中。

6、API 文档

使用 docstrings 来记录模块、函数、类和方法。

你可以使用几种样式。我更喜欢 Google 风格的 docstrings,但 ReST docstrings 也是一种选择。

Sphinx 可以同时处理 Google 风格和 ReST 的 docstrings,以将零散的文档集成为 API 文档。

7、零散文档

使用 Sphinx。(阅读我们这篇文章。)教程很有用,但同样重要的是要指明这是什么、它有什么好处、它有什么坏处、以及任何特殊的考虑因素。

8、构建

使用 tox 或 nox 自动运行测试和 linter,并构建文档。这些工具支持“依赖矩阵”。这些矩阵往往会快速增长,但你可以尝试针对合理的样本进行测试,例如 Python 版本、依赖项版本以及可能安装的可选依赖项。

9、打包

使用 setuptools 工具。写一个 setup.py 和一个 setup.cfg。如果同时支持 Python 2 和 3,请在 setup.cfg 中指定 universal 格式的 wheel。

tox 或 nox 应该做的一件事是构建 wheel 并对已安装的 wheel 进行测试。

避免使用 C 扩展。如果出于性能或绑定的原因一定需要它们,请将它们放在单独的包中。正确打包 C 扩展可以写一篇新的文章。这里有很多问题!

10、持续集成

使用公共持续工具。TravisCICircleCI 为开源项目提供免费套餐。将 GitHub 或其他仓库配置为在合并拉请求之前需要先通过检查,那么你就不必担心在代码评审中告知用户修复测试或样式。

11、版本

使用 SemVerCalVer。有许多工具可以帮助你管理版本:incrementalbumpversionsetuptools\_scm 等都是 PyPI 上的包,都可以帮助你管理版本。

12、发布

通过运行 tox 或 nox 并使用 twine 将文件上传到 PyPI 上发布。你可以通过在 DevPI 中“测试上传”。


via: https://opensource.com/article/18/12/tips-open-sourcing-python-libraries

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

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

使用此框架编写断言,提高开发测试的准确性。

测试金字塔的底部是单元测试。单元测试每次只测试一个代码单元,通常是一个函数或方法。

通常,设计单个单元测试是为了测试通过一个函数或特定分支的特定执行流程,这使得将失败的单元测试和导致失败的 bug 对应起来变得容易。

理想情况下,单元测试很少使用或不使用外部资源,从而隔离它们并使它们更快。

单元测试套件通过在开发过程的早期发现问题来帮助维护高质量的产品。有效的单元测试可以在代码离开开发人员机器之前捕获 bug,或者至少可以在特定分支上的持续集成环境中捕获 bug。这标志着好的和坏的单元测试之间的区别:好的测试通过尽早捕获 bug 并使测试更快来提高开发人员的生产力。坏的测试降低了开发人员的工作效率。

当测试附带的特性时,生产率通常会降低。当代码更改时测试会失败,即使它仍然是正确的。发生这种情况是因为输出的不同,但在某种程度上是因为它不是 函数契约 function’s contract 的一部分。

因此,一个好的单元测试可以帮助执行函数所提交的契约。

如果单元测试中断,那意味着该契约被违反了,应该(通过更改文档和测试)明确修改,或者(通过修复代码并保持测试不变)来修复。

虽然将测试限制为只执行公共契约是一项需要学习的复杂技能,但有一些工具可以提供帮助。

其中一个工具是 Hamcrest,这是一个用于编写断言的框架。最初是为基于 Java 的单元测试而发明的,但它现在支持多种语言,包括 Python

Hamcrest 旨在使测试断言更容易编写和更精确。

def add(a, b):
    return a + b

from hamcrest import assert_that, equal_to

def test_add():
    assert_that(add(2, 2), equal_to(4))  

这是一个用于简单函数的断言。如果我们想要断言更复杂的函数怎么办?

def test_set_removal():
    my_set = {1, 2, 3, 4}
    my_set.remove(3)
    assert_that(my_set, contains_inanyorder([1, 2, 4]))
    assert_that(my_set, is_not(has_item(3)))

注意,我们可以简单地断言其结果是任何顺序的 124,因为集合不保证顺序。

我们也可以很容易用 is_not 来否定断言。这有助于我们编写精确的断言,使我们能够把自己限制在执行函数的公共契约方面。

然而,有时候,内置的功能都不是我们真正需要的。在这些情况下,Hamcrest 允许我们编写自己的 匹配器 matchers

想象一下以下功能:

def scale_one(a, b):
    scale = random.randint(0, 5)
    pick = random.choice([a,b])
    return scale * pick

我们可以自信地断言其结果均匀地分配到至少一个输入。

匹配器继承自 hamcrest.core.base_matcher.BaseMatcher,重写两个方法:

class DivisibleBy(hamcrest.core.base_matcher.BaseMatcher):
    def __init__(self, factor):
        self.factor = factor

    def _matches(self, item):
        return (item % self.factor) == 0

    def describe_to(self, description):
        description.append_text('number divisible by')
        description.append_text(repr(self.factor))

编写高质量的 describe_to 方法很重要,因为这是测试失败时显示的消息的一部分。

def divisible_by(num):
    return DivisibleBy(num)

按照惯例,我们将匹配器包装在一个函数中。有时这给了我们进一步处理输入的机会,但在这种情况下,我们不需要进一步处理。

def test_scale():
    result = scale_one(3, 7)
    assert_that(result,
                any_of(divisible_by(3),
                divisible_by(7)))

请注意,我们将 divisible_by 匹配器与内置的 any_of 匹配器结合起来,以确保我们只测试函数提交的内容。

在编辑这篇文章时,我听到一个传言,取 “Hamcrest” 这个名字是因为它是 “matches” 字母组成的字谜。嗯…

>>> assert_that("matches", contains_inanyorder(*"hamcrest")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/moshez/src/devops-python/build/devops/lib/python3.6/site-packages/hamcrest/core/assert_that.py", line 43, in assert_that
    _assert_match(actual=arg1, matcher=arg2, reason=arg3)
  File "/home/moshez/src/devops-python/build/devops/lib/python3.6/site-packages/hamcrest/core/assert_that.py", line 57, in _assert_match
    raise AssertionError(description)
AssertionError:
Expected: a sequence over ['h', 'a', 'm', 'c', 'r', 'e', 's', 't'] in any order
      but: no item matches: 'r' in ['m', 'a', 't', 'c', 'h', 'e', 's']

经过进一步的研究,我找到了传言的来源:它是 “matchers” 字母组成的字谜。

>>> assert_that("matchers", contains_inanyorder(*"hamcrest"))
>>>

如果你还没有为你的 Python 代码编写单元测试,那么现在是开始的好时机。如果你正在为你的 Python 代码编写单元测试,那么使用 Hamcrest 将允许你使你的断言更加精确,既不会比你想要测试的多也不会少。这将在修改代码时减少误报,并减少修改工作代码的测试所花费的时间。


via: https://opensource.com/article/18/8/robust-unit-tests-hamcrest

作者:Moshe Zadka 选题:lujun9972 译者:MjSeven 校对:wxy

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

使用 Node.js 构建一个根据询问创建文件的命令行工具。

当用于构建命令行界面(CLI)时,Node.js 十分有用。在这篇文章中,我将会教你如何使用 Node.js 来构建一个问一些问题并基于回答创建一个文件的命令行工具。

开始

首先,创建一个新的 npm 包(NPM 是 JavaScript 包管理器)。

mkdir my-script
cd my-script
npm init

NPM 将会问一些问题。随后,我们需要安装一些包。

npm install --save chalk figlet inquirer shelljs

这是我们需要的包:

  • Chalk:正确设定终端的字符样式
  • Figlet:使用普通字符制作大字母的程序(LCTT 译注:使用标准字符,拼凑出图片)
  • Inquirer:通用交互式命令行用户界面的集合
  • ShellJS:Node.js 版本的可移植 Unix Shell 命令行工具

创建一个 index.js 文件

现在我们要使用下述内容创建一个 index.js 文件。

#!/usr/bin/env node

const inquirer = require("inquirer");
const chalk = require("chalk");
const figlet = require("figlet");
const shell = require("shelljs");

规划命令行工具

在我们写命令行工具所需的任何代码之前,做计划总是很棒的。这个命令行工具只做一件事:创建一个文件

它将会问两个问题:文件名是什么以及文件后缀名是什么?然后创建文件,并展示一个包含了所创建文件路径的成功信息。

// index.js

const run = async () => {
  // show script introduction
  // ask questions
  // create the file
  // show success message
};

run();

第一个函数只是该脚本的介绍。让我们使用 chalkfiglet 来把它完成。

const init = () => {
  console.log(
    chalk.green(
      figlet.textSync("Node JS CLI", {
        font: "Ghost",
        horizontalLayout: "default",
        verticalLayout: "default"
      })
    )
  );
}

const run = async () => {
  // show script introduction
  init();

  // ask questions
  // create the file
  // show success message
};

run();

然后,我们来写一个函数来问问题。

const askQuestions = () => {
  const questions = [
    {
      name: "FILENAME",
      type: "input",
      message: "What is the name of the file without extension?"
    },
    {
      type: "list",
      name: "EXTENSION",
      message: "What is the file extension?",
      choices: [".rb", ".js", ".php", ".css"],
      filter: function(val) {
        return val.split(".")[1];
      }
    }
  ];
  return inquirer.prompt(questions);
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  // show success message
};

注意,常量 FILENAMEEXTENSIONS 来自 inquirer 包。

下一步将会创建文件。

const createFile = (filename, extension) => {
  const filePath = `${process.cwd()}/${filename}.${extension}`
  shell.touch(filePath);
  return filePath;
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
};

最后,重要的是,我们将展示成功信息以及文件路径。

const success = (filepath) => {
  console.log(
    chalk.white.bgGreen.bold(`Done! File created at ${filepath}`)
  );
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
  success(filePath);
};

来让我们通过运行 node index.js 来测试这个脚本,这是我们得到的:

完整代码

下述代码为完整代码:

#!/usr/bin/env node

const inquirer = require("inquirer");
const chalk = require("chalk");
const figlet = require("figlet");
const shell = require("shelljs");

const init = () => {
  console.log(
    chalk.green(
      figlet.textSync("Node JS CLI", {
        font: "Ghost",
        horizontalLayout: "default",
        verticalLayout: "default"
      })
    )
  );
};

const askQuestions = () => {
  const questions = [
    {
      name: "FILENAME",
      type: "input",
      message: "What is the name of the file without extension?"
    },
    {
      type: "list",
      name: "EXTENSION",
      message: "What is the file extension?",
      choices: [".rb", ".js", ".php", ".css"],
      filter: function(val) {
        return val.split(".")[1];
      }
    }
  ];
  return inquirer.prompt(questions);
};

const createFile = (filename, extension) => {
  const filePath = `${process.cwd()}/${filename}.${extension}`
  shell.touch(filePath);
  return filePath;
};

const success = filepath => {
  console.log(
    chalk.white.bgGreen.bold(`Done! File created at ${filepath}`)
  );
};

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
  success(filePath);
};

run();

使用这个脚本

想要在其它地方执行这个脚本,在你的 package.json 文件中添加一个 bin 部分,并执行 npm link

{
  "name": "creator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chalk": "^2.4.1",
    "figlet": "^1.2.0",
    "inquirer": "^6.0.0",
    "shelljs": "^0.8.2"
  },
  "bin": {
    "creator": "./index.js"
  }
}

执行 npm link 使得这个脚本可以在任何地方调用。

这就是是当你运行这个命令时的结果。

/usr/bin/creator -> /usr/lib/node_modules/creator/index.js
/usr/lib/node_modules/creator -> /home/hugo/code/creator

这会连接 index.js 作为一个可执行文件。这是完全可能的,因为这个 CLI 脚本的第一行是 #!/usr/bin/env node

现在我们可以通过执行如下命令来调用。

$ creator

总结

正如你所看到的,Node.js 使得构建一个好的命令行工具变得非常简单。如果你希望了解更多内容,查看下列包。

  • meow:一个简单的命令行助手工具
  • yargs:一个命令行参数解析工具
  • pkg:将你的 Node.js 程序包装在一个可执行文件中。

在评论中留下你关于构建命令行工具的经验吧!


via: https://opensource.com/article/18/7/node-js-interactive-cli

作者:Hugo Dias 选题:lujun9972 译者:bestony 校对:wxy

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

要在三天内打造一个可玩的游戏,你需要一些快速而稳定的好工具。

在十月初,我们的俱乐部马歇尔大学的 Geeks and Gadgets 参加了首次 Open Jam,这是一个庆祝最佳开源工具的游戏 Jam。游戏 Jam 是一种活动,参与者以团队协作的方式来开发有趣的计算机游戏。Jam 一般都很短,仅有三天,并且非常累。Opensource.com 在八月下旬发布了 Open Jam 活动,足有 45 支游戏 进入到了竞赛中。

我们的俱乐部希望在我们的项目中创建和使用开放源码软件,所以 Open Jam 自然是我们想要参与的 Jam 了。我们提交的游戏是一个实验性的游戏,名为 Mark My Words。我们使用了多种自由和开放源码 (FOSS) 工具来开发它;在这篇文章中,我们将讨论一些我们使用的工具和我们注意到可能有潜在阻碍的地方。

音频工具

MilkyTracker

MilkyTracker 是一个可用于编曲老式视频游戏中的音乐的软件包。它是一种 音乐声道器 music tracker ,是一个强大的 MOD 和 XM 文件创建器,带有基于特征网格的模式编辑器。在我们的游戏中,我们使用它来编曲大多数的音乐片段。这个程序最好的地方是,它比我们其它的大多数工具消耗更少的硬盘空间和内存。虽然如此,MilkyTracker 仍然非常强大。

其用户界面需要一会来习惯,这里有对一些想试用 MilkyTracker 的音乐家的一些提示:

  • 转到 “Config > Misc.” ,设置编辑模式的控制风格为 “MilkyTracker”,这将给你提供几乎全部现代键盘快捷方式。
  • Ctrl+Z 撤销
  • Ctrl+Y 重做
  • 用空格键切换模式编辑方式
  • 用退格键删除先前的音符
  • 用插入键来插入一行
  • 默认情况下,一个音符将持续作用,直到它在该频道上被替换。你可以明确地结束一个音符,通过使用一个反引号(`)键来插入一个 KeyOff 音符
  • 在你开始谱写乐曲前,你需要创建或查找采样。我们建议在诸如 FreesoundccMixter 这样的网站上查找采用 Creative Commons 协议的采样,

另外,把 MilkyTracker 文档页面 放在手边。它含有数不清的教程和手册的链接。一个好的起点是在该项目 wiki 上的 MilkyTracker 指南

LMMS

我们的两个音乐家使用多用途的现代音乐创建工具 LMMS。它带有一个绝妙的采样和效果库,以及多种多样的灵活的插件来生成独特的声音。LMMS 的学习曲线令人吃惊的低,在某种程度上是因为其好用的节拍/低音线编辑器。

我们对于想试试 LMMS 的音乐家有一个建议:使用插件。对于 chiptune式音乐,我们推荐 sfxrBitInvaderFreeBoy。对于其它风格,ZynAddSubFX 是一个好的选择。它配备了各种合成仪器,可以根据您的需要进行更改。

图形工具

Tiled

在开放源码游戏开发中,Tiled 是一个流行的贴片地图编辑器。我们使用它为来为我们在游戏场景中组合连续的、复古式的背景。

Tiled 可以导出地图为 XML、JSON 或普通的图片。它是稳定的、跨平台的。

Tiled 的功能之一允许你在地图上定义和放置随意的游戏对象,例如硬币和提升道具,但在 jam 期间我们没有使用它。你需要做的全部是以贴片集的方式加载对象的图像,然后使用“插入平铺”来放置它们。

一般来说,对于需要一个地图编辑器的项目,Tiled 是我们所推荐的软件中一个不可或缺的部分。

Piskel

Piskel 是一个像素艺术编辑器,它的源文件代码以 Apache 2.0 协议 发布。在这次 Jam 期间,们的大多数的图像资源都使用 Piskel 来处理,我们当然也将在未来的工程中使用它。

在这个 Jam 期间,Piskel 极大地帮助我们的两个功能是 洋葱皮 Onion skin 精灵序列图 spritesheet 导出。

洋葱皮

洋葱皮功能将使 Piskel 以虚影显示你编辑的动画的前一帧和后一帧的,像这样:

洋葱皮是很方便的,因为它适合作为一个绘制指引和帮助你在整个动画进程中保持角色的一致形状和体积。 要启用它,只需单击屏幕右上角预览窗口下方的洋葱形图标即可。

精灵序列图导出

Piskel 将动画导出为精灵序列图的能力也非常有用。精灵序列图是一个包含动画所有帧的光栅图像。例如,这是我们从 Piskel 导出的精灵序列图:

该精灵序列图包含两帧。一帧位于图像的上半部分,另一帧位于图像的下半部分。精灵序列图通过从单个文件加载整个动画,大大简化了游戏的代码。这是上面精灵序列图的动画版本:

Unpiskel.py

在 Jam 期间,我们很多次想批量转换 Piskel 文件到 PNG 文件。由于 Piskel 文件格式基于 JSON,我们写一个基于 GPLv3 协议的名为 unpiskel.py 的 Python 小脚本来做转换。

它像这样被调用的:

python unpiskel.py input.piskel

这个脚本将从一个 Piskel 文件(这里是 input.piskel)中提取 PNG 数据帧和图层,并将它们各自存储。这些文件采用模式 NAME_XX_YY.png 命名,在这里 NAME 是 Piskel 文件的缩减名称,XX 是帧的编号,YY 是层的编号。

因为脚本可以从一个 shell 中调用,它可以用在整个文件列表中。

for f in *.piskel; do python unpiskel.py "$f"; done

Python、Pygame 和 cx\_Freeze

Python 和 Pygame

我们使用 Python 语言来制作我们的游戏。它是一个脚本语言,通常被用于文本处理和桌面应用程序开发。它也可以用于游戏开发,例如像 Angry Drunken DwarvesRen'Py 这样的项目所展示的。这两个项目都使用一个称为 Pygame 的 Python 库来显示图形和产生声音,所以我们也决定在 Open Jam 中使用这个库。

Pygame 被证明是既稳定又富有特色,并且它对我们创建的街机式游戏来说是很棒的。在低分辨率时,库的速度足够快的,但是在高分辨率时,它只用 CPU 的渲染开始变慢。这是因为 Pygame 不使用硬件加速渲染。然而,开发者可以充分利用 OpenGL 基础设施的优势。

如果你正在寻找一个好的 2D 游戏编程库,Pygame 是值得密切注意的一个。它的网站有 一个好的教程 可以作为起步。务必看看它!

cx\_Freeze

准备发行我们的游戏是有趣的。我们知道,Windows 用户不喜欢装一套 Python,并且要求他们来安装它可能很过分。除此之外,他们也可能必须安装 Pygame,在 Windows 上,这不是一个简单的工作。

很显然:我们必须放置我们的游戏到一个更方便的格式中。很多其他的 Open Jam 参与者使用专有的游戏引擎 Unity,它能够使他们的游戏在网页浏览器中来玩。这使得它们非常方便地来玩。便利性是一个我们的游戏中根本不存在的东西。但是,感谢生机勃勃的 Python 生态系统,我们有选择。已有的工具可以帮助 Python 程序员将他们的游戏做成 Windows 上的发布版本。我们考虑过的两个工具是 cx\_FreezePygame2exe(它使用 py2exe)。我们最终决定用 cx\_Freeze,因为它是跨平台的。

在 cx\_Freeze 中,你可以把一个单脚本游戏打包成发布版本,只要在 shell 中运行一个命令,像这样:

cxfreeze main.py --target-dir dist

cxfreeze 的这个调用将把你的脚本(这里是 main.py)和在你系统上的 Python 解释器捆绑到到 dist 目录。一旦完成,你需要做的是手动复制你的游戏的数据文件到 dist 目录。你将看到,dist 目录包含一个可以运行来开始你的游戏的可执行文件。

这里有使用 cx\_Freeze 的更复杂的方法,允许你自动地复制数据文件,但是我们发现简单的调用 cxfreeze 足够满足我们的需要。感谢这个工具,我们使我们的游戏玩起来更便利一些。

庆祝开源

Open Jam 是庆祝开源模式的软件开发的重要活动。这是一个分析开源工具的当前状态和我们在未来工作中需求的一个机会。对于游戏开发者探求其工具的使用极限,学习未来游戏开发所必须改进的地方,游戏 Jam 或许是最好的时机。

开源工具使人们能够在不损害自由的情况下探索自己的创造力,而无需预先投入资金。虽然我们可能不会成为专业的游戏开发者,但我们仍然能够通过我们的简短的实验性游戏 Mark My Words 获得一点点体验。它是一个以语言学为主题的游戏,描绘了虚构的书写系统在其历史中的演变。还有很多其他不错的作品提交给了 Open Jam,它们都值得一试。 真的,去看看

在本文结束前,我们想要感谢所有的 参加俱乐部的成员,使得这次经历真正的有价值。我们也想要感谢 Michael ClaytonJared SpragueOpensource.com 主办 Open Jam。简直酷毙了。

现在,我们对读者提出了一些问题。你是一个 FOSS 游戏开发者吗?你选择的工具是什么?务必在下面留下一个评论!


via: https://opensource.com/article/18/1/graphics-music-tools-game-dev

作者:Charlie Murphy 译者:robsean 校对:wxy

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

gorilla/mux 包以直观的 API 提供了 HTTP 请求路由、验证和其它服务。

Go 网络库包括 http.ServeMux 结构类型,它支持 HTTP 请求多路复用(路由):Web 服务器将托管资源的 HTTP 请求与诸如 /sales4today 之类的 URI 路由到代码处理程序;处理程序在发送 HTTP 响应(通常是 HTML 页面)之前执行适当的逻辑。 这是该体系的草图:

             +-----------+     +--------+     +---------+
HTTP 请求---->| web 服务器 |---->| 路由   |---->| 处理程序  |
             +-----------+     +--------+     +---------+

调用 ListenAndServe 方法后启动 HTTP 服务器:

http.ListenAndServe(":8888", nil) // args: port & router

第二个参数 nil 意味着 DefaultServeMux 用于请求路由。

gorilla/mux 库包含 mux.Router 类型,可替代 DefaultServeMux 或自定义请求多路复用器。 在 ListenAndServe 调用中,mux.Router 实例将代替 nil 作为第二个参数。 下面的示例代码很好的说明了为什么 mux.Router如此吸引人:

1、一个简单的 CRUD web 应用程序

crud web 应用程序(见下文)支持四种 CRUD(创建/读取/更新/删除)操作,它们分别对应四种 HTTP 请求方法:POST、GET、PUT 和 DELETE。 在这个 CRUD 应用程序中,所管理的资源是套话与反套话的列表,每个都是套话及其反面的的套话,例如这对:

Out of sight, out of mind. Absence makes the heart grow fonder.

可以添加新的套话对,可以编辑或删除现有的套话对。

CRUD web 应用程序:

package main

import (
   "gorilla/mux"
   "net/http"
   "fmt"
   "strconv"
)

const GETALL string = "GETALL"
const GETONE string = "GETONE"
const POST string   = "POST"
const PUT string    = "PUT"
const DELETE string = "DELETE"

type clichePair struct {
   Id      int
   Cliche  string
   Counter string
}

// Message sent to goroutine that accesses the requested resource.
type crudRequest struct {
   verb     string
   cp       *clichePair
   id       int
   cliche   string
   counter  string
   confirm  chan string
}

var clichesList = []*clichePair{}
var masterId = 1
var crudRequests chan *crudRequest

// GET /
// GET /cliches
func ClichesAll(res http.ResponseWriter, req *http.Request) {
   cr := &crudRequest{verb: GETALL, confirm: make(chan string)}
   completeRequest(cr, res, "read all")
}

// GET /cliches/id
func ClichesOne(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "read one")
}

// POST /cliches
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

// PUT /cliches/id
func ClichesEdit(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cliche, counter := getDataFromRequest(req)
   cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)}
   completeRequest(cr, res, "edit")
}

// DELETE /cliches/id
func ClichesDelete(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "delete")
}

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr
   msg := <-cr.confirm
   res.Write([]byte(msg))
   logIt(logMsg)
}

func main() {
   populateClichesList()

   // From now on, this gorountine alone accesses the clichesList.
   crudRequests = make(chan *crudRequest, 8)
   go func() { // resource manager
      for {
         select {
         case req := <-crudRequests:
         if req.verb == GETALL {
            req.confirm<-readAll()
         } else if req.verb == GETONE {
            req.confirm<-readOne(req.id)
         } else if req.verb == POST {
            req.confirm<-addPair(req.cp)
         } else if req.verb == PUT {
            req.confirm<-editPair(req.id, req.cliche, req.counter)
         } else if req.verb == DELETE {
            req.confirm<-deletePair(req.id)
         }
      }
   }()
   startServer()
}

func startServer() {
   router := mux.NewRouter()

   // Dispatch map for CRUD operations.
   router.HandleFunc("/", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

   router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE")

   http.Handle("/", router) // enable the router

   // Start the server.
   port := ":8888"
   fmt.Println("\nListening on port " + port)
   http.ListenAndServe(port, router); // mux.Router now in play
}

// Return entire list to requester.
func readAll() string {
   msg := "\n"
   for _, cliche := range clichesList {
      next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
      msg += next
   }
   return msg
}

// Return specified clichePair to requester.
func readOne(id int) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"

   index := findCliche(id)
   if index >= 0 {
      cliche := clichesList[index]
      msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
   }
   return msg
}

// Create a new clichePair and add to list
func addPair(cp *clichePair) string {
   cp.Id = masterId
   masterId++
   clichesList = append(clichesList, cp)
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

// Edit an existing clichePair
func editPair(id int, cliche string, counter string) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList[index].Cliche = cliche
      clichesList[index].Counter = counter
      msg = "\nCliche edited: " + cliche + " " + counter + "\n"
   }
   return msg
}

// Delete a clichePair
func deletePair(id int) string {
   idStr := strconv.Itoa(id)
   msg := "\n" + "Bad Id: " + idStr + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList = append(clichesList[:index], clichesList[index + 1:]...)
      msg = "\nCliche " + idStr + " deleted\n"
   }
   return msg
}

//*** utility functions
func findCliche(id int) int {
   for i := 0; i < len(clichesList); i++ {
      if id == clichesList[i].Id {
         return i;
      }
   }
   return -1 // not found
}

func getIdFromRequest(req *http.Request) int {
   vars := mux.Vars(req)
   id, _ := strconv.Atoi(vars["id"])
   return id
}

func getDataFromRequest(req *http.Request) (string, string) {
   // Extract the user-provided data for the new clichePair
   req.ParseForm()
   form := req.Form
   cliche := form["cliche"][0]    // 1st and only member of a list
   counter := form["counter"][0]  // ditto
   return cliche, counter
}

func logIt(msg string) {
   fmt.Println(msg)
}

func populateClichesList() {
   var cliches = []string {
      "Out of sight, out of mind.",
      "A penny saved is a penny earned.",
      "He who hesitates is lost.",
   }
   var counterCliches = []string {
      "Absence makes the heart grow fonder.",
      "Penny-wise and dollar-foolish.",
      "Look before you leap.",
   }

   for i := 0; i < len(cliches); i++ {
      cp := new(clichePair)
      cp.Id = masterId
      masterId++
      cp.Cliche = cliches[i]
      cp.Counter = counterCliches[i]
      clichesList = append(clichesList, cp)
   }
}

为了专注于请求路由和验证,CRUD 应用程序不使用 HTML 页面作为请求响应。 相反,请求会产生明文响应消息:套话对的列表是对 GET 请求的响应,确认新的套话对已添加到列表中是对 POST 请求的响应,依此类推。 这种简化使得使用命令行实用程序(如 curl)可以轻松地测试应用程序,尤其是 gorilla/mux 组件。

gorilla/mux 包可以从 GitHub 安装。 CRUD app 无限期运行;因此,应使用 Control-C 或同等命令终止。 CRUD 应用程序的代码,以及自述文件和简单的 curl 测试,可以在我的网站上找到。

2、请求路由

mux.Router 扩展了 REST 风格的路由,它赋给 HTTP 方法(例如,GET)和 URL 末尾的 URI 或路径(例如 /cliches)相同的权重。 URI 用作 HTTP 动词(方法)的名词。 例如,在HTTP请求中有一个起始行,例如:

GET /cliches

意味着得到所有的套话对,而一个起始线,如:

POST /cliches

意味着从 HTTP 正文中的数据创建一个套话对。

在 CRUD web 应用程序中,有五个函数充当 HTTP 请求的五种变体的请求处理程序:

ClichesAll(...)    # GET: 获取所有的套话对
ClichesOne(...)    # GET: 获取指定的套话对
ClichesCreate(...) # POST: 创建新的套话对
ClichesEdit(...)   # PUT: 编辑现有的套话对
ClichesDelete(...) # DELETE: 删除指定的套话对

每个函数都有两个参数:一个 http.ResponseWriter 用于向请求者发送一个响应,一个指向 http.Request 的指针,该指针封装了底层 HTTP 请求的信息。 使用 gorilla/mux 包可以轻松地将这些请求处理程序注册到Web服务器,并执行基于正则表达式的验证。

CRUD 应用程序中的 startServer 函数注册请求处理程序。 考虑这对注册,router 作为 mux.Router 实例:

router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")

这些语句意味着对单斜线 //cliches 的 GET 请求应该路由到 ClichesAll 函数,然后处理请求。 例如,curl 请求(使用 作为命令行提示符):

% curl --request GET localhost:8888/

会产生如下结果:

1: Out of sight, out of mind.  Absence makes the heart grow fonder.
2: A penny saved is a penny earned.  Penny-wise and dollar-foolish.
3: He who hesitates is lost.  Look before you leap.

这三个套话对是 CRUD 应用程序中的初始数据。

在这句注册语句中:

router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")

URI 是相同的(/cliches),但动词不同:第一种情况下为 GET 请求,第二种情况下为 POST 请求。 此注册举例说明了 REST 样式的路由,因为仅动词的不同就足以将请求分派给两个不同的处理程序。

注册中允许多个 HTTP 方法,尽管这会影响 REST 风格路由的精髓:

router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")

除了动词和 URI 之外,还可以在功能上路由 HTTP 请求。 例如,注册

router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")

要求对 POST 请求进行 HTTPS 访问以创建新的套话对。以类似的方式,注册可能需要具有指定的 HTTP 头元素(例如,认证凭证)的请求。

3、 Request validation

gorilla/mux 包采用简单,直观的方法通过正则表达式进行请求验证。 考虑此请求处理程序以获取一个操作:

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

此注册排除了 HTTP 请求,例如:

% curl --request GET localhost:8888/cliches/foo

因为 foo 不是十进制数字。该请求导致熟悉的 404(未找到)状态码。 在此处理程序注册中包含正则表达式模式可确保仅在请求 URI 以十进制整数值结束时才调用 ClichesOne 函数来处理请求:

% curl --request GET localhost:8888/cliches/3  # ok

另一个例子,请求如下:

% curl --request PUT --data "..." localhost:8888/cliches

此请求导致状态代码为 405(错误方法),因为 /cliches URI 在 CRUD 应用程序中仅在 GET 和 POST 请求中注册。 像 GET 请求一样,PUT 请求必须在 URI 的末尾包含一个数字 id:

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")

4、并发问题

gorilla/mux 路由器作为单独的 Go 协程执行对已注册的请求处理程序的每次调用,这意味着并发性被内置于包中。 例如,如果有十个同时发出的请求,例如

% curl --request POST --data "..." localhost:8888/cliches

然后 mux.Router 启动十个 Go 协程来执行 ClichesCreate 处理程序。

GET all、GET one、POST、PUT 和 DELETE 中的五个请求操作中,最后三个改变了所请求的资源,即包含套话对的共享 clichesList。 因此,CRUD app 需要通过协调对 clichesList 的访问来保证安全的并发性。 在不同但等效的术语中,CRUD app 必须防止 clichesList 上的竞争条件。 在生产环境中,可以使用数据库系统来存储诸如 clichesList 之类的资源,然后可以通过数据库事务来管理安全并发。

CRUD 应用程序采用推荐的Go方法来实现安全并发:

  • 只有一个 Go 协程,资源管理器在 CRUD app startServer 函数中启动,一旦 Web 服务器开始侦听请求,就可以访问 clichesList
  • 诸如 ClichesCreateClichesAll 之类的请求处理程序向 Go 通道发送(指向)crudRequest 实例(默认情况下是线程安全的),并且资源管理器单独从该通道读取。 然后,资源管理器对 clichesList 执行请求的操作。

安全并发体系结构绘制如下:

            crudRequest                读/写

请求处理程序 -------------> 资源托管者 ------------> 套话列表

在这种架构中,不需要显式锁定 clichesList,因为一旦 CRUD 请求开始进入,只有一个 Go 协程(资源管理器)访问 clichesList

为了使 CRUD 应用程序尽可能保持并发,在一方请求处理程序与另一方的单一资源管理器之间进行有效的分工至关重要。 在这里,为了审查,是 ClichesCreate 请求处理程序:

func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

ClichesCreate 调用实用函数 getDataFromRequest,它从 POST 请求中提取新的套话和反套话。 然后 ClichesCreate 函数创建一个新的 ClichePair,设置两个字段,并创建一个 crudRequest 发送给单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问 clichesList

请求处理程序调用实用程序函数,该函数从 POST 请求中提取新的套话和反套话。 然后,该函数创建一个新的,设置两个字段,并创建一个 crudRequest 发送到单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问它。

completeRequest 实用程序函数在 ClichesCreate 函数和其他请求处理程序的末尾调用:

completeRequest(cr, res, "create") // shown above

通过将 crudRequest 放入 crudRequests 频道,使资源管理器发挥作用:

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // 向资源托管者发送请求
   msg := <-cr.confirm       // 等待确认
   res.Write([]byte(msg))    // 向请求方发送确认
   logIt(logMsg)             // 打印到标准输出
}

对于 POST 请求,资源管理器调用实用程序函数 addPair,它会更改 clichesList 资源:

func addPair(cp *clichePair) string {
   cp.Id = masterId  // 分配一个唯一的 ID 
   masterId++        // 更新 ID 计数器
   clichesList = append(clichesList, cp) // 更新列表
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

资源管理器为其他 CRUD 操作调用类似的实用程序函数。 值得重复的是,一旦 Web 服务器开始接受请求,资源管理器就是唯一可以读取或写入 clichesList 的 goroutine。

对于任何类型的 Web 应用程序,gorilla/mux 包在简单直观的 API 中提供请求路由、请求验证和相关服务。 CRUD web 应用程序突出了软件包的主要功能。


via: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux

作者:Marty Kalin 选题:lujun9972 译者:yongshouzhang 校对:wxy

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

除了 pandas、scikit-learn 和 matplotlib,还要学习一些用 Python 进行数据科学的新技巧。

Python 是一种令人惊叹的语言。事实上,它是世界上增长最快的编程语言之一。它一次又一次地证明了它在各个行业的开发者和数据科学者中的作用。Python 及其库的整个生态系统使其成为全世界用户的恰当选择,无论是初学者还是高级用户。它成功和受欢迎的原因之一是它的一组强大的库,使它如此动态和快速。

在本文中,我们将看到 Python 库中的一些数据科学工具,而不是那些常用的工具,如 pandas、scikit-learn 和 matplotlib。虽然像 pandas、scikit-learn 这样的库是机器学习中最常想到的,但是了解这个领域的其他 Python 库也是非常有帮助的。

Wget

提取数据,尤其是从网络中提取数据,是数据科学家的重要任务之一。Wget 是一个免费的工具,用于从网络上非交互式下载文件。它支持 HTTP、HTTPS 和 FTP 协议,以及通过 HTTP 代理进行访问。因为它是非交互式的,所以即使用户没有登录,它也可以在后台工作。所以下次你想下载一个网站或者网页上的所有图片,wget 会提供帮助。

安装:

$ pip install wget

例子:

import wget
url = 'http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3'

filename = wget.download(url)
100% [................................................] 3841532 / 3841532

filename
'razorback.mp3'

钟摆

对于在 Python 中处理日期时间感到沮丧的人来说, Pendulum 库是很有帮助的。这是一个 Python 包,可以简化日期时间操作。它是 Python 原生类的一个替代品。有关详细信息,请参阅其文档

安装:

$ pip install pendulum

例子:

import pendulum

dt_toronto = pendulum.datetime(2012, 1, 1, tz='America/Toronto')
dt_vancouver = pendulum.datetime(2012, 1, 1, tz='America/Vancouver')

print(dt_vancouver.diff(dt_toronto).in_hours())

3

不平衡学习

当每个类别中的样本数几乎相同(即平衡)时,大多数分类算法会工作得最好。但是现实生活中的案例中充满了不平衡的数据集,这可能会影响到机器学习算法的学习和后续预测。幸运的是,imbalanced-learn 库就是为了解决这个问题而创建的。它与 scikit-learn 兼容,并且是 scikit-learn-contrib 项目的一部分。下次遇到不平衡的数据集时,可以尝试一下。

安装:

pip install -U imbalanced-learn
# or
conda install -c conda-forge imbalanced-learn

例子:

有关用法和示例,请参阅其文档

FlashText

在自然语言处理(NLP)任务中清理文本数据通常需要替换句子中的关键词或从句子中提取关键词。通常,这种操作可以用正则表达式来完成,但是如果要搜索的术语数达到数千个,它们可能会变得很麻烦。

Python 的 FlashText 模块,基于 FlashText 算法,为这种情况提供了一个合适的替代方案。FlashText 的最佳部分是运行时间与搜索项的数量无关。你可以在其 文档 中读到更多关于它的信息。

安装:

$ pip install flashtext

例子:

提取关键词:

from flashtext import KeywordProcessor
keyword_processor = KeywordProcessor()

# keyword_processor.add_keyword(<unclean name>, <standardised name>)

keyword_processor.add_keyword('Big Apple', 'New York')
keyword_processor.add_keyword('Bay Area')
keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')

keywords_found
['New York', 'Bay Area']

替代关键词:

keyword_processor.add_keyword('New Delhi', 'NCR region')

new_sentence = keyword_processor.replace_keywords('I love Big Apple and new delhi.')

new_sentence
'I love New York and NCR region.'

有关更多示例,请参阅文档中的 用法 一节。

模糊处理

这个名字听起来很奇怪,但是 FuzzyWuzzy 在字符串匹配方面是一个非常有用的库。它可以很容易地实现字符串匹配率、令牌匹配率等操作。对于匹配保存在不同数据库中的记录也很方便。

安装:

$ pip install fuzzywuzzy

例子:

from fuzzywuzzy import fuzz
from fuzzywuzzy import process

# 简单的匹配率
fuzz.ratio("this is a test", "this is a test!")
97

# 部分的匹配率 
fuzz.partial_ratio("this is a test", "this is a test!")
 100

更多的例子可以在 FuzzyWuzy 的 GitHub 仓库得到。

PyFlux

时间序列分析是机器学习中最常遇到的问题之一。PyFlux 是 Python 中的开源库,专门为处理时间序列问题而构建的。该库拥有一系列优秀的现代时间序列模型,包括但不限于 ARIMA、GARCH 以及 VAR 模型。简而言之,PyFlux 为时间序列建模提供了一种概率方法。这值得一试。

安装:

pip install pyflux

例子:

有关用法和示例,请参阅其 文档

IPyvolume

交流结果是数据科学的一个重要方面,可视化结果提供了显著优势。 IPyvolume 是一个 Python 库,用于在 Jupyter 笔记本中可视化 3D 体积和形状(例如 3D 散点图),配置和工作量极小。然而,它目前处于 1.0 之前的阶段。一个很好的类比是这样的: IPyVolumee volshow 是 3D 阵列,Matplotlib 的 imshow 是 2D 阵列。你可以在其 文档 中读到更多关于它的信息。

安装:

Using pip
$ pip install ipyvolume

Conda/Anaconda
$ conda install -c conda-forge ipyvolume

例子:

动画:

体绘制:

Dash

Dash 是一个用于构建 Web 应用程序的高效 Python 框架。它构建于 Flask、Plotty.js 和 Response.js 之上,将下拉菜单、滑块和图形等流行 UI 元素与你的 Python 分析代码联系起来,而不需要JavaScript。Dash 非常适合构建可在 Web 浏览器中呈现的数据可视化应用程序。有关详细信息,请参阅其 用户指南

安装:

pip install dash==0.29.0  # The core dash backend
pip install dash-html-components==0.13.2  # HTML components
pip install dash-core-components==0.36.0  # Supercharged components
pip install dash-table==3.1.3  # Interactive DataTable component (new!)

例子:

下面的示例显示了一个具有下拉功能的高度交互的图表。当用户在下拉列表中选择一个值时,应用程序代码将数据从 Google Finance 动态导出到 Pandas 数据框架中。

Gym

OpenAI 而来的 Gym 是开发和比较强化学习算法的工具包。它与任何数值计算库兼容,如 TensorFlow 或 Theano。Gym 是一个测试问题的集合,也称为“环境”,你可以用它来制定你的强化学习算法。这些环境有一个共享的接口,允许您编写通用算法。

安装:

pip install gym

例子:

以下示例将在 CartPole-v0 环境中,运行 1000 次,在每一步渲染环境。

你可以在 Gym 网站上读到 其它的环境

结论

这些是我挑选的有用但鲜为人知的数据科学 Python 库。如果你知道另一个要添加到这个列表中,请在下面的评论中提及。

本文最初发表在 Analytics Vidhya 的媒体频道上,并经许可转载。


via: https://opensource.com/article/18/11/python-libraries-data-science

作者:Parul Pandey 选题:lujun9972 译者:heguangzhi 校对:wxy

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