From 65dd7a91fef6e9b951e83ced1782883d25888280 Mon Sep 17 00:00:00 2001 From: cposture Date: Fri, 5 Aug 2016 02:08:01 +0800 Subject: [PATCH 01/11] 75 --- ...18 An Introduction to Mocking in Python.md | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/sources/tech/20160618 An Introduction to Mocking in Python.md b/sources/tech/20160618 An Introduction to Mocking in Python.md index d28fae272d..cdf9c24a69 100644 --- a/sources/tech/20160618 An Introduction to Mocking in Python.md +++ b/sources/tech/20160618 An Introduction to Mocking in Python.md @@ -223,8 +223,8 @@ class RemovalServiceTestCase(unittest.TestCase): mock_os.remove.assert_called_with("any path") ``` -很好,我们知道 RemovalService 会如期工作。接下来让我们创建另一个服务,将其声明为一个依赖 -Great, so we now know that the RemovalService works as planned. Let’s create another service which declares it as a dependency: +很好,我们知道 RemovalService 会如期工作。接下来让我们创建另一个服务,将 RemovalService 声明为一个依赖 +: ``` #!/usr/bin/env python @@ -250,18 +250,18 @@ class UploadService(object): self.removal_service.rm(filename) ``` -Since we already have test coverage on the RemovalService, we’re not going to validate internal functionality of the rm method in our tests of UploadService. Rather, we’ll simply test (without side-effects, of course) that UploadService calls the RemovalService.rm method, which we know “just works™” from our previous test case. +因为我们的测试覆盖了 RemovalService,因此我们不会对我们测试用例中 UploadService 的内部函数 rm 进行验证。相反,我们将调用 UploadService 的 RemovalService.rm 方法来进行简单测试(当然是没有其他副作用),我们通过之前的测试用例便能知道它可以正确地工作。 -There are two ways to go about this: +这里有两种方法来实现它: -1. Mock out the RemovalService.rm method itself. -2. Supply a mocked instance in the constructor of UploadService. +1. 模拟 RemovalService.rm 方法本身。 +2. 在 UploadService 的构造函数中提供一个模拟实例。 -As both methods are often important in unit-testing, we’ll review both. +因为这两种方法都是单元测试中非常重要的方法,所以我们将同时对这两种方法进行回顾。 -### Option 1: Mocking Instance Methods +### 方法 1:模拟实例的方法 -The mock library has a special method decorator for mocking object instance methods and properties, the @mock.patch.object decorator: +该模拟库有一个特殊的方法装饰器,可以模拟对象势力的方法和属性,即 @mock.patch.object decorator: ``` #!/usr/bin/env python @@ -314,11 +314,12 @@ class UploadServiceTestCase(unittest.TestCase): removal_service.rm.assert_called_with("my uploaded file") ``` -Great! We’ve validated that the UploadService successfully calls our instance’s rm method. Notice anything interesting in there? The patching mechanism actually replaced the rm method of all RemovalService instances in our test method. That means that we can actually inspect the instances themselves. If you want to see more, try dropping in a breakpoint in your mocking code to get a good feel for how the patching mechanism works. +非常棒!我们验证了 UploadService 成功调用了我们实例的 rm 方法。你是否注意到一些有趣的地方?这种修补机制实际上替换了我们测试用例中的所有 RemovalService 实例的 rm 方法。这意味着我们可以检查实例本身。如果你想要了解更多,可以试着在你模拟的代码下断点,以对这种修补机制的原理获得更好的认识。 -### Pitfall: Decorator Order +### 陷阱:装饰顺序 + +当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱。基本桑,当装饰器被映射到方法参数时,[装饰器的工作顺序是反向的][3]。思考这个例子: -When using multiple decorators on your test methods, order is important, and it’s kind of confusing. Basically, when mapping decorators to method parameters, [work backwards][3]. Consider this example: ``` @mock.patch('mymodule.sys') @@ -328,16 +329,17 @@ When using multiple decorators on your test methods, order is important, and it pass ``` -Notice how our parameters are matched to the reverse order of the decorators? That’s partly because of [the way that Python works][4]. With multiple method decorators, here’s the order of execution in pseudocode: +注意到我们的参数和装饰器的顺序是反向匹配了吗?这多多少少是由 [Python 的工作方式][4] 导致的。这里是使用多个装饰器的情况下它们的执行顺序的伪代码: ``` patch_sys(patch_os(patch_os_path(test_something))) ``` -Since the patch to sys is the outermost patch, it will be executed last, making it the last parameter in the actual test method arguments. Take note of this well and use a debugger when running your tests to make sure that the right parameters are being injected in the right order. +因为 sys 补丁位于最外层,所以它会最晚执行,使得它成为实际测试方法参数的最后一个参数。请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入。 -### Option 2: Creating Mock Instances +### 方法 2:创建 Mock 实例 +我们可以使用构造函数为 UploadService 提供一个 Mock 实例,而不是模拟特定的实例方法。我推荐上面的方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要。让我们再次重构测试用例: Instead of mocking the specific instance method, we could instead just supply a mocked instance to UploadService with its constructor. I prefer option 1 above, as it’s a lot more precise, but there are many cases where option 2 might be efficient or necessary. Let’s refactor our test again: ``` @@ -387,6 +389,7 @@ class UploadServiceTestCase(unittest.TestCase): mock_removal_service.rm.assert_called_with("my uploaded file") ``` +在这个例子中,我们甚至不需要补充任何功能,只需创建一个 In this example, we haven’t even had to patch any functionality, we simply create an auto-spec for the RemovalService class, and then inject this instance into our UploadService to validate the functionality. The [mock.create_autospec][5] method creates a functionally equivalent instance to the provided class. What this means, practically speaking, is that when the returned instance is interacted with, it will raise exceptions if used in illegal ways. More specifically, if a method is called with the wrong number of arguments, an exception will be raised. This is extremely important as refactors happen. As a library changes, tests break and that is expected. Without using an auto-spec, our tests will still pass even though the underlying implementation is broken. From e254672ba1b66dee06de9e82b5cc312ec76c0029 Mon Sep 17 00:00:00 2001 From: cposture Date: Fri, 5 Aug 2016 14:09:10 +0800 Subject: [PATCH 02/11] Translated by cposture --- ...18 An Introduction to Mocking in Python.md | 101 +++++++++--------- 1 file changed, 48 insertions(+), 53 deletions(-) rename {sources => translated}/tech/20160618 An Introduction to Mocking in Python.md (56%) diff --git a/sources/tech/20160618 An Introduction to Mocking in Python.md b/translated/tech/20160618 An Introduction to Mocking in Python.md similarity index 56% rename from sources/tech/20160618 An Introduction to Mocking in Python.md rename to translated/tech/20160618 An Introduction to Mocking in Python.md index cdf9c24a69..b8daaed937 100644 --- a/sources/tech/20160618 An Introduction to Mocking in Python.md +++ b/translated/tech/20160618 An Introduction to Mocking in Python.md @@ -1,32 +1,34 @@ Mock 在 Python 中的使用介绍 ===================================== -http://www.oschina.net/translate/an-introduction-to-mocking-in-python?cmp + 本文讲述的是 Python 中 Mock 的使用 -**如何在避免测试你的耐心的情景下执行单元测试** +**如何在避免测试你的耐心的情况下执行单元测试** -通常,我们编写的软件会直接与我们称之为肮脏无比的服务交互。用外行人的话说:交互已设计好的服务对我们的应用程序很重要,但是这会带来我们不希望的副作用,也就是那些在我们自己测试的时候不希望的功能。例如:我们正在写一个社交 app,并且想要测试一下我们 "发布到 Facebook" 的新功能,但是不想每次运行测试集的时候真的发布到 Facebook。 +很多时候,我们编写的软件会直接与那些被标记为肮脏无比的服务交互。用外行人的话说:交互已设计好的服务对我们的应用程序很重要,但是这会给我们带来不希望的副作用,也就是那些在一个自动化测试运行的上下文中不希望的功能。 + +例如:我们正在写一个社交 app,并且想要测试一下 "发布到 Facebook" 的新功能,但是不想每次运行测试集的时候真的发布到 Facebook。 -Python 的单元测试库包含了一个名为 unittest.mock 或者可以称之为依赖的子包,简言之为 mock——其提供了极其强大和有用的方法,通过它们可以模拟和打桩我们不希望的副作用。 +Python 的 `unittest` 库包含了一个名为 `unittest.mock` 或者可以称之为依赖的子包,简称为 +`mock` —— 其提供了极其强大和有用的方法,通过它们可以模拟和打桩来去除我们不希望的副作用。 >Source | -注意:mock [最近收录][1]到了 Python 3.3 的标准库中;先前发布的版本必须通过 [PyPI][2] 下载 Mock 库。 +> 注意:`mock` [最近收录][1]到了 Python 3.3 的标准库中;先前发布的版本必须通过 [PyPI][2] 下载 Mock 库。 -### -### Fear System Calls +### 恐惧系统调用 -再举另一个例子,思考一个我们会在余文讨论的系统调用。不难发现,这些系统调用都是主要的模拟对象:无论你是正在写一个可以弹出 CD 驱动的脚本,还是一个用来删除 /tmp 下过期的缓存文件的 Web 服务,这些调用都是在你的单元测试上下文中不希望的副作用。 +再举另一个例子,思考一个我们会在余文讨论的系统调用。不难发现,这些系统调用都是主要的模拟对象:无论你是正在写一个可以弹出 CD 驱动的脚本,还是一个用来删除 /tmp 下过期的缓存文件的 Web 服务,或者一个绑定到 TCP 端口的 socket 服务器,这些调用都是在你的单元测试上下文中不希望的副作用。 > 作为一个开发者,你需要更关心你的库是否成功地调用了一个可以弹出 CD 的系统函数,而不是切身经历 CD 托盘每次在测试执行的时候都打开了。 作为一个开发者,你需要更关心你的库是否成功地调用了一个可以弹出 CD 的系统函数(使用了正确的参数等等),而不是切身经历 CD 托盘每次在测试执行的时候都打开了。(或者更糟糕的是,很多次,在一个单元测试运行期间多个测试都引用了弹出代码!) -同样,保持你的单元测试的效率和性能意味着需要让如此多的 "缓慢代码" 远离自动测试,比如文件系统和网络访问。 +同样,保持单元测试的效率和性能意味着需要让如此多的 "缓慢代码" 远离自动测试,比如文件系统和网络访问。 -对于我们首个例子,我们要从原始形式到使用 mock 地重构一个标准 Python 测试用例。我们会演示如何使用 mock 写一个测试用例使我们的测试更加智能、快速,并且能展示更多关于我们软件的工作原理。 +对于首个例子,我们要从原始形式到使用 `mock` 重构一个标准 Python 测试用例。我们会演示如何使用 mock 写一个测试用例,使我们的测试更加智能、快速,并展示更多关于我们软件的工作原理。 ### 一个简单的删除函数 @@ -42,9 +44,9 @@ def rm(filename): os.remove(filename) ``` -很明显,我们的 rm 方法此时无法提供比相关 os.remove 方法更多的功能,但我们的基础代码会逐步改善,允许我们在这里添加更多的功能。 +很明显,我们的 `rm` 方法此时无法提供比 `os.remove` 方法更多的相关功能,但我们可以在这里添加更多的功能,使我们的基础代码逐步改善。 -让我们写一个传统的测试用例,即,没有使用 mock: +让我们写一个传统的测试用例,即,没有使用 `mock`: ``` #!/usr/bin/env python @@ -71,7 +73,7 @@ class RmTestCase(unittest.TestCase): self.assertFalse(os.path.isfile(self.tmpfilepath), "Failed to remove the file.") ``` -我们的测试用例相当简单,但是当它每次运行的时候,它都会创建一个临时文件并且随后删除。此外,我们没有办法测试我们的 rm 方法是否正确地将我们的参数向下传递给 os.remove 调用。我们可以基于以上的测试认为它做到了,但还有很多需要改进的地方。 +我们的测试用例相当简单,但是在它每次运行的时候,它都会创建一个临时文件并且随后删除。此外,我们没有办法测试我们的 `rm` 方法是否正确地将我们的参数向下传递给 `os.remove` 调用。我们可以基于以上的测试认为它做到了,但还有很多需要改进的地方。 ### 使用 Mock 重构 @@ -95,29 +97,25 @@ class RmTestCase(unittest.TestCase): mock_os.remove.assert_called_with("any path") ``` -使用这些重构,我们从根本上改变了该测试用例的运行方式。现在,我们有一个可以用于验证其他功能的内部对象。 +使用这些重构,我们从根本上改变了测试用例的操作方式。现在,我们有一个可以用于验证其他功能的内部对象。 ### 潜在陷阱 -第一件需要注意的事情就是,我们使用了位于 mymodule.os 且用于模拟对象的 mock.patch 方法装饰器,并且将该 mock 注入到我们的测试用例方法。相比在 mymodule.os 引用它,那么只是模拟 os 本身,会不会更有意义呢? -One of the first things that should stick out is that we’re using the mock.patch method decorator to mock an object located at mymodule.os, and injecting that mock into our test case method. Wouldn’t it make more sense to just mock os itself, rather than the reference to it at mymodule.os? +第一件需要注意的事情就是,我们使用了 `mock.patch` 方法装饰器,用于模拟位于 `mymodule.os` 的对象,并且将 mock 注入到我们的测试用例方法。那么只是模拟 `os` 本身,而不是 `mymodule.os` 下 `os` 的引用(注意 `@mock.patch('mymodule.os')` 便是模拟 `mymodule.os` 下的 `os`,译者注),会不会更有意义呢? -当然,当涉及到导入和管理模块,Python 的用法非常灵活。在运行时,mymodule 模块拥有被导入到本模块局部作用域的 os。因此,如果我们模拟 os,我们是看不到模拟在 mymodule 模块中的作用的。 +当然,当涉及到导入和管理模块,Python 的用法非常灵活。在运行时,`mymodule` 模块拥有被导入到本模块局部作用域的 `os`。因此,如果我们模拟 `os`,我们是看不到 mock 在 `mymodule` 模块中的作用的。 这句话需要深刻地记住: > 模拟测试一个项目,只需要了解它用在哪里,而不是它从哪里来。 -> Mock an item where it is used, not where it came from. -如果你需要为 myproject.app.MyElaborateClass 模拟 tempfile 模块,你可能需要 -If you need to mock the tempfile module for myproject.app.MyElaborateClass, you probably need to apply the mock to myproject.app.tempfile, as each module keeps its own imports. +如果你需要为 `myproject.app.MyElaborateClass` 模拟 `tempfile` 模块,你可能需要将 mock 用于 `myproject.app.tempfile`,而其他模块保持自己的导入。 先将那个陷阱置身事外,让我们继续模拟。 -With that pitfall out of the way, let’s keep mocking. ### 向 ‘rm’ 中加入验证 -之前定义的 rm 方法相当的简单。在盲目地删除之前,我们倾向于拿它来验证一个路径是否存在,并验证其是否是一个文件。让我们重构 rm 使其变得更加智能: +之前定义的 rm 方法相当的简单。在盲目地删除之前,我们倾向于验证一个路径是否存在,并验证其是否是一个文件。让我们重构 rm 使其变得更加智能: ``` @@ -132,7 +130,7 @@ def rm(filename): os.remove(filename) ``` -很好。现在,让我们调整测试用例来保持测试的覆盖程度。 +很好。现在,让我们调整测试用例来保持测试的覆盖率。 ``` #!/usr/bin/env python @@ -168,9 +166,9 @@ class RmTestCase(unittest.TestCase): ### 将文件删除作为服务 -到目前为止,我们只是对函数功能提供模拟测试,并没对需要传递参数的对象和实例的方法进行模拟测试。接下来我们将介绍如何对对象的方法进行模拟测试。 +到目前为止,我们只是将 mock 应用在函数上,并没应用在需要传递参数的对象和实例的方法。我们现在开始涵盖对象的方法。 -首先,我们将rm方法重构成一个服务类。实际上将这样一个简单的函数转换成一个对象,在本质上,这不是一个合理的需求,但它能够帮助我们了解mock的关键概念。让我们开始重构: +首先,我们将 `rm` 方法重构成一个服务类。实际上将这样一个简单的函数转换成一个对象,在本质上这不是一个合理的需求,但它能够帮助我们了解 `mock` 的关键概念。让我们开始重构: ``` #!/usr/bin/env python @@ -187,8 +185,7 @@ class RemovalService(object): os.remove(filename) ``` -### 你会注意到我们的测试用例没有太大的变化 -### You’ll notice that not much has changed in our test case: +你会注意到我们的测试用例没有太大变化: ``` #!/usr/bin/env python @@ -223,7 +220,7 @@ class RemovalServiceTestCase(unittest.TestCase): mock_os.remove.assert_called_with("any path") ``` -很好,我们知道 RemovalService 会如期工作。接下来让我们创建另一个服务,将 RemovalService 声明为一个依赖 +很好,我们知道 `RemovalService` 会如期工作。接下来让我们创建另一个服务,将 `RemovalService` 声明为它的一个依赖: : ``` @@ -250,9 +247,9 @@ class UploadService(object): self.removal_service.rm(filename) ``` -因为我们的测试覆盖了 RemovalService,因此我们不会对我们测试用例中 UploadService 的内部函数 rm 进行验证。相反,我们将调用 UploadService 的 RemovalService.rm 方法来进行简单测试(当然是没有其他副作用),我们通过之前的测试用例便能知道它可以正确地工作。 +因为我们的测试覆盖了 `RemovalService`,因此我们不会对我们测试用例中 `UploadService` 的内部函数 `rm` 进行验证。相反,我们将调用 `UploadService` 的 `RemovalService.rm` 方法来进行简单测试(当然没有其他副作用),我们通过之前的测试用例便能知道它可以正确地工作。 -这里有两种方法来实现它: +这里有两种方法来实现测试: 1. 模拟 RemovalService.rm 方法本身。 2. 在 UploadService 的构造函数中提供一个模拟实例。 @@ -261,7 +258,7 @@ class UploadService(object): ### 方法 1:模拟实例的方法 -该模拟库有一个特殊的方法装饰器,可以模拟对象势力的方法和属性,即 @mock.patch.object decorator: +`mock` 库有一个特殊的方法装饰器,可以模拟对象实例的方法和属性,即 `@mock.patch.object decorator` 装饰器: ``` #!/usr/bin/env python @@ -314,33 +311,32 @@ class UploadServiceTestCase(unittest.TestCase): removal_service.rm.assert_called_with("my uploaded file") ``` -非常棒!我们验证了 UploadService 成功调用了我们实例的 rm 方法。你是否注意到一些有趣的地方?这种修补机制实际上替换了我们测试用例中的所有 RemovalService 实例的 rm 方法。这意味着我们可以检查实例本身。如果你想要了解更多,可以试着在你模拟的代码下断点,以对这种修补机制的原理获得更好的认识。 +非常棒!我们验证了 UploadService 成功调用了我们实例的 rm 方法。你是否注意到一些有趣的地方?这种修补机制(patching mechanism)实际上替换了我们测试用例中的所有 `RemovalService` 实例的 `rm` 方法。这意味着我们可以检查实例本身。如果你想要了解更多,可以试着在你模拟的代码下断点,以对这种修补机制的原理获得更好的认识。 ### 陷阱:装饰顺序 -当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱。基本桑,当装饰器被映射到方法参数时,[装饰器的工作顺序是反向的][3]。思考这个例子: +当我们在测试方法中使用多个装饰器,其顺序是很重要的,并且很容易混乱。基本上,当装饰器被映射到方法参数时,[装饰器的工作顺序是反向的][3]。思考这个例子: ``` -@mock.patch('mymodule.sys') + @mock.patch('mymodule.sys') @mock.patch('mymodule.os') @mock.patch('mymodule.os.path') def test_something(self, mock_os_path, mock_os, mock_sys): pass ``` -注意到我们的参数和装饰器的顺序是反向匹配了吗?这多多少少是由 [Python 的工作方式][4] 导致的。这里是使用多个装饰器的情况下它们的执行顺序的伪代码: +注意到我们的参数和装饰器的顺序是反向匹配了吗?这多多少少是由 [Python 的工作方式][4] 导致的。这里是使用多个装饰器的情况下它们执行顺序的伪代码: ``` patch_sys(patch_os(patch_os_path(test_something))) ``` -因为 sys 补丁位于最外层,所以它会最晚执行,使得它成为实际测试方法参数的最后一个参数。请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入。 +因为 sys 补丁位于最外层,所以它最晚执行,使得它成为实际测试方法参数的最后一个参数。请特别注意这一点,并且在运行你的测试用例时,使用调试器来保证正确的参数以正确的顺序注入。 ### 方法 2:创建 Mock 实例 -我们可以使用构造函数为 UploadService 提供一个 Mock 实例,而不是模拟特定的实例方法。我推荐上面的方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要。让我们再次重构测试用例: -Instead of mocking the specific instance method, we could instead just supply a mocked instance to UploadService with its constructor. I prefer option 1 above, as it’s a lot more precise, but there are many cases where option 2 might be efficient or necessary. Let’s refactor our test again: +我们可以使用构造函数为 UploadService 提供一个 Mock 实例,而不是模拟特定的实例方法。我更推荐方法 1,因为它更加精确,但在多数情况,方法 2 或许更加有效和必要。让我们再次重构测试用例: ``` #!/usr/bin/env python @@ -389,14 +385,13 @@ class UploadServiceTestCase(unittest.TestCase): mock_removal_service.rm.assert_called_with("my uploaded file") ``` -在这个例子中,我们甚至不需要补充任何功能,只需创建一个 -In this example, we haven’t even had to patch any functionality, we simply create an auto-spec for the RemovalService class, and then inject this instance into our UploadService to validate the functionality. +在这个例子中,我们甚至不需要补充任何功能,只需为 `RemovalService` 类创建一个 auto-spec,然后将实例注入到我们的 `UploadService` 以验证功能。 -The [mock.create_autospec][5] method creates a functionally equivalent instance to the provided class. What this means, practically speaking, is that when the returned instance is interacted with, it will raise exceptions if used in illegal ways. More specifically, if a method is called with the wrong number of arguments, an exception will be raised. This is extremely important as refactors happen. As a library changes, tests break and that is expected. Without using an auto-spec, our tests will still pass even though the underlying implementation is broken. +`mock.create_autospec` 方法为类提供了一个同等功能实例。实际上来说,这意味着在使用返回的实例进行交互的时候,如果使用了非法的方式将会引发异常。更具体地说,如果一个方法被调用时的参数数目不正确,将引发一个异常。这对于重构来说是非常重要。当一个库发生变化的时候,中断测试正是所期望的。如果不使用 auto-spec,尽管底层的实现已经被破坏,我们的测试仍然会通过。 -### Pitfall: The mock.Mock and mock.MagicMock Classes +### 陷阱:mock.Mock 和 mock.MagicMock 类 -The mock library also includes two important classes upon which most of the internal functionality is built upon: [mock.Mock][6] and mock.MagicMock. When given a choice to use a mock.Mock instance, a mock.MagicMock instance, or an auto-spec, always favor using an auto-spec, as it helps keep your tests sane for future changes. This is because mock.Mock and mock.MagicMock accept all method calls and property assignments regardless of the underlying API. Consider the following use case: +`mock` 库包含了两个重要的类 [mock.Mock](http://www.voidspace.org.uk/python/mock/mock.html) 和 [mock.MagicMock](http://www.voidspace.org.uk/python/mock/magicmock.html#magic-mock),大多数内部函数都是建立在这两个类之上的。当在选择使用 `mock.Mock` 实例,`mock.MagicMock` 实例或 auto-spec 的时候,通常倾向于选择使用 auto-spec,因为对于未来的变化,它更能保持测试的健全。这是因为 `mock.Mock` 和 `mock.MagicMock` 会无视底层的 API,接受所有的方法调用和属性赋值。比如下面这个用例: ``` class Target(object): @@ -407,7 +402,7 @@ def method(target, value): return target.apply(value) ``` -We can test this with a mock.Mock instance like this: +我们可以像下面这样使用 mock.Mock 实例进行测试: ``` class MethodTestCase(unittest.TestCase): @@ -420,7 +415,7 @@ class MethodTestCase(unittest.TestCase): target.apply.assert_called_with("value") ``` -This logic seems sane, but let’s modify the Target.apply method to take more parameters: +这个逻辑看似合理,但如果我们修改 `Target.apply` 方法接受更多参数: ``` class Target(object): @@ -431,11 +426,11 @@ class Target(object): return None ``` -Re-run your test, and you’ll find that it still passes. That’s because it isn’t built against your actual API. This is why you should always use the create_autospec method and the autospec parameter with the @patch and @patch.object decorators. +重新运行你的测试,你会发现它仍能通过。这是因为它不是针对你的 API 创建的。这就是为什么你总是应该使用 `create_autospec` 方法,并且在使用 `@patch`和 `@patch.object` 装饰方法时使用 `autospec` 参数。 -### Real-World Example: Mocking a Facebook API Call +### 现实例子:模拟 Facebook API 调用 -To finish up, let’s write a more applicable real-world example, one which we mentioned in the introduction: posting a message to Facebook. We’ll write a nice wrapper class and a corresponding test case. +为了完成,我们写一个更加适用的现实例子,一个在介绍中提及的功能:发布消息到 Facebook。我将写一个不错的包装类及其对应的测试用例。 ``` import facebook @@ -450,7 +445,7 @@ class SimpleFacebook(object): self.graph.put_object("me", "feed", message=message) ``` -Here’s our test case, which checks that we post the message without actually posting the message: +这是我们的测试用例,它可以检查我们发布的消息,而不是真正地发布消息: ``` import facebook @@ -469,18 +464,18 @@ class SimpleFacebookTestCase(unittest.TestCase): mock_put_object.assert_called_with(message="Hello World!") ``` -As we’ve seen so far, it’s really simple to start writing smarter tests with mock in Python. +正如我们所看到的,在 Python 中,通过 mock,我们可以非常容易地动手写一个更加智能的测试用例。 -### Mocking in python Conclusion +### Python Mock 总结 -Python’s mock library, if a little confusing to work with, is a game-changer for [unit-testing][7]. We’ve demonstrated common use-cases for getting started using mock in unit-testing, and hopefully this article will help [Python developers][8] overcome the initial hurdles and write excellent, tested code. +对 [单元测试][7] 来说,Python 的 `mock` 库可以说是一个游戏变革者,即使对于它的使用还有点困惑。我们已经演示了单元测试中常见的用例以开始使用 `mock`,并希望这篇文章能够帮助 [Python 开发者][8] 克服初期的障碍,写出优秀、经受过考验的代码。 -------------------------------------------------------------------------------- via: http://slviki.com/index.php/2016/06/18/introduction-to-mocking-in-python/ 作者:[Dasun Sucharith][a] -译者:[译者ID](https://github.com/译者ID) +译者:[cposture](https://github.com/cposture) 校对:[校对者ID](https://github.com/校对者ID) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出 From 239166046b8c918e284ad446f9d8b22d1412ac23 Mon Sep 17 00:00:00 2001 From: cposture Date: Sat, 6 Aug 2016 14:45:13 +0800 Subject: [PATCH 03/11] Translated by cposture --- ...ce portfolio - Machine learning project.md | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md b/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md index 3bec1d0a98..71e7cc0dbe 100644 --- a/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md +++ b/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md @@ -1,17 +1,15 @@ -Translating by cposture 2016-08-02 -### Making predictions +### 做出预测 -Now that we have the preliminaries out of the way, we’re ready to make predictions. We’ll create a new file called predict.py that will use the train.csv file we created in the last step. The below code will: +既然完成了前期准备,我们可以开始准备做出预测了。我将创建一个名为 predict.py 的新文件,它会使用我们在最后一步创建的 train.csv 文件。下面的代码: -- Import needed libraries. -- Create a function called cross_validate that: - - Creates a logistic regression classifier with the right keyword arguments. - - Creates a list of columns that we want to use to train the model, removing id and foreclosure_status. - - Run cross validation across the train DataFrame. - - Return the predictions. +- 导入所需的库 +- 创建一个名为 `cross_validate` 的函数: + - 使用正确的关键词参数创建逻辑回归分类器(logistic regression classifier) + - 创建移除了 `id` 和 `foreclosure_status` 属性的用于训练模型的列 + - 跨 `train` 数据帧使用交叉验证 + - 返回预测结果 - -``` +```python import os import settings import pandas as pd @@ -29,22 +27,19 @@ def cross_validate(train): return predictions ``` -### Predicting error +### 预测误差 -Now, we just need to write a few functions to compute error. The below code will: +现在,我们仅仅需要写一些函数来计算误差。下面的代码: -- Create a function called compute_error that: - - Uses scikit-learn to compute a simple accuracy score (the percentage of predictions that matched the actual foreclosure_status values). -- Create a function called compute_false_negatives that: - - Combines the target and the predictions into a DataFrame for convenience. - - Finds the false negative rate. -- Create a function called compute_false_positives that: - - Combines the target and the predictions into a DataFrame for convenience. - - Finds the false positive rate. - - Finds the number of loans that weren’t foreclosed on that the model predicted would be foreclosed on. - - Divide by the total number of loans that weren’t foreclosed on. +- 创建函数 `compute_error`: + - 使用 scikit-learn 计算一个简单的精确分数(与实际 `foreclosure_status` 值匹配的预测百分比) +- 创建函数 `compute_false_negatives`: + - 为了方便,将目标和预测结果合并到一个数据帧 + - 查找漏报率 + - 找到原本应被预测模型取消但没有取消的贷款数目 + - 除以没被取消的贷款总数目 -``` +```python def compute_error(target, predictions): return metrics.accuracy_score(target, predictions) @@ -57,21 +52,20 @@ def compute_false_positives(target, predictions): return df[(df["target"] == 0) & (df["predictions"] == 1)].shape[0] / (df[(df["target"] == 0)].shape[0] + 1) ``` +### 聚合到一起 -### Putting it all together +现在,我们可以把函数都放在 `predict.py`。下面的代码: -Now, we just have to put the functions together in predict.py. The below code will: +- 读取数据集 +- 计算交叉验证预测 +- 计算上面的 3 个误差 +- 打印误差 -- Read in the dataset. -- Compute cross validated predictions. -- Compute the 3 error metrics above. -- Print the error metrics. - -``` +```python def read(): train = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "train.csv")) return train - + if __name__ == "__main__": train = read() predictions = cross_validate(train) @@ -83,11 +77,11 @@ if __name__ == "__main__": print("False Positives: {}".format(fp)) ``` -Once you’ve added the code, you can run python predict.py to generate predictions. Running everything shows that our false negative rate is .26, which means that of the foreclosed loans, we missed predicting 26% of them. This is a good start, but can use a lot of improvement! +一旦你添加完代码,你可以运行 `python predict.py` 来产生预测结果。运行结果向我们展示漏报率为 `.26`,这意味着我们没能预测 `26%` 的取消贷款。这是一个好的开始,但仍有很多改善的地方! -You can find the complete predict.py file [here][41]. +你可以在[这里][41]找到完整的 `predict.py` 文件 -Your file tree should now look like this: +你的文件树现在看起来像下面这样: ``` loan-prediction @@ -110,47 +104,47 @@ loan-prediction ├── settings.py ``` -### Writing up a README +### 撰写 README -Now that we’ve finished our end to end project, we just have to write up a README.md file so that other people know what we did, and how to replicate it. A typical README.md for a project should include these sections: +既然我们完成了端到端的项目,那么我们可以撰写 README.md 文件了,这样其他人便可以知道我们做的事,以及如何复制它。一个项目典型的 README.md 应该包括这些部分: -- A high level overview of the project, and what the goals are. -- Where to download any needed data or materials. -- Installation instructions. - - How to install the requirements. -- Usage instructions. - - How to run the project. - - What you should see after each step. -- How to contribute to the project. - - Good next steps for extending the project. +- 一个高水准的项目概览,并介绍项目目的 +- 任何必需的数据和材料的下载地址 +- 安装命令 + - 如何安装要求依赖 +- 使用命令 + - 如何运行项目 + - 每一步之后会看到的结果 +- 如何为这个项目作贡献 + - 扩展项目的下一步计划 -[Here’s][42] a sample README.md for this project. +[这里][42] 是这个项目的一个 README.md 样例。 -### Next steps +### 下一步 -Congratulations, you’re done making an end to end machine learning project! You can find a complete example project [here][43]. It’s a good idea to upload your project to [Github][44] once you’ve finished it, so others can see it as part of your portfolio. +恭喜你完成了端到端的机器学习项目!你可以在[这里][43]找到一个完整的示例项目。一旦你完成了项目,把它上传到 [Github][44] 是一个不错的主意,这样其他人也可以看到你的文件夹的部分项目。 -There are still quite a few angles left to explore with this data. Broadly, we can split them up into 3 categories – extending this project and making it more accurate, finding other columns to predict, and exploring the data. Here are some ideas: +这里仍有一些留待探索数据的角度。总的来说,我们可以把它们分割为 3 类 - 扩展这个项目并使它更加精确,发现预测其他列并探索数据。这是其中一些想法: -- Generate more features in annotate.py. -- Switch algorithms in predict.py. -- Try using more data from Fannie Mae than we used in this post. -- Add in a way to make predictions on future data. The code we wrote will still work if we add more data, so we can add more past or future data. -- Try seeing if you can predict if a bank should have issued the loan originally (vs if Fannie Mae should have acquired the loan). - - Remove any columns from train that the bank wouldn’t have known at the time of issuing the loan. - - Some columns are known when Fannie Mae bought the loan, but not before. - - Make predictions. -- Explore seeing if you can predict columns other than foreclosure_status. - - Can you predict how much the property will be worth at sale time? -- Explore the nuances between performance updates. - - Can you predict how many times the borrower will be late on payments? - - Can you map out the typical loan lifecycle? -- Map out data on a state by state or zip code by zip code level. - - Do you see any interesting patterns? +- 在 `annotate.py` 中生成更多的特性 +- 切换 `predict.py` 中的算法 +- 尝试使用比我们发表在这里的更多的来自 `Fannie Mae` 的数据 +- 添加对未来数据进行预测的方法。如果我们添加更多数据,我们所写的代码仍然可以起作用,这样我们可以添加更多过去和未来的数据。 +- 尝试看看是否你能预测一个银行是否应该发放贷款(相对地,`Fannie Mae` 是否应该获得贷款) + - 移除 train 中银行不知道发放贷款的时间的任何列 + - 当 Fannie Mae 购买贷款时,一些列是已知的,但不是之前 + - 做出预测 +- 探索是否你可以预测除了 foreclosure_status 的其他列 + - 你可以预测在销售时财产值多少? +- 探索探索性能更新之间的细微差别 + - 你能否预测借款人会逾期还款多少次? + - 你能否标出的典型贷款周期? +- 标出一个州到州或邮政编码到邮政级水平的数据 + - 你看到一些有趣的模式了吗? -If you build anything interesting, please let us know in the comments! +如果你建立了任何有趣的东西,请在评论中让我们知道! -If you liked this, you might like to read the other posts in our ‘Build a Data Science Porfolio’ series: +如果你喜欢这个,你可能会喜欢阅读 ‘Build a Data Science Porfolio’ 系列其他文章: - [Storytelling with data][45]. - [How to setup up a data science blog][46]. From fb71a5ca0d1df18fa0b8b6b2c4a29e50b7ff9ec3 Mon Sep 17 00:00:00 2001 From: cposture Date: Sat, 6 Aug 2016 14:54:18 +0800 Subject: [PATCH 04/11] Translated by cposture --- ...lding a data science portfolio - Machine learning project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md b/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md index 71e7cc0dbe..2ea994c119 100644 --- a/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md +++ b/sources/team_test/part 6 - Building a data science portfolio - Machine learning project.md @@ -144,7 +144,7 @@ loan-prediction 如果你建立了任何有趣的东西,请在评论中让我们知道! -如果你喜欢这个,你可能会喜欢阅读 ‘Build a Data Science Porfolio’ 系列其他文章: +如果你喜欢这个,你可能会喜欢阅读 ‘Build a Data Science Porfolio’ 系列的其他文章: - [Storytelling with data][45]. - [How to setup up a data science blog][46]. From 4126a52d34d48317c51ce4a01fe4742377346f39 Mon Sep 17 00:00:00 2001 From: cposture Date: Sun, 7 Aug 2016 13:02:32 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E5=BA=94=20erlinux=20=E7=9A=84=E8=A6=81?= =?UTF-8?q?=E6=B1=82=EF=BC=8C=E6=8A=8A=E6=9C=AA=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E7=9A=84=E6=96=87=E7=AB=A0=E7=A7=BB=E5=88=B0=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ... Rapid prototyping with docker-compose.md | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 sources/tech/20160512 Rapid prototyping with docker-compose.md diff --git a/sources/tech/20160512 Rapid prototyping with docker-compose.md b/sources/tech/20160512 Rapid prototyping with docker-compose.md new file mode 100644 index 0000000000..0c67223697 --- /dev/null +++ b/sources/tech/20160512 Rapid prototyping with docker-compose.md @@ -0,0 +1,142 @@ + +Rapid prototyping with docker-compose +======================================== + +In this write-up we'll look at a Node.js prototype for **finding stock of the Raspberry PI Zero** from three major outlets in the UK. + +I wrote the code and deployed it to an Ubuntu VM in Azure within a single evening of hacking. Docker and the docker-compose tool made the deployment and update process extremely quick. + +### Remember linking? + +If you've already been through the [Hands-On Docker tutorial][1] then you will have experience linking Docker containers on the command line. Linking a Node hit counter to a Redis server on the command line may look like this: + +``` +$ docker run -d -P --name redis1 +$ docker run -d hit_counter -p 3000:3000 --link redis1:redis +``` + +Now imagine your application has three tiers + +- Web front-end +- Batch tier for processing long running tasks +- Redis or mongo database + +Explicit linking through `--link` is just about manageable with a couple of containers, but can get out of hand as we add more tiers or containers to the application. + +### Enter docker-compose + +![](http://blog.alexellis.io/content/images/2016/05/docker-compose-logo-01.png) +>Docker Compose logo + +The docker-compose tool is part of the standard Docker Toolbox and can also be downloaded separately. It provides a rich set of features to configure all of an application's parts through a plain-text YAML file. + +The above example would look like this: + +``` +version: "2.0" +services: + redis1: + image: redis + hit_counter: + build: ./hit_counter + ports: + - 3000:3000 +``` + +From Docker 1.10 onwards we can take advantage of network overlays to help us scale out across multiple hosts. Prior to this linking only worked across a single host. The `docker-compose scale` command can be used to bring on more computing power as the need arises. + +>View the [docker-compose][2] reference on docker.com + +### Real-world example: Raspberry PI Stock Alert + +![](http://blog.alexellis.io/content/images/2016/05/Raspberry_Pi_Zero_ver_1-3_1_of_3_large.JPG) +>The new Raspberry PI Zero v1.3 image courtesy of Pimoroni + +There is a huge buzz around the Raspberry PI Zero - a tiny microcomputer with a 1GHz CPU and 512MB RAM capable of running full Linux, Docker, Node.js, Ruby and many other popular open-source tools. One of the best things about the PI Zero is that costs only 5 USD. That also means that stock gets snapped up really quickly. + +*If you want to try Docker or Swarm on the PI check out the tutorial below.* + +>[Docker Swarm on the PI Zero][3] + +### Original site: whereismypizero.com + +I found a webpage which used screen scraping to find whether 4-5 of the most popular outlets had stock. + +- The site contained a static HTML page +- Issued one XMLHttpRequest per outlet accessing /public/api/ +- The server issued the HTTP request to each shop and performed the scraping + +Every call to /public/api/ took 3 seconds to execute and using Apache Bench (ab) I was only able to get through 0.25 requests per second. + +### Reinventing the wheel + +The retailers didn't seem to mind whereismypizero.com scraping their sites for stock, so I set about writing a similar tool from the ground up. I had the intention of handing a much higher amount of requests per second through caching and de-coupling the scrape from the web tier. Redis was the perfect tool for the job. It allowed me to set an automatically expiring key/value pair (i.e. a simple cache) and also to transmit messages between Node processes through pub/sub. + +>Fork or star the code on Github: [alexellis/pi_zero_stock][4] + +If you've worked with Node.js before then you will know it is single-threaded and that any CPU intensive tasks such as parsing HTML or JSON could lead to a slow-down. One way to mitigate that is to use a second worker process and a Redis messaging channel as connective tissue between this and the web tier. + +- Web tier + -Gives 200 for cache hit (Redis key exists for store) + -Gives 202 for cache miss (Redis key doesn't exist, so issues message) + -Since we are only ever reading a Redis key the response time is very quick. +- Stock Fetcher + -Performs HTTP request + -Scrapes for different types of web stores + -Updates a Redis key with a cache expire of 60 seconds + -Also locks a Redis key to prevent too many in-flight HTTP requests to the web stores. +``` +version: "2.0" +services: + web: + build: ./web/ + ports: + - "3000:3000" + stock_fetch: + build: ./stock_fetch/ + redis: + image: redis +``` + +*The docker-compose.yml file from the example.* + +Once I had this working locally deploying to an Ubuntu 16.04 image in the cloud (Azure) took less than 5 minutes. I logged in, cloned the repository and typed in `docker compose up -d`. That was all it took - rapid prototyping a whole system doesn't get much better. Anyone (including the owner of whereismypizero.com) can deploy the new solution with just two lines: + +``` +$ git clone https://github.com/alexellis/pi_zero_stock +$ docker-compose up -d +``` + +Updating the site is easy and just involves a `git pull` followed by a `docker-compose up -d` with the `--build` argument passed along. + +If you are still linking your Docker containers manually, try Docker Compose for yourself or my code below: + +>Fork or star the code on Github: [alexellis/pi_zero_stock][5] + +### Check out the test site + +The test site is currently deployed now using docker-compose. + +>[stockalert.alexellis.io][6] + +![](http://blog.alexellis.io/content/images/2016/05/Screen-Shot-2016-05-16-at-22-34-26-1.png) + +Preview as of 16th of May 2016 + +---------- +via: http://blog.alexellis.io/rapid-prototype-docker-compose/ + +作者:[Alex Ellis][a] +译者:[译者ID](https://github.com/译者ID) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://blog.alexellis.io/author/alex/ +[1]: http://blog.alexellis.io/handsondocker +[2]: https://docs.docker.com/compose/compose-file/ +[3]: http://blog.alexellis.io/dockerswarm-pizero/ +[4]: https://github.com/alexellis/pi_zero_stock +[5]: https://github.com/alexellis/pi_zero_stock +[6]: http://stockalert.alexellis.io/ + From 182f9f1a244b8d0fb0027d8171378c789fd2deda Mon Sep 17 00:00:00 2001 From: cposture Date: Sun, 7 Aug 2016 13:04:09 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E5=BA=94=20erlinux=20=E7=9A=84=E8=A6=81?= =?UTF-8?q?=E6=B1=82=EF=BC=8C=E6=8A=8A=E6=9C=AA=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E7=9A=84=E6=96=87=E7=AB=A0=E7=A7=BB=E5=88=B0=20source,?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E5=88=A0=E9=99=A4=20translated=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ... Rapid prototyping with docker-compose.md | 141 ------------------ 1 file changed, 141 deletions(-) delete mode 100644 translated/tech/20160512 Rapid prototyping with docker-compose.md diff --git a/translated/tech/20160512 Rapid prototyping with docker-compose.md b/translated/tech/20160512 Rapid prototyping with docker-compose.md deleted file mode 100644 index a32f894d4b..0000000000 --- a/translated/tech/20160512 Rapid prototyping with docker-compose.md +++ /dev/null @@ -1,141 +0,0 @@ -使用docker快速组成样品机 -======================================== - -在写前,我们将看看 Node.js 样机 ** 找寻树莓派 PI Zero ** 的供应在英国三个主要销售. - -我写的代码,黑客部署到 Azure Ubuntu 虚拟机一个晚上就可以到位。Docker 和 docker-compose 工具做出调配和更新过程非常快。 - -### 建立链接? - - -如果您已经通过 [动手 Docker 教程指南] [1] 那么你已有在命令行建立 Docker 容器的经验。链接一个Redis 服务器计数器节点在命令行上可能是这样: - -``` -$ docker run -d -P --name redis1 -$ docker run -d hit_counter -p 3000:3000 --link redis1:redis -``` - -现在,假设应用程序中有三个等级 - -- Web 前端 -- 批次层处理长时间运行的任务 -- Redis 或 MongoDB 数据库 - -通过 `--link` 管理几个容器,但可能失效,可以添加多层级或容器到应用程序。 - -### 键入 docker 撰写 - -![](http://blog.alexellis.io/content/images/2016/05/docker-compose-logo-01.png) ->Docker 撰写图标 - -docker-compose 工具是标准的 docker工具箱的一部分,也可以单独下载。它提供了丰富功能,通过一个纯文本YAML文件配置所有应用程序组件。 - -上述提供了一个例子: - -``` -version: "2.0" -services: - redis1: - image: redis - hit_counter: - build: ./hit_counter - ports: - - 3000:3000 -``` - -从Docker 1.10起,我们可以充分利用网络来帮助我们在多个主机进行扩展覆盖。在此之前,仅通过单个主机工作。“docker-compose scale” 命令可用于更多计算能力有需要时。 - ->参考docker.com上关于"docker-compose" - -### 真实例子:树莓派 PI 到货通知 - -![](http://blog.alexellis.io/content/images/2016/05/Raspberry_Pi_Zero_ver_1-3_1_of_3_large.JPG) ->新版树莓派 PI Zero V1.3 图片提供来自Pimoroni - -树莓派 PI Zero - 巨大的轰动一个微型计算机具有一个1GHz 处理器 和 512MB 内存能够运行完整 Linux,Docker,Node.js,Ruby 和许多流行的开源工具。一个关于 PI Zero 的好消息是,成本只有5美元。这也意味着,存量迅速抢购一空。 - -*如果您想尝试Docker 或集群在PI看看下面的教程。* - ->[Docker Swarm on the PI Zero][3] - -### Original site: whereismypizero.com - -I found a webpage which used screen scraping to find whether 4-5 of the most popular outlets had stock. - -- The site contained a static HTML page -- Issued one XMLHttpRequest per outlet accessing /public/api/ -- The server issued the HTTP request to each shop and performed the scraping - -Every call to /public/api/ took 3 seconds to execute and using Apache Bench (ab) I was only able to get through 0.25 requests per second. - -### Reinventing the wheel - -The retailers didn't seem to mind whereismypizero.com scraping their sites for stock, so I set about writing a similar tool from the ground up. I had the intention of handing a much higher amount of requests per second through caching and de-coupling the scrape from the web tier. Redis was the perfect tool for the job. It allowed me to set an automatically expiring key/value pair (i.e. a simple cache) and also to transmit messages between Node processes through pub/sub. - ->Fork or star the code on Github: [alexellis/pi_zero_stock][4] - -If you've worked with Node.js before then you will know it is single-threaded and that any CPU intensive tasks such as parsing HTML or JSON could lead to a slow-down. One way to mitigate that is to use a second worker process and a Redis messaging channel as connective tissue between this and the web tier. - -- Web tier - -Gives 200 for cache hit (Redis key exists for store) - -Gives 202 for cache miss (Redis key doesn't exist, so issues message) - -Since we are only ever reading a Redis key the response time is very quick. -- Stock Fetcher - -Performs HTTP request - -Scrapes for different types of web stores - -Updates a Redis key with a cache expire of 60 seconds - -Also locks a Redis key to prevent too many in-flight HTTP requests to the web stores. -``` -version: "2.0" -services: - web: - build: ./web/ - ports: - - "3000:3000" - stock_fetch: - build: ./stock_fetch/ - redis: - image: redis -``` - -*The docker-compose.yml file from the example.* - -Once I had this working locally deploying to an Ubuntu 16.04 image in the cloud (Azure) took less than 5 minutes. I logged in, cloned the repository and typed in `docker compose up -d`. That was all it took - rapid prototyping a whole system doesn't get much better. Anyone (including the owner of whereismypizero.com) can deploy the new solution with just two lines: - -``` -$ git clone https://github.com/alexellis/pi_zero_stock -$ docker-compose up -d -``` - -Updating the site is easy and just involves a `git pull` followed by a `docker-compose up -d` with the `--build` argument passed along. - -If you are still linking your Docker containers manually, try Docker Compose for yourself or my code below: - ->Fork or star the code on Github: [alexellis/pi_zero_stock][5] - -### Check out the test site - -The test site is currently deployed now using docker-compose. - ->[stockalert.alexellis.io][6] - -![](http://blog.alexellis.io/content/images/2016/05/Screen-Shot-2016-05-16-at-22-34-26-1.png) - -Preview as of 16th of May 2016 - ----------- -via: http://blog.alexellis.io/rapid-prototype-docker-compose/ - -作者:[Alex Ellis][a] -译者:[erlinux](https://github.com/erlinux) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: http://blog.alexellis.io/author/alex/ -[1]: http://blog.alexellis.io/handsondocker -[2]: https://docs.docker.com/compose/compose-file/ -[3]: http://blog.alexellis.io/dockerswarm-pizero/ -[4]: https://github.com/alexellis/pi_zero_stock -[5]: https://github.com/alexellis/pi_zero_stock -[6]: http://stockalert.alexellis.io/ From 083c512d89d1a5d746a915809a37c69c7ee959d4 Mon Sep 17 00:00:00 2001 From: cposture Date: Sat, 20 Aug 2016 17:33:20 +0800 Subject: [PATCH 07/11] Translating --- ...hon unittest - assertTrue is truthy - assertFalse is falsy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/tech/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md b/sources/tech/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md index bfed46fb0e..30589a9d63 100644 --- a/sources/tech/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md +++ b/sources/tech/20160512 Python unittest - assertTrue is truthy - assertFalse is falsy.md @@ -1,3 +1,4 @@ +Translating by cposture Python unittest: assertTrue is truthy, assertFalse is falsy =========================== From 6701bfaa97d2b53faca4888326bf673a9ecd769f Mon Sep 17 00:00:00 2001 From: cposture Date: Sat, 20 Aug 2016 23:07:20 +0800 Subject: [PATCH 08/11] Translated by cposture --- .../20160820 Protocol Buffer Basics C++.md | 396 ++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 translated/tech/20160820 Protocol Buffer Basics C++.md diff --git a/translated/tech/20160820 Protocol Buffer Basics C++.md b/translated/tech/20160820 Protocol Buffer Basics C++.md new file mode 100644 index 0000000000..11bda510d4 --- /dev/null +++ b/translated/tech/20160820 Protocol Buffer Basics C++.md @@ -0,0 +1,396 @@ +这篇教程提供了一个面向 C++ 程序员、关于 `protocol buffers` 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示: + +* 在 `.proto` 文件中定义消息格式 +* 使用 `protocol buffer` 编译器 +* 使用 `C++ protocol buffer API` 读写消息 + +这不是一个关于使用 C++ protocol buffers 的全面指南。要获取更详细的信息,请参考 [Protocol Buffer Language Guide](https://developers.google.com/protocol-buffers/docs/proto) 和 [Encoding Reference](https://developers.google.com/protocol-buffers/docs/encoding)。 + +# 为什么使用 Protocol Buffers + +我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字,ID,邮件地址和联系电话。 + +如何序列化和获取结构化的数据?这里有几种解决方案: + +* 以二进制形式发送/接收原生的内存数据结构。通常,这是一种脆弱的方法,因为接收/读取代码的编译必须基于完全相同的内存布局、大小端等等。同时,当文件增加时,原始格式数据会随着与该格式相连的软件拷贝而迅速扩散,这将很难扩展文件格式。 + +* 你可以创造一种 `ad-hoc` 方法,将数据项编码为一个字符串——比如将 4 个整数编码为 "12:3:-23:67"。虽然它需要编写一次性的编码和解码代码且解码需要耗费小的运行时成本,但这是一种简单灵活的方法。这最适合编码非常简单的数据。 + +* 序列化数据为 `XML`。这种方法是非常吸引人的,因为 `XML` 是一种适合人阅读的格式,并且有为许多语言开发的库。如果你想与其他程序和项目共享数据,这可能是一种不错的选择。然而,众所周知,`XML` 是空间密集型的,且在编码和解码时,它对程序会造成巨大的性能损失。同时,使用 XML DOM 树被认为比操作一个类的简单字段更加复杂。 + +`Protocol buffers` 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 `Protocol buffers`,你需要写一个 `.proto` 说明,用于描述你所希望存储的数据结构。利用 `.proto` 文件,protocol buffer 编译器可以创建一个类,用于实现自动化编码和解码高效的二进制格式的 protocol buffer 数据。产生的类提供了构造 `protocol buffer` 的字段的 getters 和 setters,并且作为一个单元,关注读写 `protocol buffer` 的细节。重要的是,`protocol buffer` 格式支持扩展格式,代码仍然可以读取以旧格式编码的数据。 + +# 在哪可以找到示例代码 + +示例代码被包含于源代码包,位于 "examples" 文件夹。在[这](https://developers.google.com/protocol-buffers/docs/downloads)下载代码。 + +# 定义你的协议格式 + +为了创建自己的地址簿应用程序,你需要从 `.proto` 开始。`.proto` 文件中的定义很简单:为你所需要序列化的数据结构添加一个消息(message),然后为消息中的每一个字段指定一个名字和类型。这里是定义你消息的 `.proto` 文件,`addressbook.proto`。 + +``` +package tutorial; + +message Person { + required string name = 1; + required int32 id = 2; + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; + optional PhoneType type = 2 [default = HOME]; + } + + repeated PhoneNumber phone = 4; +} + +message AddressBook { + repeated Person person = 1; +} +``` + +如你所见,其语法类似于 C++ 或 Java。我们开始看看文件的每一部分内容做了什么。 + +`.proto` 文件以一个 package 声明开始,这可以避免不同项目的命名冲突。在 C++,你生成的类会被置于与 package 名字一样的命名空间。 + +下一步,你需要定义消息(message)。消息只是一个包含一系列类型字段的集合。大多标准简单数据类型是可以作为字段类型的,包括 `bool`、`int32`、`float`、`double` 和 `string`。你也可以通过使用其他消息类型作为字段类型,将更多的数据结构添加到你的消息中——在以上的示例,`Person` 消息包含了 `PhoneNumber` 消息,同时 `AddressBook` 消息包含 `Person` 消息。你甚至可以定义嵌套在其他消息内的消息类型——如你所见,`PhoneNumber` 类型定义于 `Person` 内部。如果你想要其中某一个字段拥有预定义值列表中的某个值,你也可以定义 `enum` 类型——这儿你想指定一个电话号码可以是 `MOBILE`、`HOME` 或 `WORK` 中的某一个。 + +每一个元素上的 “=1”、"=2" 标记确定了用于二进制编码的唯一"标签"(tag)。标签数字 1-15 的编码比更大的数字少需要一个字节,因此作为一种优化,你可以将这些标签用于经常使用或 repeated 元素,剩下 16 以及更高的标签用于非经常使用或 optional 元素。每一个 repeated 字段的元素需要重新编码标签数字,因此 repeated 字段对于这优化是一个特别好的候选者。 + +每一个字段必须使用下面的修饰符加以标注: + +* required:必须提供字段的值,否则消息会被认为是 "未初始化的"(uninitialized)。如果 `libprotobuf` 以 debug 模式编译,序列化未初始化的消息将引起一个断言失败。以优化形式构建,将会跳过检查,并且无论如何都会写入消息。然而,解析未初始化的消息总是会失败(通过 parse 方法返回 `false`)。除此之外,一个 required 字段的表现与 optional 字段完全一样。 + +* optional:字段可能会被设置,也可能不会。如果一个 optional 字段没被设置,它将使用默认值。对于简单类型,你可以指定你自己的默认值,正如例子中我们对电话号码的 `type` 一样,否则使用系统默认值:数字类型为 0、字符串为空字符串、布尔值为 false。对于嵌套消息,默认值总为消息的"默认实例"或"原型",它的所有字段都没被设置。调用 accessor 来获取一个没有显式设置的 optional(或 required) 字段的值总是返回字段的默认值。 + +* repeated:字段可以重复任意次数(包括 0)。repeated 值的顺序会被保存于 protocol buffer。可以将 repeated 字段想象为动态大小的数组。 + +你可以查找关于编写 `.proto` 文件的完整指导——包括所有可能的字段类型——在 [Protocol Buffer Language Guide](https://developers.google.com/protocol-buffers/docs/proto)。不要在这里面查找与类继承相似的特性,因为 protocol buffers 不会做这些。 + +Required Is Forever You should be very careful about marking fields as required. If at some point you wish to stop writing or sending a required field, it will be problematic to change the field to an optional field – old readers will consider messages without this field to be incomplete and may reject or drop them unintentionally. You should consider writing application-specific custom validation routines for your buffers instead. Some engineers at Google have come to the conclusion that using required does more harm than good; they prefer to use only optional and repeated. However, this view is not universal. + +> required 是永久性的,在把一个字段标识为 required 的时候,你应该特别小心。如果在某些情况下你不想写入或者发送一个 required 的字段,那么将该字段更改为 optional 可能会遇到问题——旧版本的读者(译者注:即读取、解析旧版本 Protocol Buffer 消息的一方)会认为不含该字段的消息是不完整的,从而有可能会拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google 的一些工程师得出了一个结论:使用 required 弊多于利;他们更愿意使用 optional 和 repeated 而不是 required。当然,这个观点并不具有普遍性。 + +# 编译你的 Protocol Buffers + +既然你有了一个 `.proto`,那你需要做的下一件事就是生成一个将用于读写 `AddressBook` 消息的类(从而包括 `Person` 和 `PhoneNumber`)。为了做到这样,你需要在你的 `.proto` 上运行 protocol buffer 编译器 `protoc`: + +1. 如果你没有安装编译器,请[下载这个包](https://developers.google.com/protocol-buffers/docs/downloads.html),并按照 README 中的指令进行安装。 +2. 现在运行编译器,知道源目录(你的应用程序源代码位于哪里——如果你没有提供任何值,将使用当前目录),目标目录(你想要生成的代码放在哪里;常与 `$SRC_DIR` 相同),并且你的 `.proto` 路径。在此示例,你...: + +``` +protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto +``` + +因为你想要 C++ 的类,所以你使用了 `--cpp_out` 选项——也为其他支持的语言提供了类似选项。 + +在你指定的目标文件夹,将生成以下的文件: + +* `addressbook.pb.h`,声明你生成类的头文件。 +* `addressbook.pb.cc`,包含你的类的实现。 + +# Protocol Buffer API + +让我们看看生成的一些代码,了解一下编译器为你创建了什么类和函数。如果你查看 `tutorial.pb.h`,你可以看到有一个在 `tutorial.proto` 中指定所有消息的类。关注 `Person` 类,可以看到编译器为每个字段生成了读写函数(accessors)。例如,对于 `name`、`id`、`email` 和 `phone` 字段,有下面这些方法: + +```c++ +// name +inline bool has_name() const; +inline void clear_name(); +inline const ::std::string& name() const; +inline void set_name(const ::std::string& value); +inline void set_name(const char* value); +inline ::std::string* mutable_name(); + +// id +inline bool has_id() const; +inline void clear_id(); +inline int32_t id() const; +inline void set_id(int32_t value); + +// email +inline bool has_email() const; +inline void clear_email(); +inline const ::std::string& email() const; +inline void set_email(const ::std::string& value); +inline void set_email(const char* value); +inline ::std::string* mutable_email(); + +// phone +inline int phone_size() const; +inline void clear_phone(); +inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const; +inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone(); +inline const ::tutorial::Person_PhoneNumber& phone(int index) const; +inline ::tutorial::Person_PhoneNumber* mutable_phone(int index); +inline ::tutorial::Person_PhoneNumber* add_phone(); +``` + +正如你所见到,getters 的名字与字段的小写名字完全一样,并且 setter 方法以 set_ 开头。同时每个单一(singular)(required 或 optional)字段都有 `has_` 方法,该方法在字段被设置了值的情况下返回 true。最后,所有字段都有一个 `clear_` 方法,用以清除字段到空(empty)状态。 + +数字 `id` 字段仅有上述的基本读写函数集合(accessors),而 `name` 和 `email` 字段有两个额外的方法,因为它们是字符串——一个是可以获得字符串直接指针的`mutable_` getter ,另一个为额外的 setter。注意,尽管 `email` 还没被设置(set),你也可以调用 `mutable_email`;因为 `email` 会被自动地初始化为空字符串。在本例中,如果你有一个单一的(required 或 optional)消息字段,它会有一个 `mutable_` 方法,而没有 `set_` 方法。 + +repeated 字段也有一些特殊的方法——如果你看看 repeated `phone` 字段的方法,你可以看到: + +* 检查 repeated 字段的 `_size`(也就是说,与 `Person` 相关的电话号码的个数) +* 使用下标取得特定的电话号码 +* 更新特定下标的电话号码 +* 添加新的电话号码到消息中,之后你便可以编辑。(repeated 标量类型有一个 `add_` 方法,用于传入新的值) + +为了获取 protocol 编译器为所有字段定义生成的方法的信息,可以查看 [C++ generated code reference](https://developers.google.com/protocol-buffers/docs/reference/cpp-generated)。 + +### 枚举和嵌套类(Enums and Nested Classes) + +与 `.proto` 的枚举相对应,生成的代码包含了一个 `PhoneType` 枚举。你可以通过 `Person::PhoneType` 引用这个类型,通过 `Person::MOBILE`、`Person::HOME` 和 `Person::WORK` 引用它的值。(实现细节有点复杂,但是你无须了解它们而可以直接使用) + +编译器也生成了一个 `Person::PhoneNumber` 的嵌套类。如果你查看代码,你可以发现真正的类型为 `Person_PhoneNumber`,但它通过在 `Person` 内部使用 typedef 定义,使你可以把 `Person_PhoneNumber` 当成嵌套类。唯一产生影响的一个例子是,如果你想要在其他文件前置声明该类——在 C++ 中你不能前置声明嵌套类,但是你可以前置声明 `Person_PhoneNumber`。 + +### 标准的消息方法 + +所有的消息方法都包含了许多别的方法,用于检查和操作整个消息,包括: + +* `bool IsInitialized() const;` :检查是否所有 `required` 字段已经被设置。 +* `string DebugString() const;`:返回人类可读的消息表示,对 debug 特别有用。 +* `void CopyFrom(const Person& from);`:使用给定的值重写消息。 +* `void Clear();`:清除所有元素为空(empty)的状态。 + +上面这些方法以及下一节要讲的 I/O 方法实现了被所有 C++ protocol buffer 类共享的消息(Message)接口。为了获取更多信息,请查看 [complete API documentation for Message](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message)。 + +### 解析和序列化(Parsing and Serialization) + +最后,所有 protocol buffer 类都有读写你选定类型消息的方法,这些方法使用了特定的 protocol buffer [二进制格式](https://developers.google.com/protocol-buffers/docs/encoding) +。这些方法包括: + +* `bool SerializeToString(string* output) const;`:序列化消息以及将消息字节数据存储在给定的字符串。注意,字节数据是二进制格式的,而不是文本格式;我们只使用 `string` 类作为合适的容器。 +* `bool ParseFromString(const string& data);`:从给定的字符创解析消息。 +* `bool SerializeToOstream(ostream* output) const;`:将消息写到给定的 C++ `ostream`。 +* `bool ParseFromIstream(istream* input);`:从给定的 C++ `istream` 解析消息。 + +这些只是两个用于解析和序列化的选择。再次说明,可以查看 `Message API reference` 完整的列表。 + +> Protocol Buffers 和 面向对象设计的 Protocol buffer 类通常只是纯粹的数据存储器(像 C++ 中的结构体);它们在对象模型中并不是一等公民。如果你想向生成的 protocol buffer 类中添加更丰富的行为,最好的方法就是在应用程序中对它进行封装。如果你无权控制 .proto 文件的设计的话,封装 protocol buffers 也是一个好主意(例如,你从另一个项目中重用一个 .proto 文件)。在那种情况下,你可以用封装类来设计接口,以更好地适应你的应用程序的特定环境:隐藏一些数据和方法,暴露一些便于使用的函数,等等。但是你绝对不要通过继承生成的类来添加行为。这样做的话,会破坏其内部机制,并且不是一个好的面向对象的实践。 + +# 写消息(Writing A Message) + +现在我们尝试使用 protocol buffer 类。你的地址簿程序想要做的第一件事是将个人详细信息写入到地址簿文件。为了做到这一点,你需要创建、填充 protocol buffer 类实例,并且将它们写入到一个输出流(output stream)。 + +这里的程序可以从文件读取 `AddressBook`,根据用户输入,将新 `Person` 添加到 `AddressBook`,并且再次将新的 `AddressBook` 写回文件。这部分直接调用或引用 protocol buffer 类的代码会高亮显示。 + +```c++ +#include +#include +#include +#include "addressbook.pb.h" +using namespace std; + +// This function fills in a Person message based on user input. +void PromptForAddress(tutorial::Person* person) { + cout << "Enter person ID number: "; + int id; + cin >> id; + person->set_id(id); + cin.ignore(256, '\n'); + + cout << "Enter name: "; + getline(cin, *person->mutable_name()); + + cout << "Enter email address (blank for none): "; + string email; + getline(cin, email); + if (!email.empty()) { + person->set_email(email); + } + + while (true) { + cout << "Enter a phone number (or leave blank to finish): "; + string number; + getline(cin, number); + if (number.empty()) { + break; + } + + tutorial::Person::PhoneNumber* phone_number = person->add_phone(); + phone_number->set_number(number); + + cout << "Is this a mobile, home, or work phone? "; + string type; + getline(cin, type); + if (type == "mobile") { + phone_number->set_type(tutorial::Person::MOBILE); + } else if (type == "home") { + phone_number->set_type(tutorial::Person::HOME); + } else if (type == "work") { + phone_number->set_type(tutorial::Person::WORK); + } else { + cout << "Unknown phone type. Using default." << endl; + } + } +} + +// Main function: Reads the entire address book from a file, +// adds one person based on user input, then writes it back out to the same +// file. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!input) { + cout << argv[1] << ": File not found. Creating a new file." << endl; + } else if (!address_book.ParseFromIstream(&input)) { + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + // Add an address. + PromptForAddress(address_book.add_person()); + + { + // Write the new address book back to disk. + fstream output(argv[1], ios::out | ios::trunc | ios::binary); + if (!address_book.SerializeToOstream(&output)) { + cerr << "Failed to write address book." << endl; + return -1; + } + } + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} +``` + +注意 `GOOGLE_PROTOBUF_VERIFY_VERSION` 宏。它是一种好的实践——虽然不是严格必须的——在使用 C++ Protocol Buffer 库之前执行该宏。它可以保证避免不小心链接到一个与编译的头文件版本不兼容的库版本。如果被检查出来版本不匹配,程序将会终止。注意,每个 `.pb.cc` 文件在初始化时会自动调用这个宏。 + +同时注意在程序最后调用 `ShutdownProtobufLibrary()`。它用于释放 Protocol Buffer 库申请的所有全局对象。对大部分程序,这不是必须的,因为虽然程序只是简单退出,但是 OS 会处理释放程序的所有内存。然而,如果你使用了内存泄漏检测工具,工具要求全部对象都要释放,或者你正在写一个库,该库可能会被一个进程多次加载和卸载,那么你可能需要强制 Protocol Buffer 清除所有东西。 + +# 读取消息 + +当然,如果你无法从它获取任何信息,那么这个地址簿没多大用处!这个示例读取上面例子创建的文件,并打印文件里的所有内容。 + +```c++ +#include +#include +#include +#include "addressbook.pb.h" +using namespace std; + +// Iterates though all people in the AddressBook and prints info about them. +void ListPeople(const tutorial::AddressBook& address_book) { + for (int i = 0; i < address_book.person_size(); i++) { + const tutorial::Person& person = address_book.person(i); + + cout << "Person ID: " << person.id() << endl; + cout << " Name: " << person.name() << endl; + if (person.has_email()) { + cout << " E-mail address: " << person.email() << endl; + } + + for (int j = 0; j < person.phone_size(); j++) { + const tutorial::Person::PhoneNumber& phone_number = person.phone(j); + + switch (phone_number.type()) { + case tutorial::Person::MOBILE: + cout << " Mobile phone #: "; + break; + case tutorial::Person::HOME: + cout << " Home phone #: "; + break; + case tutorial::Person::WORK: + cout << " Work phone #: "; + break; + } + cout << phone_number.number() << endl; + } + } +} + +// Main function: Reads the entire address book from a file and prints all +// the information inside. +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 2) { + cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl; + return -1; + } + + tutorial::AddressBook address_book; + + { + // Read the existing address book. + fstream input(argv[1], ios::in | ios::binary); + if (!address_book.ParseFromIstream(&input)) { + cerr << "Failed to parse address book." << endl; + return -1; + } + } + + ListPeople(address_book); + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} +``` + +# 扩展 Protocol Buffer + +早晚在你发布了使用 protocol buffer 的代码之后,毫无疑问,你会想要 "改善" + protocol buffer 的定义。如果你想要新的 buffers 向后兼容,并且老的 buffers 向前兼容——几乎可以肯定你很渴望这个——这里有一些规则,你需要遵守。在新的 protocol buffer 版本: + + * 你绝不可以修改任何已存在字段的标签数字 + * 你绝不可以添加或删除任何 required 字段 + * 你可以删除 optional 或 repeated 字段 + * 你可以添加新的 optional 或 repeated 字段,但是你必须使用新的标签数字(也就是说,标签数字在 protocol buffer 中从未使用过,甚至不能是已删除字段的标签数字)。 + + (这是对于上面规则的一些[异常情况](https://developers.google.com/protocol-buffers/docs/proto#updating),但它们很少用到。) + + 如果你能遵守这些规则,旧代码则可以欢快地读取新的消息,并且简单地忽略所有新的字段。对于旧代码来说,被删除的 optional 字段将会简单地赋予默认值,被删除的 `repeated` 字段会为空。新代码显然可以读取旧消息。然而,请记住新的 optional 字段不会呈现在旧消息中,因此你需要显式地使用 `has_` 检查它们是否被设置或者在 `.proto` 文件在标签数字后使用 `[default = value]` 提供一个合理的默认值。如果一个 optional 元素没有指定默认值,它将会使用类型特定的默认值:对于字符串,默认值为空字符串;对于布尔值,默认值为 false;对于数字类型,默认类型为 0。注意,如果你添加一个新的 repeated 字段,新代码将无法辨别它被留空(left empty)(被新代码)或者从没被设置(被旧代码),因为 repeated 字段没有 `has_` 标志。 + +# 优化技巧 + +C++ Protocol Buffer 库已极度优化过了。但是,恰当的用法能够更多地提高性能。这里是一些技巧,可以帮你从库中挤压出最后一点速度: + +* 尽可能复用消息对象。即使它们被清除掉,消息也会尽量保存所有被分配来重用的内存。因此,如果我们正在处理许多相同类型或一系列相似结构的消息,一个好的办法是重用相同的消息对象,从而减少内存分配的负担。但是,随着时间的流逝,对象可能会膨胀变大,尤其是当你的消息尺寸(译者注:各消息内容不同,有些消息内容多一些,有些消息内容少一些)不同的时候,或者你偶尔创建了一个比平常大很多的消息的时候。你应该自己通过调用 [SpaceUsed](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.SpaceUsed.details) 方法监测消息对象的大小,并在它太大的时候删除它。 + +* 对于在多线程中分配大量小对象的情况,你的操作系统内存分配器可能优化得不够好。你可以尝试使用 google 的 [tcmalloc](http://code.google.com/p/google-perftools/)。 + +# 高级用法 + +Protocol Buffers 绝不仅用于简单的数据存取以及序列化。请阅读 [C++ API reference](https://developers.google.com/protocol-buffers/docs/reference/cpp/index.html) 来看看你还能用它来做什么。 + +protocol 消息类所提供的一个关键特性就是反射。你不需要编写针对一个特殊的消息类型的代码,就可以遍历一个消息的字段并操作它们的值。一个使用反射的有用方法是 protocol 消息与其他编码互相转换,比如 XML 或 JSON。反射的一个更高级的用法可能就是可以找出两个相同类型的消息之间的区别,或者开发某种 "协议消息的正则表达式",利用正则表达式,你可以对某种消息内容进行匹配。只要你发挥你的想像力,就有可能将 Protocol Buffers 应用到一个更广泛的、你可能一开始就期望解决的问题范围上。 + +反射是由 [Message::Reflection interface](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.Reflection) 提供的。 + +-------------------------------------------------------------------------------- + +via: https://developers.google.com/protocol-buffers/docs/cpptutorial + +作者:[Google][a] +译者:[cposture](https://github.com/cposture) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From 6e297e7ae07dd686f6f237030e8f296ddf1869fb Mon Sep 17 00:00:00 2001 From: Ezio Date: Sat, 20 Aug 2016 23:25:40 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20160820 Protocol Buffer Basics C++.md | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/translated/tech/20160820 Protocol Buffer Basics C++.md b/translated/tech/20160820 Protocol Buffer Basics C++.md index 11bda510d4..6554564ca2 100644 --- a/translated/tech/20160820 Protocol Buffer Basics C++.md +++ b/translated/tech/20160820 Protocol Buffer Basics C++.md @@ -1,12 +1,15 @@ +Protocol Buffer Basics: C++ +============================ + 这篇教程提供了一个面向 C++ 程序员、关于 `protocol buffers` 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示: * 在 `.proto` 文件中定义消息格式 * 使用 `protocol buffer` 编译器 * 使用 `C++ protocol buffer API` 读写消息 -这不是一个关于使用 C++ protocol buffers 的全面指南。要获取更详细的信息,请参考 [Protocol Buffer Language Guide](https://developers.google.com/protocol-buffers/docs/proto) 和 [Encoding Reference](https://developers.google.com/protocol-buffers/docs/encoding)。 +这不是一个关于使用 C++ protocol buffers 的全面指南。要获取更详细的信息,请参考 [Protocol Buffer Language Guide][1] 和 [Encoding Reference][2]。 -# 为什么使用 Protocol Buffers +### 为什么使用 Protocol Buffers 我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字,ID,邮件地址和联系电话。 @@ -22,7 +25,7 @@ # 在哪可以找到示例代码 -示例代码被包含于源代码包,位于 "examples" 文件夹。在[这](https://developers.google.com/protocol-buffers/docs/downloads)下载代码。 +示例代码被包含于源代码包,位于 "examples" 文件夹。在[这][4]下载代码。 # 定义你的协议格式 @@ -71,17 +74,15 @@ message AddressBook { * repeated:字段可以重复任意次数(包括 0)。repeated 值的顺序会被保存于 protocol buffer。可以将 repeated 字段想象为动态大小的数组。 -你可以查找关于编写 `.proto` 文件的完整指导——包括所有可能的字段类型——在 [Protocol Buffer Language Guide](https://developers.google.com/protocol-buffers/docs/proto)。不要在这里面查找与类继承相似的特性,因为 protocol buffers 不会做这些。 - -Required Is Forever You should be very careful about marking fields as required. If at some point you wish to stop writing or sending a required field, it will be problematic to change the field to an optional field – old readers will consider messages without this field to be incomplete and may reject or drop them unintentionally. You should consider writing application-specific custom validation routines for your buffers instead. Some engineers at Google have come to the conclusion that using required does more harm than good; they prefer to use only optional and repeated. However, this view is not universal. +你可以查找关于编写 `.proto` 文件的完整指导——包括所有可能的字段类型——在 [Protocol Buffer Language Guide][6]。不要在这里面查找与类继承相似的特性,因为 protocol buffers 不会做这些。 > required 是永久性的,在把一个字段标识为 required 的时候,你应该特别小心。如果在某些情况下你不想写入或者发送一个 required 的字段,那么将该字段更改为 optional 可能会遇到问题——旧版本的读者(译者注:即读取、解析旧版本 Protocol Buffer 消息的一方)会认为不含该字段的消息是不完整的,从而有可能会拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google 的一些工程师得出了一个结论:使用 required 弊多于利;他们更愿意使用 optional 和 repeated 而不是 required。当然,这个观点并不具有普遍性。 -# 编译你的 Protocol Buffers +### 编译你的 Protocol Buffers 既然你有了一个 `.proto`,那你需要做的下一件事就是生成一个将用于读写 `AddressBook` 消息的类(从而包括 `Person` 和 `PhoneNumber`)。为了做到这样,你需要在你的 `.proto` 上运行 protocol buffer 编译器 `protoc`: -1. 如果你没有安装编译器,请[下载这个包](https://developers.google.com/protocol-buffers/docs/downloads.html),并按照 README 中的指令进行安装。 +1. 如果你没有安装编译器,请[下载这个包][4],并按照 README 中的指令进行安装。 2. 现在运行编译器,知道源目录(你的应用程序源代码位于哪里——如果你没有提供任何值,将使用当前目录),目标目录(你想要生成的代码放在哪里;常与 `$SRC_DIR` 相同),并且你的 `.proto` 路径。在此示例,你...: ``` @@ -95,7 +96,7 @@ protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto * `addressbook.pb.h`,声明你生成类的头文件。 * `addressbook.pb.cc`,包含你的类的实现。 -# Protocol Buffer API +### Protocol Buffer API 让我们看看生成的一些代码,了解一下编译器为你创建了什么类和函数。如果你查看 `tutorial.pb.h`,你可以看到有一个在 `tutorial.proto` 中指定所有消息的类。关注 `Person` 类,可以看到编译器为每个字段生成了读写函数(accessors)。例如,对于 `name`、`id`、`email` 和 `phone` 字段,有下面这些方法: @@ -143,15 +144,15 @@ repeated 字段也有一些特殊的方法——如果你看看 repeated `phone` * 更新特定下标的电话号码 * 添加新的电话号码到消息中,之后你便可以编辑。(repeated 标量类型有一个 `add_` 方法,用于传入新的值) -为了获取 protocol 编译器为所有字段定义生成的方法的信息,可以查看 [C++ generated code reference](https://developers.google.com/protocol-buffers/docs/reference/cpp-generated)。 +为了获取 protocol 编译器为所有字段定义生成的方法的信息,可以查看 [C++ generated code reference][5]。 -### 枚举和嵌套类(Enums and Nested Classes) +#### 枚举和嵌套类(Enums and Nested Classes) 与 `.proto` 的枚举相对应,生成的代码包含了一个 `PhoneType` 枚举。你可以通过 `Person::PhoneType` 引用这个类型,通过 `Person::MOBILE`、`Person::HOME` 和 `Person::WORK` 引用它的值。(实现细节有点复杂,但是你无须了解它们而可以直接使用) 编译器也生成了一个 `Person::PhoneNumber` 的嵌套类。如果你查看代码,你可以发现真正的类型为 `Person_PhoneNumber`,但它通过在 `Person` 内部使用 typedef 定义,使你可以把 `Person_PhoneNumber` 当成嵌套类。唯一产生影响的一个例子是,如果你想要在其他文件前置声明该类——在 C++ 中你不能前置声明嵌套类,但是你可以前置声明 `Person_PhoneNumber`。 -### 标准的消息方法 +#### 标准的消息方法 所有的消息方法都包含了许多别的方法,用于检查和操作整个消息,包括: @@ -160,12 +161,11 @@ repeated 字段也有一些特殊的方法——如果你看看 repeated `phone` * `void CopyFrom(const Person& from);`:使用给定的值重写消息。 * `void Clear();`:清除所有元素为空(empty)的状态。 -上面这些方法以及下一节要讲的 I/O 方法实现了被所有 C++ protocol buffer 类共享的消息(Message)接口。为了获取更多信息,请查看 [complete API documentation for Message](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message)。 +上面这些方法以及下一节要讲的 I/O 方法实现了被所有 C++ protocol buffer 类共享的消息(Message)接口。为了获取更多信息,请查看 [complete API documentation for Message][7]。 -### 解析和序列化(Parsing and Serialization) +#### 解析和序列化(Parsing and Serialization) -最后,所有 protocol buffer 类都有读写你选定类型消息的方法,这些方法使用了特定的 protocol buffer [二进制格式](https://developers.google.com/protocol-buffers/docs/encoding) -。这些方法包括: +最后,所有 protocol buffer 类都有读写你选定类型消息的方法,这些方法使用了特定的 protocol buffer [二进制格式][8]。这些方法包括: * `bool SerializeToString(string* output) const;`:序列化消息以及将消息字节数据存储在给定的字符串。注意,字节数据是二进制格式的,而不是文本格式;我们只使用 `string` 类作为合适的容器。 * `bool ParseFromString(const string& data);`:从给定的字符创解析消息。 @@ -176,7 +176,7 @@ repeated 字段也有一些特殊的方法——如果你看看 repeated `phone` > Protocol Buffers 和 面向对象设计的 Protocol buffer 类通常只是纯粹的数据存储器(像 C++ 中的结构体);它们在对象模型中并不是一等公民。如果你想向生成的 protocol buffer 类中添加更丰富的行为,最好的方法就是在应用程序中对它进行封装。如果你无权控制 .proto 文件的设计的话,封装 protocol buffers 也是一个好主意(例如,你从另一个项目中重用一个 .proto 文件)。在那种情况下,你可以用封装类来设计接口,以更好地适应你的应用程序的特定环境:隐藏一些数据和方法,暴露一些便于使用的函数,等等。但是你绝对不要通过继承生成的类来添加行为。这样做的话,会破坏其内部机制,并且不是一个好的面向对象的实践。 -# 写消息(Writing A Message) +### 写消息(Writing A Message) 现在我们尝试使用 protocol buffer 类。你的地址簿程序想要做的第一件事是将个人详细信息写入到地址簿文件。为了做到这一点,你需要创建、填充 protocol buffer 类实例,并且将它们写入到一个输出流(output stream)。 @@ -282,7 +282,7 @@ int main(int argc, char* argv[]) { 同时注意在程序最后调用 `ShutdownProtobufLibrary()`。它用于释放 Protocol Buffer 库申请的所有全局对象。对大部分程序,这不是必须的,因为虽然程序只是简单退出,但是 OS 会处理释放程序的所有内存。然而,如果你使用了内存泄漏检测工具,工具要求全部对象都要释放,或者你正在写一个库,该库可能会被一个进程多次加载和卸载,那么你可能需要强制 Protocol Buffer 清除所有东西。 -# 读取消息 +### 读取消息 当然,如果你无法从它获取任何信息,那么这个地址簿没多大用处!这个示例读取上面例子创建的文件,并打印文件里的所有内容。 @@ -355,7 +355,7 @@ int main(int argc, char* argv[]) { } ``` -# 扩展 Protocol Buffer +### 扩展 Protocol Buffer 早晚在你发布了使用 protocol buffer 的代码之后,毫无疑问,你会想要 "改善" protocol buffer 的定义。如果你想要新的 buffers 向后兼容,并且老的 buffers 向前兼容——几乎可以肯定你很渴望这个——这里有一些规则,你需要遵守。在新的 protocol buffer 版本: @@ -365,25 +365,25 @@ int main(int argc, char* argv[]) { * 你可以删除 optional 或 repeated 字段 * 你可以添加新的 optional 或 repeated 字段,但是你必须使用新的标签数字(也就是说,标签数字在 protocol buffer 中从未使用过,甚至不能是已删除字段的标签数字)。 - (这是对于上面规则的一些[异常情况](https://developers.google.com/protocol-buffers/docs/proto#updating),但它们很少用到。) + (这是对于上面规则的一些[异常情况][9],但它们很少用到。) 如果你能遵守这些规则,旧代码则可以欢快地读取新的消息,并且简单地忽略所有新的字段。对于旧代码来说,被删除的 optional 字段将会简单地赋予默认值,被删除的 `repeated` 字段会为空。新代码显然可以读取旧消息。然而,请记住新的 optional 字段不会呈现在旧消息中,因此你需要显式地使用 `has_` 检查它们是否被设置或者在 `.proto` 文件在标签数字后使用 `[default = value]` 提供一个合理的默认值。如果一个 optional 元素没有指定默认值,它将会使用类型特定的默认值:对于字符串,默认值为空字符串;对于布尔值,默认值为 false;对于数字类型,默认类型为 0。注意,如果你添加一个新的 repeated 字段,新代码将无法辨别它被留空(left empty)(被新代码)或者从没被设置(被旧代码),因为 repeated 字段没有 `has_` 标志。 -# 优化技巧 +### 优化技巧 C++ Protocol Buffer 库已极度优化过了。但是,恰当的用法能够更多地提高性能。这里是一些技巧,可以帮你从库中挤压出最后一点速度: -* 尽可能复用消息对象。即使它们被清除掉,消息也会尽量保存所有被分配来重用的内存。因此,如果我们正在处理许多相同类型或一系列相似结构的消息,一个好的办法是重用相同的消息对象,从而减少内存分配的负担。但是,随着时间的流逝,对象可能会膨胀变大,尤其是当你的消息尺寸(译者注:各消息内容不同,有些消息内容多一些,有些消息内容少一些)不同的时候,或者你偶尔创建了一个比平常大很多的消息的时候。你应该自己通过调用 [SpaceUsed](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.SpaceUsed.details) 方法监测消息对象的大小,并在它太大的时候删除它。 +* 尽可能复用消息对象。即使它们被清除掉,消息也会尽量保存所有被分配来重用的内存。因此,如果我们正在处理许多相同类型或一系列相似结构的消息,一个好的办法是重用相同的消息对象,从而减少内存分配的负担。但是,随着时间的流逝,对象可能会膨胀变大,尤其是当你的消息尺寸(译者注:各消息内容不同,有些消息内容多一些,有些消息内容少一些)不同的时候,或者你偶尔创建了一个比平常大很多的消息的时候。你应该自己通过调用 [SpaceUsed][10] 方法监测消息对象的大小,并在它太大的时候删除它。 -* 对于在多线程中分配大量小对象的情况,你的操作系统内存分配器可能优化得不够好。你可以尝试使用 google 的 [tcmalloc](http://code.google.com/p/google-perftools/)。 +* 对于在多线程中分配大量小对象的情况,你的操作系统内存分配器可能优化得不够好。你可以尝试使用 google 的 [tcmalloc][11]。 -# 高级用法 +### 高级用法 -Protocol Buffers 绝不仅用于简单的数据存取以及序列化。请阅读 [C++ API reference](https://developers.google.com/protocol-buffers/docs/reference/cpp/index.html) 来看看你还能用它来做什么。 +Protocol Buffers 绝不仅用于简单的数据存取以及序列化。请阅读 [C++ API reference][12] 来看看你还能用它来做什么。 protocol 消息类所提供的一个关键特性就是反射。你不需要编写针对一个特殊的消息类型的代码,就可以遍历一个消息的字段并操作它们的值。一个使用反射的有用方法是 protocol 消息与其他编码互相转换,比如 XML 或 JSON。反射的一个更高级的用法可能就是可以找出两个相同类型的消息之间的区别,或者开发某种 "协议消息的正则表达式",利用正则表达式,你可以对某种消息内容进行匹配。只要你发挥你的想像力,就有可能将 Protocol Buffers 应用到一个更广泛的、你可能一开始就期望解决的问题范围上。 -反射是由 [Message::Reflection interface](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.Reflection) 提供的。 +反射是由 [Message::Reflection interface][13] 提供的。 -------------------------------------------------------------------------------- @@ -394,3 +394,18 @@ via: https://developers.google.com/protocol-buffers/docs/cpptutorial 校对:[校对者ID](https://github.com/校对者ID) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://developers.google.com/protocol-buffers/docs/cpptutorial +[1]: https://developers.google.com/protocol-buffers/docs/proto +[2]: https://developers.google.com/protocol-buffers/docs/encoding +[3]: https://developers.google.com/protocol-buffers/docs/downloads +[4]: https://developers.google.com/protocol-buffers/docs/downloads.html +[5]: https://developers.google.com/protocol-buffers/docs/reference/cpp-generated +[6]: https://developers.google.com/protocol-buffers/docs/proto +[7]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message +[8]: https://developers.google.com/protocol-buffers/docs/encoding +[9]: https://developers.google.com/protocol-buffers/docs/proto#updating +[10]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.SpaceUsed.details +[11]: http://code.google.com/p/google-perftools/ +[12]: https://developers.google.com/protocol-buffers/docs/reference/cpp/index.html +[13]: https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message.html#Message.Reflection From 235cfd46d6eca3821c8889008474ef9441b6ebb4 Mon Sep 17 00:00:00 2001 From: cposture Date: Sun, 21 Aug 2016 14:21:48 +0800 Subject: [PATCH 10/11] Translated by cposture --- translated/tech/20160820 Protocol Buffer Basics C++.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translated/tech/20160820 Protocol Buffer Basics C++.md b/translated/tech/20160820 Protocol Buffer Basics C++.md index 6554564ca2..ed86014010 100644 --- a/translated/tech/20160820 Protocol Buffer Basics C++.md +++ b/translated/tech/20160820 Protocol Buffer Basics C++.md @@ -23,11 +23,11 @@ Protocol Buffer Basics: C++ `Protocol buffers` 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 `Protocol buffers`,你需要写一个 `.proto` 说明,用于描述你所希望存储的数据结构。利用 `.proto` 文件,protocol buffer 编译器可以创建一个类,用于实现自动化编码和解码高效的二进制格式的 protocol buffer 数据。产生的类提供了构造 `protocol buffer` 的字段的 getters 和 setters,并且作为一个单元,关注读写 `protocol buffer` 的细节。重要的是,`protocol buffer` 格式支持扩展格式,代码仍然可以读取以旧格式编码的数据。 -# 在哪可以找到示例代码 +### 在哪可以找到示例代码 示例代码被包含于源代码包,位于 "examples" 文件夹。在[这][4]下载代码。 -# 定义你的协议格式 +### 定义你的协议格式 为了创建自己的地址簿应用程序,你需要从 `.proto` 开始。`.proto` 文件中的定义很简单:为你所需要序列化的数据结构添加一个消息(message),然后为消息中的每一个字段指定一个名字和类型。这里是定义你消息的 `.proto` 文件,`addressbook.proto`。 From 3ef879cc0f075fa5c55eeb42ba095c2373518512 Mon Sep 17 00:00:00 2001 From: cposture Date: Mon, 5 Sep 2016 16:32:52 +0800 Subject: [PATCH 11/11] Translating by cposture 2016-09-05 --- ... Remote Linux Filesystem or Directory Using SSHFS Over SSH.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/tech/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md b/sources/tech/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md index 6b51e41a9c..3ba803ca1d 100644 --- a/sources/tech/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md +++ b/sources/tech/20160811 How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH.md @@ -1,3 +1,4 @@ +Translating by cposture How to Mount Remote Linux Filesystem or Directory Using SSHFS Over SSH ============================