<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Python</title>
	<atom:link href="https://blog.gmem.cc/category/work/python/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 06 Apr 2026 12:46:48 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Python单元测试</title>
		<link>https://blog.gmem.cc/python-unit-test</link>
		<comments>https://blog.gmem.cc/python-unit-test#comments</comments>
		<pubDate>Wed, 16 Oct 2019 05:51:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[单元测试]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=29327</guid>
		<description><![CDATA[<p>unittest 简介 属于标准库的一部分，类似于JUnit。下面是一个基本的例子： [crayon-69d6611a13b58460252162/] 从这个例子可以看到： 测试用例以类的形式进行分组，从[crayon-69d6611a13b5e872639116-i/]继承 测试方法以[crayon-69d6611a13b61187162317-i/]开头 unittest支持类似于JUnit的准备/清理机制 单元测试的入口点均为unittest.main() unittest.TestCase提供了一系列断言方法 跳过测试 可以在测试类、测试方法上添加装饰器，以便在特定条件下，跳过某些测试： [crayon-69d6611a13b63955393903/] 命令行 基本用法 你可以运行测试模块、测试类，甚至测试方法： [crayon-69d6611a13b65482843280/] 自动搜索用例 如果要自动搜索测试用例，执行：  [crayon-69d6611a13b67719811757/] 要支持自动搜索，测试用例必须编写为模块、或者包，且可以从项目根目录导入。  默认情况下，仅仅需要命名为[crayon-69d6611a13b69485542497-i/]的文件，可以通过[crayon-69d6611a13b6b974081827-i/]参数修改此行为。 <a class="read-more" href="https://blog.gmem.cc/python-unit-test">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-unit-test">Python单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">unittest</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>属于标准库的一部分，类似于JUnit。下面是一个基本的例子：</p>
<pre class="crayon-plain-tag"># 编写测试用例模块
import util        # 被测试模块
import unittest
class TestUtilFunc(unittest.TestCase):
    def setUp(self): pass     # 每个测试函数运行之前
    def tearDown(self): pass  # 每个测试函数运行之后
    def test_indexOf(self):    # 测试用例
        self.assertEqual(0,util.stringutils.indexOf('123','1'))

# 运行单元测试
if __name__ == '__main__'
    unittest.main()</pre>
<p>从这个例子可以看到：</p>
<ol>
<li>测试用例以类的形式进行分组，从<pre class="crayon-plain-tag">unittest.TestCase</pre>继承</li>
<li>测试方法以<pre class="crayon-plain-tag">test_</pre>开头</li>
<li>unittest支持类似于JUnit的准备/清理机制</li>
<li>单元测试的入口点均为unittest.main()</li>
<li>unittest.TestCase提供了一系列断言方法</li>
</ol>
<div class="blog_h2"><span class="graybg">跳过测试</span></div>
<p>可以在测试类、测试方法上添加装饰器，以便在特定条件下，跳过某些测试：</p>
<pre class="crayon-plain-tag"># 无条件跳过
@unittest.skip("reason")

# 条件跳过
@unittest.skipIf( True, "reason" )
@unittest.skipUnless(sys.platform.startswith("win"), "reason") </pre>
<div class="blog_h2"><span class="graybg">命令行</span></div>
<div class="blog_h3"><span class="graybg">基本用法</span></div>
<p>你可以运行测试模块、测试类，甚至测试方法：</p>
<pre class="crayon-plain-tag"># 测试两个模块
python -m unittest test_module1 test_module2
# 测试一个类
python -m unittest test_module.TestClass
# 测试一个方法
python -m unittest test_module.TestClass.test_method

# 也可以指定模块的路径
python -m unittest tests/test_something.py</pre>
<div class="blog_h3"><span class="graybg">自动搜索用例</span></div>
<p>如果要自动搜索测试用例，执行： </p>
<pre class="crayon-plain-tag">cd project_directory
python -m unittest discover</pre>
<p>要支持自动搜索，测试用例<span style="background-color: #c0c0c0;">必须编写为模块、或者包，且可以从项目根目录导入</span>。 </p>
<p>默认情况下，仅仅需要命名为<pre class="crayon-plain-tag">test*.py</pre>的文件，可以通过<pre class="crayon-plain-tag">-p</pre>参数修改此行为。</p>
<div class="blog_h1"><span class="graybg">pytest</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>pytest是第三方框架，和unittest的主要区别是：</p>
<ol>
<li>测试模块的文件名必须以<pre class="crayon-plain-tag">test_</pre>开头或<pre class="crayon-plain-tag">_test</pre>结尾</li>
<li>测试类名必须以<pre class="crayon-plain-tag">Test</pre>开头</li>
<li>支持模块、类、函数级别的准备/清理方法，unittest仅支持类级别</li>
<li>不提供断言方法，直接使用assert表达式</li>
<li>支持失败用例的重跑</li>
</ol>
<div class="blog_h2"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">pip install -U pytest</pre>
<div class="blog_h2"><span class="graybg">示例</span></div>
<div class="blog_h3"><span class="graybg">第一个用例 </span></div>
<pre class="crayon-plain-tag"># 被测试者
def func(x):
    return x + 1


def test_answer():
    # 断言
    assert func(3) == 5</pre>
<p>运行<pre class="crayon-plain-tag">pytest</pre>即可执行测试。 不带任何参数表示，<span style="background-color: #c0c0c0;">递归的寻找当前目录下test_*.py和*_test.py</span>文件并执行其中定义的测试。</p>
<div class="blog_h3"><span class="graybg">测试分组</span></div>
<p>可以将多个测试用例组合为类：</p>
<pre class="crayon-plain-tag">class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")</pre>
<p>运行<pre class="crayon-plain-tag">pytest -q test_class.py</pre>表示仅仅测试上面这个文件。</p>
<div class="blog_h3"><span class="graybg">断言异常发生</span></div>
<pre class="crayon-plain-tag">import pytest


def f():
    raise SystemExit(1)


def test_mytest():
    # 断言f()调用会产生SystemExit异常
    with pytest.raises(SystemExit):
        f()</pre>
<div class="blog_h3"><span class="graybg">使用fixture</span></div>
<p>测试方法中的参数tmpdir提示系统，自动为此测试创建一个独特的临时目录。</p>
<pre class="crayon-plain-tag">def test_needsfiles(tmpdir):
    print(tmpdir)
    assert 0</pre>
<div class="blog_h2"><span class="graybg">准备/清理</span></div>
<p>pytest支持不同级别的setup/teardown方法。</p>
<div class="blog_h3"><span class="graybg">模块级别</span></div>
<p>整个模块仅仅调用一次：</p>
<pre class="crayon-plain-tag">def setup_module(module):
    """ setup any state specific to the execution of the given module."""


def teardown_module(module):
    """ teardown any state that was previously setup with a setup_module
    method.
    """</pre>
<div class="blog_h3"><span class="graybg">类级别</span></div>
<p>对于类中的所有测试，调用一次：</p>
<pre class="crayon-plain-tag">@classmethod
def setup_class(cls):
    """ setup any state specific to the execution of the given class (which
    usually contains tests).
    """


@classmethod
def teardown_class(cls):
    """ teardown any state that was previously setup with a call to
    setup_class.
    """</pre>
<div class="blog_h3"><span class="graybg">方法级别</span></div>
<p>对于每个测试方法，都会调用：</p>
<pre class="crayon-plain-tag">def setup_method(self, method):
    """ setup any state tied to the execution of the given method in a
    class.  setup_method is invoked for every test method of a class.
    """


def teardown_method(self, method):
    """ teardown any state that was previously setup with a setup_method
    call.
    """ </pre>
<div class="blog_h2"><span class="graybg">fixtures</span></div>
<p>pytest支持fixtures，所谓fixtures是一系列让测试可靠、可重复执行的机制。比起经典的xUnit风格的setup/teardown函数，pytest fixtures具有以下优势：</p>
<ol>
<li>每个fixture具有精确的名称，通过在测试函数、模块、类，或者整个项目中声明这些名称即可自动激活</li>
<li>fixture使用模块化设计，每个fixture名称会触发一个<span style="background-color: #c0c0c0;">fixture</span>函数，此函数本身<span style="background-color: #c0c0c0;">亦可使用其它fixture</span></li>
<li>fixture可以在简单的单元测试场景，到复杂的功能测试中使用</li>
<li>你可以根据配置、组件选项来参数化fixture和测试</li>
<li>可以跨越函数、类、模块、整个测试会话重用fixture</li>
</ol>
<div class="blog_h3"><span class="graybg">内置fixture列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">Fixture</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cache</td>
<td>返回一个缓存对象，可以跨越多个测试session共享数据：<br />
<pre class="crayon-plain-tag"># key是由 / 分隔的字符串，通常第一部分是你的插件/应用的名称，避免和其它缓存用户冲突
cache.get(key, default)
cache.set(key, value) </pre>
</td>
</tr>
<tr>
<td>capsys</td>
<td>启用对输出到sys.stdout / sys.stderr中的文本的捕获，调用<pre class="crayon-plain-tag">capsys.readouterr()</pre>返回命名元组<pre class="crayon-plain-tag">(out ,err)</pre></td>
</tr>
<tr>
<td>capsysbinary</td>
<td>类似上面，但是捕获的是byte而非text</td>
</tr>
<tr>
<td>tmpdir_factory [session scope]</td>
<td>返回临时目录工厂</td>
</tr>
<tr>
<td>tmp_path_factory [session scope]</td>
<td>返回临时路径工厂</td>
</tr>
<tr>
<td>tmpdir</td>
<td>返回一个临时目录对象</td>
</tr>
<tr>
<td>tmp_path</td>
<td>返回一个临时目录路径对象</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">注册fixture</span></div>
<p>Fixture由函数创建，任何函数加上<pre class="crayon-plain-tag">@pytest.fixture</pre>即可创建Fixture：</p>
<pre class="crayon-plain-tag">@pytest.fixture
def smtp_connection():
    import smtplib
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) </pre>
<p>你可以指定Fixture的共享范围：</p>
<pre class="crayon-plain-tag"># 可选值 function, class, module, package, session
@pytest.fixture(scope="module") </pre>
<div class="blog_h3"><span class="graybg">作为函数参数</span></div>
<p>直接将Fixture函数名作为参数，声明在被测试用例的参数列表中，即可使用Fixture： </p>
<pre class="crayon-plain-tag">def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250 </pre>
<div class="blog_h3"><span class="graybg">清理fixture</span></div>
<p>在你的Fixture函数中使用yield语句，即可自动在Fixture超过作用域范围后自动清理： </p>
<pre class="crayon-plain-tag">@pytest.fixture(scope="module")
def smtp_connection():
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    # 提供
    yield smtp_connection
    print("teardown smtp")
    # 清理
    smtp_connection.close()


# 等价形式
@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection</pre>
<div class="blog_h3"><span class="graybg">参数化fixture</span></div>
<p>使用参数化fixture，可以让所有依赖于此fixture的测试运行多次：</p>
<pre class="crayon-plain-tag">import pytest
import smtplib

@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
    yield smtp_connection
    print("finalizing {}".format(smtp_connection))
    smtp_connection.close()</pre>
<p>使用smtp_connection的用例会执行两次，一次连接到stmp服务器smtp.gmail.com，另一次连接到mail.python.org。</p>
<div class="blog_h3"><span class="graybg">读取上下文</span></div>
<p>为Fixture函数传入<pre class="crayon-plain-tag">request</pre>对象，则可以动态获取当前被测试函数、类、模块的上下文信息：</p>
<pre class="crayon-plain-tag">import pytest
import smtplib


@pytest.fixture(scope="module")
def smtp_connection(request):
    # 读取被测试模块的smtpserver属性
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server, 587, timeout=5)
    yield smtp_connection
    print("finalizing {} ({})".format(smtp_connection, server))
    smtp_connection.close()</pre>
<p>被测试模块可以提供stmpserver变量供Fixture读取：</p>
<pre class="crayon-plain-tag">smtpserver = "mail.python.org"  # 被Fixture读取

def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()</pre>
<div class="blog_h3"><span class="graybg">Fixture工厂</span></div>
<p>如果在单个测试中，需要多次得到全新的fixture，可以让Fixture函数返回一个函数而不是值： </p>
<pre class="crayon-plain-tag">@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return {"name": name, "orders": []}
    # 此Fixture函数返回一个工厂函数
    return _make_customer_record


def test_customer_records(make_customer_record):
                 # 可以多次生成fixture
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")</pre>
<div class="blog_h3"><span class="graybg">在类/模块/项目级别启用</span></div>
<p>在类级别启用fixture：</p>
<pre class="crayon-plain-tag">@pytest.fixture()
def cleandir():
    newpath = tempfile.mkdtemp()
    os.chdir(newpath)


# 所有测试都会在一个临时目录下执行
@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:</pre>
<p>支持同时使用多个fixture：</p>
<pre class="crayon-plain-tag">@pytest.mark.usefixtures("cleandir", "anotherfixture")</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-unit-test">Python单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/python-unit-test/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Graphite学习笔记</title>
		<link>https://blog.gmem.cc/graphite-study-note</link>
		<comments>https://blog.gmem.cc/graphite-study-note#comments</comments>
		<pubDate>Mon, 11 Jul 2016 08:33:51 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[TSDB]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12245</guid>
		<description><![CDATA[<p>简介 Graphite是一个开源项目，可以作为时间序列数据库（TSDB）使用，当你需要存储随着时间变化的数值时，应当考虑使用时间序列数据库。 除了数据的存储、查询外，Graphite还提供数据可视化（UI层）功能，它可以很好的在廉价的硬件上运行。你可以使用Graphite来监控网站、应用程序、网络服务器等的性能数据（Metrics），轻松实现基于时间维的分析。 Graphite本身不负责性能数据的采集，但是它提供了简单易用的接口，公共这些接口你可以把基于数字的性能数据存储到Graphite中。 术语 术语 说明 datapoint 数据点，存放在timestamp bucket中的数值。timestamp bucket中的默认值是None function 时间序列（ time-series）函数，用来转换、合并、计算多个series resolution 分辨率，也称precision。序列中，一个数据点所跨越（代表）的秒数。分辨率确定了存储数据点频率如果一个series每N秒存储一个数据点，则其分辨率为N retention 驻留，series中包含的数据点的个数 series 一已命名的数据点的集合，每个series由其名称唯一确定，名称由点号分隔的字符串组成也称为Metrics、Metric series series <a class="read-more" href="https://blog.gmem.cc/graphite-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/graphite-study-note">Graphite学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p><a href="http://graphiteapp.org/">Graphite</a>是一个开源项目，可以作为时间序列数据库（TSDB）使用，当你需要存储<span style="background-color: #c0c0c0;">随着时间变化的数值</span>时，应当考虑使用时间序列数据库。</p>
<p>除了<span style="background-color: #c0c0c0;">数据的存储、查询</span>外，Graphite还提供<span style="background-color: #c0c0c0;">数据可视化</span>（UI层）功能，它可以很好的在廉价的硬件上运行。你可以使用Graphite来监控网站、应用程序、网络服务器等的性能数据（Metrics），轻松实现基于时间维的分析。</p>
<p>Graphite本身不负责性能数据的采集，但是它提供了简单易用的接口，公共这些接口你可以把基于数字的性能数据存储到Graphite中。</p>
<div class="blog_h2"><span class="graybg">术语</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>datapoint</td>
<td>数据点，存放在timestamp bucket中的数值。timestamp bucket中的默认值是None</td>
</tr>
<tr>
<td>function</td>
<td>时间序列（ time-series）函数，用来转换、合并、计算多个series</td>
</tr>
<tr>
<td>resolution</td>
<td>分辨率，也称precision。序列中，一个数据点所跨越（代表）的秒数。分辨率确定了存储数据点频率<br />如果一个series每N秒存储一个数据点，则其分辨率为N</td>
</tr>
<tr>
<td>retention</td>
<td>驻留，series中包含的数据点的个数</td>
</tr>
<tr>
<td>series</td>
<td>一已命名的数据点的集合，每个series由其名称唯一确定，名称由点号分隔的字符串组成<br />也称为Metrics、Metric series</td>
</tr>
<tr>
<td>series list</td>
<td>包含通配符的series名称，匹配多个series</td>
</tr>
<tr>
<td>target</td>
<td>图形展示时的数据源，可以是metrics名称、metrics通配符、或者基于前两者的函数调用表达式</td>
</tr>
<tr>
<td>timestamp</td>
<td>数据点所关联的时间，1970-01-01到产生数据点那一刻的秒数</td>
</tr>
<tr>
<td>timestamp bucket</td>
<td>经过舍入后，能够整除分辨率的timestamp</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Graphite由三个组件构成：</p>
<ol>
<li>Carbon：一个基于<a href="https://twistedmatrix.com/">Twisted</a>（Python事件驱动网络框架）的守护程序，负责监听外部的时间序列数据</li>
<li>Whisper：一个简单的存储时间序列数据的数据库，设计上和<a href="http://oss.oetiker.ch/rrdtool/">RRDtool</a>类似 </li>
<li>Graphite-Web：一个基于<a href="https://www.djangoproject.com/">Django</a>（Python Web框架）的Web应用，使用<a href="https://www.cairographics.org/">Cairo</a>（一个2D图形库）来渲染性能数据的图表，使用<a href="https://www.sencha.com/products/extjs/">ExtJS</a>作为基础UI框架</li>
</ol>
<p>下面是Graphite的架构图：<img class="aligncenter size-full wp-image-12262" src="https://blog.gmem.cc/wp-content/uploads/2016/07/graphite-arch.png" alt="graphite-arch" width="100%" /></p>
<p>一旦你把数据送给Carbon，它就立刻可以在Webapp的图表中显示，因为数据在被写入文件系统之前，会驻留在缓存中。</p>
<p>除了使用Graphite Webapp，你也可以通过URL API，将图表嵌入到自己的应用程序中去。如何使用Graphite</p>
<div class="blog_h2"><span class="graybg">如何使用Graphite</span></div>
<p>使用Graphite来监控你的性能数据，你需要完成以下工作：</p>
<ol>
<li>理解Graphite组件的职责和相互关系</li>
<li>安装Graphite及其依赖</li>
<li>基础的配置，让Graphite能运行起来</li>
<li>设计Metrics路径</li>
<li>配置Metrics的驻留规则、聚合规则等</li>
<li>向Graphite发送Metrics</li>
<li>从Graphite获取Metrics并展示</li>
</ol>
<div class="blog_h3"><span class="graybg">Metrics路径设计</span></div>
<p>Metrics路径由点号分隔的字符串构成，类似于Python的包名称。路径是Metrics的标识。</p>
<p>你应当仔细的设计此路径的命名空间，以反映出所有Metrics之间的层次关系。例如servers.zircon.cpu，这个三级路径设计中，第一级表示设备类别，第二级表示设备名称，第三级表示监测点类型。</p>
<div class="blog_h1"><span class="graybg">理解Graphite组件</span></div>
<div class="blog_h2"><span class="graybg">Carbon</span></div>
<p>Carbon由一系列的守护进程组成，这些守护进程共同组成Graphite的存储后端。在最小化的安装下，只有一个守护进程carbon-cache.py。根据需要你可以启用carbon-relay.py、carbon-aggregator.py以便实现Metrics分发、定制聚合规则。各Carbon守护进程简介如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">守护进程</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>缓存进程<br />carbon-cache</td>
<td>
<p>缓存进程的程序文件是carbon-cache.py。它通过多种协议接收Metrics，然后尽可能高效的将其写入磁盘。从实现角度来说，缓存进程先把Metrics存放在RAM中，然后定期的通过whisper库进行入库</p>
<p>缓存进程提供一个查询服务供Graphite Webapp使用，用来快速获取位于内存中的Metrics数据点</p>
<p>要定制此进程的行为，可以修改carbon.conf的[cache]段、storage-schemas.conf、storage-aggregation.conf</p>
</td>
</tr>
<tr>
<td>中继进程<br />carbon-relay</td>
<td>
<p>该进程可以担任这两个职责之一：复制（<span style="color: #404040;">replication</span>）和分片（<span style="color: #404040;">sharding</span>）</p>
<p>要定制此进程的行为，可以修改carbon.conf的[relay]段、relay-rules.conf</p>
</td>
</tr>
<tr>
<td>前置聚合进程<br />carbon-aggregator</td>
<td>
<p>前置于缓存进程运行，能够在存入whisper之前，缓冲、聚合Metrics。当不需要细粒度的数据时启用该进程，可以减少I/O和.wsp文件的大小</p>
<p>要定制此进程的行为，可以修改carbon.conf的[aggregator]段、aggregation-rules.conf</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Whisper</span></div>
<p>Whisper是一个固定大小（fixed-size）的数据库，其设计和用途与RRD（round-robin-database）类似。它能提供快速、可靠的数值数据的存储。</p>
<p>Whisper能够对最近的数据进行高分辨率的存储，而对久远的历史数据，自动降低其存储精度（减少样本数量）。</p>
<p>在大多数场景下，Whisper有足够好的性能。它比RRDtool慢，主要原因是Whisper基于Python编写，这个性能差异很小，通常不需要考虑。</p>
<p>Whisper数据库以文件方式存放在磁盘上，扩展名.wsp。Carbon会为每个Metrics创建一个.wsp文件，路径的最后一节作为文件的basename，路径的其它部分形成目录层次。</p>
<div class="blog_h3"><span class="graybg">数据点</span></div>
<p>在Whisper中存储的每一个数值，称为数据点（Data Points）。</p>
<p>在磁盘上，数据点以大端、双精度浮点数存储。每个数据还附带一个时间戳信息，时间戳为1970-01-01到数据采集时间的秒数。</p>
<div class="blog_h3"><span class="graybg">归档</span></div>
<p>一个Whisper数据库文件可以包含一个或者多个“归档”，归档是数据文件中的逻辑段。</p>
<p>每个归档具有不同的数据分辨率（一定时间段内，数据点数量越多，则分辨率越高）。归档以最高分辨率（最小驻留时间） —— 最低分辨率（最长驻留时间）的顺序，在数据库文件中顺序排列。</p>
<p>为了精确的从高分辨率向低分辨率的归档聚合，高分辨率归档和它之后的低分辨率归档，其分辨率应当具有整除关系。例如，第一个归档分辨率为60秒/数据点，那么第二个归档可以是300秒/数据点，第三个归档可以是3600秒/数据点。</p>
<p>一个数据库的总计驻留时间（存放的数据点跨越的时间），由最长驻留时间的（最后一个）归档确定。因为之前的那些归档，时间区间都是它的子区间。</p>
<div class="blog_h3"><span class="graybg">聚合</span></div>
<p>把数据点转移到低分辨率归档时，面临着如何把多个数据点转变为单个数据点的问题。Whisper支持average、sum、last、max、min等聚合函数。</p>
<p>注意，这里的聚合和Carbon提供的前置聚合不是一回事。</p>
<div class="blog_h3"><span class="graybg">多归档的读写策略</span></div>
<p>当Whisper向一个多归档数据写入Metrics时，数据点将被同时写入到所有的归档中。这意味着聚合动作随时可能发生。</p>
<p>当从Whisper中获取数据时，第一个能完整覆盖所需区间的归档被使用。</p>
<div class="blog_h3"><span class="graybg">磁盘空间利用率问题</span></div>
<p>Whisper的磁盘利用效率不高，因为：</p>
<ol>
<li>每个数据点要附加一个时间戳信息</li>
<li>由于归档的时间区间有重叠，因此数据存在冗余</li>
<li>归档中的时间槽位（time-slots）总是占据着磁盘空间，不管有没有值存储在其中</li>
</ol>
<p>这些特征是故意的，主要出于性能方面的考虑。</p>
<div class="blog_h3"><span class="graybg">Whisper与RRD的区别</span></div>
<ol>
<li>RRD不能去填充以前的时间槽位。这意味着每一条数据都必须是更新的，不会“补录”</li>
<li>RRD不能很好的支持不规则的数据更新。如果RRD接收到一条数据，但是后续数据没有到来，则前一条数据可能丢失</li>
</ol>
<div class="blog_h1"><span class="graybg">安装与配置</span></div>
<div class="blog_h2"><span class="graybg">检查并安装依赖</span></div>
<div class="blog_h3"><span class="graybg">检查依赖</span></div>
<pre class="crayon-plain-tag">git clone https://github.com/graphite-project/graphite-web.git
graphite-web/check-dependencies.py
# 根据输出的提示来判断缺少哪些依赖，然后安装</pre>
<div class="blog_h3"><span class="graybg">安装依赖</span></div>
<pre class="crayon-plain-tag">yum -y install python-devel     # Carbon 依赖于 Python Development Headers

yum install pycairo             # Cairo库的Python绑定

pip install django              # Web框架
pip install django-tagging

pip install pytz

yum install fontconfig

yum install -y memcached        # 可选，缓存支持
pip install python-memcached    
                                # 可选，RDDTool
yum install cairo-devel libxml2-devel pango-devel pango libpng-devel freetype freetype-devel libart_lgpl-devel rrdtool-devel

pip install python-rrdtool      # 可选，RRD支持

pip install whitenoise          # 用于Web静态文件处理

yum install pyOpenSSL           # OpenSSL的Python绑定
pip install service_identity    # SSL相关</pre>
<div class="blog_h2"><span class="graybg">通过pip安装Craphite组件</span></div>
<pre class="crayon-plain-tag">pip install https://github.com/graphite-project/ceres/tarball/master
pip install whisper
pip install carbon
pip install graphite-web</pre>
<div class="blog_h3"><span class="graybg">设置目录权限</span></div>
<pre class="crayon-plain-tag">sudo groupadd graphite
sudo usermod -a -G graphite root
sudo usermod -a -G graphite apache
sudo chgrp -R graphite /opt/graphite/storage
sudo chmod -R 770 /opt/graphite/storage</pre>
<div class="blog_h2"><span class="graybg">默认安装布局</span></div>
<p>Whisper被安装到Python全局site-packages目录，另外两个Graphite组件安装到<pre class="crayon-plain-tag">/opt/graphite</pre>，该目录（记为<pre class="crayon-plain-tag">$GRAPHITE_ROOT</pre>  ）的布局如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子目录</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>bin</td>
<td>一些脚本</td>
</tr>
<tr>
<td>conf</td>
<td>配置文件</td>
</tr>
<tr>
<td>lib</td>
<td>一些Python依赖库，Carbon的PYTHONPATH</td>
</tr>
<tr>
<td>storage</td>
<td>存储SQLite数据文件。该目录记为<pre class="crayon-plain-tag">$STORAGE_DIR</pre> </td>
</tr>
<tr>
<td>storage/log</td>
<td>Carbon和Graphite-web的日志</td>
</tr>
<tr>
<td>storage/rrd</td>
<td>待读取的RRD文件</td>
</tr>
<tr>
<td>storages/whisper</td>
<td>Whisper数据文件</td>
</tr>
<tr>
<td>webapp</td>
<td>
<p>Graphite-web的Web资源、PYTHONPATH</p>
</td>
</tr>
<tr>
<td>webapp/graphite</td>
<td>
<p>标准的Django工程结构<br />local_settings.py所在位置</p>
</td>
</tr>
<tr>
<td>webapp/content</td>
<td>静态Web资源</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置Graphite-web数据库</span></div>
<p>你需要让Graphite-web的底层框架Django执行数据库的初始化。此数据库被用来存放用户设置、仪表盘，以及支持事件功能。</p>
<p>默认情况下，Graphite-web使用位于$STORAGE_DIR/graphite.db的SQLite数据库。如果要运行多个Graphite-web实例，则必须使用MySQL等数据库以便多个实例可以共享数据。 </p>
<div class="blog_h3"><span class="graybg">配置SQLite</span></div>
<p>执行下面的命令初始化SQLite数据库：</p>
<pre class="crayon-plain-tag">PYTHONPATH=/opt/graphite/webapp django-admin.py migrate --settings=graphite.settings --run-syncdb
# 完毕后 $STORAGE_DIR/graphite.db自动创建</pre>
<p>Django应用需要在Web服务器中运行，Web服务器需要对SQLite数据文件有读写权限。假设你使用Apache2，运行Apache2的用户为apache，则需要执行：</p>
<pre class="crayon-plain-tag">sudo chgrp graphite /opt/graphite/storage/graphite.db</pre>
<div class="blog_h2"><span class="graybg">配置WebApp</span></div>
<div class="blog_h3"><span class="graybg">httplocal_settings.py</span></div>
<p>Graphite不建议修改settings.py，所有定制化的配置，都应该在此文件中进行。常用的设置项如下表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">设置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TIME_ZONE</td>
<td>时区，规范化的<a href="https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones">时区名称</a>。默认America/Chicago</td>
</tr>
<tr>
<td>DEBUG</td>
<td>是否启用Django错误页面。默认False</td>
</tr>
<tr>
<td>FLUSHRRDCACHED</td>
<td>如果设置，在从RRD文件获取数据前，执行<pre class="crayon-plain-tag">rrdtool flushcached</pre> <br />设置为rrdcached的地址或者Socket（例如unix:/var/run/rrdcached.sock）</td>
</tr>
<tr>
<td>MEMCACHE_HOSTS</td>
<td>如果设置，启用对计算出的目标、渲染过的图片的缓存。如果运行Graphite Web应用集群，则每个实例应当设置为一样的值<br />设置为memcached主机的数组，例如：['10.10.10.10:11211', '10.10.10.12:11211']</td>
</tr>
<tr>
<td>DEFAULT_CACHE_DURATION</td>
<td>数据、图片默认缓存时间。默认值60</td>
</tr>
<tr>
<td>DEFAULT_CACHE_POLICY</td>
<td>默认缓存策略。为元组的数组，每个元组指定最小查询时间、数据缓存时间<br />通过该设置，你可以让大的查询缓存更长时间。例如：<br />
<pre class="crayon-plain-tag">DEFAULT_CACHE_POLICY = [
    (0, 60), # 默认缓存60秒
    (7200, 120) # &gt;= 2小时以上时间范围的查询，缓存2分钟
] </pre>
</td>
</tr>
<tr>
<td>GRAPHITE_ROOT</td>
<td>Graphite的安装目录。默认/opt/graphite </td>
</tr>
<tr>
<td>CONF_DIR</td>
<td>额外Graphite-web配置文件目录。默认/opt/graphite/conf</td>
</tr>
<tr>
<td>STORAGE_DIR</td>
<td>存储目录。WHISPER_DIR、RRD_DIR、LOG_DIR、INDEX_FILE参照的基准目录</td>
</tr>
<tr>
<td>STATIC_ROOT</td>
<td>Graphite-web的静态文件目录。默认/opt/graphite/static<br />该目录一开始会不存在，你需要在设置STATIC_ROOT、STATIC_URL后执行：<br />
<pre class="crayon-plain-tag"># 如果报Unknown command: 'collectstatic' 说明INSTALLED_APPS中缺少
# django.contrib.staticfiles	
PYTHONPATH=/opt/graphite/webapp django-admin.py collectstatic 
    --noinput --settings=graphite.settings</pre></p>
<p>你还需要在Web服务器中把/static这个前缀映射到此目录，里Apache2为例：</p>
<pre class="crayon-plain-tag">Alias /static/ "/opt/graphite/static"</pre>
<p>如果你安装了whitenoise包，静态文件可以直接由Graphite webapp来处理，不通过Web服务器</p>
</td>
</tr>
<tr>
<td>DASHBOARD_CONF</td>
<td>仪表盘的配置文件。默认$CONF_DIR/dashboard.conf</td>
</tr>
<tr>
<td>GRAPHTEMPLATES_CONF</td>
<td>图形模板的配置文件。默认$CONF_DIR/graphTemplates.conf </td>
</tr>
<tr>
<td>WHISPER_DIR</td>
<td> Whisper数据文件目录。默认/opt/graphite/storage/whisper</td>
</tr>
<tr>
<td>RRD_DIR</td>
<td> RRD数据文件目录。默认/opt/graphite/storage/rrd</td>
</tr>
<tr>
<td>LOG_DIR</td>
<td> Graphite webapp的日志目录。默认$TORAGE_DIR/log/webapp</td>
</tr>
<tr>
<td>INDEX_FILE</td>
<td>搜索索引位置。默认/opt/graphite/storage/index<br />由build-index.sh脚本生成，运行Web应用的用户必须有写权限</td>
</tr>
<tr>
<td>URL_PREFIX</td>
<td>URL前缀</td>
</tr>
</tbody>
</table>
<p>我们的配置如下：</p>
<pre class="crayon-plain-tag"># 如果不设置，会导致报错：AttributeError: 'Settings' object has no attribute 'URL_PREFIX
URL_PREFIX = '/'</pre>
<div class="blog_h3"><span class="graybg">安装Apache和mod_wsgi</span></div>
<p>多种Web服务器可以用于运行基于Django的Web应用，这里我们选择Apache2。在CentOS下安装Apache2：</p>
<pre class="crayon-plain-tag">yum install httpd</pre>
<p>要让Apache2能够运行Python Web应用，需要安装模块mod_wsgi。参考<a href="https://blog.gmem.cc/django-study-note#mod-wsgi">Django学习笔记</a>完成mod_wsgi的构建与安装。</p>
<div class="blog_h3"><span class="graybg">配置Graphite虚拟主机</span></div>
<p>在/opt/graphite/examples/目录下，example-graphite-vhost.conf可以作为Apache虚拟主机的模板，复制该文件到/etc/httpd/conf.d/目录下，然后修改：</p>
<pre class="crayon-plain-tag">&lt;IfModule !wsgi_module.c&gt;
    LoadModule wsgi_module modules/mod_wsgi.so
&lt;/IfModule&gt;
WSGISocketPrefix run/wsgi
&lt;VirtualHost *:7767&gt;
        ServerName xcentos7.local
        DocumentRoot "/opt/graphite/webapp"
        ErrorLog /opt/graphite/storage/log/webapp/error.log
        CustomLog /opt/graphite/storage/log/webapp/access.log common
        WSGIDaemonProcess graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120
        WSGIProcessGroup graphite
        WSGIApplicationGroup %{GLOBAL}
        WSGIImportScript /opt/graphite/conf/graphite.wsgi process-group=graphite application-group=%{GLOBAL}
        WSGIScriptAlias / /opt/graphite/conf/graphite.wsgi
        Alias /content/ /opt/graphite/webapp/content/
        &lt;Directory /opt/graphite/webapp/content/&gt;
            Require all granted
        &lt;/Directory&gt;
        &lt;Directory /opt/graphite/conf/&gt;
            Require all granted
        &lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
<div class="blog_h3"><span class="graybg">验证配置</span></div>
<p>如果配置没有问题，启动Web服务器后访问：<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/render</pre> ，会显示一张330x250大小的图片，上面写着No Data。</p>
<div class="blog_h2"><span class="graybg">配置Carbon</span></div>
<p>所有Carbon守护进程可以基于多种通信协议，监听时间序列数据，并且对数据进行不同的处理。</p>
<p>Carbon的配置文件位于/opt/graphite/conf/目录，默认情况下没有预置的配置文件，但是Graphite提供了若干配置文件样例。你可以复制这些配置文件并定制：</p>
<pre class="crayon-plain-tag">pushd /opt/graphite/conf
cp carbon.conf.example carbon.conf
cp storage-schemas.conf.example storage-schemas.conf</pre>
<div class="blog_h3"><span class="graybg">carbon.conf</span></div>
<p>这是主配置文件，为每个Carbon守护进程定义配置项。该配置文件按段区分不同守护进程的配置：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 14%; text-align: center;">配置项段</td>
<td style="width: 30%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="17">[cache]   </td>
<td>
<p><span style="font-size: 8pt;">ENABLE_LOGROTATION</span></p>
</td>
<td>
<p> 是否启用每日的日志轮转，启用后每天创建一个日志</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER </span></td>
<td>运行该进程的用户 </td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_CACHE_SIZE</span></td>
<td>内存中Metrics缓存的最大尺寸</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_UPDATES_PER_SECOND</span></td>
<td>每秒执行whisper的update_many()调用的最大次数，对应磁盘IO的次数，该配置项避免过度的磁盘使用</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_CREATES_PER_MINUTE</span></td>
<td>对每分钟最多创建的.wsp文件的个数进行软限制。超过限制的新Metrics对应的.wsp文件不会被创建，Metrics也被丢弃</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收文本格式数据的监听端口，默认0.0.0.0:2003</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE<br /></span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">ENABLE_UDP_LISTENER</span></td>
<td>是否启用UDP监听</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">UDP_RECEIVER_INTERFACE</span></td>
<td rowspan="2">通过UDP接收数据的监听端口，默认0.0.0.0:2003</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">UDP_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_QUERY_INTERFACE</span></td>
<td rowspan="2">缓存查询服务的监听端口，默认0.0.0.0：7002</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_QUERY_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_CACHE_SIZE后，会暂停接收数据，直到缓存占用小于MAX_CACHE_SIZE的95%</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_WRITE_STRATEGY</span></td>
<td>按何种顺序将缓存从内存中移除，并写入到磁盘的策略：sorted、max、naive</td>
</tr>
<tr>
<td rowspan="13">[relay]</td>
<td><span style="font-size: 8pt;">LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">
<p>接收文本格式数据的监听端口，默认0.0.0.0:2003</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER</span></td>
<td>运行该进程的用户</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">RELAY_METHOD</span></td>
<td>
<p>设置为rules：则该进程可以代替carbon-cache.py，然后中继所有Metrics给多个作为后端的carbon-cache.py</p>
<p>设置为consistent-hashing：则依据DESTINATIONS定义的分片策略，分发Metrics给多个作为后端的carbon-cache.py</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">REPLICATION_FACTOR</span></td>
<td>RELAY_METHOD=consistent-hashing时，可以指定N，从而把每个数据点分发到N台机器</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">DESTINATIONS</span></td>
<td>转发的目标，每个目标的格式是IP:PORT<br />RELAY_METHOD=rules时relay-rules.conf每个servers都要在此字段定义</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_DATAPOINTS_PER_MESSAGE</span></td>
<td>单个转发报文中包含的数据点的最大个数</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_QUEUE_SIZE</span></td>
<td>待转发的队列最大包含多少数据点</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">QUEUE_LOW_WATERMARK_PCT</span></td>
<td>队列低水位的百分比，0-1之间</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_QUEUE_SIZE后，会暂停接收数据，直到转发队列低于QUEUE_LOW_WATERMARK_PCT</td>
</tr>
<tr>
<td rowspan="13">[aggregator]</td>
<td><span style="font-size: 8pt;"> LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收文本格式数据的监听端口，默认0.0.0.0:2023</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER</span></td>
<td>运行该进程的用户</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">FORWARD_ALL</span></td>
<td>如果设置为True，除了根据aggregation-rules.conf进行聚合外，还把原始数据转发给DESTINATIONS</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">DESTINATIONS</span></td>
<td>聚合后的数据发送到的地方</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">REPLICATION_FACTOR</span></td>
<td>如果设置为N，则把数据转发给N个DESTINATION</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_QUEUE_SIZE</span></td>
<td>待转发的队列最大包含多少数据点</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_QUEUE_SIZE后，会暂停接收数据，直到转发队列低于80%</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_DATAPOINTS_PER_MESSAGE</span></td>
<td>单个转发报文中包含的数据点的最大个数</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_AGGREGATION_INTERVALS</span></td>
<td>控制最多记住多少数据点，只有这些数据点才参与聚合<br />仅最近MAX_AGGREGATION_INTERVALS  * intervalSize秒内的数据点会被记住</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">storage-schemas.conf</span></div>
<p>这个配置文件用于定义Metrics的驻留率（ retention rates）——即Metrics的数据点（datapoints）以什么频率保存，保存多长时间。关于该配置文件，需要注意：</p>
<ol>
<li>该配置文件可以由多个段（Section）组成。每个段定义一个存储规则</li>
<li>这些规则按照从上到下的顺序对Metrics进行匹配，第一个匹配的规则对Metrics生效</li>
<li>匹配Metrics时，采用的是正则表达式</li>
<li>对于一个Metrics，其存储规则在接收到第一个数据时固化。因而修改此配置文件不会影响到既有的.wsp文件。要应用到既有的.wsp可以调用whisper-resize.py</li>
</ol>
<p>每个规则由三个部分组成：</p>
<pre class="crayon-plain-tag"># 段名称（规则名称），主要是文档用途。在匹配段的Metrics被创建时，日志creates.log中会显示此名称
[rulename]
# 匹配Metrics路径（Metrics的全限定名称，点号分隔）的正则式
# 举例：^servers\.www.*\.workers\.busyWorkers$
pattern=regex
# 驻留率表达式，数据点间隔:存留天数，时间后缀s,m,h,d,y分别表示秒、分、小时、天、年
# 举例：10s:14d 表示每个数据点表示10秒（相当于每10秒采集数据一次），并且存储14天的数据
retentions=retention rate</pre>
<p>注意，你可以指定多重驻留率表达式（retention rate），逗号分隔每个表达式。一般从最高精度:最短存留时间开始指定，直到最低精度:最长存留时间。例如：<pre class="crayon-plain-tag">15s:7d,1m:21d,15m:5y</pre> 表示7天内每15秒存留一个采样，而大于21天小于5年的则每15分钟一个采样。</p>
<p>设置多重驻留率表达式，可以在保存足够长时间的历史数据的前提下，尽量减少磁盘I/O和消耗的存储空间。当跨越驻留表达式的时间区间（上例中7天，21天）后，whisper会自动降低采样率（downsamples），<span style="background-color: #c0c0c0;">默认算法是取平均值</span>，可以通过storage-aggregation.conf定制聚合方式</p>
<div class="blog_h3"><span class="graybg">storage-aggregation.conf</span></div>
<p>这个配置文件定义如何在降低采样率（转换为低精度存储时） 如何对数据进行聚合。该文件的格式与storage-schemas.conf类似：</p>
<pre class="crayon-plain-tag">[rulename]
pattern = rexexp
# 0-1之间的浮点数，默认0.5。聚合区间内的值，至少多少比例为非空，聚合后的值才是非空
xFilesFactor = 0.5
# 数据聚合方式，默认average
aggregationMethod = average | sum | min | max | last</pre>
<p>同样的，修改此文件不会影响已经生成的.wsp文件， 要应用到既有的.wsp文件，可以调用whisper-set-aggregation-method.py。</p>
<div class="blog_h3"><span class="graybg">relay-rules.conf</span></div>
<p>这个配置文件指定中继规则——即需要把何种Metrics转发给何种后端。中继由Carbon的中继进程负责执行。该文件格式如下：</p>
<pre class="crayon-plain-tag">[rulename]
pattern = regex
servers = ip:port,ip:port,...</pre>
<div class="blog_h3"><span class="graybg">aggregation-rules.conf</span></div>
<p>该配置文件定义聚合出来的Metrics——由几个Metrics聚合而成的新的Metrics。聚合由Carbon的聚合进程负责执行。</p>
<p>注意：这里的聚合storage-aggregation.conf提及的聚合不同。后者用于单一Metrics的降低采样，而前者用于生成新的Metrics。</p>
<p>与其它配置文件不同，该文件一旦更改，立即生效。</p>
<p>该文件的格式如下：</p>
<pre class="crayon-plain-tag"># 捕获任何匹配input_pattern的Metrics，使用method聚合成新的Metrics：output_template
# frequency：每隔多久执行一次聚合
# method：可选sum/avg
output_template (frequency) = method input_pattern</pre>
<p>举例，假设你的Metrics的命名规则是：</p>
<pre class="crayon-plain-tag">&lt;env&gt;.applications.&lt;app&gt;.&lt;server&gt;.&lt;metric&gt;</pre>
<p>这是你可以配置如下聚合规则，来计算所有应用程序的请求的总数：</p>
<pre class="crayon-plain-tag">&lt;env&gt;.applications.&lt;app&gt;.all.requests (60) = sum &lt;env&gt;.applications.&lt;app&gt;.*.requests</pre>
<p>在此规则下，下面的Metrics：</p>
<pre class="crayon-plain-tag">test.applications.pems.221.request
test.applications.pems.6.request
test.applications.pems.5.request
test.applications.pems.201.request</pre>
<p>会每个60秒求和并生成 <pre class="crayon-plain-tag">test.applications.pems.all.request</pre> 的一个数据点。</p>
<div class="blog_h3"><span class="graybg">rewrite-rules.conf </span></div>
<p>该配置文件定义Metrics名称的改写规则。 与其它配置文件不同，该文件一旦更改，立即生效。该文件的格式如下：</p>
<pre class="crayon-plain-tag">[pre]
# pre段的规则，在接收到数据后立即执行改写
[post]
# post段的规则，在聚合完毕后执行改写
regex-pattern = replacement-text</pre>
<p> 举例：</p>
<pre class="crayon-plain-tag"># \1表示第一个捕获，即[a-z0-9]+
^collectd\.([a-z0-9]+)\. = \1.system.
# collectd.prod.cpu-0.idle-time 会被改写为 prod.system.cpu-0.idle-item</pre>
<div class="blog_h3"><span class="graybg">whitelist.conf和blacklist.conf </span></div>
<p>Carbon提供黑白名单功能。白名单：仅仅接受其中列出的Metrics；黑名单：拒绝其中列出Metrics。</p>
<p>要启用黑白名单功能，需要修改carbon.conf，设置<pre class="crayon-plain-tag">USE_WHITELIST = True</pre> </p>
<div class="blog_h2"><span class="graybg">启动Graphite</span></div>
<p>如何启动Graphite Webapp依赖于其运行的Web服务软件，以Apache为例：</p>
<pre class="crayon-plain-tag"># CentOS 7
systemctl restart httpd</pre>
<p>Carbon组件需要执行下面的命令来启动：</p>
<pre class="crayon-plain-tag">/opt/graphite/bin/carbon-cache.py start</pre>
<p>SQLite是一个“文件数据库”，不需要启动。如果Graphite Webapp使用其它数据库，例如MySQL，则需要启动之。 </p>
<div class="blog_h1"><span class="graybg">向Graphite发送数据点</span></div>
<div class="blog_h2"><span class="graybg">数据格式</span></div>
<p>你可以通过多种方式向Graphite（的Carbon组件）发送Metrics数据点。主要的三种数据格式是：Plaintext、Pickle、AMQP </p>
<div class="blog_h3"><span class="graybg">Plaintext协议</span></div>
<p>这种方式非常简单，你可以用如下格式来发送一条Metrics数据点：</p>
<pre class="crayon-plain-tag">&lt;metric path&gt; &lt;metric value&gt; &lt;metric timestamp&gt;
# 举例：
servers.zircon.cpu 85 1470303923</pre>
<p>出于测试目的，你可以直接通过命令向Carbon发送Metrics数据：</p>
<pre class="crayon-plain-tag">PORT=2003
SERVER=127.0.0.1
echo "servers.zircon.cpu 85 `date +%s`" | nc ${SERVER} ${PORT}</pre>
<div class="blog_h3"><span class="graybg">Pickle协议</span></div>
<p>Pickle是Python下的对象串行化框架，Pickle协议即它的串行化格式协议。使用该协议，你可以一次发送多个Metrics，并且串行化后的数据比较紧凑，因此Pickle协议性能更好。</p>
<p>下面是构建Pickle报文的示例代码：</p>
<pre class="crayon-plain-tag">listOfMetricTuples = [
    (path, (timestamp, value)),
    ...
]
payload = pickle.dumps(listOfMetricTuples, protocol=2)
header = struct.pack("!L", len(payload))
message = header + payload
# 然后通过Socket把message发送出去即可</pre>
<div class="blog_h2"><span class="graybg">客户端示例</span></div>
<div class="blog_h3"><span class="graybg">简单Pickle客户端</span></div>
<pre class="crayon-plain-tag">import pickle
import socket
import struct
import sys
import time
from time import sleep

import psutil


def get_cpu_load():
    load = psutil.cpu_percent()
    print load
    return load


if __name__ == '__main__':
    CARBON_HOST = '172.16.87.132'
    CARBON_PORT = 2004
    s = socket.socket()
    try:
        s.connect((CARBON_HOST, CARBON_PORT))
        while True:
            data = [
                ('servers.zircon.cpu', (time.time(), get_cpu_load()))
            ]
            pkg = pickle.dumps(data, 1)
            s.sendall(struct.pack('!L', len(pkg)))
            s.sendall(pkg)
            sleep(5)
    except socket.error:
        raise SystemExit("Failed to connect to %(host)s:%(port)" % {'host': CARBON_HOST, 'port': CARBON_PORT})
    except KeyboardInterrupt:
        sys.stderr.write("\nExiting on CTRL-C\n")
        sys.exit(0) </pre>
<div class="blog_h1"><span class="graybg">从Graphite获取数据</span></div>
<p>要从Graphite获取数据用于展示，可以使用Graphite Webapp暴露的Render URL API（RUA）。你可以通过</p>
<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/render</pre>
<p>访问此API。要向RUA传递参数，可以使用URL请求参数方式：<pre class="crayon-plain-tag">&amp;name=value</pre> 。注意大部分的参数名、函数名是大小写敏感的。</p>
<p>下面列出几个RUA的URL示例：</p>
<pre class="crayon-plain-tag"># Zircon的CPU负载图，获取800x600的图片
http://graphite/render?target=servers.zircon.cpu&amp;height=800&amp;width=600
# 最近12小时，所有服务器的CPU负载平均值
http://graphite/render?target=averageSeries(servers.*.load)&amp;from=-12hours
# 获取原始数据而不是图片，JSON格式
http://graphite/render?target=servers.zircon.cpu&amp;format=json</pre>
<div class="blog_h2"><span class="graybg">target参数</span></div>
<p>该参数用来指定从何处获取数据，你可以指定：</p>
<ol>
<li>单个Metrics路径</li>
<li>带有通配符的的Metrics路径，匹配多个Metrics</li>
<li>函数调用，针对作为入参的Metrics进行各种转换、合并操作</li>
</ol>
<div class="blog_h3"><span class="graybg">通配符</span></div>
<p>你可以在路径中使用三种风格的通配符：</p>
<ol>
<li><pre class="crayon-plain-tag">*</pre> 可以匹配0-N个字符，例如<pre class="crayon-plain-tag">servers.dev-*.cpu</pre> ，可以匹配所有开发服务器的CPU负载Metrics。</li>
<li><pre class="crayon-plain-tag">[...]</pre> 可以匹配列表中枚举的单个字符，例如<pre class="crayon-plain-tag">servers.dev-[a-z0-9].cpu</pre> ，可以匹配dev-0、dev-1等服务器的CPU负载Metrics。</li>
<li><pre class="crayon-plain-tag">{...}</pre> 可以匹配列表中枚举的单个字符串，例如<pre class="crayon-plain-tag">servers.{dev-0,dev-1}.cpu</pre> ，匹配dev-0、dev-2的CPU负载Metrics。</li>
</ol>
<p>注意：所有通配符都<span style="background-color: #c0c0c0;">不能跨越点号</span>。 </p>
<div class="blog_h3"><span class="graybg">template函数</span></div>
<p>你可以指定target为template函数调用，从而在Metrics路径中使用变量，例如：</p>
<pre class="crayon-plain-tag"># $varname 用来声明变量占位符
# template[varname]参数用来传递变量值
&amp;target=template(servers.$servername.cpu)&amp;template[servername]=zircon

# 可以使用数字代替变量名
&amp;target=template(servers.$1.cpu)&amp;template[1]=zircon

# template可以内嵌其它函数
&amp;target=template(constantLine($number))&amp;template[number]=123</pre>
<div class="blog_h3"><span class="graybg">所有函数</span></div>
<p>可用的函数较多，这里不一一列举说明，参见<a href="http://graphite.readthedocs.io/en/latest/functions.html">官方文档</a>。</p>
<div class="blog_h2"><span class="graybg">from/until参数</span></div>
<p>这两个可选参数用来指定相对或者绝对的时间区间（time period）。from表示区间起点，如果忽略，默认值是24小时之前；until表示区间终点，如果或略，默认值是当前时间点。</p>
<div class="blog_h3"><span class="graybg">相对时间</span></div>
<p>如果要使用相对时间，需要加上<pre class="crayon-plain-tag">-</pre> 前缀（负号），后面跟着数值和时间单位。时间单位包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">时间单位</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>s</td>
<td>秒</td>
</tr>
<tr>
<td>min</td>
<td>分钟</td>
</tr>
<tr>
<td>h</td>
<td>小时</td>
</tr>
<tr>
<td>d</td>
<td>天</td>
</tr>
<tr>
<td>w</td>
<td>周</td>
</tr>
<tr>
<td>mon</td>
<td>月（30天）</td>
</tr>
<tr>
<td>y</td>
<td>年（365天）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">绝对时间</span></div>
<p>你可以指定<pre class="crayon-plain-tag">HH:MM_YYYYMMDD</pre> 、<pre class="crayon-plain-tag">YYYYMMDD</pre> 、<pre class="crayon-plain-tag"> MM/DD/YY</pre> 等格式的时间绝对值。</p>
<div class="blog_h2"><span class="graybg">format参数</span></div>
<p>该参数用来指定要获取的数据的格式。 支持以下取值：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">格式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>png</td>
<td>根据指定的width、height，直接把数据渲染为PNG图片</td>
</tr>
<tr>
<td>raw</td>
<td>原始数据，分为多行，每行格式为：<br />
<pre class="crayon-plain-tag">&lt;target name&gt;,&lt;start timestamp&gt;,&lt;end timestamp&gt;,&lt;series step&gt;|[data]*</pre></p>
<p> 示例：</p>
<pre class="crayon-plain-tag">entries,1311836008,1311836013,1|1.0,2.0,3.0,5.0,6.0</pre>
</td>
</tr>
<tr>
<td>csv</td>
<td>基于逗号分隔符的格式，每行表示一个数据点。示例：<br />
<pre class="crayon-plain-tag">entries,2011-07-28 01:53:28,1.0
entries,2011-07-28 01:53:29,2.0
entries,2011-07-28 01:53:30,3.0
entries,2011-07-28 01:53:31,5.0
entries,2011-07-28 01:53:32,6.0</pre>
</td>
</tr>
<tr>
<td>json</td>
<td>
<p>JSON数组格式，示例：
<pre class="crayon-plain-tag">[{
  "target": "entries",
  "datapoints": [
    [1.0, 1311836008],
    [2.0, 1311836009],
    [3.0, 1311836010],
    [5.0, 1311836011],
    [6.0, 1311836012]
  ]
}]</pre>
<p>可以和jsonp参数联用，以便把数据包装成函数调用，进行跨域请求</p>
<p>可以和maxDataPoints参数联用，限定最大的数据点个数。超过数量的数据点将被压缩掉</p>
<p>可以和noNullPoints参数联用，移除所有Null值的数据点</p>
</td>
</tr>
<tr>
<td>svg</td>
<td>渲染为SVG图片格式</td>
</tr>
<tr>
<td>pdf</td>
<td>渲染为PDF文档</td>
</tr>
<tr>
<td>dygraph</td>
<td>dygraphs是一个快速、灵活的JavaScript图表（Chart）库。该格式返回dygraphs支持的数据格式。示例：<br />
<pre class="crayon-plain-tag">{
  "labels" : [
    "Time",
    "entries"
  ],
  "data" : [
    [1468791890000, 0.0],
    [1468791900000, 0.0]
  ]
}</pre>
</td>
</tr>
<tr>
<td>rickshaw</td>
<td>rickshaw是一个简单的JavaScript图表库。该格式返回rickshaw支持的数据格式。示例：<br />
<pre class="crayon-plain-tag">[{
  "target": "entries",
  "datapoints": [{
    "y": 0.0,
    "x": 1468791890
  }, {
    "y": 0.0,
    "x": 1468791900
  }]
}]</pre>
</td>
</tr>
<tr>
<td>pickle</td>
<td>返回Pickle串行化格式，设置MIME类型为application/pickle。反串行化后的对象示例：<br />
<pre class="crayon-plain-tag">[
  {
    'name' : 'summarize(test.data, "30min", "sum")',
    'start': 1335398400,
    'end'  : 1335425400,
    'step' : 1800,
    'values' : [None, None, 1.0, None],
  }
]</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">图形参数</span></div>
<p>你可以指定多个参数，来控制生成的图形的样式：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>areaAlpha</td>
<td>启用areaMode时，填充区域的透明度。0-1之间的浮点数</td>
</tr>
<tr>
<td>areaMode</td>
<td>填充曲线与X轴之间的区域，形成Area图，可以取值：<br />none 不进行填充<br />first 填充第一个目标<br />all  填充所有目标<br />stacked 堆叠模式，填充所有目标，一个目标的取值为前面所有其它目标的取值+该目标的取值</td>
</tr>
<tr>
<td>bgcolor</td>
<td>背景颜色。示例：<br />
<pre class="crayon-plain-tag"># 颜色名称
&amp;bgcolor=blue
# HEX代码
&amp;bgcolor=2222FF
# HEX代码，包含透明度
&amp;bgcolor=5522FF60</pre>
</td>
</tr>
<tr>
<td>cacheTimeout</td>
<td>被渲染出的图形，其有效缓存时间</td>
</tr>
<tr>
<td>colorList</td>
<td>多个Target时，每个Target的颜色，逗号分隔颜色代码</td>
</tr>
<tr>
<td>drawNullAsZero</td>
<td>是否把空值渲染为0</td>
</tr>
<tr>
<td>fontBold</td>
<td>是否使用粗体</td>
</tr>
<tr>
<td>fontItalic</td>
<td>是否使用斜体</td>
</tr>
<tr>
<td>fontName</td>
<td>字体名称，该字体必须安装在Graphite服务器上</td>
</tr>
<tr>
<td>fontSize</td>
<td>字体大小，大于1的浮点数</td>
</tr>
<tr>
<td>graphOnly</td>
<td>是否不显示网格线、X/Y轴和图例</td>
</tr>
<tr>
<td>graphType</td>
<td>图表类型，line或者pie</td>
</tr>
<tr>
<td>hideLegend</td>
<td>是否隐藏图例</td>
</tr>
<tr>
<td>hideAxes</td>
<td rowspan="3">是否隐藏X/Y轴</td>
</tr>
<tr>
<td>hideXAxis</td>
</tr>
<tr>
<td>hideYAxis</td>
</tr>
<tr>
<td>hideGrid</td>
<td>是否隐藏网格线</td>
</tr>
<tr>
<td>height</td>
<td rowspan="2">图形的高度、宽度，单位像素</td>
</tr>
<tr>
<td>width</td>
</tr>
<tr>
<td>leftColor</td>
<td>在双Y轴模式下，设置与左轴关联的Metrics的颜色</td>
</tr>
<tr>
<td>rightColor</td>
<td>在双Y轴模式下，设置与右轴关联的Metrics的颜色</td>
</tr>
<tr>
<td>leftDashed</td>
<td>在双Y轴模式下，是否以虚线绘制与左轴关联的Metrics</td>
</tr>
<tr>
<td>rightDashed</td>
<td>在双Y轴模式下，是否以虚线绘制与右轴关联的Metrics</td>
</tr>
<tr>
<td>leftWidth</td>
<td>在双Y轴模式下，设置与左轴关联的Metrics的线条宽度</td>
</tr>
<tr>
<td>rightWidth</td>
<td>在双Y轴模式下，设置与右轴关联的Metrics的线条宽度</td>
</tr>
<tr>
<td>lineMode</td>
<td>设置线条绘制的行为：<br />slope：从一个数据点向下一个数据点绘制斜线，Null值的区间不被绘制<br />connected：与slope类似，但是数据点总是被连接起来，无论它们之间是否存在Null值<br />staircase：绘制直方图</td>
</tr>
<tr>
<td>lineWidth</td>
<td>线条的宽度</td>
</tr>
<tr>
<td>majorGridLineColor</td>
<td>网格线主色</td>
</tr>
<tr>
<td>minorGridLineColor</td>
<td>网格线从色</td>
</tr>
<tr>
<td>minorY</td>
<td>每两个网格主线之间，有几个从线，Y方向</td>
</tr>
<tr>
<td>margin</td>
<td>图形四周的边距</td>
</tr>
<tr>
<td>maxDataPoints</td>
<td>使用</td>
</tr>
<tr>
<td>minXStep</td>
<td>
<p>两个连续的数据点之间，间隔的最小像素</p>
<p>如果数据点过多，则压缩，以满足此配置</p>
</td>
</tr>
<tr>
<td>noCache</td>
<td>禁止图片缓存</td>
</tr>
<tr>
<td>pieLabels</td>
<td>饼图标签如何显示，horizontal或者rotated</td>
</tr>
<tr>
<td>pieMode</td>
<td>饼图聚合方式：<br />average，取series中非空数据点的平均值<br />maximum，取series中非空数据点的最大值<br />minimum，取series中非空数据点的最小值</td>
</tr>
<tr>
<td>valueLabels</td>
<td>如何显示饼图分块的标签：<br />none，不显示<br />numbers，显示原始值<br />percent，显示百分比</td>
</tr>
<tr>
<td>valueLabelsColor</td>
<td>如何显示饼图分块的标签的颜色</td>
</tr>
<tr>
<td>valueLabelsMin</td>
<td>饼图中，分块占比小于此数值的分块，不显示其标签</td>
</tr>
<tr>
<td>title</td>
<td>在图形顶端显示的标题</td>
</tr>
<tr>
<td>vtitle</td>
<td>Y轴标题，垂直显示</td>
</tr>
<tr>
<td>vtitleRight</td>
<td>双Y轴模式下，右Y轴的标题</td>
</tr>
<tr>
<td>tz</td>
<td>用于显示时间值的时区</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">展示Graphite数据</span></div>
<p>上一章内容我们讨论了如何获取Graphite数据。通过Render URL API，我们不但可以获得文本数据，还可以直接获得渲染好的图片。这意味着通过Render URL API本身就可以实现Metrics的渲染，你只需要把生成的图片嵌入到自己的应用程序中即可。</p>
<p>Graphite Webapp本身提供了基于ExtJS的一个管理界面，你可以通过<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/admin</pre> 浏览Metrics。</p>
<p>Graphite生成的Metrics曲线的图片，不是非常美观，而静态图片也缺乏交互性。因此，实际项目中常常结合使用第三方基于JavaScript的Charts库来做展示，例如：</p>
<ol>
<li><a href="http://grafana.org/">Grafana</a>：UI比较绚丽，支持设计仪表盘、时间区间联动。参见：<a href="/time-series-data-renderering-with-grafana">使用Grafana展示时间序列数据</a></li>
<li><a href="http://jondot.github.io/graphene/">Graphene</a>：一个较为简单的，基于D3.js和Backbone.js的Graphite仪表盘工具</li>
</ol>
<div class="blog_h1"><span class="graybg">Graphite事件</span></div>
<p>除了简单的，基于Key/Value的Metrics数据，Graphite还可以存储、展示随机出现的数据——事件。</p>
<p>事件不适合存储在Whisper中，因此它被存储在Graphite的Webapp的数据库中（默认使用SQLite）。</p>
<div class="blog_h2"><span class="graybg">发布事件</span></div>
<p>通过向<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/events/</pre> 发送POST请求，即可发布Graphite事件。事件使用JSON格式编码在请求体中：</p>
<pre class="crayon-plain-tag">{ 
    "what": "事件类型", 
    "tags": "标签",
    "data": "事件相关的数据" 
}</pre>
<div class="blog_h2"><span class="graybg">查询事件</span></div>
<p>指定target为<pre class="crayon-plain-tag">event(*tags)</pre> 函数调用，即可通过<pre class="crayon-plain-tag">/render</pre> 查询事件，例如</p>
<pre class="crayon-plain-tag">[
   {
      "target" : "events(mytag)",
      "datapoints" : [
         [
            1,
            1388966651
         ],
         [
            3,
            1388966652
         ]
      ]
   }
]</pre>
<p>你也可以通过<pre class="crayon-plain-tag">/render/events/get_data</pre> 获得原始的事件数据，例如：</p>
<pre class="crayon-plain-tag">[
   {
      "when" : 1392046352,
      "tags" : "mytag",
      "data" : "...",
      "id" : 2,
      "what" : "Event - deploy"
   }
] </pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">AttributeError: 'WSGIRequest' object has no attribute 'REQUEST'</span></div>
<p>Django的request对象曾经有一个属性REQUEST，用来获得通过GET或者POST请求传递的请求参数，在1.9版本中此属性已经移除。</p>
<p>Graphite代码没有即时更新，存在不兼容的问题，修改一下即可：</p>
<pre class="crayon-plain-tag">def parseOptions(request):
   queryParams = request.GET # request.REQUEST已经被移除</pre>
<p>还有很多其它views.py存在同样的问题，最好搜索一下一并修改。如果觉得麻烦可以安装兼容版本的Django：</p>
<pre class="crayon-plain-tag">pip install django==1.8.14</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/graphite-study-note">Graphite学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/graphite-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Shinken学习笔记</title>
		<link>https://blog.gmem.cc/shinken-study-note</link>
		<comments>https://blog.gmem.cc/shinken-study-note#comments</comments>
		<pubDate>Wed, 22 Jun 2016 01:29:03 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Shinken]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12060</guid>
		<description><![CDATA[<p>Shinken简介 Shinken是一个开源的IT监控框架，基于Python编写。Shinken从2009年开始发布，起初是作为一个简单的监控解决方案，由于越来越多的模块的加入，至2014年它被重新定位为“框架”。 Shinken的优势包括： 跨平台，它可以在Windows、Linux上部署和运行 独立性，不依赖于其它监控解决方案 可扩容，能够很好的支持不断扩张的、大规模的监控需求 特性列表 特性 说明 基于角色分离的守护程序 Shinken中的每个后端程序只做一种事情，这些后端程序有6类 强大的灵活性 大量的可拔插模块，让你能监控很多东西 从数据库导入配置 支持的数据库包括：GLPI、MySQL、MongoDB等 导出数据到数据库 支持的数据库包括：Graphite、InfluxDB、RRD、GLPI、CouchDB、Livestatus、MySQL、Oracle等 与WebUI集成 支持内置的WebUI，或者与Thruk、Adagios、Multisite、Nagvis、PNP4Nagios、NConf等集成 支持大量监控目标 数据库：MySQL、Oracle等关系型数据库；MongoDB等NoSQL；Memcached等缓存路由器/交换机：包括Cisco、Nortel、Procurve等公司的产品操作系统：Linux、Windows、Aix、HP-UX等网络协议：HTTP、SSH、LDAP、DNS、IMAP、FTP等应用程序：Weblogic、Exchange、AD、Tomcat等存储：IBM-DS、Safekit、Hacmp等 智能的SNMP轮询 <a class="read-more" href="https://blog.gmem.cc/shinken-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/shinken-study-note">Shinken学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Shinken简介</span></div>
<p>Shinken是一个开源的<span style="background-color: #c0c0c0;">IT监控框架</span>，基于Python编写。Shinken从2009年开始发布，起初是作为一个简单的监控<span style="background-color: #c0c0c0;">解决方案</span>，由于越来越多的模块的加入，至2014年它被重新定位为“框架”。</p>
<p>Shinken的优势包括：</p>
<ol>
<li>跨平台，它可以在Windows、Linux上部署和运行</li>
<li>独立性，不依赖于其它监控解决方案</li>
<li>可扩容，能够很好的支持不断扩张的、大规模的监控需求</li>
</ol>
<div class="blog_h2"><span class="graybg">特性列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">特性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>基于角色分离的守护程序</td>
<td>Shinken中的每个后端程序只做一种事情，这些后端程序有6类</td>
</tr>
<tr>
<td>强大的灵活性</td>
<td>大量的可拔插模块，让你能监控很多东西</td>
</tr>
<tr>
<td>从数据库导入配置</td>
<td>支持的数据库包括：GLPI、MySQL、MongoDB等</td>
</tr>
<tr>
<td>导出数据到数据库</td>
<td>支持的数据库包括：Graphite、InfluxDB、RRD、GLPI、CouchDB、Livestatus、MySQL、Oracle等</td>
</tr>
<tr>
<td>与WebUI集成</td>
<td>支持内置的WebUI，或者与Thruk、Adagios、Multisite、Nagvis、PNP4Nagios、NConf等集成</td>
</tr>
<tr>
<td>支持大量监控目标</td>
<td>数据库：MySQL、Oracle等关系型数据库；MongoDB等NoSQL；Memcached等缓存<br />路由器/交换机：包括Cisco、Nortel、Procurve等公司的产品<br />操作系统：Linux、Windows、Aix、HP-UX等<br />网络协议：HTTP、SSH、LDAP、DNS、IMAP、FTP等<br />应用程序：Weblogic、Exchange、AD、Tomcat等<br />存储：IBM-DS、Safekit、Hacmp等</td>
</tr>
<tr>
<td>智能的SNMP轮询</td>
<td>如果你需要监控大量的路由器、交换机等基础设施，可以使用SNMP Booster模块</td>
</tr>
<tr>
<td>可扩容性</td>
<td>只需要在其它服务器上安装守护程序，负载均衡就会自动完成</td>
</tr>
<tr>
<td>高可用性</td>
<td>守护程序可以有多个备用的</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">架构</span></div>
<div class="blog_h3"><a href="https://blog.gmem.cc/wp-content/uploads/2016/06/shinken-architecture.png"><img class="aligncenter wp-image-12067 size-large" src="https://blog.gmem.cc/wp-content/uploads/2016/06/shinken-architecture-1024x724.png" alt="shinken-architecture" width="710" height="501" /></a><span class="graybg">守护程序角色</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">角色</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Arbiter</td>
<td>
<p>此类守护程序负责：</p>
<ol>
<li><span style="background-color: #c0c0c0;">读取配置</span>，拆分成N份（N=Scheduler的数量），然后<span style="background-color: #c0c0c0;">分发</span>给相应的其它守护程序</li>
<li>管理<span style="background-color: #c0c0c0;">高可用性</span>：管理其它守护进程的健康状况，如果某个守护程序宕机，它负责把<span style="background-color: #c0c0c0;">此守护程序管理的配置重新路由</span>给其它空闲的守护程序</li>
<li><span style="background-color: #c0c0c0;">接受用户输入</span>，并路由给合适的守护程序</li>
</ol>
<p>同时<span style="background-color: #c0c0c0;">只能有一个Arbiter处于活动状态</span>，其它只能Standby</p>
<p>和Arbiter相关的模块包括：<br />数据收集模块：NSCA、TSCA、Ws_arbiter<br />配置数据保存模块：MongoDB<br />状态保持模块：PickleRententionArbiter<br />配置数据导入模块：MySQLImport、GLPI<br />配置修改模块：vmware autolinking、IP_Tag</p>
</td>
</tr>
<tr>
<td>Scheduler</td>
<td>
<p>此类守护程序负责：</p>
<ol>
<li><span style="background-color: #c0c0c0;">分发检查</span>（Check）给Poller、<span style="background-color: #c0c0c0;">分发动作</span>（Action）给Reactionner。它<span style="background-color: #c0c0c0;">自己不会</span>执行检查或动作</li>
<li>处理检查结果队列（Check result queue）、<span style="background-color: #c0c0c0;">分析</span>结果，根据结果可能将动作请求纳入队列</li>
</ol>
<p>和Scheduler相关的模块包括：<br />用于状态保持的模块：pickle、nagios、memcache、redis、MongoDB</p>
</td>
</tr>
<tr>
<td>Poller</td>
<td>
<p>依据Scheduler的指令，启动检查插件（Check plugins），检查完毕后，Poller把结果返回给Scheduler</p>
<p>和Poller相关的模块包括：<br />数据获取模块：NRPE、CommandFile、SnmpBooster</p>
</td>
</tr>
<tr>
<td>Reactionner</td>
<td>
<p>发送通知、启动事件处理器（Event handler），集中处理和外部系统的通信</p>
<p>和Reactionner相关的模块包括：<br />外部通信模块：AndroidSMS</p>
</td>
</tr>
<tr>
<td>Broker</td>
<td>
<p>导出、管理来自Scheduler的数据，代理外部系统与Shinken的交互</p>
<p>和Broker相关的模块包括：<br />LiveStatus API模块——实时状态、状态保持、历史：SQLite（默认）、MongoDB<br />状态保持模块：Pickle、ToNdodb_Mysql、ToNdodb_Oracle<br />导出数据的模块：Graphite-Perfdata、NPCDMOD、raw_tcp、Syslog<br />WebUI相关模块：WebUI、GRAPHITE_UI、PNP_UI</p>
</td>
</tr>
<tr>
<td>Receiver</td>
<td>
<p>被动的接收检查数据，并作为分布式的命令缓冲</p>
<p>和Receiver相关的模块：<br />被动数据获取模块：NSCA、TSCA、Ws_arbiter</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">Linux</span></div>
<div class="blog_h3"><span class="graybg">通过PIP安装</span></div>
<pre class="crayon-plain-tag"># 如果机器上没有安装pip
apt-get install python-pip python-pycurl
# 添加shiken专用户
adduser shinken
# 安装shinken
pip install shinken</pre>
<div class="blog_h3"><span class="graybg">通过源码安装</span></div>
<p>使用这种方式，你可以方便的调试或者修改Shinken的源码：</p>
<pre class="crayon-plain-tag">adduser shinken
wget http://www.shinken-monitoring.org/pub/shinken-2.4.tar.gz
tar -xvzf shinken-2.4.tar.gz
cd shinken-2.4
python setup.py install</pre>
<div class="blog_h3"><span class="graybg">启动</span></div>
<p>在生产环境下可以设置Shinken为自启动：</p>
<pre class="crayon-plain-tag"># RedHat / CentOS
chkconfig shinken on
# Debian / Ubuntu
update-rc.d shinken defaults

# 启动
service shinken start</pre>
<p>你可以可以通过脚本启动：<pre class="crayon-plain-tag">./bin/launch_all.sh</pre>  </p>
<p>开发环境下，如果你需要修改Shinken源码，需使用第二种安装方式。然后注释掉shinken.cfg中用户、组信息，以当前用户的身份启动Shinken：</p>
<pre class="crayon-plain-tag">./bin/shinken-scheduler -c /etc/shinken/daemons/schedulerd.ini -d
./bin/shinken-poller -c /etc/shinken/daemons/pollerd.ini -d
./bin/shinken-broker -c /etc/shinken/daemons/brokerd.ini -d
./bin/shinken-reactionner -c /etc/shinken/daemons/reactionnerd.ini -d
./bin/shinken-arbiter -c /etc/shinken/shinken.cfg -d
./bin/shinken-receiver -c /etc/shinken/daemons/receiverd.ini -d</pre>
<div class="blog_h3"><span class="graybg">目录结构</span></div>
<p>安装后以及运行时，Shinken会使用下面的目录：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">目录</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>/etc/shinken</td>
<td>存放Shinken配置文件</td>
</tr>
<tr>
<td>/var/lib/shinken</td>
<td>存放Shinken模块、保留文件（Retention files）</td>
</tr>
<tr>
<td>/var/log/shinken</td>
<td>存放日志文件</td>
</tr>
<tr>
<td>/var/run/shinken</td>
<td>存放PID</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Windows</span></div>
<p>安装步骤参考Linux下基于源码的安装方式，不需要添加用户，解压可以手工进行。</p>
<p>Windows下所有目录都位于Shinken安装目录下，例如<pre class="crayon-plain-tag">/etc/shinken</pre> 对应<pre class="crayon-plain-tag">Windows下%SHINKEN_HOME%\etc</pre> 。</p>
<div class="blog_h1"><span class="graybg">启动和运行</span></div>
<div class="blog_h2"><span class="graybg">验证配置</span></div>
<p>每次修改配置后，你都应该在重启Shinken之前进行验证，因为配置存在问题会导致Shinken无法启动。</p>
<p>调用下面的命令执行验证：</p>
<pre class="crayon-plain-tag">/usr/bin/shinken-arbiter -v -c /etc/shinken/shinken.cfg </pre>
<p>如果配置存在错误，该命令会输出ERROR信息并指出问题所在位置。警告信息一般可以忽略。</p>
<div class="blog_h2"><span class="graybg">启动和停止</span></div>
<pre class="crayon-plain-tag"># 启动
/etc/rc.d/init.d/shinken start
# 重启
/etc/rc.d/init.d/shinken restart
# 停止
/etc/rc.d/init.d/shinken stop</pre>
<div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">shinken命令</span></div>
<p>Shinken提供了一个同名的命令，可以用于在shinken.io上检索、安装，上传<span style="background-color: #c0c0c0;">包（Pack，包含对象定义模板）</span>或者<span style="background-color: #c0c0c0;">模块（扩充Shinken功能的Python模块）</span>。初次使用该命令，需要初始化：</p>
<pre class="crayon-plain-tag"># 首次使用shinken命令时执行
shinken --init</pre>
<div class="blog_h3"><span class="graybg">常用选项</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--version</td>
<td>显示版本信息并退出</td>
</tr>
<tr>
<td>--proxy=PROXY</td>
<td>指定代理服务器，格式：<pre class="crayon-plain-tag">http://user:password@proxy-server:port</pre> </td>
</tr>
<tr>
<td>-A API_KEY</td>
<td>上传包时使用的API Key</td>
</tr>
<tr>
<td>-l, --list</td>
<td>列出子命令</td>
</tr>
<tr>
<td>--init</td>
<td>初始化并生成配置文件shinken.ini</td>
</tr>
<tr>
<td>-D</td>
<td>调试模式</td>
</tr>
<tr>
<td>-v</td>
<td>打印更多信息</td>
</tr>
<tr>
<td>-c INICONFIG</td>
<td>指定shinken.ini位置</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>desc</td>
<td>列出对象类型的属性</td>
</tr>
<tr>
<td>doc-compile</td>
<td>编译文档</td>
</tr>
<tr>
<td>doc-serve</td>
<td>发布在线文档</td>
</tr>
<tr>
<td>install</td>
<td>从shinken.io下载并安装包</td>
</tr>
<tr>
<td>inventory</td>
<td>列出本地安装的包</td>
</tr>
<tr>
<td>publish</td>
<td>发布包到shinken.io</td>
</tr>
<tr>
<td>search</td>
<td>在shinken.io搜索包</td>
</tr>
<tr>
<td>update</td>
<td>更新一个软件包</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基本配置</span></div>
<p>在安装完毕后，你就获得一个基本的Shinken配置，可以直接启动之。</p>
<div class="blog_h3"><span class="graybg">守护程序的配置文件</span></div>
<p>/etc/shinken/daemons目录存放了*.ini文件，这些文件描述了<span style="background-color: #c0c0c0;">在本机需要运行的</span>守护程序。基本配置下Shinken单机运行，因而此目录下有5个文件（Arbiter的配置在主配置文件中）。分别定义Broker、Poller、Reactionner、Receiver、Scheduler这五个守护程序使用的端口、工作目录等重要信息。</p>
<p>需要注意的是，这些守护程序不一定要在单机上运行，你可以分散部署，只需要将它们作为对象引用到主配置文件中。</p>
<p>我们看一下配置文件的内容：</p>
<pre class="crayon-plain-tag">[daemon]

# 该程序启动后，立即修改工作目录为：
workdir = /var/run/shinken
logdir  = /var/log/shinken
# PID文件，有了它以后我们可以杀死该守护程序
pidfile=%(workdir)s/schedulerd.pid
# TCP监听端口，默认：
# scheduler: 7768
# poller: 7771
# reactionner: 7769
# broker: 7772
# arbiter: 7770
port=7768
# TCP监听地址
host=0.0.0.0
# 用于运行守护程序的用户，以及用户的组
user=shinken
group=shinken
# 如果设置为1，则你可以在root用户下运行此守护程序
idontcareaboutsecurity=0

# Set to 0 if you want to make this daemon NOT run
# 是否启用此守护程序，如果设置为0则不会运行
daemon_enabled=1


#-- SSL相关的配置 --
use_ssl=0
# WARNING : Use full paths for certs
#ca_cert=/etc/shinken/certs/ca.pem
#server_cert=/etc/shinken/certs/server.cert
#server_key=/etc/shinken/certs/server.key
hard_ssl_name_check=0
http_backend=auto
daemon_thread_pool_size=16

#-- 和该守护程序相关的本地日志配置 --
# Enabled by default to ease troubleshooting
use_local_log=1
local_log=%(logdir)s/schedulerd.log

# accepted log level values= DEBUG,INFO,WARNING,ERROR,CRITICAL
log_level=WARNING

# The path to the modules directory
modules_dir=/var/lib/shinken/modules</pre>
<div class="blog_h3"><span class="graybg">定义守护程序</span></div>
<p>守护程序作为Shinken架构中的<span style="background-color: #c0c0c0;">一种资源</span>，必须在主配置文件中进行定义，才能被使用。 </p>
<p>通常，<a href="#daemon-def-file">守护程序定义文件</a>依据类型的不同，存放在不同目录中，并且每个文件对应一个守护程序。该文件使用类似于对象定义的语法，描述此守护程序的名称、如何连接、是否Standby（Spare）。下面是一个Scheduler定义的例子：</p>
<pre class="crayon-plain-tag">define scheduler {
    # 守护程序的名称
    scheduler_name      scheduler-master
    # 如何连接到此守护程序
    address             localhost 
    port                7768
    # 是否备用
    spare               0
    # 权重，某些Scheduler可以管理更多的主机
    weight              1
    # PING超时
    timeout             3
    # 数据发送超时
    data_timeout        120 
    # 如果PING超时多少次，认为此节点挂掉
    max_check_attempts  3
    # 每60秒PING此节点
    check_interval      60
    # 此守护程序加载的模块
    modules
    # 所属的领域，用于多数据中心等大型场景
    realm   All
    skip_initial_broks  0
    use_ssl	          0
    hard_ssl_name_check   0
}</pre>
<p>注意，某些守护程序有特殊的配置选项，例如：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">选项</td>
<td style="width: 15%; text-align: center;">用于</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><span style="color: #404040;">host_name</span></td>
<td>Arbiter</td>
<td>Arbiter所在的机器的主机名，在高可用性场景下（两个或更多Arbiter）必须配置</td>
</tr>
<tr>
<td><span style="color: #404040;">poller_tags</span></td>
<td>Poller</td>
<td>该Poller管理的Poller tags</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">定义模块</span></div>
<p>所有守护程序都可以使用模块。对于Broker来说模块是必须的，它依赖模块来完成实际的工作。基本配置没有预定义任何模块。</p>
<p>模块具有公共的选项：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>module_name</td>
<td>模块的名称，用于被其它组件（例如守护程序）引用</td>
</tr>
<tr>
<td>module_type</td>
<td>模块的类型，由模块本身提供的固定值</td>
</tr>
</tbody>
</table>
<p>每个模块具有自己的其它特殊配置项，需要查看模块的文档。</p>
<p>这里，我们定义一个模块，为基本配置增加日志记录的功能：</p>
<pre class="crayon-plain-tag">define module{
     module_name      simple-log
     module_type      simple-log
     path             /var/log/shinken/shinken.log
}</pre>
<p>然后修改默认的Broker：</p>
<pre class="crayon-plain-tag">modules            simple-log</pre>
<p>默认情况下simple-log<span style="background-color: #c0c0c0;">模块是没有安装的</span>，因此你会在/var/log/shinken/brokerd.log中发现警告信息：The module type simple-log for simple-log was not found in modules。执行下面的命令来安装模块：</p>
<pre class="crayon-plain-tag"># 到shinken.io搜索包
shinken search log
# 安装模块到/var/lib/shinken/modules/目录下
shinken install simple-log</pre>
<p>重启Shinken，你会发现所有守护程序的日志被收集到 /var/log/shinken/shinken.log文件中了。</p>
<div class="blog_h2"><span class="graybg">监控插件</span></div>
<p>Shinken包含一系列内部模块，这些模块可以被多种守护程序加载，参与到数据获取中去。这些模块包括NPRE、SNMP等。</p>
<p>Shinken同时<span style="background-color: #c0c0c0;">依赖于外部程序——检查插件（Check plugins）</span>来监控更多种类的设备、应用、以及网络服务。</p>
<p>插件可以是编译好的可执行文件或者脚本，它们可以被调用，以便检查主机/服务的状态。Shinken使用插件的调用返回值来确定<span style="background-color: #c0c0c0;">主机、服务的当前状态</span>，以及被监控<span style="background-color: #c0c0c0;">服务的性能数据</span>。</p>
<p>插件作为监控逻辑组件和被监控主机/服务之间的<span style="background-color: #c0c0c0;">抽象层</span>。具有一定的接口规范，你可以编写自己的插件来监控任何东西。</p>
<div class="blog_h3"><span class="graybg">有哪些插件</span></div>
<p>Shinken插件有很多，支持的监控协议包括：WMI, SNMP, SSH, NRPE, TCP, UDP, ICMP, OPC, LDAP 等。</p>
<p>支持的监控对象包括各种OS、服务器和网络硬件、各种网络协议、各种性能数据、应用程序和数据库。</p>
<div class="blog_h3"><span class="graybg">获取插件</span></div>
<p>插件没有和Shinken运行时一同分发，需要单独获取。可以从：</p>
<ol>
<li><a href="https://www.monitoring-plugins.org/">Monitoring Plugins Project</a></li>
<li><a href="http://www.nagios.org/download/">Nagios Downloads Page</a></li>
<li><a href="http://www.nagiosexchange.org/">NagiosExchange.org</a></li>
</ol>
<p>等地方获取插件。很多插件都提供帮助，你应该通过阅读帮助来了解如何使用之：<pre class="crayon-plain-tag">./check_http --help</pre> </p>
<div class="blog_h2"><span class="graybg">理解并使用宏</span></div>
<p>Shinken的灵活性依赖于宏机制，你可以<span style="background-color: #c0c0c0;">在命令中使用宏</span>。通过宏你可以引用主机、服务等信息。</p>
<p>Shinken宏使用<pre class="crayon-plain-tag">$</pre> 作为其起始、结束标记，如果要在命令中使用$字符必须用<pre class="crayon-plain-tag">$$</pre> 代替。</p>
<div class="blog_h3"><span class="graybg">宏替换</span></div>
<p>在执行命令前，Shinken会替换命令定义中所有宏为实际值，这些值由上下文决定。宏替换发生在任何命令上，包括主机/服务检查、通知、事件处理器等。</p>
<div class="blog_h3"><span class="graybg">参数宏</span></div>
<p>你可以使用<pre class="crayon-plain-tag">$ARGn$</pre> 来访问传递给命令的参数：</p>
<pre class="crayon-plain-tag"># 定义命令
define command{
  command_name    check_ping
  # ARG1表示第一个入参
  command_line    /var/lib/shinken/libexec/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$
}</pre>
<p>调用命令时，以<pre class="crayon-plain-tag">!</pre> 开头向命令传递参数：</p>
<pre class="crayon-plain-tag">define service{
  host_name    linux
  # 调用命令。给check_ping命令传递了两个参数，每个参数以叹号开始
  # 200.0,80%
  # 400.0,40%
  check_command    check_ping!200.0,80%!400.0,40%
}</pre>
<p>如果调用命令需要传递叹号（!）本身，可以使用反斜杠转义。 </p>
<div class="blog_h3"><span class="graybg">On-Demand宏</span></div>
<p>通常，当你使用主机/服务宏时，这些宏指向当前命令所针对的主机/服务。如果你想引用其它主机/服务时，可以使用按需宏机制。</p>
<p>按需宏和普通宏类似，只是它后缀一个用于识别主机/服务的标识符：</p>
<pre class="crayon-plain-tag"># 语法格式。把HOSTMACRONAME、SERVICEMACRONAME替换成真实的宏名称，:后面替换成真实的属性值即可
$HOSTMACRONAME:host_name$
$SERVICEMACRONAME:host_name:service_description$

# 举例
$HOSTDOWNTIME:myhost$   #myhosq的宕机时间
$SERVICESTATEID:server:database$  #server的database服务的状态ID
$CONTACTEMAIL:john$   #john的电子邮件</pre>
<div class="blog_h3"><span class="graybg">On-Demand组宏</span></div>
<p>你还可以使用宏来获得一个组中的全部联系人、主机、服务的属性，使用特定分隔符分隔多个值：</p>
<pre class="crayon-plain-tag">#语法格式。HOSTMACRONAME等替换成真实的宏名称，*group_name替换为真实的组名称，delimiter替换为分隔符即可
$HOSTMACRONAME:hostgroup_name:delimiter$
$SERVICEMACRONAME:servicegroup_name:delimiter$
$CONTACTMACRONAME:contactgroup_name:delimiter$</pre>
<div class="blog_h3"><span class="graybg">定制变量宏</span></div>
<p>你在主机、服务、联系人等的定义中声明的定制对象变量（custom object variables） 均可以作为宏使用：</p>
<pre class="crayon-plain-tag">#语法格式。
$_HOSTvarname$
$_SERVICEvarname$
$_CONTACTvarname$

#举例：
define host{
  ...
  # 定制变量必须以_开头
  _MACADDRESS    00:01:02:03:04:05
  ...
}
# 你可以使用 $_HOSTMACADDRESS$ 来访问MAC地址这一定制变量</pre>
<div class="blog_h3"><span class="graybg">作为环境变量</span></div>
<p>大部分宏被导出为环境变量，脚本或者命令可以很容易的引用之。<pre class="crayon-plain-tag">$USERn$</pre> 和On-Demand宏不可通过环境变量访问。 </p>
<div class="blog_h3"><span class="graybg">标准宏列表</span></div>
<p>参考<a href="https://shinken.readthedocs.io/en/latest/05_thebasics/macrolist.html">官方文档</a>。</p>
<div class="blog_h2"><span class="graybg">主机检查</span></div>
<p>以下时机Shinken的守护程序对主机执行检查：</p>
<ol>
<li>根据主机的<span style="color: #404040;">check_interval、retry_interval选项</span>周期性、定时检查。如果check_interval设置为0则不进行周期性检查</li>
<li>当主机下某个服务的状态改变，按需检查</li>
<li>作为主机可到达性逻辑的一部分，按需检查。这种情况下，主机是路由器等网络设备</li>
<li>在进行预测性主机依赖性检查（predictive host dependency checks）时，按需进行</li>
</ol>
<p>主机检查是<span style="background-color: #c0c0c0;">并行</span>执行的。</p>
<p><span style="background-color: #c0c0c0;">按需检查的性能</span>可以通过<span style="background-color: #c0c0c0;">已缓存的主机检查</span>（Cached Host Checks）极大的提高。Shinken可以放弃检查执行，而使用最近的检查结果。</p>
<p>通过定义主机执行依赖（host execution dependencies），你可以<span style="background-color: #c0c0c0;">基于其它一个或多个主机的状态，禁止某一主机的检查</span>。</p>
<div class="blog_h3"><span class="graybg">主机状态</span></div>
<p>被检查的主机可以处于三种状态之一：UP、DOWN、UNREACHABLE。</p>
<p>主机检查由插件进行，插件的返回状态和主机状态映射如下表。注意，依据插件结果初步判定为DOWN时，需要依赖于父主机状态，来确定主机的真实（最终）状态：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">插件结果</td>
<td style="width: 20%; text-align: center;">主机状态（初步）</td>
<td style="text-align: center;">父主机状态</td>
<td style="text-align: center;">主机状态（最终）</td>
</tr>
</thead>
<tbody>
<tr>
<td>OK</td>
<td>UP</td>
<td> </td>
<td>UP</td>
</tr>
<tr>
<td>WARNING</td>
<td>DOWN*</td>
<td colspan="2" rowspan="3">
<p>至少一个父主机UP，则最终状态为DOWN</p>
<p>所有父主机都DOWN/UNREACHABLE，则最终状态为UNREACHABLE</p>
</td>
</tr>
<tr>
<td>UNKNOWN</td>
<td>DOWN</td>
</tr>
<tr>
<td>CRITICAL</td>
<td>DOWN</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">状态变更</span></div>
<p>Shinken可能发现主机状态在<span style="color: #404040;">UP/DOWN/UNREACHABLE之间变化，并采取一定的操作。</span></p>
<p>状态变革会导致不同的状态类型（State types）：<span style="color: #404040;">HARD/SOFT，可能相应的触发事件处理器或者通知。</span></p>
<p>当主机状态频繁变化时，称为动荡（<span style="color: #404040;">flapping</span>），动荡的一个例子是，主机由于某种原因反复的重启（例如系统更新导致）。Shinken可以在检测到动荡后暂停通知发送，直到主机状态稳定下来。 </p>
<div class="blog_h2"><span class="graybg">服务检查</span></div>
<p>以下时机Shinken的守护程序对服务执行检查：</p>
<ol>
<li>根据服务的<span style="color: #404040;">check_interval、retry_interval选项</span>周期性、定时检查</li>
<li>在进行预测性服务依赖性检查（predictive service dependency checks）时，按需进行</li>
</ol>
<p>与主机检查一样：</p>
<ol>
<li>缓存可以很大的提高On-demand服务检查的性能</li>
<li>检查是并行执行的</li>
<li>通过定义服务执行依赖（service execution dependencies），你可以基于其它一个或多个服务的状态，禁止某一服务的检查</li>
</ol>
<div class="blog_h3"><span class="graybg">服务状态</span></div>
<p>被检查的服务可以处于几种状态之一：OK、WARNING、UNKNOWN、CRITICAL。</p>
<p>服务检查由插件执行，插件的返回状态直接对应到上面几个服务状态。</p>
<div class="blog_h3"><span class="graybg">状态变更</span></div>
<p>服务的状态变更、动荡的处理和主机类似。</p>
<div class="blog_h2"><span class="graybg">主动检查</span></div>
<p>主动检查是Shinken<span style="background-color: #c0c0c0;">主要使用的检查方式</span>。主动检查由Shinken发起，依据计划周期性的执行。具体步骤如下：</p>
<ol>
<li>周期性、On-demand触发检查</li>
<li>守护程序调用插件，并传递必要信息</li>
<li>插件执行实际的坚持并报告结果给守护程序</li>
<li>守护程序处理结果，执行适当的操作，例如发送通知、执行事件处理器</li>
</ol>
<div class="blog_h2"><span class="graybg">主动数据获取模块</span></div>
<p>集成的数据获取模块，是可以被守护程序启动的组件。这些组件可以高性能的获取数据，效率比周期性的调用插件脚本高。</p>
<p>SNMP数据获取模块：SnmpBooster</p>
<p>NRPE数据获取模块：NRPE</p>
<p>NRPE是一种通信协议，它和安装在远程主机上的代理（Agent）进行交互。</p>
<div class="blog_h2"><span class="graybg">被动检查</span></div>
<p>Shinken也支持被动的得到对象的状态。被动检查由外部程序发起，并将检查结果提交给Shinken。</p>
<p>对于天生异步的、不能通过轮询（Polling）很好的检查的服务，被动检查很适用。适合被动检查的例子包括：</p>
<ol>
<li>SNMP陷阱、安全报警。你永远不知道一定时间内有多少陷阱、报警</li>
<li>安装了代理（Agent）的主机上的聚合检查，这种检查的执行间隔会相当低</li>
<li>直接从英语程序中提交检查结果，不依赖于中介日志文件（syslog、event log等）</li>
</ol>
<div class="blog_h3"><span class="graybg">启用主动检查</span></div>
<p>在主配置文件中设置accept_passive_service_checks为1。对于不需要主动检查的主机、服务，设置passive_checks_enabled为0。</p>
<div class="blog_h3"><span class="graybg">提交服务检查结果</span></div>
<p>外部程序可以向Shinken的外部命令管道（external command pipe）写入一条PROCESS_SERVICE_CHECK_RESULT外部命令，以提交检查结果。该命令的格式为：</p>
<pre class="crayon-plain-tag"># <span style="color: #404040;">timestamp，格式为time_t即1970到现在的秒数。执行服务检查的时间点</span>
# <span style="color: #404040;">host_name，服务所属主机的短名</span>
# <span style="color: #404040;">svc_description，服务定义中的服务描述</span>
# <span style="color: #404040;">return_code，检查结果，0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN</span>
# <span style="color: #404040;">plugin_output，插件的文本输出</span>
[timestamp] PROCESS_SERVICE_CHECK_RESULT;configobjects/host_name;svc_description;return_code;plugin_output</pre>
<p>被检查的服务必须在Shinken中预先定义，否则提交的检查结果自动丢弃。</p>
<div class="blog_h3"><span class="graybg">提交主机检查结果</span></div>
<p>外部程序可以向Shinken的外部命令管道（external command pipe）写入一条<span style="color: #404040;">PROCESS_HOST_CHECK_RESULT</span>外部命令，以提交检查结果。该命令的格式为：</p>
<pre class="crayon-plain-tag"># host_status，主机状态 0=UP, 1=DOWN, 2=UNREACHABLE
[timestamp] PROCESS_HOST_CHECK_RESULT;configobjects/host_name;configobjects/host_status;plugin_output</pre>
<p>与主动检查不同，Shinken不会把结果作为初步结果，进而根据父主机状态判断最终结果。你提交的必须就是主机的实际状态。 </p>
<div class="blog_h2"><span class="graybg">状态类型</span></div>
<p>被监控主机/服务的当前状态（Current state）由两个字段决定：</p>
<ol>
<li>主机或服务的状态（status，先前所有提及的状态，都是这个单词），即OK, WARNING, UP, DOWN...</li>
<li>主机或服务所处的状态类型（State type）</li>
</ol>
<p>状态类型有两种：软状态（SOFT）、硬状态（HARD）。状态类型<span style="background-color: #c0c0c0;">非常重要</span>，它们用于决定何时执行事件处理器，何时发送最初的通知。</p>
<div class="blog_h3"><span class="graybg">重新检查</span></div>
<p>为了防止因为临时故障而错误的报警，Shinken允许配置主机/服务的max_check_attempts。最有在最大尝试次数到达后，才会认为是真正出现问题。</p>
<p>重新检查与状态类型密切相关</p>
<div class="blog_h3"><span class="graybg">软状态</span></div>
<p>以下情况下，服务/主机处于软状态：</p>
<ol>
<li>当服务/主机从OK/UP变为non-OK/non-UP状态后，到达max_check_attempts之前。这种情况称为软错误</li>
<li>当服务/主机从软错误中恢复时。这种情况称为软恢复</li>
</ol>
<p>当对象进入软状态时：</p>
<ol>
<li>日志记录SOFT状态，仅当你在主配置文件启用log_service_retries或log_host_retries时</li>
<li>调用事件处理器来处理SOFT状态。这是软状态下重要的行为，你可以<span style="background-color: #c0c0c0;">在到达HARD状态前积极的修复</span>问题。事件处理器执行时， $HOSTSTATETYPE$、 $SERVICESTATETYPE$宏的值为SOFT提示当前处于软状态</li>
</ol>
<div class="blog_h3"><span class="graybg">硬状态</span></div>
<p>以下情况下，服务/主机处于硬状态：</p>
<ol>
<li>服务/主机从OK/UP变为non-OK/non-UP状态后，到达max_check_attempts之后仍然没有恢复。这种情况称为硬错误</li>
<li>当服务/主机从硬错误转换到其它错误状态时，例如WARNING、CRITICAL</li>
<li>当服务的检查结果是一个non-OK状态且它所属主机为DOWN或UNREACHABLE状态</li>
<li>当服务/主机从硬错误状态恢复。这种情况称为硬恢复</li>
<li>当被动检查结果送达，默认总是认为是硬状态。除非启用passive_host_checks_are_soft选项</li>
</ol>
<p>当对象进入硬状态时：</p>
<ol>
<li>日志记录HARD状态</li>
<li>调用事件处理器来处理HARD状态</li>
<li>通知联系人，告知问题或者恢复。事件处理器执行时， $HOSTSTATETYPE$、 $SERVICESTATETYPE$宏的值为HARD提示当前处于硬状态</li>
</ol>
<div class="blog_h2"><span class="graybg">网络依赖</span></div>
<p>我们常说“外网挂了”，外网真的会挂吗？几乎不可能，问题肯定出现在你的机器到Internet的链路上。链路上的任何一个路由器/交换机出现故障，你都会无法上网。</p>
<p>在Shinken的监控业务中也是这样，守护程序所在机器到目标主机之间的链路上任何一个节点出现故障，目标主机都会处于non-UP状态。Shinken有能力区分这个non-Up是DOWN还是UNREACHABLE，前提是你正确的配置网络依赖。</p>
<p>你必须站在Shinken守护程序的角度来看问题，守护程序需要经过哪些主机（网络设备）才能到到目标主机？你需要把这些中介的设备配置为目标主机的祖先主机，例如：</p>
<pre class="crayon-plain-tag"># 父子关系的配置必须真实的反映网络拓扑
define host{
  host_name    Switch1
  parents    Shinken  #声明自己的父主机，父主机总是离Shinken守护程序更近（跳数少）
}
define host{
  host_name    Web
  parents    Switch1
}
define host{
  host_name    FTP
  parents    Switch1
}
define host{
  host_name    Router1
  parents    Switch1
}
define host{
  host_name    somewebsite.com
  parents    Router1
}</pre>
<p>类似上面的配置，形成从守护程序到目标主机的单向图，Shinken依据此图判断目标主机是不可达，还是宕机。 </p>
<div class="blog_h3"><span class="graybg">多个父主机</span></div>
<p>某些情况下你可能有多重链路可以到达目标主机，此时应该为目标主机（或者链路上的中介节点）配置多个父主机。</p>
<p>只有全部父主机不可达、宕机，子主机才会被判定为不可达。</p>
<div class="blog_h3"><span class="graybg">不可达与通知</span></div>
<p>Shinken只会针对问题的根源（Root problems）发送通知，这避免了发送大量的通知给联系人。只有主机状态是DOWN时通知才会发送，主机是UNREACHABLE时不会发送，但是它的某个祖先主机的DOWN通知可能被发送。</p>
<p>同样的，DOWN/UNREACHABLE主机上服务的通知也不会发送，它们不是问题的根源所在。</p>
<div class="blog_h2"><span class="graybg">逻辑依赖</span></div>
<p>服务和主机（逻辑）依赖是Shinken的高级特性。允许你基于其它主机/服务的状态，来控制目标主机/服务的行为。</p>
<p>以服务依赖为例，一个Web服务常常会依赖于一个数据库服务。如果数据库服务宕机了，你向联系人通知说Web服务挂了是没有意义的。你应该正确配置服务之间的依赖关系，以便Shinken能够报告问题的根源。</p>
<p>关于服务依赖，你需要知道：</p>
<ol>
<li>一个服务可以依赖于1-N个其它服务</li>
<li>一个服务可以依赖于其它主机上的服务</li>
<li>通过服务依赖配置，你可以在服务的某些状态下，抑制检查的执行、通知的发送</li>
<li>依赖关系可以仅在特定时间段内启用</li>
</ol>
<div class="blog_h3"><span class="graybg">服务依赖示例</span></div>
<pre class="crayon-plain-tag"># 位于srv-web上的service服务
define service{
  host_name              srv-web
  service_description    Http
  # 依赖位于srv-db上的mysql服务
  service_dependencies   srv-db,mysql
}</pre>
<div class="blog_h3"><span class="graybg">依赖层次</span></div>
<p>依赖是可以继承（inherited，应该理解为传递性依赖）。以上面的例子讲，假设mysql服务依赖srv-dns,dns，那么http出现问题时会检查mysql，进一步会检查srv-dns以确认问题根源。</p>
<div class="blog_h3"><span class="graybg">结合网络依赖</span></div>
<p>网络依赖同样会影响问题根源的判断，如果http出现问题的同时发现srv-db宕机，那么后者会被看作问题的根源，前者只作为后者的impact。</p>
<div class="blog_h1"><span class="graybg">高级主题</span></div>
<div class="blog_h2"><span class="graybg">性能数据</span></div>
<p>Shinken允许在返回状态数据的同时，附带记录<span style="background-color: #c0c0c0;">可选的性能数据</span>。你可以把性能数据返回给外部程序进行处理。</p>
<div class="blog_h3"><span class="graybg">性能数据的类型</span></div>
<p>性能数据分为两个类别：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 15%;"><strong>检查性能数据</strong></td>
<td>
<p>与主机、服务检查的执行过程本身相关的性能数据，例如检查的：</p>
<ol>
<li>延迟时间，即实际发起检查与计划检查之间的延迟，相关宏 $HOSTLATENCY$、$SERVICELATENCY$ </li>
<li>消耗时间，即检查执行开始到结束的时间，相关宏$HOSTEXECUTIONTIME$、 $SERVICEEXECUTIONTIME$</li>
</ol>
</td>
</tr>
<tr>
<td><strong>插件性能数据</strong></td>
<td>
<p>与特定插件相关，可能包括丢包率、磁盘剩余空间、处理器负载、当前登陆用户数量……等等插件在执行时获取的任何度量数据</p>
<p>插件性能数据相关的宏有$HOSTPERFDATA$ 、$SERVICEPERFDATA$，不是所有插件都支持性能数据</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">插件性能数据</span></div>
<p>Shinken插件至少会返回一行<span style="background-color: #c0c0c0;">人类可读</span>的文本，用来描述某种类型的<span style="background-color: #c0c0c0;">可度量数据</span>，例如check_ping插件返回的文本可以如下：</p>
<pre class="crayon-plain-tag">PING ok - Packet loss = 0%, RTA = 0.80 ms</pre>
<p>类似这样的输出，可以通过<pre class="crayon-plain-tag">$HOSTOUTPUT$</pre> 或者<pre class="crayon-plain-tag">$SERVICEOUTPUT$</pre> 宏来获取。 </p>
<p>可选的，插件还可以返回性能数据，以管道符号<pre class="crayon-plain-tag">|</pre> 和上述可读数据分隔：</p>
<pre class="crayon-plain-tag">PING ok - Packet loss = 0%, RTA = 0.80 ms | percent_packet_loss=0, rta=0.80</pre>
<p>输出中的性能数据部分，可以通过 <pre class="crayon-plain-tag">$HOSTPERFDATA$ </pre> 或者<pre class="crayon-plain-tag">$SERVICEPERFDATA$</pre> 宏来获取。</p>
<p>Shinken守护程序不直接处理性能数据，因此它也不关心其格式。</p>
<div class="blog_h3"><span class="graybg">处理性能数据</span></div>
<p>如果你需要处理性能数据，则需要启用process_performance_data选项，并配置Shinken，让性能数据写入到文件，或者执行命令。</p>
<p>选项host_perfdata_command、service_perfdata_command用于指定<span style="background-color: #c0c0c0;">处理性能数据的命令</span>。下面是命令的例子：</p>
<pre class="crayon-plain-tag">define command{
  command_name    store-service-perfdata
  command_line    /bin/echo -e "$LASTSERVICECHECK$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICESTATE$\t$SERVICEATTEMPT$\t
                                $SERVICESTATETYPE$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t
                                $SERVICEPERFDATA$" &gt;&gt; /var/lib/shinken/service-perfdata.dat
}</pre>
<p>调用命令的方式可能会导致较高的CPU负载，特别是需要处理大量主机/服务时，最好先写入文件，然后由外部程序处理。使用host_perfdata_file、service_perfdata_file选项你可以指定<span style="background-color: #c0c0c0;">写入到什么文件</span>；而host_perfdata_file_template、service_perfdata_file_template 选项用于指定<span style="background-color: #c0c0c0;">输出的模板</span>。</p>
<div class="blog_h1"><span class="graybg">配置详解</span></div>
<p>在让Shinken真正能工作之前，你需要配置若干个文件。在目录<pre class="crayon-plain-tag">/etc/shinken/</pre> 下有很多样例配置文件可供参考。</p>
<p>编辑任何配置文件时，都要记住：</p>
<ol>
<li>#开头的行是注释，不进行处理</li>
<li>变量名大小写敏感</li>
</ol>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>从2.0开始Shinken引入新的配置文件布局，基本的配置文件现在被分割到多个小的文件中。新的布局更好管理，例如一个文件对应一个对象的配置。</p>
<p>配置文件可以分为以下几类：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">配置文件类别</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>主配置文件</td>
<td>即<pre class="crayon-plain-tag">shinken.cfg</pre> 文件，它是整个配置的入口点，它会作为命令行参数传递给Arbiter</td>
</tr>
<tr>
<td><a id="daemon-def-file"></a>守护程序定义文件</td>
<td>定义守护程序，依据类型的不同存放在不同的目录中，例如poller目录存放的是Poller守护程序<br />目录中每个文件对应一个守护程序实例</td>
</tr>
<tr>
<td>模块定义文件</td>
<td>位于modules目录中，每个模块具有自己的配置文件<br />由于模块由守护程序加载，因而守护程序定义文件会通过<pre class="crayon-plain-tag">module</pre> 指令引用这些模块定义文件</td>
</tr>
<tr>
<td>资源文件</td>
<td>存储用户定义的宏，主要用于存放敏感信息，例如密码<br />在主配置文件中需要使用<pre class="crayon-plain-tag">resource_file</pre> 指令引用资源文件</td>
</tr>
<tr>
<td>对象定义文件</td>
<td>定义各种对象，例如主机、服务、主机组、联系人、联系人组，等等<br />你可以在主配置文件中多次使用<pre class="crayon-plain-tag">cfg_file</pre> 或<pre class="crayon-plain-tag">cfg_dir</pre> 指令，来引用对象定义</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">主配置文件配置项</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>cfg_dir</td>
<td rowspan="2">这两个属于声明（statements）而非参数（parameters）<br />Arbiter会这些配置文件，或者目录中的<span style="background-color: #c0c0c0;">.cfg</span>文件。对于目录，Arbiter会<span style="background-color: #c0c0c0;">递归</span>的读取，这些被读取的文件中如果包含这两条声明，会被忽略</td>
</tr>
<tr>
<td>cfg_file</td>
</tr>
<tr>
<td>retention_update_interval</td>
<td>自动的状态保持（State Retention）更新间隔，单位分钟，默认60<br />该参数指示Scheduler自动保存Retention数据的间隔，如果设置为0则不会定期保存，但是Shinken重启或关闭时仍然会保存</td>
</tr>
<tr>
<td>max_service_check_spread</td>
<td rowspan="2">Shinken启动后，最长多少分钟内服务、主机被检查，默认30<br />该参数用于确保主机、服务状态在一定时间内被检查</td>
</tr>
<tr>
<td>max_host_check_spread</td>
</tr>
<tr>
<td>service_check_timeout</td>
<td rowspan="2">服务检查最大消耗时间（秒），如果超时Shinken会杀死Check并返回一个CRITICAL状态并记录超时错误日志。该配置作为终止不正常运作的插件的最后手段。默认值：<br />
<pre class="crayon-plain-tag">service_check_timeout=60
host_check_timeout=30 </pre>
</td>
</tr>
<tr>
<td>host_check_timeout</td>
</tr>
<tr>
<td>timeout_exit_status</td>
<td>超时的退出状态，可选值0、1、2、3，默认2<br />当超时时由Shinken自动设置</td>
</tr>
<tr>
<td>flap_history</td>
<td>对象状态的历史数值的保持数量，这些历史值用于判断对象处于动荡——状态频繁变化——的状态。默认20</td>
</tr>
<tr>
<td>max_plugins_output_length</td>
<td>负责检查的插件（Checks plugin）输出的最大字节数，默认8192</td>
</tr>
<tr>
<td>enable_problem_impacts_states_change</td>
<td>
<p>布尔值0/1默认0，当一个主机/服务受一个根源性问题（例如服务所属的主机、主机的parent宕机）影响时，是否改变其状态</p>
<p>如果启用，服务的状态会变成UNKNOWN而主机的状态会变成<span style="color: #404040;">UNREACHABLE，直到下一轮检查</span></p>
</td>
</tr>
<tr>
<td>disable_old_nagios_parameters_whining </td>
<td>布尔值0/1默认0，如果启用，在检查配置时所有通知、警告消息禁用</td>
</tr>
<tr>
<td>use_timezone</td>
<td>覆盖时区设置</td>
</tr>
<tr>
<td>enable_environment_macros</td>
<td>布尔值0/1默认1，是否将所有标准宏作为环境变量，暴露给检查、通知、事件处理器</td>
</tr>
<tr>
<td>log_initial_states </td>
<td>布尔值0/1默认1，是否强制记录所有主机/服务的初始状态，即使它们是OK</td>
</tr>
<tr>
<td>no_event_handlers_during_downtimes</td>
<td>布尔值0/1默认0，Shinken是否在主机/服务处于计划内宕机时间内，仍然运行事件处理器</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>Arbiter默认配置<br /></strong><em>适用于所有Arbiter</em></td>
</tr>
<tr>
<td><em>workdir</em></td>
<td>守护程序的工作目录，默认/var/run/shinken/。对于Arbiter默认值为lock_file所在目录</td>
</tr>
<tr>
<td>lock_file</td>
<td>Arbiter作为后台程序（-d）运行时存放PID的文件</td>
</tr>
<tr>
<td>local_log</td>
<td>守护程序日志文件位置，默认/var/log/shinken/arbiterd.log</td>
</tr>
<tr>
<td>log_level</td>
<td>日志级别，可选：DEBUG,INFO,WARNING,ERROR,CRITICAL，默认WARNING</td>
</tr>
<tr>
<td>shinken_user</td>
<td>Arbiter进程（主进程）的有效（Effective）用户</td>
</tr>
<tr>
<td>shinken_group</td>
<td>Arbiter进程的有效组</td>
</tr>
<tr>
<td>modules_dir</td>
<td>模块存放的目录，默认/var/lib/shinken/modules</td>
</tr>
<tr>
<td>daemon_enabled</td>
<td>布尔值0/1默认1，设置为0则Arbiter不会运行</td>
</tr>
<tr>
<td>use_ssl</td>
<td>布尔值0/1默认0，是否使用SSL，如果启用，则其它守护程序也必须启用</td>
</tr>
<tr>
<td>ca_cert</td>
<td>CA证书</td>
</tr>
<tr>
<td>server_cert</td>
<td>服务器证书</td>
</tr>
<tr>
<td>server_key</td>
<td>服务器密钥</td>
</tr>
<tr>
<td>hard_ssl_name_check</td>
<td>布尔值0/1默认0，启用SSL名称检查</td>
</tr>
<tr>
<td>http_backend</td>
<td>使用的HTTP后端组件，可选：auto, cherrypy, swsgiref</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>主配置文件进阶配置项</strong></td>
</tr>
<tr>
<td>perfdata_timeout</td>
<td>主机/服务性能数据处理器命令（performance data processor command ）的处理超时<br />默认5秒，如果超时，处理器被终结并在日志中记录警告</td>
</tr>
<tr>
<td>process_performance_data</td>
<td>是否处理主机/服务检查的性能数据<br />如果你希望使用PNP、NagiosGrapher、Graphite等工具，需要设置为1</td>
</tr>
<tr>
<td>host_perfdata_command</td>
<td rowspan="2">主机/服务的性能数据处理命令<br />每次主机/服务检查后，都会调用这些命令处理性能数据</td>
</tr>
<tr>
<td>service_perfdata_command</td>
</tr>
<tr>
<td>host_perfdata_file</td>
<td rowspan="2">允许你指定一个存储性能数据的文件，供外部程序后续处理</td>
</tr>
<tr>
<td>service_perfdata_file</td>
</tr>
<tr>
<td>host_perfdata_file_template</td>
<td rowspan="2">性能数据文件的格式模板</td>
</tr>
<tr>
<td>service_perfdata_file_template</td>
</tr>
<tr>
<td>host_perfdata_file_mode</td>
<td rowspan="2">性能数据存储文件的操作模式：<br />a 附加模式（默认）；w写模式；p非阻塞的读写模式，写入管道时使用</td>
</tr>
<tr>
<td>service_perfdata_file_mode</td>
</tr>
<tr>
<td>cached_host_check_horizon</td>
<td rowspan="2">多久（秒）以内的历史检查结果被认为是“当前的”<br />默认15秒，过大的值会导致不精确的结果，但是大的值会提高性能</td>
</tr>
<tr>
<td>cached_service_check_horizon</td>
</tr>
<tr>
<td>use_large_installation_tweaks</td>
<td>设置为1，可以在大规模环境下提高性能，但是会丢失一些特性</td>
</tr>
<tr>
<td>enable_flap_detection</td>
<td>是否启用状态动荡检测，默认0</td>
</tr>
<tr>
<td>low_service_flap_threshold</td>
<td rowspan="4">单位百分比，下限默认25，上限默认50<br />用于设定检测状态动荡的上下限阈值</td>
</tr>
<tr>
<td>low_host_flap_threshold</td>
</tr>
<tr>
<td>high_service_flap_threshold</td>
</tr>
<tr>
<td>high_host_flap_threshold</td>
</tr>
<tr>
<td>event_handler_timeout</td>
<td>事件处理器的执行超时时间，默认30秒</td>
</tr>
<tr>
<td>notification_timeout</td>
<td>通知的发送超时时间，默认30秒</td>
</tr>
<tr>
<td>ocsp_timeout</td>
<td>ocsp执行的超时时间，默认15秒</td>
</tr>
<tr>
<td>ochp_timeout</td>
<td>ochp执行的超时时间，默认15秒</td>
</tr>
<tr>
<td>check_service_freshness</td>
<td rowspan="2">是否允许Shinken检测<span style="background-color: #c0c0c0;">被动检查</span>的“新鲜度”，用于确认是否被动检查被定期的发送给Shinken</td>
</tr>
<tr>
<td>check_host_freshness</td>
</tr>
<tr>
<td>service_freshness_check_interval</td>
<td rowspan="2">新鲜度检查的时间间隔，默认60秒</td>
</tr>
<tr>
<td>host_freshness_check_interval</td>
</tr>
<tr>
<td>human_timestamp_log</td>
<td>日志中的时间戳，是否保存为人类可读的方式<br />默认0，保存为unixtime格式</td>
</tr>
<tr>
<td>date_format</td>
<td>日期格式，可选值：iso8601、strict-iso8601、us、euro</td>
</tr>
<tr>
<td>resource_file</td>
<td>指定资源文件的位置</td>
</tr>
<tr>
<td>triggers_dir</td>
<td>指定触发器的位置，会递归的寻找*.trig文件</td>
</tr>
<tr>
<td>idontcareaboutsecurity</td>
<td>是否允许以root身份运行守护程序，默认否</td>
</tr>
<tr>
<td>enable_notifications</td>
<td>是否启用通知机制，默认1</td>
</tr>
<tr>
<td>check_external_commands</td>
<td>指示Shinken是否检查外部命令文件（ External Command File ），以发现需要被Arbiter执行的命令</td>
</tr>
<tr>
<td>command_file</td>
<td>外部命令文件（命名管道）的路径</td>
</tr>
<tr>
<td>execute_service_checks</td>
<td rowspan="2">是否执行主机/服务状态主动检查，默认1</td>
</tr>
<tr>
<td>execute_host_checks</td>
</tr>
<tr>
<td>accept_passive_service_checks</td>
<td rowspan="2">是否接收主机/服务状态被动检查结果，默认1</td>
</tr>
<tr>
<td>accept_passive_host_checks</td>
</tr>
<tr>
<td>enable_event_handlers</td>
<td>是否启用事件处理器</td>
</tr>
<tr>
<td>use_syslog</td>
<td>是否记录系统日志，仅Linux</td>
</tr>
<tr>
<td>log_notifications</td>
<td>是否记录通知发送情况，默认1</td>
</tr>
<tr>
<td>log_event_handlers</td>
<td>是否记录事件处理器执行情况，默认1</td>
</tr>
<tr>
<td>log_external_commands</td>
<td>是否记录外部命令执行情况，默认1</td>
</tr>
<tr>
<td>interval_length</td>
<td>
<p>单位时间间隔（interval）的长度，默认60秒<br />很多其它配置项间接受到该配置项的影响</p>
<p>注意，Shinken<span style="background-color: #c0c0c0;">并没有被设计为硬实时监控</span>系统，因此把这个参数设置为5或者更低的值不是好主意</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">对象继承</span></div>
<p>在进行对象定义的时候，你可以使用对象继承机制，降低配置文件字段的冗余。</p>
<div class="blog_h3"><span class="graybg">基础知识</span></div>
<p>所有对象都可以使用以下三个和继承、递归相关的变量：</p>
<pre class="crayon-plain-tag">define someobjecttype{
       object-specific variables ...
       # 对象的名称，可以被其它对象引用，对于同一类型的对象，其名称必须唯一
       name            template_name
       # 你想从中继承属性/变量的“模板对象”
       use             name_of_template_to_use
       # 是否向Shinken注册此定义，如果该定义纯粹是作为模板使用的不完全定义，应该设置为0
       # 默认1，表示注册，该字段不会被继承
       register        [0/1]
}</pre>
<div class="blog_h3"><span class="graybg">本地变量vs继承的变量</span></div>
<p>有一点很重要，对象在<span style="background-color: #c0c0c0;">本地自己定义的变量</span>，总是比继承自模板的<span style="background-color: #c0c0c0;">优先级高</span>。也就是说，对象定义可以覆盖继承得到的值。</p>
<div class="blog_h3"><span class="graybg">继承层次</span></div>
<p>Shinken不限制对象继承的深度，上述的覆盖行为从祖代向子代逐步进行。</p>
<div class="blog_h3"><span class="graybg">定制变量的继承</span></div>
<p>你在主机/服务/联系人中声明的自定义变量（_开头的），可以和普通变量一样被继承。</p>
<div class="blog_h3"><span class="graybg">阻止字符串继承</span></div>
<p>对于字符串类型的变量，你可以将其设置为null，防止从模板继承值：<pre class="crayon-plain-tag">event_handler null</pre> </p>
<div class="blog_h3"><span class="graybg">追加而不覆盖</span></div>
<p>默认的，你在对象里声明一个变量，会覆盖模板中的同名变量。</p>
<p>对于标准变量（非自定义）的<span style="background-color: #c0c0c0;">字符串变量</span>，Shinken支持所谓加性继承（additive inheritance），即把对象声明的值附加到继承值的后面。</p>
<p>要启用加性继承，只需要在声明值的时候<span style="background-color: #c0c0c0;">前缀+号</span>：</p>
<pre class="crayon-plain-tag"># 模板
hostgroups              all-servers

# use模板的对象
hostgroups             +linux-servers,web-servers
# 实际相当于
hostgroups             all-servers,linux-servers,web-servers</pre>
<div class="blog_h3"><span class="graybg">隐含继承</span></div>
<p>通常情况下，你要么继承，要么声明一个变量值，但是由几个例外情况。在这些情况下，Shinken会从相关对象继承值：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">对象类型</td>
<td style="text-align: center;">变量</td>
<td style="text-align: center;">隐含继承源</td>
</tr>
</thead>
<tbody>
<tr>
<td>Services</td>
<td>contact_groups</td>
<td>关联的host定义</td>
</tr>
<tr>
<td>Host Escalations</td>
<td>contact_groups</td>
<td>关联的host定义</td>
</tr>
<tr>
<td>Service Escalations</td>
<td>contact_groups</td>
<td>关联的service定义</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">多重继承</span></div>
<p> 一个对象可以同时继承多个模板，例如：</p>
<pre class="crayon-plain-tag">define host{
       name                    generic-host
       active_checks_enabled   1
       check_interval          10
       register                0
}
define host{
       name                    development-server
       check_interval          15
       notification_options    d,u,r
       register                0
}
define host{
       use                    generic-host,development-server
       host_name              devweb
}</pre>
<p>对象devweb的实际定义相当于：</p>
<pre class="crayon-plain-tag">define host{
       host_name               devweb1
       active_checks_enabled   1
       check_interval          10
       notification_options    d,u,r
}</pre>
<p>可以看到，声明在<span style="background-color: #c0c0c0;">use列表前面的模板，具有更高的优先级</span>。 </p>
<div class="blog_h3"><span class="graybg">继承覆盖（Inheritance overriding）</span></div>
<p>上面提到过，通过隐含继承，可以让Service自动从Host继承特定变量。对于不支持隐含继承的变量，你可以在主机中使用指令：</p>
<pre class="crayon-plain-tag"># 对于属于该主机的xxx服务，设置其yyy为zzz
service_overrides xxx,yyy zzz
# 支持多行值
service_overrides xxx,yyy zzz
                  aaa,bbb ccc

# 举例
define host {
       host_name               web-back-01
       hostgroups              web
       service_overrides       HTTP,notification_options c,r
}</pre>
<p>这样可以避免从Pack中继承不匹配实际需求的值。</p>
<p>注意：service_overrides属性可以从模板继承。</p>
<div class="blog_h3"><span class="graybg">继承排除（Inheritance exclusions）</span></div>
<p>你可以使用service_excludes指令，从Pack中排除继承得来的服务定义：</p>
<pre class="crayon-plain-tag">define host {
       use                     web-front
       host_name               web-back-01
       service_excludes        mgr
}</pre>
<div class="blog_h2"><span class="graybg">对象定义文件配置项</span></div>
<p>通过多次使用cfg_file、cfg_dir指令，你可以编写多个对象定义文件。安装好Shinken后，/etc/shinken/目录中有很多样例对象定义文件，这些文件存放在各自的目录中。</p>
<div class="blog_h3"><span class="graybg">对象分类</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">对象类别</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>主机（Hosts）</td>
<td>
<p>主机是监控逻辑中的中心对象之一。主机包括以下重要特征：</p>
<ol>
<li>主机常常是网络中的一个物理设备，例如服务器、工作站、路由器、交换机、打印机等</li>
<li>主机拥有某种形式的地址，例如IP、MAC</li>
<li>主机拥有1-N个与之关联的服务</li>
<li>主机可以和其它主机具有父子关系，这呈现了真实世界的网络结构，用于网络可到达性检查</li>
</ol>
</td>
</tr>
<tr>
<td>主机组（Host Groups）</td>
<td>
<p>将一系列主机分组，可以：</p>
<ol>
<li>在WebUI中查看一组相关主机的状态</li>
<li>简化配置</li>
</ol>
</td>
</tr>
<tr>
<td>服务（Services）</td>
<td>
<p>服务是监控逻辑中的中心对象之一。它附属于主机，可以是：</p>
<ol>
<li>主机的某个属性值：CPU负载、磁盘用量、启动时间等</li>
<li>主机提供的某种服务：HTTP、POP3、FTP、SSH等</li>
<li>其它与主机关联的东西，例如DNS记录</li>
</ol>
</td>
</tr>
<tr>
<td>服务组（Service Groups）</td>
<td>
<p>将一系列服务分组，可以：</p>
<ol>
<li>在WebUI中查看一组相关服务的状态</li>
<li>简化配置</li>
</ol>
</td>
</tr>
<tr>
<td>联系人（Contacts）</td>
<td>
<p>联系人是指在通知处理中牵涉进来的人员的信息：</p>
<ol>
<li>联系人可以包含1-N种通知方法，例如电话、邮件、即时消息</li>
<li>联系人接收他们负责的主机、服务的通知信息</li>
</ol>
</td>
</tr>
<tr>
<td>联系人组（Contact Groups）</td>
<td>联系人的分组，用于简化通知的配置，例如当特定主机、服务发生故障时通知若干个联系人</td>
</tr>
<tr>
<td>命令（Commands）</td>
<td>
<p>命令用于告知Shinken需要执行什么程序、脚本以便：</p>
<ol>
<li>执行主机、服务检查</li>
<li>发送通知</li>
<li>执行事件处理器</li>
</ol>
<p>等等</p>
</td>
</tr>
<tr>
<td>时间段（Time Periods）</td>
<td>
<p>时间段用于控制：</p>
<ol>
<li>何时主机、服务可以被监控</li>
<li>何时联系人可以被通知</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">主机定义</span></div>
<p>定义格式： </p>
<pre class="crayon-plain-tag"># 语法
define host {
    optionname   optionvalue
}

# 示例
define host{
    host_name                      bogus-router
    alias                          Bogus Router #1
    address                        192.168.1.254
    parents                        server-backbone
    check_command                  check-host-alive
    check_interval                 5
    retry_interval                 1
    max_check_attempts             5
    check_period                   24x7
    process_perf_data              0
    retain_nonstatus_information   0
    contact_groups                 router-admins
    notification_interval          30
    notification_period            24x7
    notification_options           d,u,r
    realm                          Europe
    poller_tag                     DMZ
    icon_set                       server
}</pre>
<p>选项列表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><strong>host_name</strong></td>
<td>用于识别主机的<span style="background-color: #c0c0c0;">短名称</span>，在主机组、服务中引用的就是该名称<br />对应宏<span style="color: #404040;">$HOSTNAME$</span></td>
</tr>
<tr>
<td>alias</td>
<td>用于识别主机的长名称<br />对应宏<span style="color: #404040;">$HOSTALIAS$</span></td>
</tr>
<tr>
<td>display_name</td>
<td>在WebUI中的显示名称，默认为host_name</td>
</tr>
<tr>
<td><strong>address</strong></td>
<td>主机的通信地址，一般是IP<br />对应宏<span style="color: #404040;"> $HOSTADDRESS$</span></td>
</tr>
<tr>
<td>parents</td>
<td>逗号分隔的父主机的短名称，父主机常常是路由器、交换机、防火墙等位于监控服务器与目标主机之间的网络设备<br />位于同一网段的目标主机不需要设置该字段</td>
</tr>
<tr>
<td>hostgroups</td>
<td>逗号分隔的、所属的主机组的短名称</td>
</tr>
<tr>
<td>check_command</td>
<td>用于检查主机开/关状态的命令的短名称，典型情况下命令使用PING来确定主机是否连通，命令必须返回状态码0来表示主机OK，否则Shinken一律认为主机宕机</td>
</tr>
<tr>
<td>initial_state</td>
<td>主机的初始状态，默认的，Shinken认为主机是开启的，可选值：<br /><span style="background-color: #c0c0c0;">o 正常；d 宕机；u不可达</span></td>
</tr>
<tr>
<td><strong>max_check_attempts</strong></td>
<td>如果返回状态不是OK，Shinken会重试对主机的检查的最大次数</td>
</tr>
<tr>
<td>check_interval</td>
<td>周期性的对主机进行检查，检查的间隔<br />除非你修改 interval_length的默认值60，否则单位为分钟</td>
</tr>
<tr>
<td>retry_interval</td>
<td>在到达max_check_attempts前，重新检查的执行间隔<br />除非你修改 interval_length的默认值60，否则单位为分钟</td>
</tr>
<tr>
<td>active_checks_enabled</td>
<td>布尔值0/1，是否启用主动的状态检查</td>
</tr>
<tr>
<td>passive_checks_enabled</td>
<td>布尔值0/1，是否启用被动的状态检查</td>
</tr>
<tr>
<td>check_period</td>
<td>引用时间段的短名，指示何时允许执行主动状态检查</td>
</tr>
<tr>
<td>obsess_over_host</td>
<td> </td>
</tr>
<tr>
<td>check_freshness</td>
<td>布尔值0/1，指示是否对该主机启用freshness checks</td>
</tr>
<tr>
<td>freshness_threshold</td>
<td>指定freshness的阈值（单位秒），如果设置为0则Shinken自动确定阈值</td>
</tr>
<tr>
<td>event_handler</td>
<td>引用一个命令的短名，当主机状态变化时，该命令被作为事件处理器执行 </td>
</tr>
<tr>
<td>event_handler_enabled</td>
<td>布尔值0/1，是否启用事件处理器 </td>
</tr>
<tr>
<td>low_flap_threshold</td>
<td rowspan="2">在状态动荡检测时，指定高、低状态变化阈值</td>
</tr>
<tr>
<td>high_flap_threshold</td>
</tr>
<tr>
<td>flap_detection_enabled</td>
<td>布尔值0/1，是否启状态动荡检测 </td>
</tr>
<tr>
<td>flap_detection_options</td>
<td>状态动荡检测逻辑使用哪些状态，o、d、u的组合，逗号分隔</td>
</tr>
<tr>
<td>process_perf_data</td>
<td>是否对该主机启用性能数据处理</td>
</tr>
<tr>
<td>retain_status_information</td>
<td>是否在Shinken重启后，仍然保持（retain）该主机的状态</td>
</tr>
<tr>
<td>retain_nonstatus_information</td>
<td>是否在Shinken重启后，仍然保持（retain）该主机的非状态信息</td>
</tr>
<tr>
<td><strong>contacts</strong></td>
<td>逗号分隔的联系人短名称 </td>
</tr>
<tr>
<td><strong>contact_groups </strong></td>
<td>逗号分隔的联系人组短名称</td>
</tr>
<tr>
<td><strong>notification_interval</strong></td>
<td>如果主机状态持续在d、u，则在重复通知联系人前需要等待的时间<br />除非你修改interval_length的默认值60，否则单位为分钟</td>
</tr>
<tr>
<td>first_notification_delay</td>
<td>第一次发送通知的延迟时间，如果在此期间主机状态变为o则不会发送通知<br />除非你修改interval_length的默认值60，否则单位为分钟</td>
</tr>
<tr>
<td>notification_period</td>
<td>引用时间段的短名，指示何时允许发送通知</td>
</tr>
<tr>
<td>notification_options</td>
<td>哪些主机状态会导致通知的发送，逗号分隔：<br />d宕机；u不可达；r恢复正常；f开始或结束状态动荡；s计划内的启动和停机<br />如果设置为n，则不会发送任何关于该主机的通知</td>
</tr>
<tr>
<td>notifications_enabled</td>
<td>是否启用针对该主机的状态通知</td>
</tr>
<tr>
<td>stalking_options</td>
<td>启用对该主机哪些状态的跟踪，逗号分隔的o、d、u </td>
</tr>
<tr>
<td>notes</td>
<td>主机的备注字段</td>
</tr>
<tr>
<td>notes_url</td>
<td>主机的备注URL</td>
</tr>
<tr>
<td>action_url</td>
<td>可以提供针对主机的额外操作的URL </td>
</tr>
<tr>
<td>icon_image</td>
<td>主机的图标</td>
</tr>
<tr>
<td>icon_image_alt</td>
<td>图标不可用时显示的文字</td>
</tr>
<tr>
<td>vrml_image</td>
<td>与statuswrl CGI相关 </td>
</tr>
<tr>
<td>statusmap_image</td>
<td>与statusmap相关</td>
</tr>
<tr>
<td>2d_coords</td>
<td>在statusmap CGI中绘制主机时，主机的二维坐标</td>
</tr>
<tr>
<td>3d_coords</td>
<td>在statuswrl CGI中绘制主机时，主机的三维坐标</td>
</tr>
<tr>
<td>realm</td>
<td>定义主机归属的领域，主机将被领域的一个Scheduler管理</td>
</tr>
<tr>
<td>poller_tag</td>
<td>定义主机的poller_tag，该主机上的任何检查，只能被poller_tags包含该poller_tag的Poller执行</td>
</tr>
<tr>
<td>reactionner_tag</td>
<td>与上面类似，指定哪些Reactionner才能处理来自该主机的通知</td>
</tr>
<tr>
<td>business_impact</td>
<td>指示该主机的重要性，0-5之间，默认2，数字越大越重要</td>
</tr>
<tr>
<td>resultmodulations</td>
<td>与resultmodulations对象连接，从而应用modulation到主机 </td>
</tr>
<tr>
<td>escalations</td>
<td>与escalations对象连接，从而应用escalations规则到主机</td>
</tr>
<tr>
<td>business_impact_modulations</td>
<td>与business_impact_modulations对象连接 </td>
</tr>
<tr>
<td>icon_set</td>
<td>设置在Shinken Web UI中的图标，可用值：database, disk, network_service, server</td>
</tr>
<tr>
<td>maintenance_period</td>
<td>指定一个周期性的维护时间</td>
</tr>
<tr>
<td>service_overrides</td>
<td>
<p>用于覆盖属于该主机的服务的选项，在使用继承（inherited，例如从一个pack中）来的服务时很有用，它允许你为服务提供不一样的值</p>
<p>举例：HTTP服务（继承自http pack）用于生产环境时可能需要24x7启用通知，而演示环境可能仅需要工作时间启用通知。这时你可以覆盖：</p>
<pre class="crayon-plain-tag">service_overrides      HTTP,notification_period workhours</pre>
<p>你可以指定多个覆盖项，但是每个项应当占据独立的一行 </p>
</td>
</tr>
<tr>
<td>service_excludes</td>
<td>从主机中排除服务，逗号分隔。服务可能来自从一个pack中继承，或者附加到当前主机所属的组上</td>
</tr>
<tr>
<td>service_includes</td>
<td>和上面的功能类似，但是指出该主机仅<span style="background-color: #c0c0c0;">仅包含</span>的服务 </td>
</tr>
<tr>
<td>labels</td>
<td>任意逗号分隔的多个标签</td>
</tr>
<tr>
<td>business_rule_output_template</td>
<td>商业规则的输出模板</td>
</tr>
<tr>
<td>business_rule_smart_notifications</td>
<td>用于启用智能化的针对商业规则的通知，可以用于在底层问题被确认后，停止继续发送通知 </td>
</tr>
<tr>
<td>business_rule_downtime_as_ack</td>
<td> </td>
</tr>
<tr>
<td>business_rule_host_notification_options</td>
<td> </td>
</tr>
<tr>
<td>business_rule_service_notification_options</td>
<td> </td>
</tr>
<tr>
<td>snapshot_enabled</td>
<td>是否启用针对该主机的快照 </td>
</tr>
<tr>
<td>snapshot_command</td>
<td>执行快照时启动的命令</td>
</tr>
<tr>
<td>snapshot_period</td>
<td>允许快照调用的时间段 </td>
</tr>
<tr>
<td>snapshot_criteria</td>
<td>启用快照的状态列表（通常都是坏的状态），逗号分隔 </td>
</tr>
<tr>
<td>snapshot_interval</td>
<td>两次快照执行的最小间隔<br />除非你修改interval_length的默认值60，否则单位为分钟</td>
</tr>
<tr>
<td>trigger_name</td>
<td>主动/被动检查结果得到后，执行的触发器，触发器文件trigger_name.trig必须被预先保存在触发器目录或者子目录中</td>
</tr>
<tr>
<td>trigger_broker_raise_enabled</td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">主机组定义</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><strong>hostgroup_name</strong></td>
<td>用于识别主机组的短名称</td>
</tr>
<tr>
<td><strong>alias</strong></td>
<td>一个较长的别名</td>
</tr>
<tr>
<td>members</td>
<td>归属为该组的主机成员，亦可在主机定义的hostgroups配置项声明归属关系</td>
</tr>
<tr>
<td>hostgroup_members</td>
<td>把其它组的成员加入到本组</td>
</tr>
<tr>
<td>realm</td>
<td>该组的所有成员属于的领域</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">服务定义</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><strong>host_name</strong></td>
<td>该服务在哪个主机上运行（属于哪个主机），指定主机的短名</td>
</tr>
<tr>
<td>hostgroup_name</td>
<td>让目标组的所有主机都具有该服务，支持逻辑操作符：<span style="background-color: #c0c0c0;">&amp;逻辑与；|逻辑或（亦可用逗号,）；!逻辑非</span>、支持<span style="background-color: #c0c0c0;">括号限定优先级</span>。举例：<br />
<pre class="crayon-plain-tag">hostgroup_name=(linux|windows)&amp;!qualification,routers</pre>
</td>
</tr>
<tr>
<td><strong>service_description</strong></td>
<td>服务的描述，一个主机下，不得有两个服务具有相同的描述</td>
</tr>
<tr>
<td>display_name</td>
<td>在UI中显示的名称</td>
</tr>
<tr>
<td>servicegroups</td>
<td>所属的服务组的短名，逗号分隔</td>
</tr>
<tr>
<td>is_volatile</td>
<td>声明服务是否“易变的”，默认否</td>
</tr>
<tr>
<td>duplicate_foreach</td>
<td>用于基于一个服务定义，生成多个类似的服务</td>
</tr>
<tr>
<td><strong>check_command</strong></td>
<td>
<p>指定用于检查该服务状态的命令的短名称，主配置文件选项service_check_timeout限制了命令执行的最大时间</p>
<p>由一个内部定义的、特殊用途的命令<pre class="crayon-plain-tag">bp_rule</pre> ，该命令不需要定义。该命令不是执行一个特定的插件，而是<span style="background-color: #c0c0c0;">依据其它服务的状态</span>来执行一个逻辑操作，例如：</p>
<pre class="crayon-plain-tag"># 支持逻辑操作符
# 如果websrv1的apache服务或者websrv2的apache服务OK，并且dbsrv1上的oracle数据库OK
# 那么当前服务的状态判断为OK
bp_rule(websrv1,apache | websrv2,apache) &amp; dbsrv1,oracle</pre>
</td>
</tr>
<tr>
<td><strong>initial_state</strong></td>
<td>
<p>默认的，Shinken认为所有服务的状态是正常（OK），你可以覆盖默认状态：<br /><span style="background-color: #c0c0c0;">o正常；w警告；u未知；c关键（错误）</span>
</td>
</tr>
<tr>
<td><strong>max_check_attempts</strong></td>
<td rowspan="36">参考主机配置同名配置项 </td>
</tr>
<tr>
<td><strong>check_interval</strong></td>
</tr>
<tr>
<td><strong>retry_interval</strong></td>
</tr>
<tr>
<td>active_checks_enabled</td>
</tr>
<tr>
<td>passive_checks_enabled</td>
</tr>
<tr>
<td><strong>check_period</strong></td>
</tr>
<tr>
<td>obsess_over_service</td>
</tr>
<tr>
<td>check_freshness</td>
</tr>
<tr>
<td>freshness_threshold</td>
</tr>
<tr>
<td>event_handler</td>
</tr>
<tr>
<td>event_handler_enabled</td>
</tr>
<tr>
<td>low_flap_threshold</td>
</tr>
<tr>
<td>high_flap_threshold</td>
</tr>
<tr>
<td>flap_detection_enabled</td>
</tr>
<tr>
<td>flap_detection_options</td>
</tr>
<tr>
<td>process_perf_data</td>
</tr>
<tr>
<td>retain_status_information</td>
</tr>
<tr>
<td>retain_nonstatus_information</td>
</tr>
<tr>
<td><strong>notification_interval</strong></td>
</tr>
<tr>
<td>notification_interval</td>
</tr>
<tr>
<td>notification_period</td>
</tr>
<tr>
<td>notification_options</td>
</tr>
<tr>
<td>notifications_enabled</td>
</tr>
<tr>
<td><strong>contacts</strong></td>
</tr>
<tr>
<td><strong>contact_groups</strong></td>
</tr>
<tr>
<td>stalking_options</td>
</tr>
<tr>
<td>notes</td>
</tr>
<tr>
<td>notes_url</td>
</tr>
<tr>
<td>action_url</td>
</tr>
<tr>
<td>icon_image</td>
</tr>
<tr>
<td>icon_image_alt</td>
</tr>
<tr>
<td>poller_tag</td>
</tr>
<tr>
<td>reactionner_tag</td>
</tr>
<tr>
<td>business_impact</td>
</tr>
<tr>
<td>icon_set</td>
</tr>
<tr>
<td>maintenance_period</td>
</tr>
<tr>
<td>service_dependencies</td>
<td>
<p>定义该服务依赖的其它服务，在通知时有用，为了避免通知泛滥，Shinken只会将Root问题通知给联系人，此配置项用于发现问题的根</p>
<p>值格式：<pre class="crayon-plain-tag">host,service_description,host,service_description</pre> ，其中主机可以<span style="background-color: #c0c0c0;">忽略，表示依赖于当前主机</span>下的服务</p>
</td>
</tr>
<tr>
<td>host_dependency_enabled</td>
<td>可以移除服务和它的主机之间的依赖关系，对于需要依据其自身而不是所属主机进行通知的volatile服务有用</td>
</tr>
<tr>
<td>labels</td>
<td rowspan="13">参考主机配置同名配置项 </p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>business_rule_output_template</td>
</tr>
<tr>
<td>business_rule_smart_notifications</td>
</tr>
<tr>
<td>business_rule_downtime_as_ack</td>
</tr>
<tr>
<td>business_rule_host_notification_options</td>
</tr>
<tr>
<td>business_rule_service_notification_options</td>
</tr>
<tr>
<td>snapshot_enabled</td>
</tr>
<tr>
<td>snapshot_command</td>
</tr>
<tr>
<td>snapshot_period</td>
</tr>
<tr>
<td>snapshot_criteria</td>
</tr>
<tr>
<td>snapshot_interval</td>
</tr>
<tr>
<td>trigger_name</td>
</tr>
<tr>
<td>trigger_broker_raise_enabled</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">集成</span></div>
<div class="blog_h2"><span class="graybg">如何查看数据</span></div>
<p>通过Shinken支持的接口，管理员可以直观的看到监控数据，例如主机/服务的状态。</p>
<p>Shinken自带一个WebUI，你也可以使用其它开源/商业的Web前端，这些前端使用<span style="background-color: #c0c0c0;">LiveStatus API</span>或者S<span style="background-color: #c0c0c0;">QL backend</span>。LiveStatus具有很好的实时性、可扩容性和灵活性。</p>
<div class="blog_h2"><span class="graybg">基于内存直接访问的UI</span></div>
<div class="blog_h3"><span class="graybg">使用Shinken WebUI</span></div>
<p>安装必要的模块：</p>
<pre class="crayon-plain-tag"># Web UI
shinken install webui
# 身份验证机制
shinken install auth-cfg-password
# 用户配置存储
shinken install sqlitedb</pre>
<p>修改模块定义（安装WebUI模块后，<span style="background-color: #c0c0c0;">此定义会自动生成</span>）：</p>
<pre class="crayon-plain-tag">define module {
    module_name         webui
    module_type         webui
    host                0.0.0.0     # 监听所有网络接口
    port                7767
    auth_secret         CHANGEME    # 修改，防止Cookie伪造
    allow_html_output   1           # 是否允许插件输出HTML特殊字符
    max_output_length   1024        # 插件输出的最大长度
    manage_acl          1           # Use contacts ACL. 0 allow actions for all.
    play_sound          0           # 出现新的未确认问题时播放声音
    login_text          Welcome on Shinken WebUI   # 登陆表单提示信息
    modules             auth-cfg-password SQLitedb # 使用Shinken联系人进行身份验证，使用SQLitedb存储用户设置
}</pre>
<p>修改用于登陆的联系人：</p>
<pre class="crayon-plain-tag"># 基本配置已经包含此联系人
define contact{
    use             generic-contact
    contact_name    admin
    email           shinken@localhost
    # 修改密码
    password        admin             
    is_admin        1
    expert          1
}</pre>
<p>然后，需要让代理加载此模块定义：</p>
<pre class="crayon-plain-tag">define broker {
    ...
    modules   webui
}</pre>
<p>现在你可以访问 http://ip:7767/user/login并登陆了。</p>
<p><span class="graybg"><span style="font-weight: bold;">使用Shinken WebUI2</span> </span></p>
<p>新版本的WebUI，内部集成了身份验证、存储、日志模块，简化了安装和配置。</p>
<p>安装模块：</p>
<pre class="crayon-plain-tag">pip install bottle
shinken install webui2</pre>
<p>完毕后修改代理定义，把webui模块改为webui2，重启Shinken即可。</p>
<div class="blog_h2"><span class="graybg">基于网络访问（Livestatus）的UI</span></div>
<p>基于Livestatus的前端包括：</p>
<ol>
<li>Thruk，适用于中小规模的场景</li>
<li>Multisite，适用于最大规模的可扩容性需求</li>
<li>Nagvis，用于图形化的展示，可以显示网络拓扑图，并标注每个主机的状态</li>
</ol>
<div class="blog_h3"><span class="graybg">启用Livestatus模块</span></div>
<p>获得livestatus模块定义：</p>
<pre class="crayon-plain-tag"># 使用SQLite存储历史日志
shinken install logstore-sqlite
# 获得livestatus模块定义
shinken install livestatus</pre>
<p>在代理定义中加载livestatus模块</p>
<pre class="crayon-plain-tag">define broker {
    modules   webui2,livestatus
} </pre>
<div class="blog_h2"><span class="graybg">使用Thruk作为Shinken前端</span></div>
<div class="blog_h3"><span class="graybg">安装Thruk</span></div>
<p>禁用SELinux： </p>
<pre class="crayon-plain-tag">SELINUX=disabled</pre>
<p>安装必要的软件：</p>
<pre class="crayon-plain-tag">yum install httpd
yum install mod_fcgid

wget http://download.thruk.org/pkg/v2.08/rhel7/x86_64/libthruk-2.08-1.rhel7.x86_64.rpm
rpm -ivh ./libthruk-2.08-1.rhel7.x86_64.rpm
wget http://download.thruk.org/pkg/v2.08/rhel7/x86_64/thruk-base-2.08-1.rhel7.x86_64.rpm
rpm -ivh ./thruk-base-2.08-1.rhel7.x86_64.rpm 
wget http://download.thruk.org/pkg/v2.08/rhel7/x86_64/thruk-plugin-reporting-2.08-1.rhel7.x86_64.rpm
rpm -ivh ./thruk-plugin-reporting-2.08-1.rhel7.x86_64.rpm
wget http://download.thruk.org/pkg/v2.08/rhel7/x86_64/thruk-2.08-1.rhel7.x86_64.rpm
rpm -ivh ./thruk-2.08-1.rhel7.x86_64.rpm 

chkconfig httpd on
service httpd start</pre>
<p>上述步骤完毕后，你可以通过<span style="background-color: #c0c0c0;">http://ip/thruk/</span>来访问Thruk了，默认登陆用户名密码是thrukadmin/thrukadmin。</p>
<div class="blog_h3"><span class="graybg">将Shinken配置为Thruk的后端</span></div>
<pre class="crayon-plain-tag">enable_shinken_features = 1
&lt;Component Thruk::Backend&gt;
    &lt;peer&gt;
        name   = External Shinken
        type   = livestatus
        &lt;options&gt;
            peer    = 127.0.0.1:50000
        &lt;/options&gt;
    &lt;/peer&gt;
&lt;/Component&gt;</pre>
<p>注意livestatus的IP和端口要和livestatus的模块定义匹配。</p>
<div class="blog_h3"><span class="graybg">连通性测试</span></div>
<p>完成配置后，重启Shinken，然后登陆到Thruk。在左侧菜单点击Config Tool，右上角Configuration Type选择Backends，点击Test测试。参考下图：<img class="aligncenter size-full wp-image-12150" src="https://blog.gmem.cc/wp-content/uploads/2016/06/Selection_001.png" alt="Selection_001" width="361" height="253" /></p>
<p>注意：从启动Shinken到livestatus可用，可能需要几分钟时间。</p>
<div class="blog_h1"><span class="graybg">如何监控...</span></div>
<div class="blog_h2"><span class="graybg">Windows设备</span></div>
<p>你可以使用预定义的模板来监控Windows设备，并获得内存用量、CPU负载、磁盘用量、系统服务状态、进程、系统事件日志等方面的信息。</p>
<div class="blog_h3"><span class="graybg">监控方式</span></div>
<p>有两种方法监控Windows设备：</p>
<ol>
<li>基于代理的方式：在目标设备上安装NSClient++</li>
<li>无代理的方式：通过WMI协议直接通过网络轮询Windows状态</li>
</ol>
<div class="blog_h3"><span class="graybg">基于WMI的监控</span></div>
<p>前提条件：你需要一个合法的Windows本地/域账号，用于执行WMI查询。</p>
<p>安装windows包：<pre class="crayon-plain-tag">shinken install windiws</pre> </p>
<p>安装必要的Perl模块：</p>
<pre class="crayon-plain-tag">yum -y install perl-Number-Format 
yum -y install perl-Config-IniFiles 
yum -y install -y perl-DateTime  </pre>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host{
   # 继承windows模板
   use                     windows  
   _DOMAIN                 $DOMAIN$
   _DOMAINUSERSHORT        logonuser
   _DOMAINPASSWORD         passwd
}</pre>
<div class="blog_h2"><span class="graybg">Linux设备</span></div>
<p>与Windows设备的监控类似，你也可以监控Linux的内存用量、CPU负载、磁盘用量、进程等方面的信息。</p>
<div class="blog_h3"><span class="graybg">监控方式</span></div>
<p>你可以通过多种方式来监控Linux：</p>
<ol>
<li>通过SNMP代理</li>
<li>基于一个本地的代理，可以高频率获取数据，支持主动、被动通信方式</li>
<li>基于SSH的方式，只适用于非频繁的检测，可扩容性差</li>
</ol>
<div class="blog_h3"><span class="graybg">基于SNMP的监控</span></div>
<p>首先，你需要在<span style="background-color: #c0c0c0;">目标设备上</span>安装SNMP守护程序：</p>
<pre class="crayon-plain-tag"># CentOS 7
yum install net-snmp net-snmp-devel net-snmp-libs net-snmp-utils
systemctl enable snmpd
systemctl start  snmpd

# Debian/Ubuntu
apt-get install snmpd
sudo update-rc.d snmpd defaults
service snmpd start</pre>
<p>修改snmpd的配置文件：</p>
<pre class="crayon-plain-tag"># Ubuntu 14.04
# 注释掉 agentAddress  udp:127.0.0.1:161，添加
agentAddress udp:161,udp6:[::1]:161
# 将 rocommunity public  default    -V systemonly 改为
rocommunity public

# CentOS 7
# 把默认的两行view systemview去掉，改为：
view systemview included .1.3.6.1

# 最好更改团体名（下面的public），以避免安全问题
# CentOS 7
com2sec notConfigUser  default       public
# Ubuntu 14.04
rocommunity public  default    -V systemonly</pre>
<p>然后，你需要在监控服务器上执行以下操作：</p>
<ol>
<li>安装Perl的SNMP支持：<pre class="crayon-plain-tag">yum -y install perl-Net-SNMP</pre> </li>
<li>安装相关Shinken包：<pre class="crayon-plain-tag">shinken install linux-snmp</pre> </li>
<li>修改主机配置：<br />
<pre class="crayon-plain-tag">define host{
    # 继承linux-snmp模板
    use                 linux-snmp
    # 指定团体名，默认是读取宏$SNMPCOMMUNITYREAD$的值，你可以在资源中定义该宏
    _SNMPCOMMUNITY      public
    # 监控哪个网络接口的使用情况
    _NET_IFACES         eth0
}</pre>
</li>
</ol>
<p>完毕后，重启Shinken即可。</p>
<div class="blog_h2"><span class="graybg">路由器和交换机</span></div>
<p>某些廉价的交换机、集线器没有IP地址，因而无法监控。相反的，一些高档网络设备则提供SNMP查询功能。</p>
<p>Shinken支持监控：</p>
<ol>
<li>丢包率、延迟</li>
<li>SNMP状态信息，需要交换机支持SNMP</li>
<li>带宽/通信速率，需要安装MRTG，需要交换机支持SNMP</li>
</ol>
<div class="blog_h3"><span class="graybg">监控丢包率</span></div>
<pre class="crayon-plain-tag">define service{
   use                    generic-service
   host_name              router
   service_description    PING
   # CRITICAL：RTA大于600ms或者丢包率不小于60%
   # WARNING：RTA大于200ms或者丢包率不小于20%
   # OK：RTA小于200ms并且丢包率小于20%
   check_command          check_ping!200.0,20%!600.0,60%
}</pre>
<div class="blog_h3"><span class="graybg">监控SNMP</span></div>
<p>你可以执行命令：<pre class="crayon-plain-tag">snmpwalk -v1 -c public 192.168.1.1 -m ALL .1</pre> 来获取路由器/交换机上可以监控的信息列表。</p>
<p>监控命令举例：</p>
<pre class="crayon-plain-tag"># 监控路由器已经启动的时间
check_command        check_snmp!-C public -o sysUpTime.0
# 监控端口1的连接状态
check_command       check_snmp!-C public -o ifOperStatus.1 -r 1 -m RFC1213-MIB</pre>
<div class="blog_h3"><span class="graybg">监控带宽/速率</span></div>
<p>如果你安装了<a href="http://oss.oetiker.ch/mrtg/">MRTG </a>，则Shinken支持通过<pre class="crayon-plain-tag">check_mrtgtraf</pre> 插件来监控路由器的带宽使用情况：</p>
<pre class="crayon-plain-tag">define service{
   use                 generic-service
   host_name           router
   # 端口1的带宽使用情况
   service_description Port 1 Bandwidth Usage
   check_command       check_local_mrtgtraf!/var/lib/mrtg/192.168.1.253_1.log!AVG!1000000,2000000!5000000,5000000!10
}</pre>
<div class="blog_h3"><span class="graybg">使用check_nwc_health</span></div>
<p>通过该插件，你可以监控任意支持SNMP的网络设备（主要是交换机、路由器）的网络使用情况、CPU负载、内存使用、端口使用、硬件状态等方面的信息。 监控命令举例：</p>
<pre class="crayon-plain-tag"># 列出所有接口的状态
/var/lib/shinken/libexec/check_nwc_health --hostname 192.168.0.1 --timeout 60 --community "public" --mode interface-status
# 显示健康状态 
/var/lib/shinken/libexec/check_nwc_health --hostname 192.168.0.1 --timeout 60 --community "public" --mode hardware-health</pre>
<div class="blog_h2"><span class="graybg">监控打印机</span></div>
<p>支持JetDirect协议的打印机往往也启用了SNMP，可以通过插件check_hpjd监控之。</p>
<p>安装包：<pre class="crayon-plain-tag">shinken install hp-printers</pre> </p>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host {
    use    printer-hp
} </pre>
<div class="blog_h2"><span class="graybg">公共服务</span></div>
<p>这里的公共服务是指对外开放的，可以通过网络访问的服务、应用或者协议。公共服务的例子包括：HTTP、POP3、IMAP、FTP、SSH等。</p>
<div class="blog_h3"><span class="graybg">用于监控公共服务的插件</span></div>
<p>官方的<span style="color: #404040;">Nagios/Shinken插件支持很多常见服务、协议的监控。如果找不到合适的插件，你也可以自己开发。</span></p>
<div class="blog_h3"><span class="graybg">监控HTTP/HTTPS</span></div>
<p>安装http包：<pre class="crayon-plain-tag">shinken install http</pre> </p>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host{
        # 继承http或（和）https模板
        use                http,https  
        # 监听端口
        _CHECK_HTTP_PORT   80
        _CHECK_HTTPS_PORT  443
        # 测试的URI
        _CHECK_HTTP_URI    /index.html
        _CHECK_HTTPS_URI   /index.html
}</pre>
<div class="blog_h3"><span class="graybg">监控FTP</span></div>
<p>安装ftp包：<pre class="crayon-plain-tag">shinken install ftp</pre> </p>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host{
        # 继承ftp模板
        use               ftp
}</pre>
<div class="blog_h3"><span class="graybg">监控SSH</span></div>
<p>安装ssh包：<pre class="crayon-plain-tag">shinken install ssh</pre>  </p>
<p>修改主机配置，继承ssh。注意，如果你的主机已经继承linux，则不需要再继承ssh。</p>
<div class="blog_h3"><span class="graybg">监控MySQL</span></div>
<p>使用Shinken你可以很好的监控MySQL的运行状态，获取包括InnoDB缓冲池状态、连接数、查询缓存命中率、缓慢查询、锁争用在内的数据。</p>
<p>安装mysql包：<pre class="crayon-plain-tag">shinken install mysql</pre> </p>
<p>安装MySQL的Perl驱动：<pre class="crayon-plain-tag">yum install -y perl-DBD-MySQL</pre> </p>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host{
        # 继承MySQL模板
        use                     mysql
        _MYSQLUSER              pems
        _MYSQLPASSWORD          pemsroot
} </pre>
<div class="blog_h3"><span class="graybg">监控IIS</span></div>
<p>前提条件：你需要一个合法的Windows本地/域账号。</p>
<p>你可以监控：连接数、错误数量、登陆用户数等方面的信息。</p>
<p>安装需要的包：</p>
<pre class="crayon-plain-tag">shiken install windows
shinken install  iis</pre>
<p>修改主机配置：</p>
<pre class="crayon-plain-tag">define host {
    # 继承iis和windows
    user    iis,windows
}</pre>
<div class="blog_h3"><span class="graybg">监控其它公共服务</span></div>
<p>Shinken还提供了很多其它的包，方便你监控常见的服务：</p>
<pre class="crayon-plain-tag"># 监控邮件服务
shinken install imap
shinken install smtp
shinken install pop3</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">主机检查报错：[Errno 2] No such file or directory</span></div>
<div class="blog_h3">报错示例</div>
<p>[1467096368] ERROR: [Shinken] Fail launching command: /usr/lib/nagios/plugins/check_ping -H localhost -w 1000,100% -c 3000,100% -p 1 [Errno 2] No such file or directory False</p>
<div class="blog_h3"><span class="graybg">报错原因</span></div>
<p>Shinken基本配置中的命令，主要是调用$NAGIOSPLUGINSDIR$目录下的监控插件（命令行），例如：</p>
<pre class="crayon-plain-tag">define command {
    command_name    check_ping
    command_line    $NAGIOSPLUGINSDIR$/check_icmp -H $HOSTADDRESS$ -w 3000,100% -c 5000,100% -p 10
}</pre>
<p>宏$NAGIOSPLUGINSDIR$指向插件的安装目录，该宏的默认值定义在： </p>
<pre class="crayon-plain-tag"># Nagios legacy macros
$USER1$=$NAGIOSPLUGINSDIR$
$NAGIOSPLUGINSDIR$=/usr/lib/nagios/plugins

#-- Location of the plugins for Shinken
$PLUGINSDIR$=/var/lib/shinken/libexec</pre>
<p>如果你没有安装这些插件，或者安装位置不是/usr/lib/nagios/plugins，就会报找不到文件的错误。</p>
<div class="blog_h3"><span class="graybg">解决办法</span></div>
<p>你可以从多个地方获取插件：</p>
<ol>
<li><a href="http://www.nagios-plugins.org/download/nagios-plugins-2.1.1.tar.gz">Nagios Core Plugins</a>：你可以下载50个核心Nagios插件</li>
<li><a href="https://www.monitoring-plugins.org/">The Monitoring Plugins Project</a>：监控插件项目，这个开源项目维护更多的插件</li>
</ol>
<p>安装插件的示例脚本：</p>
<pre class="crayon-plain-tag">yum -y install openssl openssl-devel

wget https://www.monitoring-plugins.org/download/monitoring-plugins-2.1.2.tar.gz
tar xzf monitoring-plugins-2.1.2.tar.gz 
cd monitoring-plugins-2.1.2/
./configure --with-openssl
make &amp;&amp; make install</pre>
<p>修改宏定义，指向监控插件实际安装目录：</p>
<pre class="crayon-plain-tag">$NAGIOSPLUGINSDIR$=/usr/local/libexec</pre>
<div class="blog_h2"><span class="graybg">https服务检查报错</span></div>
<div class="blog_h3"><span class="graybg">check_http: Invalid option - SSL is not available</span></div>
<p>出现此错误的原因是编译插件的时候没有安装OpenSSL，参考上面的脚本，先安装OpenSSL再配置、构建、安装插件。</p>
<div class="blog_h2"><span class="graybg">检查MySQL报错</span></div>
<div class="blog_h3"><span class="graybg">cannot connect to information_schema. Can't locate DBI.pm</span></div>
<p>出现此错误的原因是没有安装Perl的MySQL驱动，安装即可：<pre class="crayon-plain-tag">yum -y install -y perl-DBD-MySQL</pre> </p>
<div class="blog_h2"><span class="graybg">监控Windows设备时报错</span></div>
<div class="blog_h3"><span class="graybg">Can't locate Number/Format.pm</span></div>
<p>出现此错误的原因是缺少对应的Perl模块，安装即可：<pre class="crayon-plain-tag">yum -y install perl-Number-Format</pre> </p>
<div class="blog_h3"><span class="graybg">can't locate Config/IniFiles.pm </span></div>
<p>出现此错误的原因是缺少对应的Perl模块，安装即可：<pre class="crayon-plain-tag">yum -y install perl-Config-IniFiles</pre> </p>
<div class="blog_h3"><span class="graybg">Can't locate DateTime.pm</span></div>
<p>出现此错误的原因是缺少对应的Perl模块，安装即可：<pre class="crayon-plain-tag">yum -y install -y perl-DateTime</pre> </p>
<div class="blog_h2"><span class="graybg">CentOS下Shinken启动System V脚本报错</span></div>
<div class="blog_h3"><span class="graybg">log_warning_msg: command not found </span></div>
<p>安装缺少的函数库：<pre class="crayon-plain-tag">yum -y install redhat-lsb redhat-lsb-core</pre> </p>
<div class="blog_h2"><span class="graybg">基于SNMP的监控报错</span></div>
<div class="blog_h3"><span class="graybg">/bin/sh: /var/lib/shinken/libexec/check_netint.pl: No such file or directory</span></div>
<p>某些Shinken插件可能没有随基本配置安装，你可以到<a href="https://github.com/Sysnove/shinken-plugins">shiken-plugins</a>网站下载：</p>
<pre class="crayon-plain-tag">cd /var/lib/shinken/libexec
wget https://raw.githubusercontent.com/Sysnove/shinken-plugins/master/check_netint.pl
chmod +x check_netint.pl</pre>
<div class="blog_h3"><span class="graybg">/bin/sh: /var/lib/shinken/libexec/check_nwc_health: No such file or directory</span></div>
<p>该插件是Native应用程序，需要手工编译：</p>
<pre class="crayon-plain-tag">wget https://labs.consol.de/assets/downloads/nagios/check_nwc_health-5.7.1.1.tar.gz
tar xzf check_nwc_health-5.7.1.1.tar.gz  &amp;&amp; rm -f check_nwc_health-5.7.1.1.tar.gz
cd check_nwc_health-5.7.1.1/
./configure  &amp;&amp; make
cp plugins-scripts/check_nwc_health* /var/lib/shinken/libexec/</pre>
<div class="blog_h3"><span class="graybg">Can't locate Module/Load.pm in @INC </span></div>
<pre class="crayon-plain-tag"># 初次使用cpanm命令时
cpan App::cpanminus

# 安装Load模块
cpanm Module::Load

# cpanm默认安装位置为~/perl5/lib/perl5/无法被check_nwc_health命令找到，因此需要手工拷贝一下
mkdir /usr/lib64/perl5/vendor_perl/Module
cp /root/perl5/lib/perl5/Module/Load.pm /usr/lib64/perl5/vendor_perl/Module</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/shinken-study-note">Shinken学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/shinken-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django学习笔记</title>
		<link>https://blog.gmem.cc/django-study-note</link>
		<comments>https://blog.gmem.cc/django-study-note#comments</comments>
		<pubDate>Fri, 13 Feb 2015 08:45:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12290</guid>
		<description><![CDATA[<p>简介 Django是一个Python的Web开发框架，它是一个WSGI规范的框架端实现。使用该框架可以把你从Web开发的很多重复劳动中解放出来而专注于业务逻辑。 Django运行速度非常快、并且是可扩容的，安全性方面也做的很好，可以避免一些常见的安全性错误。 安装 安装Python 最新的1.9版本，要求Python版本为2.7、3.4或者3.5。具体安装步骤本文不赘述，可以参考Python知识集锦。 安装Web服务器 Django内置了一个简单的开发用Web服务器，可以用于开发和测试，要启动此服务器，只需要： [crayon-69d6611a173a6168596655/] 这个开发服务器会自动重新载入最新的Python代码，便于开发调试。 在生产环境下你应当使用可靠的Web服务器，例如Apache、Nginx等。 Apache + mod_wsgi 这个组合可以作为Django的Web容器。Apache是广泛使用的、模块化的Web服务器；而mod_wsgi模块则是一个Apache模块，同时也是WSGI规范的服务器端实现，安装此模块后Apache可以和任何支持WSGI的Python Web应用通信，例如基于Django的应用。 mod_wsgi有两种运行模式： 嵌入式模式：类似于mod_perl，Python被嵌入在Apache中，Python代码在Apache启动时加载到Apache进程空间并驻留直到Apache进程结束。这种方式具有较高的性能 守护模式：mod_wsgi产生独立进程，由该进程来处理请求。此进程的运行用户可以不同于Apache进程。此进程可以独立于Apache重启，因而在开发期间可以更无缝的更新代码 手工构建mod_wsgi的步骤如下： [crayon-69d6611a173ac918364091/] 然后，你需要让Apache加载mod_wsgi模块。你可以使用a2enmod命令或者修改配置文件：  <a class="read-more" href="https://blog.gmem.cc/django-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/django-study-note">Django学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Django是一个Python的Web开发框架，它是一个WSGI规范的<span style="background-color: #c0c0c0;">框架端实现</span>。使用该框架可以把你从Web开发的很多重复劳动中解放出来而专注于业务逻辑。</p>
<p>Django运行速度非常快、并且是可扩容的，安全性方面也做的很好，可以避免一些常见的安全性错误。</p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">安装Python</span></div>
<p>最新的1.9版本，要求Python版本为2.7、3.4或者3.5。具体安装步骤本文不赘述，可以参考<a href="/python-faq">Python知识集锦</a>。</p>
<div class="blog_h2"><span class="graybg">安装Web服务器</span></div>
<p>Django内置了一个简单的开发用Web服务器，可以用于开发和测试，要启动此服务器，只需要：</p>
<pre class="crayon-plain-tag"># 先进入工程根目录
cd librarymgr
# 启动开发服务器
python manage.py runserver</pre>
<p>这个开发服务器会<span style="background-color: #c0c0c0;">自动重新载入最新的Python代码</span>，便于开发调试。</p>
<p>在生产环境下你应当使用可靠的Web服务器，例如Apache、Nginx等。</p>
<div class="blog_h3"><span class="graybg"><a id="mod-wsgi"></a>Apache + mod_wsgi</span></div>
<p>这个组合可以作为Django的Web容器。Apache是广泛使用的、模块化的Web服务器；而mod_wsgi模块则是一个Apache模块，同时也是WSGI规范的<span style="background-color: #c0c0c0;">服务器端实现</span>，安装此模块后Apache可以和任何支持WSGI的Python Web应用通信，例如基于Django的应用。</p>
<p>mod_wsgi有两种运行模式：</p>
<ol>
<li>嵌入式模式：类似于mod_perl，Python被嵌入在Apache中，Python代码在Apache启动时加载到Apache进程空间并驻留直到Apache进程结束。这种方式具有较高的性能</li>
<li>守护模式：mod_wsgi产生独立进程，由该进程来处理请求。此进程的运行用户可以不同于Apache进程。此进程可以独立于Apache重启，因而在开发期间可以更无缝的更新代码</li>
</ol>
<p>手工构建mod_wsgi的步骤如下：</p>
<pre class="crayon-plain-tag">wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.3.tar.gz
tar xzf 4.5.3.tar.gz 
cd mod_wsgi-4.5.3/
./configure
# 在Linux上，Apache2默认使用的MPM（多处理模块）是prefork，下面安装开发支持
# 如果是CentOS，安装 yum install httpd-devel
sudo apt-get install apache2-prefork-dev
make &amp;&amp; sudo make install 
# 构建好的模块默认被安装到：
# Ubuntu 14 /usr/lib/apache2/modules/mod_wsgi.so
# CentOS 7  /usr/lib64/httpd/modules/mod_wsgi.so</pre>
<p>然后，你需要让Apache加载mod_wsgi模块。你可以使用<a href="/apache-http-server-faq#a2enmod-usage">a2enmod</a>命令或者修改配置文件： </p>
<pre class="crayon-plain-tag">LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so</pre>
<div class="blog_h2"><span class="graybg">配置数据库</span></div>
<p>如果要使用Django的数据库API，你需要安装数据库服务器。Django支持多种数据库后端，官方支持包括：PostgreSQL、MySQL、Oracle和SQLite，其它主流数据库有<a href="https://docs.djangoproject.com/en/1.9/ref/databases/#third-party-notes">第三方支持</a>。开发一个简单的项目，不需要在生成环境部署时，你可以使用SQLite，它不需要独立的服务器，也不需要安装第三方Python模块。</p>
<p>除了后端以外，你还需要安装对应的Python database bindings：</p>
<pre class="crayon-plain-tag"># MySQL
sudo pip install mysqlclient
# PostgreSQL
sudo pip install  psycopg2</pre>
<p>如果你想用Django的<pre class="crayon-plain-tag">manage.py migrate</pre> 命令为工程模型自动创建表结构，你需要目标数据库用户授予对应的权限。</p>
<div class="blog_h2"><span class="graybg">安装Django</span></div>
<p>你可以通过pip安装：</p>
<pre class="crayon-plain-tag"># 安装Release
sudo pip install django
# 安装开发版本
git clone git://github.com/django/django.git
sudo pip install -e django/</pre>
<div class="blog_h1"><span class="graybg">Hello World</span></div>
<p>本章通过一个简单的图书馆管理系统，来了解Django的开发流程和基本的API。</p>
<div class="blog_h2"><span class="graybg">创建新工程</span></div>
<p>开始编程之前，你需要创建一个Django工程：</p>
<pre class="crayon-plain-tag">cd ~/Python/projects/pycharm
django-admin startproject librarymgr</pre>
<p>上面的命令会在当前目录下创建一个librarymgr目录，这是<span style="background-color: #c0c0c0;">新工程的根目录</span>，其结构如下：</p>
<pre class="crayon-plain-tag">└── librarymgr               # 工程容器目录
    ├── manage.py            # 一个命令行工具，通过它你可以和Django工程互动
    └── librarymgr           # 当前工程对应的Python包
        ├── __init__.py      # 空白文件，仅仅用于识别当前目录为一个包
        ├── settings.py      # 当前Django工程的设置/配置
        ├── urls.py          # 当前工程的URL映射声明
        └── wsgi.py          # WSGI兼容的Web服务器的入口点，部署工程时用到此文件</pre>
<p>一个Django实例的全部设置：  数据库配置、Django选项、应用程序选项，都存放在工程目录下。</p>
<div class="blog_h2"><span class="graybg">导入工程到PyCharm</span></div>
<p>你可以直接把工程容器目录作为PyCharm工程打开，PyCharm会自动识别并启用Django支持。</p>
<div class="blog_h2"><span class="graybg">启动开发服务器</span></div>
<p>执行下面的命令，可以启动开发服务器：</p>
<pre class="crayon-plain-tag"># 默认监听127.0.0.1:8000
python manage.py runserver   
# 监听所有网络接口
python manage.py runserver 0.0.0.0:8000</pre>
<p>注意此服务器会自动Reload最新的Python代码，因而你不需要在修改代码后重启服务器。但是某些时候，例如<span style="background-color: #c0c0c0;">添加了新文件，可能不会自动载入</span>，这时你需要手工重启。 </p>
<p>使用PyCharm时，用于启动开发服务器的Run Configuration会自动创建，点击工具栏按钮<img class="aligncenter size-full wp-image-12323 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2015/02/Selection_003.png" alt="Selection_003" width="132" height="21" />即可启动。 </p>
<div class="blog_h2"><span class="graybg">图书管理模块V1</span></div>
<p>到目前为止，开发环境已经搭建完毕，可以实现功能了。</p>
<p>在一个工程中，可以包含多个<span style="background-color: #c0c0c0;">Django应用，有时你可能觉得称之为模块更合适</span>。而<span style="background-color: #c0c0c0;">每个应用也可以归属于多个工程</span>。工程中的应用共享工程的配置——例如数据库连接。每个Django应用由一个Python包组成，这个包包含一些约定俗成的内容。</p>
<p>你可以把Django应用放置在PYTHONPATH中的任意目录，在这里，我们把它们放置在工程根目录下。执行下面的命令，创建一个应用：</p>
<pre class="crayon-plain-tag">python manage.py startapp bookmgr</pre>
<p>命令执行完毕后，工程结构如下：</p>
<pre class="crayon-plain-tag">├── bookmgr
│   ├── admin.py
│   ├── apps.py                 # 包含当前应用的定义及配置
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py               # 在这里定义模型类
│   ├── tests.py
|   ├── urls.py                 # 在这里定义URLconf，即URL和视图的映射关系
│   └── views.py                # 在这里创建新的视图
├── librarymgr
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   └── wsgi.py
└── manage.py</pre>
<div class="blog_h3"><span class="graybg">开发第一个视图</span></div>
<p>我们为图书管理模块添加一个最简单的视图，在响应中简单的输出欢迎文字：</p>
<pre class="crayon-plain-tag">from django.http.response import HttpResponse

def index(request):
    return HttpResponse("Welcome to Book Management module.") </pre>
<p>要调用视图，必须将其映射到某个URL，因而我们需要<pre class="crayon-plain-tag">URLconf</pre> ，要为bookmgr创建URLconf，只需要新建一个<pre class="crayon-plain-tag">urls.py</pre> 文件：</p>
<pre class="crayon-plain-tag">from django.conf.urls import url

from . import views

# 该固定名称的变量定义一系列的URL映射规则
urlpatterns = [
    # url函数定义URL到视图的映射，注意URL的base部分可能已经被父URLconf脱去。该函数接受4个参数
    # regex 匹配URL的正则式，注意此正则式不搜索GET/POST参数或者网址的域名部分，例如对于
    #       https://librarymgr.gmem.cc/bookmgr/?id=1，进行匹配判断的仅仅时bookmgr/
    # view  匹配后，需要调用的视图函数，并把HttpRequest作为第一个入参，正则式中捕获的分组依次作为后续位置参数
    #       如果使用正则式的命名分组，则捕获的分组作为关键字参数传入
    # kwargs 所谓关键字参数
    # name  为URL命名，以便从Django的其它地方——例如模板中——无歧义的引用之
    url(r'^$', views.index, name = 'index')
]</pre>
<p>完成URL到视图的映射后，必须把URLconf<span style="background-color: #c0c0c0;">包含到工程的根URLconf</span>中去： </p>
<pre class="crayon-plain-tag">from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 使用include函数引用其它URLconf
    # 在处理请求时，前缀匹配部分bookmgr/会自动去除，把剩余的部分传递给子URLconf，进行匹配
    url(r'^bookmgr/', include('bookmgr.urls'))
]</pre>
<p>现在启动开发服务器，输入网址http://127.0.0.1:8000/bookmgr/，你应该可以看到之前编写的欢迎文字。 </p>
<div class="blog_h2"><span class="graybg">配置数据库</span></div>
<p>传统的增删改查应用是离不开数据库的，我们可以为工程中所有模块配置共享的数据源。</p>
<p><pre class="crayon-plain-tag">librarymgr/settings.py</pre> 是一个<span style="background-color: #c0c0c0;">用作配置文件的Python模块</span>，我们可以在其中配置很多代表了Django设置的模块变量，例如数据库连接信息。</p>
<p>默认情况下，配置文件包含了SQLite的配置：</p>
<pre class="crayon-plain-tag"># 工程使用的所有数据源
DATABASES = {
    # 单个数据源
    'default': {
        # 引擎（数据库后端），其它可选引擎包括：
        # django.db.backends.postgresql、django.db.backends.mysql、django.db.backends.oracle等
        'ENGINE': 'django.db.backends.sqlite3',
        # 数据库的名称，如果使用SQLite，则指定数据库文件的绝对路径
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}</pre>
<p>不使用SQLite时，你需要提供额外的配置项。本章使用MySQL数据库：</p>
<pre class="crayon-plain-tag">DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}</pre>
<div class="blog_h3"><span class="graybg">初始化数据库</span></div>
<p>由于默认激活的那些应用程序必须使用表，因此必须初始化数据库，即<span style="background-color: #c0c0c0;">创建表结构、初始化表数据</span>，执行下面的命令：</p>
<pre class="crayon-plain-tag"># 根据DATABASES设置，以及应用的INSTALLED_APPS的database migrations，完成表创建和数据插入
python manage.py migrate</pre>
<div class="blog_h2"><span class="graybg">settings.py中的其它设置项</span></div>
<div class="blog_h3"><span class="graybg">TIME_ZONE</span></div>
<p>配置完数据库后，顺便设置一下时区：</p>
<pre class="crayon-plain-tag">TIME_ZONE = 'Asia/Shanghai'</pre>
<div class="blog_h3"><span class="graybg">INSTALLED_APPS</span></div>
<p>包含该实例激活的Django应用程序的名称，默认激活以下通用应用：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">应用程序</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>django.contrib.admin</td>
<td>管理站点（Admin site）</td>
</tr>
<tr>
<td>django.contrib.auth</td>
<td>身份验证系统</td>
</tr>
<tr>
<td>django.contrib.contenttypes</td>
<td>内容类型框架</td>
</tr>
<tr>
<td>django.contrib.sessions</td>
<td>会话支持</td>
</tr>
<tr>
<td>django.contrib.messages</td>
<td>一个消息框架</td>
</tr>
<tr>
<td>django.contrib.staticfiles</td>
<td>处理静态文件的框架</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">图书管理模块V2</span></div>
<div class="blog_h3"><span class="graybg">添加模型类</span></div>
<p>图书管理模块涉及的模型包括书籍类目、书籍、书籍实例等，我们需要将它们建模为Python类。</p>
<p>每个模型类都应该是django.db.models.Model的子类型，这是Django提供的充血模型基类。我们创建以下三个类：</p>
<pre class="crayon-plain-tag">from django.db import models

from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible  # 如果需要支持Python2
class Category(models.Model):
    # 每个模型会定义若干个Field子类型的变量，这些变量对应数据库的字段，二者默认名字一致
    # 字符串类型的字段
    # 第一个可选参数是人类友好的字段名称
    name = models.CharField('Category name', max_length = 128)
    # 对其它模型的引用，自引用必须用引号包围的类名或者'self'
    # Django支持o2m、m2o、m2m等常见数据库关系
    # blank=True表示表单验证时允许为空，null=True表示数据库字段可以为空
    parent_category = models.ForeignKey('self', blank=True, null=True )
    # 下面这个函数定义模型的字符串展示
    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length = 128)
    category = models.ForeignKey(Category)
    isbn = models.BigIntegerField()
    pub_date = models.DateTimeField()


class BookInstance(models.Model):
    no = models.IntegerField()
    book = models.ForeignKey(Book)
    # 枚举值和枚举字段
    STATUS_LEND_OUT = 1
    STATUS_RETURNED = 0
    status = models.SmallIntegerField(
        choices = ((STATUS_LEND_OUT, 'Lend out'), (STATUS_RETURNED, 'Returned')),
        # 字段可以提供默认值
        default = STATUS_RETURNED
    )</pre>
<div class="blog_h3"><span class="graybg">激活模型并生成迁移文件</span></div>
<p>Django根据上面一小片模型代码，可以自动生成Schema（表结构）。<span style="background-color: #c0c0c0;">首先，你需要安装bookmgr这个应用</span>：</p>
<pre class="crayon-plain-tag">INSTALLED_APPS = [
    # 在开始处添加下面的内容，BookmgrConfig类以及自动在apps.py中定义
    'bookmgr.apps.BookmgrConfig'
]</pre>
<p>然后执行下面的命令，生成一个<span style="background-color: #c0c0c0;">“迁移”</span>：</p>
<pre class="crayon-plain-tag"># 运行makemigrations命令，告知Django你对模型执行了修改（这里是添加了新模型）
# 并且你想把这个改变保存到一个迁移（migration）中
python manage.py makemigrations bookmgr
# 输出如下：
# Migrations for 'bookmgr':
#   0001_initial.py:
#     - Create model Book
#     - Create model BookInstance
#     - Create model Category
#     - Add field category to book</pre>
<p>所谓迁移，只是磁盘上的文件，Django用它来记录模型（以及对应的数据库Schema）的变化。</p>
<p>你可以到应用的migrations目录查看新生成的迁移0001_initial.py，它同样是一段标准的Python代码。</p>
<div class="blog_h3"><span class="graybg">执行数据库迁移</span></div>
<p>使用命令<pre class="crayon-plain-tag">python manage.py sqlmigrate bookmgr 0001</pre> ，可以将上面的0001号<span style="background-color: #c0c0c0;">迁移文件转换为SQL脚本</span>：</p>
<pre class="crayon-plain-tag">BEGIN;
--
-- Create model Book
--
CREATE TABLE `bookmgr_book` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(128) NOT NULL,
    `isbn` bigint NOT NULL, `pub_date` datetime NOT NULL
);
--
-- Create model BookInstance
--
CREATE TABLE `bookmgr_bookinstance` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `no` integer NOT NULL, 
    `status` smallint NOT NULL, `book_id` integer NOT NULL)
;
--
-- Create model Category
--
CREATE TABLE `bookmgr_category` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(128) NOT NULL, `parent_category_id` integer NOT NULL
);
--
-- Add field category to book
--
ALTER TABLE `bookmgr_book` ADD COLUMN `category_id` integer NOT NULL;
ALTER TABLE `bookmgr_book` ALTER COLUMN `category_id` DROP DEFAULT;
ALTER TABLE `bookmgr_bookinstance` ADD CONSTRAINT `bookmgr_bookinstance_book_id_73e96047_fk_bookmgr_book_id` 
    FOREIGN KEY (`book_id`) REFERENCES `bookmgr_book` (`id`);
ALTER TABLE `bookmgr_category` ADD CONSTRAINT `bookmgr_categ_parent_category_id_736b2e01_fk_bookmgr_category_id` 
    FOREIGN KEY (`parent_category_id`) REFERENCES `bookmgr_category` (`id`);
CREATE INDEX `bookmgr_book_b583a629` ON `bookmgr_book` (`category_id`);
ALTER TABLE `bookmgr_book` ADD CONSTRAINT `bookmgr_book_category_id_e3c50876_fk_bookmgr_category_id` 
    FOREIGN KEY (`category_id`) REFERENCES `bookmgr_category` (`id`);

COMMIT;</pre>
<p>关于上述脚本，你需要了解：</p>
<ol>
<li>我们没有定义主键字段id，但是父类Model定义了，因此继承而来。你也可以覆盖默认定义</li>
<li>主键的生成规则依据数据库的不同，自动设置</li>
<li>表名称默认为：应用名称_模型类名，模型类名自动转为小写</li>
<li>外键字段的名称为：引用字段名称_id</li>
</ol>
<p>sqlmigrate命令只是打印SQL，并不会执行数据库迁移。后者通过下面的命令完成：</p>
<pre class="crayon-plain-tag"># 把所有尚未执行的迁移应用到数据库中
python manage.py migrate</pre>
<p>注意，你可以在执行迁移之前执行check命令，确保工程中没有错误：</p>
<pre class="crayon-plain-tag">python manage.py check</pre>
<div class="blog_h2"><span class="graybg">关于迁移功能</span></div>
<p>Django的迁移功能很强大，它在django_migrations表中记录数据库的当前状态，因而<span style="background-color: #c0c0c0;">可以跟踪迁移的进度</span>。每次执行migrate命令时，Django总是把尚未执行的迁移应用到数据库中。迁移功能还能在升级数据库的同时<span style="background-color: #c0c0c0;">避免数据丢失</span>。</p>
<p>当你修改模型后，记住以下执行三个步骤：</p>
<ol>
<li>在models.py中修改模型类</li>
<li>创建迁移文件：<pre class="crayon-plain-tag">python manage.py makemigrations</pre> </li>
<li>执行数据库迁移：<pre class="crayon-plain-tag">python manage.py migrate</pre> </li>
</ol>
<div class="blog_h2"><span class="graybg">通过API操控模型</span></div>
<p>本节我们尝试通过交互式的Python控制台来试验Django的API，首先执行下面的命令启动控制台：</p>
<pre class="crayon-plain-tag">python manage.py shell</pre>
<p>现在可以可以编写代码试验功能了：</p>
<pre class="crayon-plain-tag"># 导入模型类
from bookmgr.models import Category, Book, BookInstance
# 查询所有类目
Category.objects.all()
# 输出：[]  因为当前是空表
# 新建一个类目，你可以使用关键字参数传递各字段的值，这是Model类的行为
c = Category(name='social science')
# 必须调用save()，才能持久化到数据库中
c.save()
# 根据主键get，亦可入参id=1
c_sc = Category.objects.get(pk=1)
# 外键字段赋值，使用模型对象，而不是其ID
c = Category(name='economics',parent_category=c_sc)
c.save()
c = Category(name='politics',parent_category=c_sc)
c.save()
# 自动产生的双向关联，使用对方模型名称_set访问
c_sc.category_set.all()    # [&lt;Category: Category object&gt;, &lt;Category: Category object&gt;]
# 通过双向关联创建新对象
c_sc.category_set.create(name='law')
c_sc.category_set.create(name='pedagogy')</pre>
<div class="blog_h2"><span class="graybg">通过Django Admin管理模型 </span></div>
<p>执行下面的命令，来创建超级用户</p>
<pre class="crayon-plain-tag">python manage.py createsuperuser</pre>
<p>现在通过http://127.0.0.1:8000/admin登陆，默认情况下你可以管理用户和组。</p>
<p>要允许Admin应用管理模型对象，你需要进行注册：</p>
<pre class="crayon-plain-tag">from django.contrib import admin

from .models import Category, Book, BookInstance

admin.site.register(Category)
admin.site.register(Book)
admin.site.register(BookInstance)</pre>
<p>重启服务后，你就可以在Web界面中添加、修改、删除模型了。 依据模型字段类型的不同，Admin应用会呈现不同的表单元素。</p>
<div class="blog_h2"><span class="graybg">图书管理模块V3</span></div>
<p>本节我们开发几个视图，用来展示Book模型。 </p>
<p>为了可维护性的考虑，不应当把HTML片段硬编码在视图函数中，我们可以使用Django提供的模板子系统来分离Python代码和HTML代码。依据Django的默认设置：</p>
<pre class="crayon-plain-tag">TEMPLATES = [
    {
        # 使用何种模板引擎
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 在应用的根目录下寻找templates目录  
        'APP_DIRS': True,
    },
]</pre>
<p>你需要在应用（bookmgr）的根目录下创建一个<pre class="crayon-plain-tag">templates</pre>子目录，Django会在此目录中寻找需要的模板。</p>
<div class="blog_h3"><span class="graybg">静态文件</span></div>
<p>任何网站都需要图片、脚本、样式表等静态资源。在Django框架中应用<pre class="crayon-plain-tag">django.contrib.staticfiles</pre> 专门负责处理静态文件，此应用默认已经加载。</p>
<p>你需要设置静态文件的URL前缀以及静态文件搜索目录：</p>
<pre class="crayon-plain-tag"># URL前缀
STATIC_URL = '/static/'
# 静态文件搜索目录，这里工程根目录下的static目录
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)</pre>
<p>在Django模板中，你可以类似<pre class="crayon-plain-tag">/static/css/default.css</pre> 这样的硬编码来引用静态文件，或者使用static这个模板标签（tag）：</p>
<pre class="crayon-plain-tag">{% load static from staticfiles %}
&lt;link rel="stylesheet" href="{% static "css/default.css" %}"&gt;</pre>
<p>要能够正常访问default.css，你必须将其存放到<pre class="crayon-plain-tag">librarymgr/static/css/default.css</pre>  </p>
<div class="blog_h3"><span class="graybg">Book首页</span></div>
<p>现在我们编写Book模块的首页，它展示欢迎信息和最新出版的10本图书。视图函数如下：</p>
<pre class="crayon-plain-tag">def book_index(request):
    # 上下文对象
    context = {
        'books': Book.objects.order_by('-pub_date')[:10]
    }

    # 加载模板
    template = loader.get_template('book/index.html')
    # 调用模板的render()方法，可以把上下文变量合并入模板，并生成字符串
    return HttpResponse(template.render(context, request))

    # 亦可使用下面的捷径：
    from django.shortcuts import render
    return render(request, 'book/index.html', context)</pre>
<p>URL映射定义如下：</p>
<pre class="crayon-plain-tag">url(r'^book/index', views.book_index, name = 'book_index')</pre>
<p>模板代码如下：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Book Index&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static 'css/default.css' %}"&gt;
&lt;/head&gt;
&lt;body&gt;

{% if books %}
    &lt;div&gt;&lt;span&gt;Recently 10 published books:&lt;/span&gt;&lt;/div&gt;
    &lt;table&gt;
        &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;Book name&lt;/th&gt;
            &lt;th&gt;Publish date&lt;/th&gt;
            &lt;th&gt;Action&lt;/th&gt;
        &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
        {% for book in books %}
            &lt;tr&gt;
                &lt;td&gt;&lt;a href="{% url 'book_view' book.id %}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;{{ book.pub_date }}&lt;/td&gt;
                &lt;td&gt;&lt;a href="{% url 'book_edit' book.id %}"&gt;&lt;img src="{% static 'images/edit.png' %}"/&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;
{% else %}
    No book available.
{% endif %}
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>这里可以大概了解一下Django模板的语法风格：</p>
<ol>
<li>使用<pre class="crayon-plain-tag">{% %}</pre> 语法声明流程控制指令</li>
<li>使用<pre class="crayon-plain-tag">{{ }}</pre> 语法声明占位符</li>
<li>使用点号导航语法来访问对象属性</li>
</ol>
<p>打开：http://127.0.0.1:8000/bookmgr/book/index，可以看到如下页面：</p>
<p><img class="aligncenter size-full wp-image-12613" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_index.png" alt="book_index" width="535" height="475" /></p>
<div class="blog_h3"><span class="graybg">避免URL硬编码</span></div>
<p>在上一节的模板中，我们有如下一段代码：</p>
<pre class="crayon-plain-tag">&lt;td&gt;&lt;a href="/bookmgr/book/{{ book.id }}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;</pre>
<p>上面的href属性中，硬编码了工程名称和应用名称，这会导致部署时修改URL困难。你随时可以url标签避免硬编码：</p>
<pre class="crayon-plain-tag">&lt;td&gt;&lt;a href="{% url 'book_view' book.id %}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;</pre>
<p>其中book_view就是URLConf中映射条目的name字段：</p>
<pre class="crayon-plain-tag">urlpatterns = [
    url(r'^book/(?P&lt;book_id&gt;[0-9]+)', views.book_view, name = 'book_view')
]</pre>
<div class="blog_h3"><span class="graybg">查看Book</span></div>
<p>上一节我们开发的视图展示了最近10条书籍信息，其中书籍名称具有链接，很明星，此链接点击后应该显示单本书籍的详细信息。</p>
<p>注意详细信息视图的URL设计：http://127.0.0.1:8000/bookmgr/book/view/4，仍然是RESTful风格的URL，最后的数字是书籍的主键。 我们可以这样映射URL：</p>
<pre class="crayon-plain-tag">url(r'^book/view/(?P&lt;book_id&gt;[0-9]+)', views.book_view, name = 'book_view')</pre>
<p>其中<pre class="crayon-plain-tag">(?P&lt;group_name&gt;...)</pre>  是正则式中的命名分组，使用命名分组后，可以把分组名作为视图函数的入参，其运行时值等于该命名分组的捕获。</p>
<p>视图函数如下：</p>
<pre class="crayon-plain-tag"># book_id这个入参名称必须和分组的命名一致
def book_view(request, book_id):
    try:
        book = Book.objects.get(pk = book_id)
    except Book.DoesNotExist:
        # 触发异常，Django会自动将其转换为404响应
        raise Http404('No such book:%i' % book_id)

    # 上面的try-except块也可以使用快捷方式代替：
    book = get_object_or_404(Book, pk = book_id)

    return render(request, 'book/view.html', {'book': book})</pre>
<p>模板代码如下：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;View Book&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static 'css/default.css' %}"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div&gt;&lt;span&gt;Detail information of: {{ book.name }}&lt;/span&gt;&lt;/div&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Book name:&lt;/th&gt;
        &lt;td&gt;{{ book.name }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;Category:&lt;/th&gt;
        &lt;td&gt;{{ book.category.name }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;ISBN:&lt;/th&gt;
        &lt;td&gt;{{ book.isbn }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;Publish date:&lt;/th&gt;
        &lt;td&gt;{{ book.pub_date }}&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>打开：http://127.0.0.1:8000/bookmgr/book/view/1，可以看到如下页面：</p>
<p><img class="aligncenter size-large wp-image-12625" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_view.png" alt="book_view" width="480" height="195" /></p>
<div class="blog_h2"><span class="graybg">图书管理模块V4</span></div>
<p>本节我们了解一下Django中的表单处理。 </p>
<div class="blog_h3"><span class="graybg">编辑Book</span></div>
<p>添加视图函数：</p>
<pre class="crayon-plain-tag">def book_edit(request, book_id, messages=[]):
    categories = Category.objects.all()
    try:
        book = Book.objects.get(pk=book_id)
    except Book.DoesNotExist:
        raise Http404('No such book:%i' % book_id)
    return render(request, 'book/edit.html', {'book': book, 'categories': categories, 'messages': messages})</pre>
<p>URL映射如下：</p>
<pre class="crayon-plain-tag">url(r'^book/edit/(?P&lt;book_id&gt;[0-9]+)', views.book_edit, name='book_edit')</pre>
<p>模板代码如下，注意其中的for和if控制结构：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Edit Book&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static "css/default.css" %}"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div&gt;&lt;span&gt;Edit: {{ book.name }}&lt;/span&gt;&lt;/div&gt;
{% if messages %}
    {% for msg in messages %}
        &lt;div class="{% if msg.ok %}ok{% else %}error{% endif %}"&gt;{{ msg.message }}&lt;/div&gt;
    {% endfor %}
{% endif %}
&lt;form action="{% url 'book_commit' %}" method="post"&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;th&gt;Book name:&lt;/th&gt;
            &lt;td&gt;
                {% csrf_token %} {% comment %} 必须，否则报错：CSRF verification failed. Request aborted. {% endcomment %}
                &lt;input type="hidden" name="id" value="{{ book.id }}"/&gt;
                &lt;input type="text" name="name" value="{{ book.name }}"/&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;Category:&lt;/th&gt;
            &lt;td&gt;
                &lt;select name="category"&gt;
                    {% for c in categories %}
                        &lt;option value="{{ c.id }}"
                                {% if c.id == book.category.id %}selected="selected"{% endif %}&gt;{{ c.name }}&lt;/option&gt;
                    {% endfor %}
                &lt;/select&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;ISBN:&lt;/th&gt;
            &lt;td&gt;
                &lt;input type="number" name="isbn" value="{{ book.isbn }}"/&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;Publish date:&lt;/th&gt;
            &lt;td&gt;
                &lt;input type="date" name="pub_date" value="{{ book.pub_date.isoformat }}"/&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/table&gt;
    &lt;input class="submit-btn" type="submit" value="Submit"/&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>点击首页的Action列的按钮 ，即可看到此编辑Book的页面：</p>
<p><img class="aligncenter size-full wp-image-12626" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_edit.png" alt="book_edit" width="326" height="232" /></p>
<div class="blog_h3"><span class="graybg">编辑Book后提交</span></div>
<p>点击上面的Submit按钮后，需要有服务器程序来处理表单数据，并返回处理结果。这些工作由book_commit视图函数完成： </p>
<pre class="crayon-plain-tag">def book_commit(request):
    # 类似于字典的、存放所有POST请求参数的对象。类似的 request.GET 用于访问GET请求参数
    data = request.POST
    messages = []
    categories = Category.objects.all()
    try:
        book_id = int(data['id'])
        category_id = int(data['category'])
        book = Book.objects.get(pk=book_id)
        category = Category.objects.get(pk=category_id)
        book.name = data['name']
        book.category = category
        book.isbn = int(data['isbn'])
        from dateutil.parser import parse
        book.pub_date = parse(data['pub_date'])
        messages.append({'ok': True, 'message': 'Book updated: %i' % book_id})
    except Book.DoesNotExist:
        messages.append({'ok': False, 'message': 'No such book: %i' % book_id})
    except Category.DoesNotExist:
        messages.append({'ok': False, 'message': 'No such category: %i' % category_id})
    except BaseException as e:
        print e.message
        messages.append({'ok': False, 'message': 'Failed to update book, %s' % e.message})
    return book_edit(request, book_id, messages)</pre>
<p>注意最后的return语句调用了其它视图函数，这类似于Java Servlet中的请求转发。另外一种处理方式是301重定向：</p>
<pre class="crayon-plain-tag"># reverse用于避免硬编码URL
return HttpResponseRedirect(reverse('book_edit', args=(book.id,)))</pre>
<p>这样也可以把用户带回到编辑页面。区别有两点：</p>
<ol>
<li>重定向方式下，不能方便的传递模板上下文变量。例如messages变量</li>
<li>重定向方式下，实际上就是让浏览器重新定位到book_edit这个URL，因此浏览器地址是/bookmgr/book/edit/3。前一种方式的浏览器地址是/bookmgr/book/commit</li>
</ol>
<div class="blog_h2"><span class="graybg">部署工程</span></div>
<p>完成开发后，我们需要把工程部署到生产环境的服务器上。</p>
<div class="blog_h3"><span class="graybg">Apache+mod_wsgi（嵌入式）</span></div>
<p>最简单的配置：</p>
<pre class="crayon-plain-tag"># /librarymgr/为URL前缀。匹配的URL将由wsgi.py处理
WSGIScriptAlias /librarymgr/ /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py
# PYTHONPATH，指定为工程根目录
WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr

&lt;Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr&gt;
&lt;Files wsgi.py&gt;
    Require all granted
&lt;/Files&gt;
&lt;/Directory&gt;

Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/
&lt;Directory /home/alex/Python/projects/pycharm/librarymgr/static/&gt;
    Require all granted
&lt;/Directory&gt;</pre>
<p>虚拟主机方式：</p>
<pre class="crayon-plain-tag">WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr
&lt;VirtualHost 127.0.0.1:80&gt;
    WSGIScriptAlias / /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py

    &lt;Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr&gt;
    &lt;Files wsgi.py&gt;
        Require all granted
    &lt;/Files&gt;
    &lt;/Directory&gt;

    Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/
    &lt;Directory /home/alex/Python/projects/pycharm/librarymgr/static/&gt;
        Require all granted
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/django-study-note">Django学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/django-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TileStache知识集锦</title>
		<link>https://blog.gmem.cc/tilestache-faq</link>
		<comments>https://blog.gmem.cc/tilestache-faq#comments</comments>
		<pubDate>Sat, 24 Jan 2015 10:02:43 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[GIS]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4868</guid>
		<description><![CDATA[<p>常见问题 如何缓存Bing、Yahoo等厂商提供的卫星图 添加代理（proxy）类型的图层，设置provider为相应的厂商。 [crayon-69d6611a17ec3883440417/] 内置的可用厂商可以到下面的代码中寻找： [crayon-69d6611a17ec8748036997/]</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/tilestache-faq">TileStache知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">如何缓存Bing、Yahoo等厂商提供的卫星图</span></div>
<p>添加代理（proxy）类型的图层，设置provider为相应的厂商。</p>
<pre class="crayon-plain-tag">{
  "layers": 
  {
    ......
    "bing-satellite":
    {
      "provider":
      {
        "name" : "proxy",
        "provider" : "MICROSOFT_AERIAL"
      }
    }
  }
}</pre><br />
内置的可用厂商可以到下面的代码中寻找：<br />
<pre class="crayon-plain-tag"># a handy list of possible providers, which isn't
# to say that you can't go writing your own.
builtinProviders = {
    'OPENSTREETMAP':    OpenStreetMap.Provider,
    'OPEN_STREET_MAP':  OpenStreetMap.Provider,
    'BLUE_MARBLE':      BlueMarble.Provider,
    'MAPQUEST_ROAD':   MapQuest.RoadProvider,
    'MAPQUEST_AERIAL':   MapQuest.AerialProvider,
    'MICROSOFT_ROAD':   Microsoft.RoadProvider,
    'MICROSOFT_AERIAL': Microsoft.AerialProvider,
    'MICROSOFT_HYBRID': Microsoft.HybridProvider,
    'YAHOO_ROAD':       Yahoo.RoadProvider,
    'YAHOO_AERIAL':     Yahoo.AerialProvider,
    'YAHOO_HYBRID':     Yahoo.HybridProvider,
    'CLOUDMADE_ORIGINAL': CloudMade.OriginalProvider,
    'CLOUDMADE_FINELINE': CloudMade.FineLineProvider,
    'CLOUDMADE_TOURIST': CloudMade.TouristProvider,
    'CLOUDMADE_FRESH':  CloudMade.FreshProvider,
    'CLOUDMADE_PALEDAWN': CloudMade.PaleDawnProvider,
    'CLOUDMADE_MIDNIGHTCOMMANDER': CloudMade.MidnightCommanderProvider,
    'STAMEN_TONER': Stamen.TonerProvider,
    'STAMEN_TERRAIN': Stamen.TerrainProvider,
    'STAMEN_WATERCOLOR': Stamen.WatercolorProvider,
    }</pre> 
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/tilestache-faq">TileStache知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/tilestache-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Python进行文本处理</title>
		<link>https://blog.gmem.cc/text-processing-with-python</link>
		<comments>https://blog.gmem.cc/text-processing-with-python#comments</comments>
		<pubDate>Fri, 06 Jan 2012 07:37:03 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[文本处理]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11459</guid>
		<description><![CDATA[<p>编程语言特性 字符串的表示 在Python2中，类型[crayon-69d6611a18291896983132-i/] 和[crayon-69d6611a18295529404786-i/] 分别用于表示单字节字符串和Unicode字符串；在Python3中，所有字符串都使用Unicode表示，类型为str。 字符串可以使用单引号、双引号、三引号包围， 三引号中的字符串原样保留，可以方便的编写多行文本。 字符串的引号开始前，可以增加[crayon-69d6611a18297163309349-i/] 、[crayon-69d6611a18299484772117-i/] 修饰符，分别表示目标字符串是Unicode类型、不启用字符转义。 Python没有单独的字符类型，字符只是长度为1的字符串 切片运算符 在Python语言中，字符串属于（不可变）序列，支持有限的切片操作： [crayon-69d6611a1829b234458533/] in运算符 在Python中，此操作符可以用于成员关系测试，或者迭代： [crayon-69d6611a1829e725897336/] +和*运算符 适用于任意切片的运算符还包括[crayon-69d6611a182a0479749290-i/] 和[crayon-69d6611a182a2984070908-i/] ，分别可以用于连接、重复字符串： [crayon-69d6611a182a4031110512/] 内置函数与方法 很多内置函数、方法可以用于操控字符串：  [crayon-69d6611a182a6269256520/] 字符串格式化 Python支持两种风格的字符串格式化操作，包括旧式的[crayon-69d6611a182a9885025153-i/] 操作符，以及新式的[crayon-69d6611a182ab795174613-i/] 方法。 基于%操作符的格式化 <a class="read-more" href="https://blog.gmem.cc/text-processing-with-python">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-python">使用Python进行文本处理</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">编程语言特性</span></div>
<div class="blog_h3"><span class="graybg">字符串的表示</span></div>
<p>在Python2中，类型<pre class="crayon-plain-tag">str</pre> 和<pre class="crayon-plain-tag">unicode</pre> 分别用于表示单字节字符串和Unicode字符串；在Python3中，所有字符串都使用Unicode表示，类型为str。</p>
<p>字符串可以使用单引号、双引号、三引号包围， 三引号中的字符串原样保留，可以方便的编写多行文本。</p>
<p>字符串的引号开始前，可以增加<pre class="crayon-plain-tag">u</pre> 、<pre class="crayon-plain-tag">r</pre> 修饰符，分别表示目标字符串是Unicode类型、不启用字符转义。</p>
<p>Python没有单独的字符类型，字符只是长度为1的字符串</p>
<div class="blog_h3"><span class="graybg">切片运算符</span></div>
<p>在Python语言中，字符串属于<span style="background-color: #c0c0c0;">（不可变）序列</span>，支持有限的切片操作：</p>
<pre class="crayon-plain-tag">s = "一二三四五六七"
# 取子串，起始索引包含，结束索引不包含
assert s[1:3] == '二三'
assert s[-3:] == '五六七'
i = 3
assert s[:i] + s[i:] == s

# 带步进的切片，默认1，即下一个元素的索引比上一个大1
assert s[1:4:1] == '二三四'
assert s[1:4:2] == '二四'
assert s[1:4:3] == '二'
# 负数的步进，下面这个用于生成倒序的串
assert s[::-1] == '七六五四三二一'
assert s[4:1:-1] == '五四三'</pre>
<div class="blog_h3"><span class="graybg">in运算符</span></div>
<p>在Python中，此操作符可以用于成员关系测试，或者迭代：</p>
<pre class="crayon-plain-tag">s = '01234567'
assert '3' in s
for c in s: print(c)</pre>
<div class="blog_h3"><span class="graybg">+和*运算符</span></div>
<p>适用于任意切片的运算符还包括<pre class="crayon-plain-tag">+</pre> 和<pre class="crayon-plain-tag">*</pre> ，分别可以用于连接、重复字符串：</p>
<pre class="crayon-plain-tag">assert '123' + '4567' == '1234567'
assert '123' * 3 == '123123123'</pre>
<div class="blog_h2"><span class="graybg">内置函数与方法</span></div>
<p>很多内置函数、方法可以用于操控字符串： </p>
<pre class="crayon-plain-tag">s = "01234567"
# 计算序列长度
assert len(s) == 8
# 获取序列元素的最小值
assert min(s) == '0'
# 获取序列元素的最大值
assert max(s) == '7'

# 查找，获得子串的索引，两个方法类似，但是index()在无法子串不存在时抛出ValueError
assert s.index('67') == 6
assert s.find('67') == 6
# 查找，子串判断
assert s.startswith('012') and s.endswith('4567')
# 替换，注意字符串是不可变的，因此返回的是副本
assert s.replace('3', 'x') == "012x4567"
# 分割
assert s.split('3') == ['012', '4567']
# 连接
assert ','.join(['1', '2', '3']) == '1,2,3'
# 对齐和填充
assert s.ljust(10, '0') == '0123456700'
# 去除空白，左侧
assert s.lstrip('0') == '1234567'
# 特性检查：is*()方法
assert s.isalnum()  # 是否仅包含字母数字</pre>
<div id="format-string" class="blog_h2"><span class="graybg"><a id="format-string"></a>字符串格式化</span></div>
<p>Python支持两种风格的字符串格式化操作，包括旧式的<pre class="crayon-plain-tag">%</pre> 操作符，以及新式的<pre class="crayon-plain-tag">format()</pre> 方法。</p>
<div class="blog_h3"><span class="graybg">基于%操作符的格式化</span></div>
<p>可以使用<pre class="crayon-plain-tag">%</pre> 操作符来进行字符串格式化，<pre class="crayon-plain-tag">s % d</pre> 左侧为格式模板，右边为占位符元组或字典，这与C语言的sprintf()函数类似。一个简单的例子如下：</p>
<pre class="crayon-plain-tag">'%s %s' % ('one', 'two') 
'%(k1)s %(k2)s' % {'k1':'one','k2':'two'}</pre>
<p>格式模板由普通字符、转换说明符组成，<span style="background-color: #c0c0c0;">转换说明符以%开头</span>，具体含义如下：</p>
<table class="full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px;"> 格式说明符</td>
<td>输出格式 </td>
</tr>
</thead>
<tbody>
<tr>
<td>d, i </td>
<td>十进制整数、长整数</td>
</tr>
<tr>
<td>u</td>
<td>无符号整数、长整数</td>
</tr>
<tr>
<td>o</td>
<td>八进制整数、长整数</td>
</tr>
<tr>
<td>x</td>
<td>十六进制整数、长整数</td>
</tr>
<tr>
<td>X</td>
<td>十六进制整数（大写字母）</td>
</tr>
<tr>
<td>f</td>
<td>浮点数，如[-]m.dddddd</td>
</tr>
<tr>
<td>e</td>
<td>浮点数，如[-]m.dddddde+xx</td>
</tr>
<tr>
<td>E</td>
<td>浮点数，如[-]m.ddddddE+xx</td>
</tr>
<tr>
<td>g, G</td>
<td>指数小于-4或更高精度时使用%e或%E,否则格式化为浮点数<br />保留N位有效数字，N为下表的修饰符字段，采取四舍五入方式</td>
</tr>
<tr>
<td>s</td>
<td>字符串或任意对象。格式化代码使用str()生成字符串</td>
</tr>
<tr>
<td>r</td>
<td>同repr ()生成的字符串</td>
</tr>
<tr>
<td>c</td>
<td>单个字符</td>
</tr>
<tr>
<td>%</td>
<td>字面值%</td>
</tr>
</tbody>
</table>
<p>在字符%与上表列出的转换字符之间，可以按顺序出现以下修饰符：</p>
<table class="full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px;"> 修饰符</td>
<td> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>(KEY)</td>
<td>表示从字典中取值的键</td>
</tr>
<tr>
<td>-</td>
<td>左对齐标志位，默认右对齐</td>
</tr>
<tr>
<td>+</td>
<td>保留数字的符号（即使正数）</td>
</tr>
<tr>
<td>0</td>
<td>表示一个零填充</td>
</tr>
<tr>
<td>M</td>
<td>表示输出的最小宽度</td>
</tr>
<tr>
<td>.</td>
<td>小数点，用于按精度分割字段宽度</td>
</tr>
<tr>
<td>N</td>
<td>要打印字符串的最大字符数、浮点数小数点后的位数、整数的最小位数</td>
</tr>
<tr>
<td>*</td>
<td>从元组中读取字段的宽度数字</td>
</tr>
</tbody>
</table>
<pre class="crayon-plain-tag">a = 42
b = 13.142783 c = "hello"
d = {'x':13, 'y':l.54321, 'z':'world'} 
e = 5628398123741234

r = "a is %d" % a                     # r = "a is 42"
r = "%10d %f" %(a,b)                  # r = "        42 13.142783"    整数宽度10，右对齐
r = "%+010d %E" %(a,b)                # r = "+000000042 1.314278E+01" 整数宽度10，右对齐，不足补0
r = "%(x)-10d %(y)0.3g" % d           # r = "13          1.54"        从字典d里取出值并对其执行转换
r = "%0.4s %s" %(c, d['z'])           # r = "hell world"              第一个字符串最大打印长度为4
r = "%*.*f" % (5,3,b)                 # r = "13.143"                  元组的前两项作为格式字符串的组成部分

#注意，格式化字符串与字典一起使用，行为类似于字符串插值：
p = {'age' : 28, 'name' : 'Alex'}
r = "%(name)s's age is %(age)d" % p   # r = "Alex's age is 28"</pre>
<div class="blog_h3"><span class="graybg">基于string.format()方法的格式化</span></div>
<p>通过调用作为模板的字符串的format()是Python新式的格式化机制。模板由普通字符和占位符组成，可以使用<pre class="crayon-plain-tag">{place:format_spec}</pre> 的形式来声明一个占位符，它包括两个部分：</p>
<ol>
<li>place，位置，可以是数字表示的位置参数、标识符表示的命名参数。默认情况下Python使用<pre class="crayon-plain-tag">__format__()</pre> 得到对象的文本表示，要改变此行为，可以扩展place为<pre class="crayon-plain-tag">place!type</pre> 形式。其中type可以取值s、r、a，分别调用<pre class="crayon-plain-tag">__str__()</pre> 、<pre class="crayon-plain-tag">__repr__()</pre> 和<pre class="crayon-plain-tag">ascii()</pre> 完成文本转换，a仅在Python3中被支持</li>
<li>format_spec，格式说明符，形式为：<pre class="crayon-plain-tag">[[fill]align][sign][#][0][width][,][.precision][type]</pre> ，各字段说明如下：</li>
</ol>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 100px;">fill</td>
<td>当指定align时，可以指定该字段，用于补白，默认空格</td>
</tr>
<tr>
<td>align</td>
<td>
<p>对齐方式：<pre class="crayon-plain-tag">&lt;</pre> 表示左对齐，<pre class="crayon-plain-tag">&gt;</pre> 表示右对齐，<pre class="crayon-plain-tag">=</pre> 表示在数字左侧补白，<pre class="crayon-plain-tag">^</pre> 表示居中对齐</p>
</td>
</tr>
<tr>
<td>sign</td>
<td>符号，可以取值<pre class="crayon-plain-tag">+</pre> 、<pre class="crayon-plain-tag">-</pre> 和空格</td>
</tr>
<tr>
<td>#</td>
<td>仅用于二、八、十六进制，如果出现此符号，输出分别添加前缀<pre class="crayon-plain-tag">0b</pre> 、<pre class="crayon-plain-tag">0o</pre> 、<pre class="crayon-plain-tag">0x</pre> </td>
</tr>
<tr>
<td>,</td>
<td>千位分隔符</td>
</tr>
<tr>
<td>width</td>
<td>指定最小宽度</td>
</tr>
<tr>
<td>precision</td>
<td>使用f或者F格式化时，小数的位数<br />使用g或者G格式化时，数字的位数<br />对于非数字，表示最大字符数<br />不可用于整数</td>
</tr>
<tr>
<td>type</td>
<td>格式化为什么类型，参考上文“格式说明符”</td>
</tr>
</tbody>
</table>
<p>代码示例：</p>
<pre class="crayon-plain-tag"># ###  参数占位符  ###
# 位置参数
'{0}, {1}, {2}'.format('a', 'b', 'c')  # 'a, b, c'
'{2}, {1}, {0}'.format('a', 'b', 'c')  # 'c, b, a'
'{}, {}, {}'.format('a', 'b', 'c')  # 'a, b, c' 要求2.7+
'Hello {0}'.format('Alex') == 'Hello Alex'
# 命名参数
'Coordinates: {latitude}, {longitude}'.format(latitude = '37.24N', longitude = '-115.81W')
'Hello {name}'.format(name = 'Alex') == 'Hello Alex'
# 访问对象属性
person = lambda: None
person.greeting = 'Hello'
person.name = 'Alex'
assert '{0.greeting} {0.name}'.format(person) == 'Hello Alex'
# 访问序列的元素
coord = (3, 5)
'X: {0[0]};  Y: {0[1]}'.format(coord)

# ###  格式控制  ###
# 对齐和填充
'{:&gt;30}'.format('right aligned')  # '                 right aligned'
'{:*^30}'.format('centered')  # '***********centered***********'
# 浮点数
'{:+f}; {:+f}'.format(3.14, -6.02)  # '+3.140000; -6.020000'
# 千分位
'{:,}'.format(1234567890)  # '1,234,567,890'

# 格式化时间
import datetime
d = datetime.datetime(2010, 7, 4, 12, 15, 58)
'{:%Y-%m-%d %H:%M:%S}'.format(d)  # '2010-07-04 12:15:58'</pre>
<div class="blog_h2"><span class="graybg">正则表达式</span></div>
<p>re模块可以用于正则表达式的处理。</p>
<div class="blog_h3"><span class="graybg">正则式字符序列</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">字符序列</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>text</td>
<td>匹配文字字符串text</td>
</tr>
<tr>
<td>.</td>
<td>匹配任何字符串，但换行符除外</td>
</tr>
<tr>
<td>^</td>
<td>匹配字符串的开始</td>
</tr>
<tr>
<td>$</td>
<td>匹配字符串的结束</td>
</tr>
<tr>
<td>*</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>+</td>
<td>匹配前面表达式的1个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>?</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能多的副本</td>
</tr>
<tr>
<td>*?</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>+?</td>
<td>匹配前面表达式的1个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>??</td>
<td>匹配前面表达式的0个或多个副本，匹配尽可能少的副本</td>
</tr>
<tr>
<td>{m}</td>
<td>准确匹配前面表达式的m个副本</td>
</tr>
<tr>
<td>{m, n}</td>
<td>匹配前面表达式的第m到n个副本，尽可能匹配多的副本</td>
</tr>
<tr>
<td>{m, n}?</td>
<td>匹配前面表达式的第m到n个副本，尽可能匹配少的副本</td>
</tr>
<tr>
<td>[...]</td>
<td>匹配一组字符，如<pre class="crayon-plain-tag">r'[abcde]'</pre> 或者<pre class="crayon-plain-tag">r'a-zA-Z'</pre> </td>
</tr>
<tr>
<td>[^...]</td>
<td>匹配集合中未包含的字符，如<pre class="crayon-plain-tag">r'[^0-9]'</pre> </td>
</tr>
<tr>
<td>A|B</td>
<td>匹配A或者B，A和B都是正则式</td>
</tr>
<tr>
<td>(...)</td>
<td>匹配圆括号中的正则表达式（圆括号中的内容为一个分组）并保存匹配的子字符串<br />在匹配时，分组中的内容可以使用所获得的<pre class="crayon-plain-tag">MatchObject</pre> 对象的<pre class="crayon-plain-tag">group()</pre> 方法获取</td>
</tr>
<tr>
<td>(?aiLmsux)</td>
<td>把字符解释为对应的标记位，例如i表示无视大小写</td>
</tr>
<tr>
<td>(?:...)</td>
<td>匹配圆括号中的正则表达式，但丢弃匹配的子字符串</td>
</tr>
<tr>
<td>(?P&lt;name&gt;...)</td>
<td>匹配圆括号中的正则表达式并创建一个命名（named）分组。分组名称必须是有效的Python标识符</td>
</tr>
<tr>
<td>(?P=name)</td>
<td>引用命名分组：匹配一个前面指定的分组所匹配的文本</td>
</tr>
<tr>
<td>(?#...)</td>
<td>一个注释。圆括号中的内容将被忽略</td>
</tr>
<tr>
<td>(?=...)</td>
<td>只有在括号中的模式匹配时，才匹配前面的表达式。例如<pre class="crayon-plain-tag">'Hello(?=World)'</pre> 只有在<pre class="crayon-plain-tag">World</pre> 匹配时才匹配<pre class="crayon-plain-tag">Hello</pre> </td>
</tr>
<tr>
<td>(?!...)</td>
<td>只有在括号中的模式不匹配时，才匹配前面的表达式</td>
</tr>
<tr>
<td>(?&lt;=...)</td>
<td>如果括号后面的表达式前面的值与括号中的模式匹配，则匹配该表达式。例如，只有当<pre class="crayon-plain-tag">'def'</pre> 前面是<pre class="crayon-plain-tag">'abc'</pre> 时，<pre class="crayon-plain-tag">r'(?&lt;=abc)def'</pre> 才会与它匹配</td>
</tr>
<tr>
<td>(?&lt;!...)</td>
<td>如果括号后面的表达式前面的值与括号中的模式不匹配，则匹配该表达式</td>
</tr>
<tr>
<td>\number</td>
<td>引用序号分组：匹配与前面的组编号匹配的文本。组编号范围为1到99，从左侧开始为组编号</td>
</tr>
<tr>
<td>\A</td>
<td>仅匹配字符串的开始标志</td>
</tr>
<tr>
<td>\b</td>
<td>匹配单词开始或结尾处的空字符串。单词是一个字母数字混合的字符序列，以空格或任何其他非字母数字字符结束</td>
</tr>
<tr>
<td>\B</td>
<td>匹配不在单词开始或结尾处的空字符串</td>
</tr>
<tr>
<td>\d</td>
<td>匹配任何十进制数。等同于<pre class="crayon-plain-tag">r'[0-9]'</pre> </td>
</tr>
<tr>
<td>\D</td>
<td>匹配任何非数字字符。等同于<pre class="crayon-plain-tag">r'[^0-9]'</pre> </td>
</tr>
<tr>
<td>\s</td>
<td>匹配任何空格字符。等同于<pre class="crayon-plain-tag">r'[\t\n\r\f\v]</pre> </td>
</tr>
<tr>
<td>\S</td>
<td>匹配任何非空格字符，等同于<pre class="crayon-plain-tag">r'[^\t\n\r\f\v]</pre> </td>
</tr>
<tr>
<td>\w</td>
<td>匹配任何字母数字字符</td>
</tr>
<tr>
<td>\W</td>
<td>匹配\w定义的集合中不包含的字符</td>
</tr>
<tr>
<td>\z</td>
<td>仅匹配字符串的结束标志</td>
</tr>
<tr>
<td>\\</td>
<td>匹配反斜杠本身</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"> 应用举例</span></div>
<pre class="crayon-plain-tag">import re
#获取字符串中与模式匹配的所有不重叠值，包括空匹配
#如果模式包含分组，将返回与分组匹配的文本列表，如果包含多个分组，则前述列表的每一项都是一个元组
#finditer与之类似，但是返回的是MatchObject的迭代器
#(\D+)(\d+) 结果：[('a', '0'), ('b', '12'), ('c', '345'), ('d', '6789')]
#(\D+)\d+   结果：['a', 'b', 'c', 'd']
#\D+\d+     结果：['a0', 'b12', 'c345', 'd6789']
m = re.findall(r'\D+\d+', 'a0b12c345d6789', re.IGNORECASE)  # 由于正则式特殊字符很多，故一般使用“原始字符串”
print m

r = re.compile(r'\D+\d+', re.IGNORECASE)       #编译得到一个正则式对象
#search()、match()返回MatcherObject对象


s = 'Iggle Piggle and Makka    Pakka are in the night garden'
# match、search等方法返回MatchObject：
# group(n)：返回匹配中第n个分组，如果n==0或者为空，返回整个匹配


# match函数从字符串起始处搜索正则式的匹配，找不到返回None
# 贪婪匹配
print re.match( r'.*ggle', s , re.IGNORECASE ).group()  # Iggle Piggle
# 非贪婪匹配
m = re.match( r'.*?(ggle)', s , re.IGNORECASE )
print m.group()  # Iggle
print m.group( 1 )  # ggle 返回匹配中的第一个分组

# search函数与match类似，但是不限定匹配从字符串首部开始
print re.search( r'.akka', s , re.IGNORECASE ).group()  # Makka

# sub(pattern, repl, string, count, flags) 函数用于字符串替换
# pattern 匹配模式 repl替换文本或回调 string被处理字符串 count需要处理的匹配个数
# 这里使用基于序号的分组，()界定了分组范围，分组可以嵌套：
# (M(\w+))是第一个分组，(\w+)是第二个分组，(P\2)是第三个分组
# repl可以指定转移字符，\g&lt;name&gt;用于引用匹配的正则式分组
print re.sub( r'(M(\w+))\s+(P\2)', '\g&lt;1&gt; \g&lt;3&gt;', s )  # 去掉Makka    Pakka之间多于的空格
# 命名分组，使用(?P&lt;GRP_NAME&gt;)定义命名分组，使用(?P=GRP_NAME)引用命名分组
print re.sub( r'(M(?P&lt;grp1&gt;\w+))\s+(P(?P=grp1))', 'M\g&lt;grp1&gt; \g&lt;3&gt;', s )
# repl还可以是函数
print re.sub( r'(M(?P&lt;grp1&gt;\w+))\s+(P(?P=grp1))', lambda match : match.group().upper(), s )</pre>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">not all arguments converted during string formatting</span></div>
<p>原因：进行字符串格式化时，如果%后面的参数时元组，那么必须对所有元组成员进行格式化，也就是说，前面的<span style="background-color: #c0c0c0;">占位符数量要和元组长度一致</span>。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/text-processing-with-python">使用Python进行文本处理</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/text-processing-with-python/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python网络编程</title>
		<link>https://blog.gmem.cc/python-network-programming</link>
		<comments>https://blog.gmem.cc/python-network-programming#comments</comments>
		<pubDate>Mon, 09 May 2011 07:40:49 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Network]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Coroutine]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=6627</guid>
		<description><![CDATA[<p>TCP编程代码示例 单线程Echo服务 [crayon-69d6611a18b5e238891114/] 以下是客户端代码： [crayon-69d6611a18b62501857764/] 基于asyncore模块的异步Echo服务 asyncore模块将网络活动抽象为事件，由事件循环分派出去进行异步处理。事件循环通过select()或者poll()系统调用构建。 [crayon-69d6611a18b64596541926/] 基于协程技术实现的异步Echo服务 [crayon-69d6611a18b67309559363/]</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-network-programming">Python网络编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">TCP编程代码示例</span></div>
<div class="blog_h3"><span class="graybg">单线程Echo服务</span></div>
<pre class="crayon-plain-tag">from socket import *  # @UnusedWildImport
DEFAULT_PORT = 1918
if __name__ == '__main__':
    # 创建基于IPv4的TCP套接字对象
    s = socket( AF_INET, SOCK_STREAM )
    # 绑定到通配符地址的1918端口
    s.bind( ( '0.0.0.0', DEFAULT_PORT ) )
    logging.debug( 'Echo server is listening on port %d', DEFAULT_PORT )
    # 开始监听，最大排队数量(backlog)为10
    s.listen( 10 )
    
    while True:
        # 接受一个客户端连接请求，返回套接字对象和地址的元组
        client, addr = s.accept()
        logging.debug( '%s connected', addr )
        msg = client.recv( 1024 )
        logging.debug( 'Received message : %s', msg )
        client.send( msg )
        client.close()</pre>
<p>以下是客户端代码：</p>
<pre class="crayon-plain-tag">from socket import *  # @UnusedWildImport

DEFAULT_PORT = 1918
if __name__ == '__main__':
    # 创建基于IPv4的TCP套接字对象
    s = socket( AF_INET, SOCK_STREAM )
    # 连接到服务器端
    s.connect( ( '127.0.0.1', DEFAULT_PORT ) )
    s.send( 'Hello Server!' )
    logging.debug( 'Echo from server: %s', s.recv( 1024 ) )</pre>
<div class="blog_h3"><span class="graybg">基于asyncore模块的异步Echo服务</span></div>
<p>asyncore模块将网络活动抽象为事件，由事件循环分派出去进行异步处理。事件循环通过select()或者poll()系统调用构建。</p>
<pre class="crayon-plain-tag"># 主分发器，关联服务器端监听套接字
class EchoSocketDispatcher( asyncore.dispatcher ):
    def __init__( self, port ):
        asyncore.dispatcher.__init__( self )
        # 创建当前分发器关联的套接字对象
        self.create_socket( socket.AF_INET, socket.SOCK_STREAM )
        self.bind( ( '0.0.0.0', port ) )
        self.listen( 1024 )
    def handle_accept( self ):
        client, addr = self.accept()
        logging.debug( 'Accepted connection from %s', addr )
        return EchoDispatcher( client )
# 子分发器，处理单个客户端连接套接字    
class EchoDispatcher( asyncore.dispatcher ):
    
    def __init__( self, client ):
        asyncore.dispatcher.__init__( self, client )
        self.chunk = None
    # 何时允许读
    def readable( self ):
        logging.debug( 'HH' )
        return True
    # 何时允许写
    def writable( self ):
        return self.chunk != None
    # 处理读取
    def handle_read( self ):
        self.chunk = self.recv( 8192 )
        logging.debug( 'Received message: %s', self.chunk )
    # 处理写入
    def handle_write( self ):
        self.send( self.chunk )
        self.chunk = None
    def handle_close( self ):
        logging.debug( 'Connection closed by peer.' )
        asyncore.dispatcher.handle_close( self )
        
DEFAULT_PORT = 1918     
if __name__ == '__main__':
    dispatcher = EchoSocketDispatcher( DEFAULT_PORT )
    logging.debug( 'Start polling on %d', DEFAULT_PORT )
    # 持续执行轮询
    asyncore.loop( use_poll=True, timeout=10 )</pre>
<div class="blog_h3"><span class="graybg"><a id="echo-service-by-coroutine"></a>基于协程技术实现的异步Echo服务</span></div>
<pre class="crayon-plain-tag"># Tasklet、SystemCall、Scheduler模拟了一个微型的操作系统

# 模拟进程
class Tasklet( object ):
    def __init__( self, target ):
        self.target = target  # 当前目标协程
        self.sendval = None  # 协程恢复时发送的值
        self.stack = []  # 历史协程的栈，对方法调用机制的一种模拟
    def run( self ):
        try:
            # 执行协程到下一次退出，并获取协程的返回值
            result = self.target.send( self.sendval )
            if isinstance( result, SystemCall ):
                # 如果是一个“系统调用”包装对象，栈状态不变，类似于中断的效果
                return result
                # 当前目标将期望此系统调用的结果被发送给它，以继续执行
            elif isinstance( result, types.GeneratorType ):
                # 如果结果是一个生成器对象实例，相当于调用一个新方法，需要将当前目标压栈
                self.stack.append( self.target )
                self.sendval = None
                self.target = result
            else:
                # 如果结果不是一个生成器对象实例，相当于新方法调用返回，需要废弃当前目标，并弹出栈顶作为新目标
                if not self.stack : return
                self.sendval = result
                self.target = self.stack.pop()
                
        except StopIteration: 
            # 当前协程已经终止，需要移除，并弹出栈顶作为新的目标
            if not self.stack: raise
            self.sendval = None
            self.target = self.stack.pop()

# 模拟系统调用
class SystemCall( object ):
    def handle( self, sched, task ):
        pass
# 读写“系统调用”实现
class ReadWait( SystemCall ):
    def __init__( self, f ):
        self.file = f
    def handle( self, sched, task ):
        fd = self.file.fileno()
        sched.readwait( task, fd )
class WriteWait( SystemCall ):
    def __init__( self, f ):
        self.file = f
    def handle( self, sched, task ):
        fd = self.file.fileno()
        sched.writewait( task, fd )

# 调度程序，相当于操作系统的调度例程
class Scheduler( object ):
    def __init__( self ):
        # 这个队列相当于操作系统的进程集
        self.task_queue = collections.deque()
        self.read_waiting = {}
        self.write_waiting = {}
        self.taskcount = 0
    def new( self, target ):
        newtask = Tasklet( target )
        self.schedule( newtask )
        self.taskcount += 1
    def schedule( self, task ):
        self.task_queue.append( task )
    def readwait( self, task, fd ):
        self.read_waiting[fd] = task
    def writewait( self, task, fd ):
        self.write_waiting[fd] = task
    def mainloop( self, count=-1, timeout=None ):
        while self.taskcount:
            # 如果有I/O事件的队列，那么先轮询I/O事件
            if self.read_waiting or self.write_waiting:
                # 如果进程队列为空则等待时间为timeout，否则等待时间为0
                # 队列为空的场景：没有创建进程；所有进程都在I/O等待集上
                # timeout默认为一直等待，直到有可能的描述符
                wait = 0 if self.task_queue else timeout
                # 查看一组文件描述符的输入、输出、异常状态。返回输入、输出、异常准备就绪的列表的元组
                # 前三个参数是整数描述符的列表；或者带有fileno()方法的对象（该方法返回文件描述符）
                # wait不指定会一直等待直到至少一个文件描述符准备好为止，为0则指进行一次轮询即返回
                r, w , e = select.select( self.read_waiting, self.write_waiting, [], wait )
                # 将就绪的文件描述符从等待集中移除，加入到正常调度集中
                for fd in r:
                    self.schedule( self.read_waiting.pop( fd ) )
                for fd in w:
                    self.schedule( self.write_waiting.pop( fd ) )
            # 逐个执行队列上的任务
            while self.task_queue:
                # 取出一个任务，从进程列表中移除
                task = self.task_queue.popleft()
                try:
                    # 执行这个任务
                    result = task.run()
                    if isinstance( result, SystemCall ):
                        # 模拟系统调用陷入内核
                        result.handle( self, task )
                    else: 
                        # 其它的，要么相当于方法调用，要么相当于方法返回，继续调度
                        self.schedule( task )
                except StopIteration :
                    # 不需要再考虑此任务，其生命周期已经结束
                    self.taskcount -= 1
            else:
                if count &gt; 0: count -= 1
                if count == 0:
                    return

# Echo服务器协程
from socket import socket, AF_INET, SOCK_STREAM
def EchoServer( host, port , sched ):
    # 服务器监听套接字
    s = socket( AF_INET, SOCK_STREAM )
    s.bind( ( host , port ) )
    logging.debug( 'EchoServer listening on %s:%d', host, port )
    s.listen( 128 )
    while True:
        # 等待服务器监听套接字可读
        yield ReadWait( s )
        conn, addr = s.accept()
        logging.debug( 'Client connected: %s', addr )
        sched.new( EchoSocket( conn ) )
def EchoSocket( conn ):
    while True:
        # 等待套接字可读
        yield ReadWait( conn )
        chunk = conn.recv( 1024 )
        if chunk:
            logging.debug( 'Received message: %s', chunk )
            yield WriteWait( conn )
            conn.send( chunk )
            

if __name__ == '__main__':
    sched = Scheduler()
    sched.new( EchoServer( '0.0.0.0', 1918, sched ) )
    sched.mainloop( -1, None )</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-network-programming">Python网络编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/python-network-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python并发编程</title>
		<link>https://blog.gmem.cc/python-concurrent-programming</link>
		<comments>https://blog.gmem.cc/python-concurrent-programming#comments</comments>
		<pubDate>Wed, 04 May 2011 07:58:48 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[并发编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4458</guid>
		<description><![CDATA[<p>基本概念 一个运行的程序称作进程。每个进程都有自己的系统状态，包括内存、已打开文件列表、用于跟踪正在执行的指令的程序计数器以及用于保存函数的局部变量的调用栈。通常在一个控制流序列中， 进程逐条执行语句，这一般称为进程的主线程。在任何一个给定的时刻，程序都只做一件事情。 程序可以使用库函数创建新的进程，比如os或subprocess模块中的函数（例如os.fork()、 subprocess.Popen等)。这些叫做子进程的进程是作为完全独立的实体运行的。 每个子进程都有自己的私有系统状态和执行主线程。因为子进程是独立的，所以它可以与原始进程并发执行。也就是说，创建子进程的进程可以继续处理别的事情，同时子进程在后台执行它自己的任务。 尽管进程是孤立的，但它们可以彼此通信，这称为进程间通信（Interprocess Communication, IPC)。 进程间通信的最常见形式是基于消息传递。一条消息就是一块原始字节的缓存。然后，就可以使用像 send()和recv()这样的简单操作，通过一个I/O通道（如管道或网络套接字）来传输或接收消息。另一种不太常见的IPC机制依赖于内存映射区域（参见nmap模块)。借助内存映射，进程可以创建共享的内存区域。如果对这些内存区域进行修改，那么査看这些区域的所有进程都能看到这些修改。 如果需要同时处理多个任务，可以在一个应用程序内使用多个进程，每个进程负责处理一部分工 作。 另一种将工作细分为多个任务的方法是使用线程。线程类似于进程，也具有自己的控制流和执行栈。但线程运行在创建它的进程内部，它们共享所有的数据和系统资源。当应用程序需要并发执行多个任务时，可以使用线程，但可能存在大量需要在各个任务之间共享的系统状态。 使用多个进程或线程时，主机操作系统负责安排它们的工作。安排的具体做法是：给每个进程（线程）分配一个小的时间片，并在所有活动任务之间快速循环——给每个任务分配一部分可用的CPU周期。例如，如果系统同时运行10个活动的进程，操作系统将给每个进程分配大约1/10的CPU时间，同时在进程之间快速循环。在具有多个CPU核心的系统上，操作系统在安排进程时可以尽可能使用每个 CPU，从而并行执行进程。 编写使用并发执行的程序原本就很复杂，其复杂性的一个主要原因就是同步和访问共享数据。也就是说，多个任务同时更新一个数据结构可能导致数据损坏和程序状态不一致（这个问题的正式说法是竞争条件）。要解决这些问题，并发程序必须找出关键的代码段，并使用互斥锁和其他类似的同步手段保护它们。例如，如果不同的线程尝试同时向一个文件写入数据，可以使用互斥锁来同步它们的 Python中的并发编程 在大多数系统上，Python同时支持消息传递和基于线程的并发编程。Python线程受到的限制有很多，Python解释器使用了内部的GIL (Global Interpreter Lock，全局解释器锁定），在任意指定的时刻只允许单个 <a class="read-more" href="https://blog.gmem.cc/python-concurrent-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-concurrent-programming">Python并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基本概念</span></div>
<p>一个<span style="background-color: #c0c0c0;">运行的程序称作进程</span>。每个进程都有自己的<span style="background-color: #c0c0c0;">系统状态，包括内存、已打开文件列表、用于跟踪正在执行的指令的程序计数器以及用于保存函数的局部变量的调用栈</span>。通常在一个控制流序列中， 进程逐条执行语句，这一般称为进程的主线程。在<span style="background-color: #c0c0c0;">任何一个给定的时刻，程序都只做一件事情</span>。 程序可以使用库函数<span style="background-color: #c0c0c0;">创建新的进程，比如os或subprocess模块中的函数（例如os.fork()、 subprocess.Popen等)</span>。这些叫做<span style="background-color: #c0c0c0;">子进程</span>的进程是作为<span style="background-color: #c0c0c0;">完全独立的实体运行的。</span> 每个子进程都有自己的私有系统状态和执行主线程。因为子进程是独立的，所以它可以与原始进程并发执行。也就是说，创建子进程的进程可以继续处理别的事情，同时子进程在后台执行它自己的任务。</p>
<p>尽管进程是孤立的，但它们可以彼此通信，这称为<span style="background-color: #c0c0c0;">进程间通信（Interprocess Communication, IPC)</span>。 进程间通信的<span style="background-color: #c0c0c0;">最常见形式是基于消息传递</span>。一条消息就是一块原始字节的缓存。然后，就可以<span style="background-color: #c0c0c0;">使用像 send()和recv()这样的简单操作，通过一个I/O通道（如管道或网络套接字）来传输或接收消息</span>。另一种不太常见的IPC机制依赖于内存映射区域（参见nmap模块)。<span style="background-color: #c0c0c0;">借助内存映射，进程可以创建共享的内存区域</span>。如果对这些内存区域进行修改，那么査看这些区域的所有进程都能看到这些修改。 如果需要同时处理多个任务，可以在一个应用程序内使用多个进程，每个进程负责处理一部分工 作。</p>
<p>另一种将工作细分为多个任务的方法是使用线程。线程类似于进程，也具有自己的控制流和执行栈。但<span style="background-color: #c0c0c0;">线程运行在创建它的进程内部，它们共享所有的数据和系统资源</span>。当应用程序需要并发执行多个任务时，可以使用线程，但可能存在大量需要在各个任务之间共享的系统状态。</p>
<p>使用多个进程或线程时，主机操作系统负责安排它们的工作。安排的具体做法是：<span style="background-color: #c0c0c0;">给每个进程（线程）分配一个小的时间片，并在所有活动任务之间快速循环</span>——给每个任务分配一部分可用的CPU周期。例如，如果系统同时运行10个活动的进程，操作系统将给每个进程分配大约1/10的CPU时间，同时在进程之间快速循环。在具有多个CPU核心的系统上，操作系统在安排进程时可以尽可能使用每个 CPU，从而并行执行进程。 编写使用并发执行的程序原本就很复杂，其复杂性的一个主要原因就是同步和访问共享数据。也就是说，<span style="background-color: #c0c0c0;">多个任务同时更新一个数据结构可能导致数据损坏和程序状态不一致（这个问题的正式说法是竞争条件）</span>。要解决这些问题，并发程序必须找出关键的代码段，并使用<span style="background-color: #c0c0c0;">互斥锁和其他类似的同步手段保护它们</span>。例如，如果不同的线程尝试同时向一个文件写入数据，可以使用互斥锁来同步它们的</p>
<div class="blog_h1"><span class="graybg">Python中的并发编程</span></div>
<p>在大多数系统上，Python同时支持消息传递和基于线程的并发编程。Python线程受到的限制有很多，Python解释器使用了内部的<span style="background-color: #c0c0c0;">GIL (Global Interpreter Lock，全局解释器锁定），在任意指定的时刻只允许单个 Python线程执行</span>。无论系统上存在多少个可用的CPU核心，这限制了 <span style="background-color: #c0c0c0;">Python程序只能在一个处理器上 运行</span>。如果一个应用程序的<span style="background-color: #c0c0c0;">大部分是I/O受限（bounded）的，那么使用线程一般没有问题</span>，因为<span style="background-color: #c0c0c0;">额外的处理器对于花费大多数时间等待事件的程序帮助不大</span>。<span style="background-color: #c0c0c0;">对于涉及大量CPU处理的应用程序而言，使用线程来细分工作没有任何好处</span>，反而还会降低程序的运行速度。</p>
<p>避免大量使用线程的较为常见的做法是把这类应用程序<span style="background-color: #c0c0c0;">重新构造为异步事件处理系统</span>。例如，<span style="background-color: #c0c0c0;">中心的事件循环可以监控使用select模块的所有I/O,并将异步事件分离给大量I/O 处理器</span>。这是诸如<span style="background-color: #c0c0c0;">asyncore</span>这样的库模块和诸如<span style="background-color: #c0c0c0;">Twisted</span>等流行的第三方模块的基础。</p>
<p>如果要在Python中进行各种类型的并发编程，<span style="background-color: #c0c0c0;">消息传递很可能是必须熟练掌握的概念</span>。 即便使用线程，常推荐的方法也是将应用程序的结构设计为<span style="background-color: #c0c0c0;">大量独立的线程集合，这些线程通过消息队列交换数据</span>。这种特别的方法往往很少出错，因为它极大地<span style="background-color: #c0c0c0;">减少了对使用锁定和其他同步手段的需求</span>。<span style="background-color: #c0c0c0;">消息传递还会自然扩展到网络和分布式系统中</span>。消息传递的抽象也与髙级Python功能（如协程）有关。例如，协程是可以接收并处 理发送给它的消息的函数。因此，掌握了消息传递之后，写出的程序会较之前更灵活。</p>
<div class="blog_h1"><span class="graybg">multiprocessing模块</span></div>
<p>该模块为<span style="background-color: #c0c0c0;">在子进程中运行任务、通信和共享数据，以及执行各种形式的同步提供支持</span>。接口风格与threading模块类似，但要注意进程之间没有任何共享状态。</p>
<div class="blog_h2"><span class="graybg">多进程处理的一般建议</span></div>
<ol>
<li>确保进程之间传递的所有数据都能够序列化</li>
<li>避免使用共享数据，尽可能使用消息传递和队列。使用消息传递时，不必过于担心同步、锁定和其他问题。当进程的数量增长时，它往往还能提供更好的扩展</li>
<li>在必须行在单独进程中的函数内部，不要使用全局变量而应当显式地传递参数</li>
<li>尽量不要在同一个程序中混合使用线程和多线程处理</li>
<li>特別要注意关闭进程的方式。一般而言，需要显式地关闭进程，并使用一种定义良好的终止模式，而不要仅仅依赖于垃圾收集或者被迫使用terminate ()操作强制终止子进程</li>
<li>管理器和代理的使用与分布式计算中的多个概念密切相关（例如，分布式对象)</li>
<li>尽管此模块可以工作在Windows上，但还是应该仔细阅读官方文档中的各种微妙细节</li>
<li>最重要的一点是：尽量让事情变得简单</li>
</ol>
<div class="blog_h2"><span class="graybg">进程管理</span></div>
<p>进程是multiprocessing模块的核心，其构造函数如下：</p>
<pre class="crayon-plain-tag">Process([group [, target [, name [, args [, kwargs ]]]]])
# group 预留参数
# target 进程启动时需要执行的可调用对象
# name  进程名称的描述性字符串
# args 传递给target的位置参数的元组
# kwargs 传递个target的关键字参数的字典</pre>
<p>Process具有以下实例方法、属性：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>is_alive()</td>
<td>如果进程仍在运行，返回True</td>
</tr>
<tr>
<td>join([timeout])</td>
<td>等待该进程停止，最大timeout</td>
</tr>
<tr>
<td>run()</td>
<td>继承Process并改写run()方法，其效果与传递target一样</td>
</tr>
<tr>
<td>start()</td>
<td>启动进程</td>
</tr>
<tr>
<td>terminate()</td>
<td>强制终止进程，不会进行任何清理操作。如果进程创建了子进程，子进程变为僵尸进程；如果进程持有锁定则可能导致死锁</td>
</tr>
<tr>
<td>autokey</td>
<td>进程的身份验证键。除非显式设定，这是由os.urandom()函数生成的32字符的字符串</td>
</tr>
<tr>
<td>daemon</td>
<td>
<p>一个Boolean标志，指示进程是否是后台进程。当创建它的Python进程终止时，后台（Daemonic）进程将自动终止。另外，禁止后台进程创建自己的新进程。daemon的值必须在使用start()函数 启动进程之前进行设置</p>
</td>
</tr>
<tr>
<td>exitcode</td>
<td>
<p>进程的整数退出代码。如果进程仍然在运行，它的值为None。如果值为负数，则-N表示进程由信号N所终止</p>
</td>
</tr>
<tr>
<td>name</td>
<td>
<p>进程的名称</p>
</td>
</tr>
<tr>
<td>pid</td>
<td>
<p>进程的整数ID</p>
</td>
</tr>
</tbody>
</table>
<p>简单的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
import time
def clock(interval):
    while True:
        print "Current time is %s" % time.ctime()
        time.sleep(interval)
if __name__ == '__main__':
    p = multiprocessing.Process(
        target=clock,
        args=(15,)
    )
    p.start()

#使用扩展子类的方式
class ClockProcess(multiprocessing.Process):
    def __init__(self,interval):
        multiprocessing.Process.__init__(self)
        self.interval = interval
    def run(self):
        while True:
            print "Current time is %s" % time.ctime()
            time.sleep(self.interval)</pre>
<div class="blog_h2"><span class="graybg">进程间通信</span></div>
<p>multiprocessing模块支持<span style="background-color: #c0c0c0;">进程间通信的两种主要形式：管道和队列</span>，这两种方法都是使用消息传递实现的。</p>
<div class="blog_h3"><span class="graybg"><strong>使用队列</strong></span></div>
<p>使用Queue([maxsize])可以创建队列，maxsize是队列中允许的最大项数，省略表示无穷大，具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>cancel_join_thread()</td>
<td>不在进程退出时自动join后台线程。可防止join_thread()方法阻塞</td>
</tr>
<tr>
<td>close() </td>
<td>关闭队列，防止队列中加入更多数据。调用此方法时，后台线程将<span style="background-color: #c0c0c0;">继续写入那些已入队列但尚未写入的数据</span>，但将在此方法完成时马上关闭。如果被垃圾收集，将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如，如果某个使用者正被阻塞在get ()操作 上，关闭生产者中的队列不会导致get ()方法返回错误</td>
</tr>
<tr>
<td>empty()</td>
<td>如果调用此方法时队列为空，返回True。如果其他进程或线程正在往队列中添加项目，结果是不可靠的。也就是说，在返回和使用结果之间，队列中可能已经加入了新的项目</td>
</tr>
<tr>
<td>full()</td>
<td>如果队列已满，则返回True。由于线程的存在，结果也可能不可靠</td>
</tr>
<tr>
<td>get([block [, timeout]]）</td>
<td>返回队列中的一个项。 如果队列为空，此方法将阻塞，直到队列中有项目可用为止。block用于控制阻塞行为，默认为True。 如果设置为False，可能引发Queue.Empty异常。timeout是可选超时时间，在阻塞模式中如果在指定的时间间隔内没有项变为可用，引发Queue.Empty异常</td>
</tr>
<tr>
<td>get_nowait()</td>
<td>等价于get(False)</td>
</tr>
<tr>
<td>join_thread()</td>
<td>当前线程join队列的后台线程。用于在调用close()方法之后，等待所有队列项被消耗</td>
</tr>
<tr>
<td>put(item [, block [, timeout]])</td>
<td>将item放入队列。如果队列已满，此方法将阻塞至有空间可用为止。block控制阻塞行为，默认为True。如果设置为False，将引发Queue.Empty异常。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常</td>
</tr>
<tr>
<td>put_nowait()</td>
<td>等价于put(item, False)</td>
</tr>
<tr>
<td>qsize()</td>
<td>返回队列中目前项目的正确数量。此函数的结果并不可靠</td>
</tr>
</tbody>
</table>
<p>使用JoinableQueue([maxsize])可以创建可连接的共享队列，允许消费者通知生产者消息项已经被成功处理，具有额外的方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>task_done()</td>
<td>消费者使用此方法发出信号，表示通过get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量，将引发ValueError异常</td>
</tr>
<tr>
<td>join()</td>
<td>生产者使用此方法进行阻塞，直到队列中的所有项目均被处理。阻塞将持续到为队列中的每个项目均调用task_done()方法为止。</td>
</tr>
</tbody>
</table>
<p>使用队列通信的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def consumer(inputq):
    while True:
        item = inputq.get()
        if item == -1: break    #特殊标记时终止
        print item
        inputq.task_done()
def produce(seq, outputq):
    for item in seq:
        outputq.put(item)
if __name__ == '__main__':
    q = multiprocessing.JoinableQueue()
    cons_p = multiprocessing.Process(target=consumer, args=(q,))
    cons_p.daemon = True
    cons_p.start()
    #这里可以继续创建多个消费者进程，但是每个消息只能被一个进程消费
    seq = [0, 1, 2, 3, 4 , -1]  #特殊标记，提示消费者流程结束
    produce(seq, q)
    q.join()  # 当前进程等待队列被消耗完毕</pre>
<div class="blog_h3"><span class="graybg">使用管道</span></div>
<p>可以在进程之间创建一根管道：</p>
<pre class="crayon-plain-tag">#创建管道的函数：duplex表示是否双向通信，默认True
#如果duplex=False，那么conn1只能接收，conn2只能发送
(conn1,conn2) = Pipe([duplex])</pre>
<p>Pipe函数的返回值为Connection对象的元组，Connection对象具有以下属性和方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>close()</td>
<td>关闭连接。如果connection被垃圾收集，将自动调用此方法</td>
</tr>
<tr>
<td>fileno()</td>
<td>返回连接使用的整数文件描述符</td>
</tr>
<tr>
<td>poll([timeout])</td>
<td>如果连接上的数据可用，返回True。timeout指定等待的最长时限。如果省略此参数，方法将立即返回结果。如果将timeout置为None，操作将无限期地等待数据到达</td>
</tr>
<tr>
<td>recv()</td>
<td>接收send()方法返回的对象。如果连接的另一端已经关闭，再也不存在任何数据，将引发EOFError异常</td>
</tr>
<tr>
<td>recv_bytes([maxlength])</td>
<td>接收send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。 如果进入的消息超过了这个最大值，将引发IOError异常，并且在连接上无法进行进一步读取。如果连接的另一端已经关闭，再也不存在任何数据，将引发EOFError异常</td>
</tr>
<tr>
<td>recv_bytes_into(buffer [, offset])</td>
<td>接收一条完整的字节消息，并把它保存在buffer对象中，该对象支持可写入的缓冲区接口（bytearry对象或类似的对象）。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间，将引发BufferTooShort异常</td>
</tr>
<tr>
<td>c.send(obi)</td>
<td>通过连接发送对象。obj是与序列化兼容的任意对象</td>
</tr>
<tr>
<td>send_bytes (buffer [, offset [, size]])</td>
<td>通过连接发送字节数据缓冲区。buffer为支持缓冲区接口的任意对象，offset是缓冲区中的字节偏移量，size是要发送字节数。结果数据以单条消息的形式发出，然后调用recv_bytes()进行接收</td>
</tr>
</tbody>
</table>
<p>如果生产者或消费者中<span style="background-color: #c0c0c0;">都没有使用管道的某个端点，就应将其关闭</span>。如果忘记执行这越步骤，程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的， 必须在所有进程中关闭管道后才能生成EOFError异常。因此，<span style="background-color: #c0c0c0;">在生产者中关闭管道不会有任何效果， 除非消费者也关闭了相同的管道端点</span>。</p>
<p>使用管道的生产者——消费者的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def consumer(pipe):
    outputp, inputp = pipe
    inputp.close()  # 关闭消费者的输入通道
    while True:
        try:
            item = outputp.recv()
        except EOFError:
            break
        print item
    print "Consumer done"
def produce(seq, inputp):
    for item in seq:
        inputp.send(item)

if __name__ == '__main__':
    (outputp, inputp) = multiprocessing.Pipe(True)
    cons_p = multiprocessing.Process(target=consumer, args=((outputp, inputp),))
    cons_p.start()
    outputp.close()  # 关闭生产者的输出通道
    seq = [0, 1, 2, 3, 4]
    produce(seq, inputp)
    # 关闭输入通道
    inputp.close()
    # 等待消费者进程关闭
    cons_p.join()</pre>
<p>双向通信的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def server(pipe):
    serverp, clientp = pipe
    clientp.close()                #服务器不会使用管道的另一端
    while True:
        try:
            x, y = serverp.recv()  #接收请求消息
        except EOFError:           #如果客户端关闭了管道另一端
            print "Pipe endpoint closed by peer"
            break
        result = x + y
        serverp.send(result)       # 返回处理结果
    print "Server done"
if __name__ == '__main__':
    (serverp, clientp) = multiprocessing.Pipe(True)
    s = multiprocessing.Process(target=server, args=((serverp, clientp),))
    s.start()
    
    #客户端逻辑部分
    serverp.close()                #客户端不会使用管道的另一端
    clientp.send((1, 1))           #发送请求消息
    print clientp.recv()           #打印处理结果
    
    clientp.send(('Hello',' World'))
    print clientp.recv() 
    clientp.close()
    s.join()                       #等待子进程结束</pre>
<div class="blog_h2"><span class="graybg">进程池</span></div>
<p>Pool类可用于创建进程池，可以把各种数据处理任务提交给进程池处理：</p>
<pre class="crayon-plain-tag">#构造函数
Pool( [numprocess [, initializer [, initargs]]])
# numprocess   池中的进程数，省略则使用cpu_count()的值
# initializer  进程启动时执行的可调用对象，默认None
# initargs     传递给initializer的参数</pre>
<p>Pool的实例支持以下方法： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;"> 方法</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 250px;">apply(func [, args [, kwargs]])</td>
<td>在池中一个工作进程中执行func(*args, **kwargs)并返回结果</td>
</tr>
<tr>
<td>apply_async(func [, args [, kwargs, [cb]]])</td>
<td>在池中一个工作进程中异步执行func(*args, **kwargs)并返回一个AsyncResult对象，该对象可用于稍后获取结果。cb为一可调用对象，当异步执行的结果可用时，会回调之</td>
</tr>
<tr>
<td>close()</td>
<td>关闭进程池，防止进行进一步操作</td>
</tr>
<tr>
<td>terminate()</td>
<td>立即终止所有工作进程，同时不执行任何清理或结束任何挂起工作，如果进程池北垃圾回收，该函数自动调用</td>
</tr>
<tr>
<td>join()</td>
<td>等待所有工作进程退出。此方法只能在close()或terminate()方法之后调用</td>
</tr>
<tr>
<td>map(func, iterable [, chunksize])</td>
<td>将可调用对象func应用给iterable中的所有项目，然后以列表的形式返回结果。通过将iterable划分为多块并将工作分派给工作进程，可以并行地执行这项操作。chunksize为每块中的项目数</td>
</tr>
<tr>
<td>map_async(func, iterable [, chunkslze [, cb]])</td>
<td>同map()函数，但结果的返回是异步的。返回值是AsyncResult类的实例</td>
</tr>
<tr>
<td>imap(func, iterable [, chunksize]) </td>
<td>map()函数的版本之一，返回迭代器而非结果列表</td>
</tr>
<tr>
<td>imap_unordered (iterable [, chunksize ])</td>
<td>同imap()函数，但从工作进程接收结果时，返回结果的次序是任意的</td>
</tr>
</tbody>
</table>
<p>AsyncResult的实例支持以下方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>get([timeout])</td>
<td>返回结果，如果有必要则等待结果到达。timeout是可选的超时。如果结果在指定时间内没有到达，将引发TimeoutError异常。如果远程操作中引发了异常，它将在调用此方法时再次被引发</td>
</tr>
<tr>
<td>ready()</td>
<td>如果调用完成，返回True</td>
</tr>
<tr>
<td>successful()</td>
<td>如果调用完成且没有引发异常，返回True。如果在结果就绪之前调用此方法，将引发AssertionError异常</td>
</tr>
<tr>
<td>wait ([timeout])</td>
<td>等待结果变为可用。timeout是可选的超时</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据共享与同步</span></div>
<p>通常，进程之间彼此是完全孤立的，唯一的通信方式是队列或管道。可以使用两个对象来表示共享数据。这些对象使用了<span style="background-color: #c0c0c0;">共享内存（通过mmap模块）使访问多个进程成为可能</span>。可以创建以下类型的共享数据：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 共享数据类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 250px;">Value(typecode, arg1, ... argN, lock)</td>
<td>
<p>在共享内存中创建ctypes对象，typecode可以是类似array模块的类型代码，或者ctypes模块的类型对象（例如ctypes.c_int）。参数arg1...argN传递给类型的构造函数，lock必须使用关键字参数调用，如果为True（默认）则会创建一个新的锁定来包含对值的访问，如果传入现有锁对象（Lock或者RLock），则该锁用于同步访问。</p>
<p>可以使用value属性访问底层的数值</p>
</td>
</tr>
<tr>
<td>RawValue (typecode, arg1, ... argN)</td>
<td> 同Value对象，但不存在锁定。</td>
</tr>
<tr>
<td>Array(typecode, initializer, lock)</td>
<td>在共享内存中创建ctypes数组。typecode描述了数组的内容，意义与Value()函数中的相同。 initializer要么是设罝数组初始大小的整数，要么是项目序列，其值和大小用于初始化数组。lock 是只能使用关键字调用的参数，意义与Value()函数中相同。如果a是Array创建的共享数组的实例，可使用标准的Python索引、切片和迭代操作访问它的内容，其中每种操作均由锁定进行同步。对于字节字符串，a还具有value属性，可以把整个数组当作一个字符串进行访问</td>
</tr>
<tr>
<td>RawArray (typecode, initializer)</td>
<td>同Array，但不存在锁定</td>
</tr>
</tbody>
</table>
<p>可以使用以下功能与threading模块类似的同步原语：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 原语</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Lock </td>
<td>互斥锁</td>
</tr>
<tr>
<td>RLock </td>
<td>可重入的互斥锁（同一进程可以多次获得它，同时不会造成阻塞)</td>
</tr>
<tr>
<td>Semaphore</td>
<td>信号量</td>
</tr>
<tr>
<td>BoundedSemaphore</td>
<td>有边界的信号量</td>
</tr>
<tr>
<td>Event </td>
<td>事件</td>
</tr>
<tr>
<td>Condition</td>
<td>条件变量</td>
</tr>
</tbody>
</table>
<p>结合信号量来把数组发送给另外一个进程的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
class FloatChannel(object):
    def __init__(self, maxsize):
        self.buffer = multiprocessing.RawArray('d', maxsize)
        self.buffer_len = multiprocessing.Value('i')
        self.empty = multiprocessing.Semaphore(1)
        self.full = multiprocessing.Semaphore(0)
    def send(self, values):
        # 等待缓冲区为空的信号
        self.empty.acquire()
        n = len(values)
        self.buffer_len = n
        self.buffer[:n] = values
        # 设置缓冲区已满的标记
        self.full.release()
    def recv(self):
        self.full.acquire()
        # 复制缓冲区中的值，注意可以使用切片语法
        values = self.buffer[:self.buffer_len.value]
        # 设置缓冲区已空的标记
        self.empty.release()
        return values
    
def consumer(count , fc):
    for i in xrange(count):
        fc.recv()
        
if __name__ == '__main__':
    fc = FloatChannel(100000)
    p = multiprocessing.Process(target=consumer, args=(1000, fc))
    p.start()
    
    values = [float(x) for x in xrange(100000)]
    for i in xrange(1000):
        fc.send(values)
    print "Produce done"
    p.join()</pre>
<div class="blog_h2"><span class="graybg">托管对象（共享任意对象）</span></div>
<p>上一节的数据同步，只能使用共享值、数组等基本类型，如果需要使用Python对象则无能为力。Python提供了“管理器”这种独立的子进程，可以用于管理多个进程之间的共享对象。“管理器”作为服务器进程来运行，其他进程则通过代理访问共享对象，相当于客户端。</p>
<p>使用Manager()函数可以创建一个管理器，其返回位于multiprocessing.managers模块中的SyncManager对象</p>
<div class="blog_h2"><span class="graybg">连接网络上的进程</span></div>
<p>使用multiprocessing模块的程序可以与运行在同一台计算机上的其他进程或者位于远程系统 上的进程进行消息传递。可以使用Client来连接到其它进程：</p>
<pre class="crayon-plain-tag">#位于multiprocessing.connections子模块
connections.Client (address [, family [, authenticate [, authkey]]])
#address       代表网络地址的元组(hostname, port)
#family        表示地址格式的字符串，省略则从address自动推断
#authenticate  是否使用摘要身份验证
#authkey       包含身份验证密钥的字符串，省略则使用current_process().authkey</pre>
<p>对方进程必须作为服务器，处于监听状态：</p>
<pre class="crayon-plain-tag">connections.Listener([address, [,family [, backlog[, authenticate [,authkey]]]]])</pre>
<p>Listener的实例具有以下方法或属性： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 150px;">accept()</td>
<td>接受一个新连接，并返回一个Connection对象。如果身份验证失败，将引发AuthenticationError</td>
</tr>
<tr>
<td>address</td>
<td>侦听器正在使用的地址</td>
</tr>
<tr>
<td>close()</td>
<td>关闭侦听器正在使用的管道或套接字</td>
</tr>
<tr>
<td>last_accepted </td>
<td>接受的最后一个客户端的地址</td>
</tr>
</tbody>
</table>
<p>下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">def server():
    from multiprocessing.connection import Listener
    serv = Listener(('127.0.0.1', 15000), authkey="12345")
    while True:
        conn = serv.accept()
        while True:
            try :
                x, y = conn.recv()
            except EOFError:  # 如果客户端断开连接
                print "Connection closed by peer"
                break
            result = x + y
            conn.send(result)
        conn.close()

if __name__ == '__main__':
    import multiprocessing
    s = multiprocessing.Process(target=server)
    s.start()
    from multiprocessing.connection import Client
    conn = Client(('127.0.0.1', 15000), authkey="12345")
    conn.send((1, 1))
    print conn.recv()
    conn.close()</pre>
<div class="blog_h2"><span class="graybg">常用工具函数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">函数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>active_children()</td>
<td>列出所有活动子进程的Process对象</td>
</tr>
<tr>
<td>cpu_count()</td>
<td>返回系统上的CPU数量，如果能够确定的话</td>
</tr>
<tr>
<td>current_prpcess()</td>
<td>返回当前进程的Process对象</td>
</tr>
<tr>
<td>freeze_support()</td>
<td>在使用各种打包工具（如py2exe)进行冻结的应用程序中，此函数应该作为主程序的首行。使用此函数可以防止与在冻结的应用程序中启动子进程相关的运行时错误</td>
</tr>
<tr>
<td>get_logger()</td>
<td>返回与多进程处理模块相关的日志记录对象，如果它不存在则创建之。返回的记录器不会把消息传播给根记录器，级别logging.NOTSET，会将所有日志消息打印到标准错误上</td>
</tr>
<tr>
<td>set_executable(executable)</td>
<td>设置用于执行子进程的Python可执行程序的名称。这个函数只定义在Windows上</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">threading模块</span></div>
<p>该模块提供Thread类和各种同步原语，用于编写多线程的程序。</p>
<div class="blog_h2"><span class="graybg">Thread类</span></div>
<p>Thread类用于表本单独的控制线程。使用下面的函数可以创建一个新线程:</p>
<pre class="crayon-plain-tag">Thread (group=None, target=None, name=None, args=(), kwarga={})
# group 预留字段
# target 可调用对象，线程启动时，其run方法将调用该对象
# name 线程的名称
# args、kwargs 传递给target的参数</pre>
<p>线程支持以下实例方法和属性：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>start()</td>
<td>通过在一个单独的控制线程中调用run ()方法，启动线程。此方法只能调用一次</td>
</tr>
<tr>
<td>run()</td>
<td>线程启动时将调用此方法。默认情况下，它将调用传递到构造函数中的目标函数。还可以在Thread 的子类中重新定义此方法</td>
</tr>
<tr>
<td>join([timeout])</td>
<td>等待直到线程终止或者出现超时为止。timeout是一个浮点数，用于指定以秒为单位的超时时间。 线程不能join自身，而且在线程启动之前就连接它将出现错误</td>
</tr>
<tr>
<td>is_alive()</td>
<td>如果线程是活动的，返回True，否则返回False。从start()方法返回的那一刻开始，线程就是活动的，直到它的run()方法终止为止</td>
</tr>
<tr>
<td>name</td>
<td>线程名称</td>
</tr>
<tr>
<td>ident</td>
<td>整数线程标识符。如果线程尚未启动，它的值为None</td>
</tr>
<tr>
<td>daemon</td>
<td>线程的布尔型后台标志。必须在调用start ()方法之前设置这个标志，它的初始值从创建线程的后台状态继承而来。当不存在任何活动的非后台线程时，整个Python程序将退出。所有程序<span style="background-color: #c0c0c0;">都有一个主线程，代表初始的控制线程，它不是后台线程</span></td>
</tr>
</tbody>
</table>
<p>下面是一个使用线程的例子：</p>
<pre class="crayon-plain-tag">import threading
import time
class ClockThread(threading.Thread):
    #除了__init__、run以外，改写别的方法
    def __init__(self, interval):
        threading.Thread.__init__(self)   #子类如果扩展__init__，必须调用Thread的构造函数
        self.daemon = True
        self.interval = interval
    def run(self):
        while True:
            print "Current time is %s" % time.ctime()
            time.sleep(self.interval)
if __name__ == '__main__':
    t = ClockThread(15)
    t.start()   
    time.sleep(150)</pre>
<div class="blog_h2"><span class="graybg">定时器Timer</span></div>
<p>用于延迟执行某一函数：</p>
<pre class="crayon-plain-tag">Timer(interval, func [, args [, kwargs]])
#interval 延迟执行的秒数
#func 需要执行的函数
#args、kwargs 函数参数</pre>
<p>Timer具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 150px;">start()</td>
<td>启动定时器</td>
</tr>
<tr>
<td>cancel() </td>
<td>如果函数尚未执行，可用于取消定时器 </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">锁（Lock）</span></div>
<p>锁定是一个同步原语，状态是“已锁定”或“未锁定”之一。两个方法acquire() 和release()用于修改锁定的状态。如果状态为已锁定，尝试获取锁定将被阻塞，直到锁定被释放为 止。如果有多个线程等待获取锁定，当锁定被释放时，只有一个线程能获得它。等待线程获得锁定的顺序没有定义</p>
<p>使用构造函数Lock()可以创建锁的实例，<span style="background-color: #c0c0c0;">初始状态为未锁定</span>。具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire([blocking])</td>
<td>获取锁定，如果有必要，需要阻塞到锁定释放为止。如果提供参数blocking并将它设为False，当无法获取锁定时将立即返回False，否则立即返回True</td>
</tr>
<tr>
<td>release ()</td>
<td>释放一个锁定。当锁定处于未锁定状态时，或者从<span style="background-color: #c0c0c0;">与原本调用acquire()方法的线程不同的线程</span>调用此方法，将出现错误</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">使用锁时的注意点</span></div>
<p>使用诸如Lock、RLock或Semphore之类的锁定原语时，必须多加小心。锁定的错误管理经常导致死锁或者竞态条件。可以使用下面代码中提到的两种方式保证锁的正确释放：</p>
<pre class="crayon-plain-tag">import threading
lock = threading.Lock()
#方法一：使用try-finally语句
try:
    lock.acquire()
    #执行同步操作
finally:
    lock.release()
#方法二：使用上下文管理协议
#进入时自动获取锁定，离开时自动释放锁定
with lock:
    #执行同步操作
    pass</pre>
<p>此外，应当尽量避免同时获取多个锁定，容易导致死锁。</p>
<div class="blog_h2"><span class="graybg">可重入锁RLock</span></div>
<p>可重入锁定是一个类似于Lock同步原语，但同一个线程可以多次获取它。这允许拥有锁定的线程执行嵌套的acquire()和release()操作。在这种情况下，只有最外面的release()操作才能将锁定重置为未锁定状态。进行若干次lock()后，必须在进行对应次数的release()才能彻底释放锁。</p>
<div class="blog_h2"><span class="graybg">信号量和有界信号量</span></div>
<p>信号量是一个基于计数器的同步原语，<span style="background-color: #c0c0c0;">每次调用acquire()方法时此计数器减1，每次调用 release()方法时此计数器加1</span>。如果计数器为0, acquire ()方法将会阻塞，直到其他线程调用release ()方法为止。</p>
<p>可以使用Semaphore([value])来创建信号量，其中value为计数器的初始值，默认为1。信号量包含以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire([blocking])</td>
<td>获取信号量。如果进入时内部计数器大于0，此方法将把它的值减1，然后立即返回。如果它的值为0，此方法将阻塞，直到另一个线程调用release()方法。blocking参数的行为与Lock类似</td>
</tr>
<tr>
<td>release ()</td>
<td>通过将内部计数器的值加1来释放一个信号量。如果计数器为0，而且另一个线程正在等待，该线程将被唤醒。如果有多个线程正在等待，只能从它的acquire()调用返回其中一个。线程释放的顺序并不确定。</td>
</tr>
</tbody>
</table>
<p>BoundedSemaphore的行为与Semaphore相同，但是限制但release()操作的次数不能超过acquire()的次数。</p>
<div class="blog_h2"><span class="graybg">事件</span></div>
<p>事件用于在线程之间通信。一个线程发出“事件”信号，一个或多个其他线程等待它，具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>is_set()</td>
<td>只有当内部标志为True时才返回True</td>
</tr>
<tr>
<td>set ()</td>
<td>将内部标志置为True。等待它变为True的所有线程都将被唤醒</td>
</tr>
<tr>
<td>clear()</td>
<td>将内部知志重置为False</td>
</tr>
<tr>
<td>wait( [timeout] )</td>
<td>阻塞直到内部标志为True。如果进入时内部标志为True，此方法将立即返回。否则，它将阻塞直到另个线程调用set()方法将志置为True，或者直到出现可选的超时。timeout是个浮点数，用于指定以秒为单位的超时期限</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">条件变量</span></div>
<p>条件变量是构建在另一个锁定上的同步原语，当需要线程关注特定的<span style="background-color: #c0c0c0;">状态变化或事件的发生</span>时将使用这个锁定。典型的用法是生产者-消费者场景，其中一个线程生产的数据供另一个线程使用。使用构造函数Condition([lock])可以创建条件变量，其中lock是Lock或者RLock的实例，如果不提供则创建新的锁供食用。Condition具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire (*args)</td>
<td>
<p>获取底层锁定。此方法将调用底层锁定上对应的acquire方法</p>
</td>
</tr>
<tr>
<td>release ()</td>
<td>释放底层锁定。此方法将调用底层锁定上对应的release()方法</td>
</tr>
<tr>
<td>wait( [timeout])</td>
<td>等待直到获得通知或出现超时为止。此方法在调用线程<span style="background-color: #c0c0c0;">已经获取锁定之后调用。调用时，将释放底层锁定，而且线程将进入睡眠状态，直到另一个线程在条件变量上执行notify()或notifyAll() 方法将其唤醒为止</span>。在线程被唤醒之后，线程将重新获取锁定，方法也会返回，timeout是浮点数， 单位为秒。如果这段时限耗尽，线程将被唤醒，锁定将被重新获取</td>
</tr>
<tr>
<td>notify([n])</td>
<td>唤醒一个或多个等待此条件变量的线程。此方法只会在调用线程已经获取锁定之后调用，而且如果没有正在等待的线程，它就什么也不做。n指定要唤醒的线程数量，默认为1。被唤醒的线程在它们 新获取锁定之前不会从wait ()调用返回</td>
</tr>
<tr>
<td>notify_all()</td>
<td>唤醒所有等待此条件的线程</td>
</tr>
</tbody>
</table>
<p>条件变量的简单例子：</p>
<pre class="crayon-plain-tag">import threading
c = threading.Condition()
def producer():
    while True:
        c.acquire()
        produce_item()
        c.notify()
        c.release()
#使用条件变量时需要注意的是，如果存在多个线程等待同一个条件，notify()操作可能唤醒它们
#中的一个或多个（这种行为通常取决于底层的操作系统)。因此，始终有这样的可能：某个线程被
#唤醒后，其等待的条件已经不满足，因此可能需要循环继续等待下一个信号
def consumer():
    while True:
        c.acquire()
        while not item_is_available():
            #释放锁定，生产者可以获取并生产物品
            c.wait()
        c.release()
        consume_item()</pre>
<div class="blog_h2"><span class="graybg">线程的终止与挂起</span></div>
<p>线程没有任何方法可用于强制终止或挂起。这是设计上的原因，因为如果某个线程已经获取了锁定，在它能够释放锁定之前强制终止或挂起它，将导致整个应用程序出现死锁。此外，终止时一般不能简单地“释放锁定”，因为复杂的线程同步经常涉及锁定和解除锁定操作，而这些操作在执行时的次序要十分精确。 如果要为终止或挂起提供支持，需要自己构建这些功能。一般的做法是在循环中运行线程，这个循环的作用是定期检査线程状态以决定它是否应该终止</p>
<div class="blog_h2"><span class="graybg">常用工具函数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>active_count()</td>
<td>
<p>返回当前活动的Thread对象数量</p>
</td>
</tr>
<tr>
<td>current_thread() </td>
<td>返回对应于调用者的控制线程的Thread对象</td>
</tr>
<tr>
<td>enumerate()</td>
<td>列出当前所有活动的Thread对象</td>
</tr>
<tr>
<td>local()</td>
<td>返回local对象，用于保存线程本地的数据，应该保证此对象在每个线程中是唯一的</td>
</tr>
<tr>
<td>setprofile(func)</td>
<td>设置一个配置文件函数，用于已创建的所有线程。func在每个线程开始运行之前被传递给 sys.setprofile()函数</td>
</tr>
<tr>
<td>settrace(func)</td>
<td>设置一个跟踪函数，用于已创建的所有线程。func在每个线程开始运行之前被传递给sys.settrace()函数</td>
</tr>
<tr>
<td>stack_size([size]）</td>
<td>返回创建新线程时使用的栈大小。size 的值可以是32768 (32KB)或更大，而且是4096 (4KB)的倍数，这样可移植性更好</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">全局解释器锁定</span></div>
<p>Python解释器被一个锁定保护，<span style="background-color: #c0c0c0;">该锁定只允许一次执行一个线程</span>，即便存在多个可用的处理器。 在计算密集型程序中，这严重限制了线程的作用。事实上，<span style="background-color: #c0c0c0;">在计算密集型程序中使用线程，经常比仅仅按照顺序执行同样的工作慢得多</span>。<span style="background-color: #c0c0c0;">因此，实际上应该只在主要关注I/O的程序，如网络服务器中使用线程</span>。对于计算密集程度更高的任务，最好使用C扩展模块或multiprocessing模块来代替。C扩展具有释放解释器锁定和并行运行的选项，可以做到当释放锁定时不与解释器进行交互。 multiprocessing模块将工作分派给不受锁定限制的单独子进程</p>
<div class="blog_h1"><span class="graybg">queue模块</span></div>
<p>queue模块（在Python 2中叫Queue)实现了各种多生产者——多消费者队列，可用于在执行的多个线程之间安全地交换信息。 该模块定义了3种不同的队列类：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 队列类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Queue([maxsize])</td>
<td>创建一个FIFO队列。maxsize是队列中可以放入的项目的最大数量。如果省略maxsize参数或将它置为0，队列大小无限制</td>
</tr>
<tr>
<td>ListQueue([maxsize])</td>
<td>创建一个LIFO队列（栈)</td>
</tr>
<tr>
<td>PriorityQueue([maxsize])</td>
<td>创建一个优先级队列，其中项目按照优先级从低到髙依次排好。使用这种队列时，项目应该是 (priority, data)形式的元组，其中priority是一个数字</td>
</tr>
</tbody>
</table>
<p>队列具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>qsize()</td>
<td>
<p>返回队列的正确大小。因为其他线程可能正在更新队列，此方法返回的数字不完全可靠</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>empty()</td>
<td>如果队列为空，返回True,否则返回False</td>
</tr>
<tr>
<td>full()</td>
<td>如果队列已满，返回True,否则返回False</td>
</tr>
<tr>
<td>put (item [,block [, timeout]])</td>
<td>将item放入队列。如果可选参数block为True (默认值)，调用者将被阻塞直到队列中出现可用的空闲位置为止。否则队列满时将引发Full异常。timeout提供可选的超时值， 单位为秒。如果出现超时，将引发Full异常</td>
</tr>
<tr>
<td>put_nowait (item)</td>
<td>等价于g.put (item, False)</td>
</tr>
<tr>
<td>get ([block [, timeout]])</td>
<td>从队列中删除一项，然后返回这个项目。如果可选参数block为True (默认值)，调用者将阻塞， 直到队列中出现可用的空闲位置。否则队列为空时将引发Empty异常，timeout提供可选的超时值，单位为秒。如果出现超时，将引发Empty异常。 </td>
</tr>
<tr>
<td>get_nowait ()</td>
<td>等价于get (0)方法</td>
</tr>
<tr>
<td>task_done()</td>
<td>队列中数据的消费者用来指示对于项目的处理已经结束。如果使用此方法，那么从队列中删除的每一项都应该调用一次</td>
</tr>
<tr>
<td>join()</td>
<td>阻塞直到队列中的所有项目均被删除和处理为止。一旦为队列中的每一项都调用了一次 task_done ()方法，此方法将会直接返回</td>
</tr>
</tbody>
</table>
<p>使用队列一般可以简化多线程的程序。例如，可以使用共享队列将线程连接在一起，而不必依赖于必须由锁定保护的共享状态。在这种模型中，工作者线程一般充当数据的消费者。下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">import threading 
from queue import Queue
class WorkerThread(threading.Thread):	
   def __init__(self,*args,**kwargs)： 
       threading.Thread.__init__(self, *args, **kwargs) 
       self.input_queue = Queue()
   def send(self,item):
       self.input_queue.put(item)
   def close(self):
       self.input_queue.put(None) 
       self.input_queue.join()
   def run(self):
       while True:
           item = self.input_queue.get()
           if item is None:
               break
           print item #处理条目
           self.input_queue.task_done()
       self.input_queue.task_done()
       return
#使用示例
w = WorkerThread()
w.start()
w.send('Hello')   #把条目发送给工作线程处理
w.close()</pre>
<p>上面的例子几乎完全等同于协程。如果要执行的工作不涉及任何阻塞操作，可以将run()方法重新实现为协程，这就省却了使用线程的麻烦。后一种方法的运行速度可能更快，因为节省了线程上下文切换带来的开销</p>
<div class="blog_h1"><span class="graybg">协程与微线程</span></div>
<p>在某些类型的应用程序中，可以<span style="background-color: #c0c0c0;">使用一个任务调度器和一些生成器或协程实现协作式用户空间多线程，这有时称为微线程</span>。这种技术的一种常见用法是在<span style="background-color: #c0c0c0;">需要管理大量的已打开文件或套接字的程序中</span>，例如一台需要同时管理 1000个客户端连接的网络服务器。此时的解决方案是<span style="background-color: #c0c0c0;">联合使用异步I/O或轮询（使用select模块）与处理I/O事件的任务调度器</span>，而不是创建1000个线程，因为过多的线程会导致大量系统资源浪费在上下文切换上。 这种编程技术的基础概念是这样产生的：生成器或协程函数中的yield语句挂起函数的执行，直到稍后使用next () 或者send()操作进行恢复为止。这样就可以使用一个调度器循环在一组生成器函数之间协作多个任务。</p>
<p>基于协程的并发编程的例子：<a href="/python-network-programming#echo-service-by-coroutine">基于协程技术实现的异步Echo服务</a></p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-concurrent-programming">Python并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/python-concurrent-programming/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Python学习笔记</title>
		<link>https://blog.gmem.cc/python-study-note</link>
		<comments>https://blog.gmem.cc/python-study-note#comments</comments>
		<pubDate>Fri, 29 Apr 2011 07:32:42 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Coroutine]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4186</guid>
		<description><![CDATA[<p>安装与配置 安装Python Windows：可以使用WinPython，这是一个免安装、开箱即用的Python发布版，包含很多预置工具 Linux：通常已经随操作系统安装 环境变量设置  环境变量 说明  PYTHON_HOME Python安装目录 PATH 添加[crayon-69d6611a1a3c1158119635-i/]  PYTHONPATH Python的模块搜索路径，在前面出现的优先级高添加[crayon-69d6611a1a3c6701629745-i/] ，任何需要Python解释器找到的模块，都需要加到此环境变量上 仅在Windows下你可能需要手工设置此环境变量 Linux的模块安装位置与Windows不同： 模块执行脚本编译的二进制文件通常存放到[crayon-69d6611a1a3c8922401401-i/] 或者[crayon-69d6611a1a3ca490538705-i/] ，而不是[crayon-69d6611a1a3cc550794667-i/] 子目录 模块安装位置则可能是[crayon-69d6611a1a3ce390171600-i/] ，还可以安装到当前用户的目录[crayon-69d6611a1a3d0489330458-i/] 下 可以使用下面的命令得到PYTHONPATH： [crayon-69d6611a1a3d2045319885/] Linux下安装额外版本 Ubuntu14.04.3自带的Python版本时2.7.6和3.4.3。你可以下载并构建自己的版本，但是不要全局的安装，替换系统的python2、python3符号链接可能导致系统无法工作。 下面的脚本示例如何安装3.5版本的Python： <a class="read-more" href="https://blog.gmem.cc/python-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-study-note">Python学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">安装与配置</span></div>
<div class="blog_h2"><span class="graybg">安装Python</span></div>
<ol>
<li>Windows：可以使用<a href="http://winpython.github.io/">WinPython</a>，这是一个免安装、开箱即用的Python发布版，包含很多预置工具</li>
<li>Linux：通常已经随操作系统安装</li>
</ol>
<div class="blog_h3"><span class="graybg">环境变量设置</span></div>
<table border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td> 环境变量</td>
<td>说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>PYTHON_HOME</td>
<td>Python安装目录</td>
</tr>
<tr>
<td>PATH</td>
<td>添加<pre class="crayon-plain-tag">%PYTHON_HOME%;%PYTHON_HOME%\Scripts</pre> </td>
</tr>
<tr>
<td>PYTHONPATH</td>
<td>
<p>Python的模块搜索路径，在前面出现的优先级高<br />添加<pre class="crayon-plain-tag">%PYTHON_HOME%\Lib;%PYTHON_HOME%\Lib\site-packages</pre> ，任何需要Python解释器找到的模块，都需要加到此环境变量上</p>
<p>仅在<span style="background-color: #c0c0c0;">Windows下你可能</span>需要手工设置此环境变量</p>
<p>Linux的模块安装位置与Windows不同：</p>
<ol>
<li>模块执行脚本编译的二进制文件通常存放到<pre class="crayon-plain-tag">/usr/local/bin</pre> 或者<pre class="crayon-plain-tag">/usr/bin</pre> ，而不是<pre class="crayon-plain-tag">Scripts</pre> 子目录</li>
<li>模块安装位置则可能是<pre class="crayon-plain-tag">/usr/local/lib/python2.7/dist-packages:/usr/lib/python2.7/dist-packages</pre> ，还可以安装到当前用户的目录<pre class="crayon-plain-tag">~/.local</pre> 下</li>
</ol>
<p>可以使用下面的命令得到PYTHONPATH：</p>
<pre class="crayon-plain-tag">python -c "import sys; print ('\n'.join(x for x in sys.path if x))"</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Linux下安装额外版本</span></div>
<p>Ubuntu14.04.3自带的Python版本时2.7.6和3.4.3。你可以下载并构建自己的版本，但是<span style="background-color: #c0c0c0;">不要全局的安装</span>，替换系统的python2、python3符号链接可能导致系统无法工作。
<p>下面的脚本示例如何安装3.5版本的Python：</p>
<pre class="crayon-plain-tag">cd ~/Downloads/3.5.1
./configure --prefix=$HOME/Python/3.5.1
make &amp;&amp; make install

# 检查PYTHONPATH
cd $HOME/Python/3.5.1/bin
./python3 -c "import sys; print ('\n'.join(x for x in sys.path if x))"
# 输出如下：
# /home/alex/Python/3.5.1/lib/python35.zip
# /home/alex/Python/3.5.1/lib/python3.5
# /home/alex/Python/3.5.1/lib/python3.5/plat-linux
# /home/alex/Python/3.5.1/lib/python3.5/lib-dynload
# /home/alex/Python/3.5.1/lib/python3.5/site-packages</pre>
<div class="blog_h1"><span class="graybg">使用Python解释器</span></div>
<div class="blog_h2"><span class="graybg">调用Python解释器</span></div>
<p>Ubuntu下，Python解释器的默认安装位置为/usr/bin/python；Windows下是C:\python27。需要将其添加到环境变量PATH中，然后打开Shell窗口：</p>
<pre class="crayon-plain-tag">#运行交互式的解释器
python
#输入文件结束符（Unix的Ctrl+D、Windows的Ctrl+Z）可以退出解释器
#调用quit()函数亦可退出解释器

#启动解释器并执行命令，应当用单引号包围命令，防止有空格之类的特殊字符
python -c command [arg] ...

#调用作为脚本使用的模块
python -m module [arg] ...</pre>
<div class="blog_h3"><span class="graybg">参数传递</span></div>
<p>调用解释器时，<span style="background-color: #c0c0c0;">脚本名、参数</span>传入字符串列表<span style="background-color: #c0c0c0;">sys.argv</span>中，该列表至少有一个元素。</p>
<ol>
<li>没有给定脚本名、参数时，sys.argv[0] = ''</li>
<li>脚本名指定为标准输入时，sys.argv[0] = '-'</li>
<li>使用-c参数调用解释器时，sys.argv[0] = '-c'</li>
<li>使用-m参数调用解释器时，sys.argv[0]  = 模块全名</li>
</ol>
<div class="blog_h3"><span class="graybg">交互模式</span></div>
<p>从终端读取命令并执行，称为交互模式，以主提示符（&gt;&gt;&gt;）为依据执行，输入多行结构，则需要附加从属提示符（...），例如：</p>
<pre class="crayon-plain-tag">&gt;&gt;&gt; if name == 'Alex':
...     print "Hello " + name
...
Hello Alex</pre>
<div class="blog_h2"><span class="graybg">解释器及其环境</span></div>
<div class="blog_h3"><span class="graybg">错误处理</span></div>
<p>有错误发生时，解释器在stderr上打印错误信息、调用栈跟踪。在交互模式下，会返回主提示符；如果从文件输入执行，则以非零状态退出。<br /> 使用try的except子句可以捕获异常</p>
<div class="blog_h3"><span class="graybg">执行Python脚本</span></div>
<p>Linux下，Python脚本可以直接执行（需要chmod +x）：</p>
<pre class="crayon-plain-tag">#! /usr/bin/env python
...</pre>
<p>在Windows下，安装程序会把*.py与python.exe关联，可以双击执行，*pyw类似，但是不显示控制台窗口</p>
<div class="blog_h3"><span class="graybg">交互执行文件</span></div>
<p>如果需要让解释器在每次启动时均执行一个脚本，可以设置环境变量：PYTHONSTARTUP，这类似于Linux Shell的.profile文件</p>
<div class="blog_h3"><span class="graybg">本地化模块</span></div>
<p>钩子方法sitecustomize、usercustomize用于提供本地化。</p>
<div class="blog_h1"><span class="graybg">Python3新特性</span></div>
<div class="blog_h2"><span class="graybg">版本3.0</span></div>
<div class="blog_h3"><span class="graybg">print语句变为函数</span></div>
<pre class="crayon-plain-tag">print "The answer is", 2*2
print("The answer is", 2*2)
# 可以定制打印项之间的分隔符
print("There are &lt;", 2**32, "&gt; possibilities!", sep="")</pre>
<div class="blog_h3"><span class="graybg">视图和迭代器</span></div>
<p>字典的<pre class="crayon-plain-tag">dict.keys()</pre>, <pre class="crayon-plain-tag">dict.items()</pre>, <pre class="crayon-plain-tag">dict.values()</pre>方法返回的不再是列表，而是“视图”。因此：</p>
<pre class="crayon-plain-tag">k = d.keys();
k.sort()       # ERR
k = sorted(d)  # OK</pre>
<p><pre class="crayon-plain-tag">map()</pre>, <pre class="crayon-plain-tag">filter()</pre>, <pre class="crayon-plain-tag">zip()</pre>等返回迭代器。要获得列表，可以用<pre class="crayon-plain-tag">list()</pre>包装，或者使用列表推导：</p>
<pre class="crayon-plain-tag">result = [ x for x in map()]</pre>
<div class="blog_h3"><span class="graybg">文本和数据</span></div>
<p>在Python 3中，使用文本和（二进制）数据两个概念，来代替Uincode字符串和8-bit字符串。所有文本都是基于Unicode的，但是编码后的Unicode表示为二进制数据。存储文本的类型是<pre class="crayon-plain-tag">str</pre>。存储二进制数据的类型为<pre class="crayon-plain-tag">bytes</pre>。</p>
<p>不再需要使用u前缀来表示文本：<pre class="crayon-plain-tag">u"..."</pre>，但是要表示二进制数据直接量则必须使用前缀<pre class="crayon-plain-tag">b"..."</pre>。</p>
<p>任何混合文本、数据的操作会导致TypeError。你必须明确的进行转换：</p>
<pre class="crayon-plain-tag"># 编码为二进制数据
str.encode() 
# 编码为Unicode文本
bytes.decode()</pre>
<p>在原始字符串中，反斜杠被原样看待，例如： <pre class="crayon-plain-tag">r'\u20ac'</pre> 是6字符的串。</p>
<div class="blog_h3"><span class="graybg">函数注解</span></div>
<p>PEP 3107 – Function Annotations引入了为函数添加任何元数据（注解）的能力，注意这些注解没有任何具体语义，不改变函数的运行时行为，仅仅用于文档、类型提示（这是最重要的用法）以及为第三方框架提供信息。</p>
<p>语法形式：</p>
<pre class="crayon-plain-tag">def my_function(arg1: annotation1, arg2: annotation2) -&gt; annotation3:

# 类型提示的例子：
def greet(name: str, age: int) -&gt; str:
    return f"Hello, {name}! You are {age} years old."</pre>
<div class="blog_h3"><span class="graybg">仅关键字参数</span></div>
<p>调用函数时，这种参数必须使用关键字参数语法来传入。要定义仅关键字参数，使用一个<pre class="crayon-plain-tag">*</pre>号，其后面的参数均为仅关键字参数：</p>
<pre class="crayon-plain-tag">def my_function(arg1, *, kwarg1, kwarg2):
    print(f"arg1: {arg1}, kwarg1: {kwarg1}, kwarg2: {kwarg2}")

my_function(10, kwarg1="a", kwarg2="b")</pre>
<div class="blog_h3"><span class="graybg">nonlocal关键字</span></div>
<p>用于直接对外层作用域（非顶级作用域）中的变量进行赋值：</p>
<pre class="crayon-plain-tag">def outer_function():
    outer_var = 10

    def inner_function():
        nonlocal outer_var  # Declare outer_var as nonlocal to access it from the outer_function scope
        outer_var += 5</pre>
<div class="blog_h3"><span class="graybg">扩展迭代器解包语法</span></div>
<p>用于接收迭代器中所有其它对象：</p>
<pre class="crayon-plain-tag">(a, *rest, b) = range(5)     # rest为 [1, 2, 3 ]</pre>
<div class="blog_h3"><span class="graybg">字典推导</span></div>
<p>类似与列表推导：</p>
<pre class="crayon-plain-tag">{k: v for k, v in stuff}

squares = {i: i**2 for i in range(1, 6)}
even_squares = {i: i**2 for i in range(1, 11) if i % 2 == 0}</pre>
<div class="blog_h3"><span class="graybg">集合直接量</span></div>
<p>例如：<pre class="crayon-plain-tag">{1, 2}</pre>，注意{}表示空字典而非集合。空集合是<pre class="crayon-plain-tag">set()</pre>。</p>
<p>集合推导的语法和列表推导一样：<pre class="crayon-plain-tag">{x for x in stuff}</pre></p>
<div class="blog_h3"><span class="graybg">二进制/八进制直接量</span></div>
<pre class="crayon-plain-tag">0o720
0b1010</pre>
<div class="blog_h3"><span class="graybg">元类新语法</span></div>
<pre class="crayon-plain-tag"># 不在支持：
class C:
    __metaclass__ = M

# 新语法：
class C(metaclass=M):</pre>
<div class="blog_h2"><span class="graybg">版本3.1</span></div>
<div class="blog_h3"><span class="graybg"> 有序字典</span></div>
<p>原先字典迭代的顺序是任意的，可以使用<pre class="crayon-plain-tag">collections.OrderedDict</pre>来保证迭代顺序（按插入顺序迭代）：</p>
<pre class="crayon-plain-tag">from collections import OrderedDict

# Creating an OrderedDict
fruits = OrderedDict([
    ('apple', 4),
    ('banana', 6),
    ('orange', 2),
    ('grapes', 10),
])

# Adding a new item to the OrderedDict
fruits['strawberry'] = 8

# Output: apple -&gt; 4, banana -&gt; 6, orange -&gt; 2, grapes -&gt; 10, strawberry -&gt; 8
for fruit, count in fruits.items():
    print(f"{fruit} -&gt; {count}", end=", ")</pre>
<div class="blog_h3"><span class="graybg">增强的str.format</span></div>
<pre class="crayon-plain-tag">print("{}, {}".format("a", "b"))  # It now implicitly auto-numbers the fields. Output: "a, b"
print("{:,}".format(9876543210))  # The comma format specifier. Output: "9,876,543,210"</pre>
<div class="blog_h3"><span class="graybg">精确浮点加法</span></div>
<pre class="crayon-plain-tag">import math

nums = [1e20, 1, -1e20]
print(sum(nums))       # Result: 0.0, which can have an accumulated error
print(math.fsum(nums)) # Result: 1.0, a mathematically accurate summation</pre>
<div class="blog_h2"><span class="graybg">版本3.2</span></div>
<div class="blog_h3"><span class="graybg"> argparse模块<br /></span></div>
<p>强大、灵活的命令行参数解析模块，支持位置参数、子命令等：</p>
<pre class="crayon-plain-tag">import argparse

def main():
    parser = argparse.ArgumentParser(description="A simple script demonstrating argparse.")
    parser.add_argument("-n", "--name", required=True, help="Your name")
    parser.add_argument("-a", "--age", type=int, help="Your age")
    
    args = parser.parse_args()
    
    print(f"Hello, {args.name}!")
    if args.age:
        print(f"You are {args.age} years old.")

if __name__ == "__main__":
    main()</pre>
<div class="blog_h3"><span class="graybg">concurrent.futures</span></div>
<p>提供了基于线程/进程等方式来异步执行callable的高层接口，简化了工作线程、进程的管理：</p>
<pre class="crayon-plain-tag">import concurrent.futures
import time

def perform_work(n):
    time.sleep(n)
    return f"Work completed after {n} seconds"

# Run two tasks concurrently using ThreadPoolExecutor:
with concurrent.futures.ThreadPoolExecutor() as executor:
    work_items = [2, 3]
    results = [executor.submit(perform_work, n) for n in work_items]

    for result in concurrent.futures.as_completed(results):
        print(result.result())

# Run two tasks concurrently using ProcessPoolExecutor:
with concurrent.futures.ProcessPoolExecutor() as executor:
    work_items = [2, 3]
    results = [executor.submit(perform_work, n) for n in work_items]

    for result in concurrent.futures.as_completed(results):
        print(result.result())</pre>
<div class="blog_h2"><span class="graybg">版本3.3</span></div>
<div class="blog_h3"><span class="graybg">yield from</span></div>
<p>该表达式允许一个生成器将它的部分操作，委托给另外一个生成器。当一个生成器在迭代由另外一个生成器产生的条目时，可以简化代码。</p>
<pre class="crayon-plain-tag">def concat_gen(list_of_generators):
    for gen in list_of_generators:
        yield from gen   # 每次调用触发一次yield，第一个生成器完毕后，才循环到第二个

gen1 = (x for x in range(1, 4))  # Generates 1, 2, 3
gen2 = (x for x in range(4, 7))  # Generates 4, 5, 6

# Concatenate the generators using `yield from`
result_gen = concat_gen([gen1, gen2])

for x in result_gen:
    print(x)

# Output: 1, 2, 3, 4, 5, 6</pre>
<div class="blog_h3"><span class="graybg">venv模块和pyvenv脚本</span></div>
<p>用于代替第三方虚拟（隔离）环境模块virtualenv。pyvenv脚本用于管理虚拟环境。</p>
<pre class="crayon-plain-tag"># Create a virtual environment
python -m venv my_virtual_env

# Activate the virtual environment (on Linux or macOS)
source my_virtual_env/bin/activate

# Activate the virtual environment (on Windows)
my_virtual_env\Scripts\activate.bat

# Install packages within the virtual environment
pip install requests

# Deactivate the virtual environment when done
deactivate</pre>
<div class="blog_h3"><span class="graybg">lzma模块</span></div>
<p>支持基于LZMA算法的压缩，也就是那些扩展名为.xz  .7z  .lzma的压缩包。</p>
<pre class="crayon-plain-tag">import lzma

data = b"Example data that will be compressed using LZMA."

# Compress data using LZMA
compressed_data = lzma.compress(data)

# Decompress the data back to its original form
original_data = lzma.decompress(compressed_data)

assert data == original_data, "Decompressed data should match the original data."</pre>
<div class="blog_h3"><span class="graybg">faulthandler模块</span></div>
<p>用于在关键事件（例如段错误）时dump出Python的调用栈。</p>
<pre class="crayon-plain-tag">import faulthandler
import os
import time
import sys

# Enable faulthandler to dump tracebacks to a file
with open("traceback.log", "w") as logfile:
    faulthandler.dump_traceback(file=logfile)

# You can also enable fault handling for uncaught exceptions and signals (e.g., SIGSEGV)
faulthandler.enable(file=sys.stderr, all_threads=True)

# Your program code...
time.sleep(1)</pre>
<div class="blog_h3"><span class="graybg">命名空间包</span></div>
<p>引入一种机制，允许单个包跨越多个目录，可以实现更好的模块化，并且让过大的包易于维护。例如对于下面的目录结构：</p>
<pre class="crayon-plain-tag">dir1/
    my_package/
        __init__.py
        module1.py

dir2/
    my_package/
        # no __init__.py file needed
        module2.py</pre>
<p>可以将my_package看作单一的命名空间包，不需要任何额外配置，导入该包中定义的两个模块：</p>
<pre class="crayon-plain-tag">from my_package.module1 import class1
from my_package.module2 import class2</pre>
<div class="blog_h2"><span class="graybg">版本3.4</span></div>
<div class="blog_h3"><span class="graybg">asyncio模块</span></div>
<p> 这个模块用于实现异步IO、并发编程。从3.5开始，可以利用关键字<pre class="crayon-plain-tag">async</pre> /  <pre class="crayon-plain-tag">await</pre>。</p>
<pre class="crayon-plain-tag">import asyncio

async def hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

async def main():
    # Schedule two coroutines to run concurrently
    task1 = asyncio.create_task(hello())
    task2 = asyncio.create_task(hello())

    await task1
    await task2

# Execute main asynchronously using asyncio.run (Python 3.7 and later)
asyncio.run(main())</pre>
<p> 注意，对async函数的调用，会得到一个协程（或者生成器），而不是直接同步的执行函数体：</p>
<pre class="crayon-plain-tag"># 异步函数（协程）
async def async_function():
    return 1
print(type(async_function()) is types.CoroutineType)
 
 
# 异步生成器
async def async_generator():
    yield 1
print(type(async_generator()) is types.AsyncGeneratorType)</pre>
<p>你可以向操作普通协程那样，对其调用send()：</p>
<pre class="crayon-plain-tag">try:
    async_function().send(None)
except StopIteration as e:
    # 生成器/协程在正常返回退出时会抛出一个StopIteration异常，而原来的返回值会存放在StopIteration对象的value属性中
    print(e.value)</pre>
<p>在async函数中，可以使用await挂起自身，并等待另外一个协程的结果：</p>
<pre class="crayon-plain-tag">async def async_function():
    return 1
 
async def await_coroutine():
    # await语法只能出现在通过async修饰的函数中，否则会报SyntaxError错误
    result = await async_function()
    print(result)
    
run(await_coroutine())



# 注意 await后面必须跟着一个 Awaitable，或者实现了 __await__ 方法：
class Awaitable(metaclass=ABCMeta):
    __slots__ = ()
 
    @abstractmethod
    def __await__(self):
        yield
 
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Awaitable:
            return _check_methods(C, "__await__")
        return NotImplemente</pre>
<div class="blog_h3"><span class="graybg">enum模块</span></div>
<p>可以方便的定义简单的枚举类。</p>
<pre class="crayon-plain-tag">from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# Usage examples
print(Color.RED)        # Output: Color.RED
print(Color.RED.name)   # Output: RED
print(Color.RED.value)  # Output: 1

# 可以让枚举类同时继承A，这样它就有了A类型的能力，例如用在需要A的地方</pre>
<div class="blog_h3"><span class="graybg">pathlib模块</span></div>
<p>用于简化文件系统路径操作。</p>
<pre class="crayon-plain-tag">from pathlib import Path

path = Path("example.txt")

# Check if the file exists
if not path.exists():
    # Create a new file and write some content
    path.write_text("Hello, World!")

# Read file content
content = path.read_text()
print(content)  # Output: Hello, World!

# List all files in the current directory
for file_path in Path(".").iterdir():
    print(file_path) </pre>
<div class="blog_h3"><span class="graybg">pickle模块</span></div>
<p>为pickle增加了新版本（4）的默认协议，支持更加高效的、针对大规模数据结构的串行化。</p>
<pre class="crayon-plain-tag">import pickle

data = {
    "name": "Alice",
    "age": 30,
    "city": "New York",
}

# Serialize data using protocol version 4 (new in Python 3.4)
serialized = pickle.dumps(data, protocol=4)

# Deserialize the data back to a Python object
deserialized_data = pickle.loads(serialized)

assert data == deserialized_data, "Deserialized data should match the original data."</pre>
<div class="blog_h3"><span class="graybg">selectors模块</span></div>
<p>提供了事件驱动的IO框架、在套接字以及其它非阻塞IO上的多路复用的IO操作支持。</p>
<pre class="crayon-plain-tag"># This example shows how to use `selectors` for a simple echo server.
# For brevity, error handling is omitted. This example is for Linux/Unix-based systems using `selectors.DefaultSelector`.
import socket
import selectors

sel = selectors.DefaultSelector()

def accept(sock):
    conn, addr = sock.accept()
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn):
    received_data = conn.recv(1000)
    if received_data:
        # Echo the received data back to the client
        conn.sendall(received_data)
    else:
        # Connection closed, unregister the socket from selector
        sel.unregister(conn)
        conn.close()

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 1234))
server_socket.listen(5)
server_socket.setblocking(False)
sel.register(server_socket, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, _ in events:
        key.data(key.fileobj) </pre>
<div class="blog_h3"><span class="graybg">单分发通用函数</span></div>
<p>通过装饰器<pre class="crayon-plain-tag">functools.singledispatch</pre>实现，用于定义多个版本的函数实现，每个函数针对不同的参数类型。</p>
<pre class="crayon-plain-tag">from functools import singledispatch

@singledispatch
def process_data(data):
    print(f"Unknown data type: {data}")

@process_data.register(int)
def _(data: int):
    print(f"Processing integer data: {data}")

@process_data.register(str)
def _(data: str):
    print(f"Processing string data: {data}")

process_data(42)         # Output: Processing integer data: 42
process_data("Hello")    # Output: Processing string data: Hello
process_data([1, 2, 3])  # Output: Unknown data type: [1, 2, 3]</pre>
<div class="blog_h2"><span class="graybg">3.5</span></div>
<div class="blog_h3"><span class="graybg"> 矩阵乘法操作符</span></div>
<pre class="crayon-plain-tag">import numpy as np

mat1 = np.array([[1, 2], [3, 4]])
mat2 = np.array([[5, 6], [7, 8]])

mat_product = mat1 @ mat2  # Matrix multiplication using new @ operator

# Output: array([[19, 22], [43, 50]])
print(mat_product)</pre>
<div class="blog_h3"><span class="graybg">解包能力增强</span></div>
<p>可以在单个函数调用、单个推导（comprehension）操作中，从多个可迭代对象中解包多个元素：</p>
<pre class="crayon-plain-tag">a = [1, 2]
b = [3, 4, 5]

# Merging lists using unpacking generalizations
merged_list = [*a, *b]  # Output: [1, 2, 3, 4, 5]

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}

merged_dict = {**dict1, **dict2}  # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}</pre>
<div class="blog_h3"><span class="graybg">类型提示</span></div>
<p>包<pre class="crayon-plain-tag">typing</pre>提供了增强的类型提示能力，例如，可以指定容器类型的元素的类型。</p>
<pre class="crayon-plain-tag">from typing import List, Dict

#                列表值类型            字典键值了类型       非容器类型
def greet(names: List[str], age_dict: Dict[str, int]) -&gt; str:
    greetings = []
    for name in names:
        age = age_dict.get(name)
        greeting = f"Hello, {name}! You are {age} years old."
        greetings.append(greeting)
    return "\n".join(greetings)

names_list = ["Alice", "Bob", "Charlie"]
ages_dict = {"Alice": 25, "Bob": 30, "Charlie": 35}

result = greet(names_list, ages_dict)
print(result)</pre>
<p>除了常用的集合类型容器，typing模块还提供了：</p>
<ol>
<li><pre class="crayon-plain-tag">Optional</pre>：用于提示参数或者字段是可选的</li>
<li><pre class="crayon-plain-tag">Union</pre>：用于提示参数可能是多个类型之一</li>
<li><pre class="crayon-plain-tag">Any</pre>：不限制类型</li>
<li><pre class="crayon-plain-tag">Callable</pre>：提示参数是可调用的</li>
</ol>
<p>对于复杂的、反复使用的类型提示，可以定义别名：</p>
<pre class="crayon-plain-tag">Person = Tuple[str, int]
People = List[Person]</pre>
<div class="blog_h3"><span class="graybg">async with</span></div>
<p>这个语句初始化一个异步的上下文管理器。异步上下文管理器对象必须实现特定的方法，以创建/清理临时上下文，即使有异常抛出，清理工作也会执行。需要实现的方法如下：</p>
<ol>
<li><pre class="crayon-plain-tag">async def __aenter__(self)</pre>：进入async with块时该方法被调用</li>
<li><pre class="crayon-plain-tag">async def __aexit__(self, exc_type, exc_value, traceback)</pre>：离开块时该方法被调用</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">import aiofiles

async def read_file(file_name: str) -&gt; str:
    async with aiofiles.open(file_name, mode="r") as f:
        text = await f.read()
    return text</pre>
<p>和同步的上下文管理器的主要区别是：资源创建、主体逻辑、资源清理都是异步进行的，而后者这三个操作是<span style="background-color: #c0c0c0;">同步进行（即在同一个事件循环中、阻塞其它操作）</span>。</p>
<div class="blog_h2"><span class="graybg">版本3.6</span></div>
<div class="blog_h3"><span class="graybg">f格式化字符串</span></div>
<pre class="crayon-plain-tag">name = "John"
age = 25
print(f"Hello, my name is {name} and I'm {age} years old.")</pre>
<div class="blog_h3"><span class="graybg">数字可以用下划线</span></div>
<pre class="crayon-plain-tag">one_million = 1_000_000
print(f"One million is written as {one_million}.")</pre>
<div class="blog_h3"><span class="graybg">变量注解语法 </span></div>
<pre class="crayon-plain-tag">count: int = 0
def greet(name: str) -&gt; str:
    return f'Hello, {name}'

print(greet("John"))</pre>
<div class="blog_h3"><span class="graybg">异步生成器</span></div>
<p>可以对列表、集合、字典以及列表推导、生成器等使用<pre class="crayon-plain-tag">async for</pre>操作：</p>
<pre class="crayon-plain-tag">import asyncio

async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

async def run():
    # 这里的迭代是异步进行的
    async for i in ticker(1, 5):
        print(i)

asyncio.run(run())</pre>
<div class="blog_h2"><span class="graybg">版本3.7 </span></div>
<div class="blog_h3"><span class="graybg">类型注解的延迟估算</span></div>
<p>这个特性使得使用前向引用（forward references ，即引用尚未定义的类型）和生命类型提示变得简单，不再需要在类型注解中使用字符串直接量。</p>
<p>当存在类型之间的循环引用时，类型注解可能会导致问题，为了解决此问题，需要使用字符串直接量：</p>
<pre class="crayon-plain-tag">class A:
    def x(self) -&gt; "B":  # Using string literal for forward reference
        pass

class B:
    def y(self) -&gt; A:
        pass</pre>
<p>有了该特性后，则可以：</p>
<pre class="crayon-plain-tag">from __future__ import annotations  # Import annotations from __future__

class A:
    def x(self) -&gt; B:  # No need for string literal
        pass

class B:
    def y(self) -&gt; A:
        pass</pre>
<div class="blog_h3"><span class="graybg">dataclasses </span></div>
<p>这个模块提供了一个装饰器，用来装饰一个仅仅用来存放数据的类型：</p>
<pre class="crayon-plain-tag">from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p1 = Point(1.0, 2.5)
p2 = Point(3.5, 0.5)

print(p1)  # Output: Point(x=1.0, y=2.5)
print(p2)  # Output: Point(x=3.5, y=0.5)</pre>
<div class="blog_h3"><span class="graybg">breakpoint()函数</span></div>
<p>调用该函数，可以直接从代码进入Python Debugger（PDB），可以方便调试：</p>
<pre class="crayon-plain-tag">def divide(a, b):
    breakpoint()  # Debugger will be triggered here
    return a / b

result = divide(4, 2)</pre>
<div class="blog_h2"><span class="graybg">版本3.8</span></div>
<div class="blog_h3"><span class="graybg">:=操作符</span></div>
<p>支持将赋值作为表达式的一部分：</p>
<pre class="crayon-plain-tag">n = 10
while (squared := n * n) &lt; 100:
    print(squared)
    n += 1</pre>
<div class="blog_h3"><span class="graybg">仅位置参数</span></div>
<p>支持指定某些函数参数，必须以位置参数的形式传入：</p>
<pre class="crayon-plain-tag">def my_function(pos1, pos2, /, pos_or_kwarg1, *, kwarg1, kwarg2):
    pass

# Allowed:
my_function(1, 2, 3, kwarg1=4, kwarg2=5)

# Not allowed:
my_function(1, 2, pos_or_kwarg1=3, kwarg1=4, kwarg2=5)</pre>
<p>符号 <pre class="crayon-plain-tag">/</pre> 前面的都是仅位置参数。</p>
<div class="blog_h3"><span class="graybg">逆转字段顺序</span></div>
<p>内置函数可以用来逆转字典键值对顺序：</p>
<pre class="crayon-plain-tag">my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
reversed_items = list(reversed(my_dict.items()))

print(reversed_items)  # Output: [('d', 4), ('c', 3), ('b', 2), ('a', 1)]</pre>
<div class="blog_h2"><span class="graybg">版本3.9</span></div>
<div class="blog_h3"><span class="graybg">字典合并操作符</span></div>
<pre class="crayon-plain-tag">dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}

merged = dict1 | dict2
print(merged)  # Output: {"a": 1, "b": 3, "c": 4}

dict1 |= dict2
print(dict1)  # Output: {"a": 1, "b": 3, "c": 4}</pre>
<div class="blog_h3"><span class="graybg">新的字符串方法</span></div>
<p>移除字符串前后缀的新方法：</p>
<pre class="crayon-plain-tag">filename = "document.pdf"

name = filename.removesuffix(".pdf")
print(name)  # Output: "document"

name = filename.removeprefix("docu")
print(name)  # Output: "ment.pdf"</pre>
<div class="blog_h3"><span class="graybg">标准库的时区支持</span></div>
<pre class="crayon-plain-tag">from datetime import datetime
from zoneinfo import ZoneInfo

dt = datetime(2021, 1, 1, tzinfo=ZoneInfo("America/New_York"))
print(dt)  # Output: 2021-01-01 00:00:00-05:00</pre>
<div class="blog_h3"><span class="graybg">类型提示的泛型支持</span></div>
<p>在老版本，你必须借助typing模块：</p>
<pre class="crayon-plain-tag">from typing import List

def process_numbers(numbers: List[int]) -&gt; None:
    for number in numbers:
        print(number * 2)</pre>
<p>新版本中直接使用内置类型：</p>
<pre class="crayon-plain-tag">def process_dict(data: dict[str, int]):
    for key, value in data.items():
        print(key, value * 2)

def process_tuple(data: tuple[int, int, int]):
    for value in data:
        print(value * 2)

def process_set(data: set[int]):
    for value in data:
        print(value * 2)</pre>
<p>需要注意，某些库可能仍然依赖旧的语法。</p>
<div class="blog_h2"><span class="graybg">版本3.10</span></div>
<div class="blog_h3"><span class="graybg">更简单的联合类型提示</span></div>
<p>不再需要Union</p>
<pre class="crayon-plain-tag"># Before Python 3.10 Release
from typing import Union
def f(list: List[Union[int, str]], param: Optional[int]):
    pass

# In Python 3.10 Release
def f(list: List[int | str], param: int | None):
    pass

# Calling the function
f([1, “abc”], None)</pre>
<div class="blog_h3"><span class="graybg">match-case分支判断</span></div>
<pre class="crayon-plain-tag">def process_number(number):
    match number:
        case 1:
            print("One")
        case 2:
            print("Two")
        case _:
            print("Other")

process_number(1)  # Output: One
process_number(5)  # Output: Other </pre>
<div class="blog_h3"><span class="graybg">结构化模式匹配</span></div>
<p>使用match - case语句，可以对复杂结构进行模式匹配：</p>
<pre class="crayon-plain-tag">def process_data(data):
    match data:
        case None:
            print("No data")
        case {"status": "success", "result": int(result)}:
            print(f"Success: {result}")
        case {"status": "error", "message": str(message)}:
            print(f"Error: {message}")
        case _:
            print("Unknown data format")</pre>
<div class="blog_h3"><span class="graybg">多行上下文管理器</span></div>
<pre class="crayon-plain-tag">with open("file1.txt") as file1, open("file2.txt") as file2:  # Before Python 3.10
    pass

# In Python 3.10
with (
    open("file1.txt") as file1,
    open("file2.txt") as file2,
):
    pass</pre>
<div class="blog_h2"><span class="graybg">版本3.11</span></div>
<div class="blog_h3"><span class="graybg">异常组和except*</span></div>
<p>异常组让相关的异常被一起处理。</p>
<p>在Python中，所有异常均是BaseException的子类型。异常具有一个message参数来提供消息：</p>
<pre class="crayon-plain-tag">raise SyntaxError("Just raising a syntax error")</pre>
<p> 使用try - except块可以捕获并处理异常：</p>
<pre class="crayon-plain-tag">try:
   prin(34/0)
except (ZeroDivisionError, NameError) as exc:
   print(exc)
except  XxError as e:
   pass</pre>
<p>这种处理方式有以下限制：</p>
<ol>
<li>一次只能处理一个异常</li>
<li>仅仅会执行第一个匹配的except块</li>
</ol>
<p><pre class="crayon-plain-tag">ExceptionGroup</pre>是Exception的子类，你可以像处理普通异常一样处理它：</p>
<pre class="crayon-plain-tag">print(issubclass(ExceptionGroup, Exception))

raise ExceptionGroup("exception groups", [ValueError(1), TypeError(2)]) 

try:
    raise ExceptionGroup("An exception group", [ValueError(), TypeError(1)])
except ExceptionGroup:
    print("I caught an exception group")</pre>
<p>异常组的第二参数，是组中包含的异常的列表。你可以捕获其中任何成员：</p>
<pre class="crayon-plain-tag">try:
     raise ExceptionGroup("An exception group", [ValueError(), \
     TypeError(1)])
except TypeError:
     print("I am handling the TypeError in the exception group")</pre>
<p>使用特殊的<pre class="crayon-plain-tag">except *</pre>可以捕获多个异常成员：</p>
<pre class="crayon-plain-tag">try:
    raise ExceptionGroup("An exception group", [ValueError(), TypeError(1)])
except * TypeError:
    print("I am handling a Type error")
except * ValueError:
    print("I am handling a ValueError")

# I am handling a Type error
# I am handling a ValueError </pre>
<div class="blog_h3"><span class="graybg">类型提示的参数化</span></div>
<pre class="crayon-plain-tag">from typing import TypeVar

T0 = TypeVar("T0")
T1 = TypeVar("T1")

def flip(pair: tuple[T0, T1]) -&gt; tuple[T1, T0]:
    first, second = pair
    return (second, first)</pre>
<div class="blog_h3"><span class="graybg">Self类型提示</span></div>
<p>这个特殊的类型表示当前类的类型。</p>
<pre class="crayon-plain-tag">from typing import Self

class Article:
   def a_method_that_returns_an_instance(self) -&gt; Self:
       ...</pre>
<p>&nbsp;</p>
<div class="blog_h3"><span class="graybg">TypeVarTuple类型提示</span></div>
<p>这个类型允许容器类型具有任意数量的元素类型：</p>
<pre class="crayon-plain-tag">from typing import TypeVarTuple, TypeVar, Tuple

TS = TypeVarTuple("TS")
T = TypeVar("T")
def example(value_1:Tuple[T, *TS]):  # 必须使用*解包语法
   ...


example(value_1=(1, 'a number', 3.0))</pre>
<div class="blog_h3"><span class="graybg">TypedDict类型化字典</span></div>
<p>用于限定字典具有哪些键值、值的类型：</p>
<pre class="crayon-plain-tag">from typing import TypedDict

class ArticleType(TypedDict):
   article_id: int
   title: str
   rating: float


article_1: ArticleType = {
   "article_id": 23,
   "title": "Introducing the new features in Python 3.11",
   "rating":4.5
}</pre>
<p>在3.11，你可以控制某个键值是否为必须：</p>
<pre class="crayon-plain-tag">from typing import TypedDict, Required, NotRequired

class ArticleType(TypedDict):
   article_id: Required[int]
   title: NotRequired[str]
   rating: float</pre>
<p>&nbsp;</p>
<div class="blog_h1"><span class="graybg">Python语言基础</span></div>
<div class="blog_h2"><span class="graybg">基于缩进的代码风格</span></div>
<p>Python的每个语句以换行符结束，如果太长，可以使用续行符（反斜杠）跨行</p>
<pre class="crayon-plain-tag">a = math.cos(3 * (x - n)) + \
    math.sin(3 * (x - n))</pre>
<p>使用<span style="background-color: #c0c0c0;">三引号定义的字符串</span>、<span style="background-color: #c0c0c0;">列表</span>、<span style="background-color: #c0c0c0;">元组</span>或<span style="background-color: #c0c0c0;">字典</span>分布在多行上时，不需要使用续行符。</p>
<p>缩进用于表示<span style="background-color: #c0c0c0;">不同的代码块，如函数体、条件语句、循环和类</span>。代码块中首条语句的<span style="background-color: #c0c0c0;">缩进</span>可以任意的，但是后续语句必须与之<span style="background-color: #c0c0c0;">保持一致</span>。</p>
<p>如果函数体、分支、循环等较短，可以放在一行，不需要缩进：</p>
<pre class="crayon-plain-tag">if a: pass</pre>
<p>应当使用空格，而不是制表符进行缩进。</p>
<div class="blog_h2"><span class="graybg">标识符和保留字</span></div>
<div class="blog_h3"><span class="graybg">标识符</span></div>
<p>变量标识符<span style="background-color: #c0c0c0;">仅支持：数字、下划线、A-Za-z</span>，并且数字不能作为标识符的开头。标识符区分大小写。</p>
<p>以下划线开始或结束的标识符具有特殊含义：</p>
<ol>
<li>以单下划线开头的标识符不能通过from module import *导入</li>
<li>__func__用于定义特殊方法</li>
<li>__priv用于定义类私有成员</li>
</ol>
<div class="blog_h2"><span class="graybg">基础数据类型</span></div>
<div class="blog_h3"><span class="graybg">数字（int、Long、float、complex、bool）</span></div>
<pre class="crayon-plain-tag">#Python中，整数的位数是任意的
bigInt = 15156165496484919616
#不同进制的整数
0644        #八进制
0x100fea8   #十六进制
0b1110001   #二进制
#浮点数表示
3.14
1.2334e+02
#使用等号赋值
width = 20
#整数与浮点数运算时，自动转换为浮点数
3 * 3.75 / 1.5
#实数的类型转换函数：int、float、long等
int(3.75)

#支持复数，j（或者J）表示虚部
z = 1.5 + 0.5j
#获取实部、虚部
z.real
z.imag</pre>
<div class="blog_h3"><span class="graybg">布尔值</span></div>
<p>标识符<span style="background-color: #c0c0c0;">True和False</span>被解释为布尔值，其整数值分别是<span style="background-color: #c0c0c0;">1和0</span>。</p>
<div class="blog_h3"><span class="graybg">序列类型</span></div>
<p>字符串、列表、元组，统称为序列类型。序列类型具有共同的特征：</p>
<ol>
<li>支持索引访问，例如s[0]</li>
<li>支持切片运算符，例如s[0:5]，对于可变序列，还可以删除切片，例如del s[i:j]</li>
<li>使用len(s)可以返回序列长度</li>
<li>使用min(s)、max(s)可以返回序列中元素的最小最大值</li>
<li>使用all(s)、any(s)可以检查是否每个元素、存在任何元素为True</li>
</ol>
<div class="blog_h3"><span class="graybg">字符串（str、unicode）</span></div>
<p>支持双引号或者单引号的字符串。Python字符串是不可变的。</p>
<pre class="crayon-plain-tag">#使用单引号、双引号定义多行文本，必须在行尾添加换行+续行符，行首的空白符也会被识别
str = "This is a long string\n   \
    containing two lines os text \
"

#可以用三引号对来标识字符串，不需要\n转义
"""
Hello there
Greetings
"""

#原始字符串，不进行转义
str = r"\n is just \n"
#字符串可以使用+来连接，使用*来重复：
str = str + str * str

#字符串切片，起始索引、结束索引如果不指定，分别为0、字符串长度
#容错：如果结束索引过大，自动认为等于字符串长度，如果开始索引小于结束索引，返回空串
str = HelpA
word[4] == 'A'
word[0:2] == 'He'
#如果索引为负数，则表示从右侧算索引
word[-1] = 'A'
word[-2] = 'p'
word[-2:] = 'pA'
word[-0] = 'H' #-0就作为0看待
word[:] = 'HelpA'  #这种特殊的写法表示返回完整的字符串

#字符串相关函数
len(s) #返回字符串的长度
str(3.4) #转换为字符串
repr(3.4)#转化为字符串，显示为对象内部精确值：3.3999999999999999
format(3.4,'0.5f') #格式化输出3.40000</pre>
<p>Python 2.0以后引入Unicode，来表示Unicode字符串，必要时可以与原始字符串进行转换：</p>
<pre class="crayon-plain-tag">#使用u前缀表示Unicode字符串
str = u"蟒蛇"
#使用Unicode转义\u****来插入特殊Unicode字符
str = u"Hello\u0020World"
#Unicode字符串的原始模式（不进行转义）
str = ur"Hello\u0020World"</pre>
<p>利用str()函数进行转换时，会使用默认编码（通常是ASCII）：</p>
<pre class="crayon-plain-tag">&gt;&gt;&gt; str(u"蟒蛇")
Traceback (most recent call last):
  File "", line 1, in 
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)</pre>
<p>可以使用encode方法来获取特定编码的16进制转写：</p>
<pre class="crayon-plain-tag">u"蟒蛇".encode('utf-8')
#内置函数unicode可以使用所有已注册的Unicode编码来解码
unicode('\xc3\xa4\xc3\xb6\xc3\xbc', 'utf-8')</pre>
<div class="blog_h3"><span class="graybg">列表（list）</span></div>
<pre class="crayon-plain-tag">#列表的元素可以是不同的类型
a = ['spam', 'eggs', 100, 1234]
#类似字符串，列表可以被切片或者连接，切片返回浅拷贝的副本
a[0] == 'spam'
a[-2] == 100
a[:2] + ['bacon', 2*2] == ['spam', 'eggs', 'bacon', 4]
a = [1, 2, 3] + [4, 5]  #连接列表

#内置函数len可以用于获取列表的长度
len(2)

#相关方法
a = list()  #等价于a = []
a.append(1) #在尾部插入元素
a.insert(2,200) #插入元素到指定索引位置</pre>
<div class="blog_h3"><span class="graybg">元组（tuple）</span></div>
<p>在圆括号里面包含一组值，即为元组。元组创建后，不能修改其内容（替换、添加或者删除元素）。使用元组代替小列表，更加节约内存</p>
<pre class="crayon-plain-tag">corp = ("3203001102256", "徐州工业集团", 3200) #定义元组
corp = "3203001102256", "徐州工业集团", 3200   #这样也可以识别元组
a = ()   #空元组
b = (1,) #一元组，注意结尾的逗号
c = 1,   #一元组

#可以使用数字索引获取元组中的值，但是更常见的做法是将元组解包为一组变量
regNo, corpName, regCapi = corp</pre>
<div class="blog_h3"><span class="graybg">集合（set、frozenset）</span></div>
<p>集合是无序的、不包含重复元素的对象组：</p>
<pre class="crayon-plain-tag">s = set ([3,5,9,10])	#创建一个数值集合
t = set ("Hello")	#创建包含4个字符的集合（两个l只能保存一个在集合中）

#集合运算符
a = t | s   #t和S的并集
a = t &amp; s   #t和s的交集
a = t - s   #求差集（项在t中，但不在s中）
a = t ^ s   #对称差集（项在t或S中，但不会同时出现在二者中）

#集合方法
t.add('x')  #添加一个项
s.update([10,37,42])#添加多个项
t.remove('H') #删除一个项</pre>
<div class="blog_h3"><span class="graybg">字典（dict）</span></div>
<p>字典就是关联数组（散列表）。字符串、元组等可以作为散列键，但是可变对象例如列表、字典则不可以作为键。</p>
<pre class="crayon-plain-tag">prices = {
    "GOOG" : 490.10, 
    "APPL" : 123.50, 
    "IBM"  : 91.50, 
    "MSFT" : 52.13
}

#创建空字典
prices = {}
prices = dict()

#使用in运算符可以测试某个项是否字典成员
if "GOOG" in prices:
    p = prices["GOOG"]

#相关函数
keys = list(prices) #获取关键字的列表
del prices["GOOG"]  #删除字典元素

#相关方法
p = prices.get("GOOG", 0.0) #如果不存在，返回0.0</pre>
<div class="blog_h3"><span class="graybg">None</span></div>
<p>None是一个特殊的类型，用于表示null对象。<br /> 如果一个函数没有显式的返回值，则自动返回None。布尔求值时为False</p>
<div class="blog_h2"><span class="graybg">流程控制</span></div>
<div class="blog_h3"><span class="graybg">if分支结构</span></div>
<pre class="crayon-plain-tag">x = int(raw_input("Please enter an integer: "))
if x &lt; 0:
    x = 0
    print 'Negative changed to zero'
elif x == 0:
    print 'Zero'
elif x == 1:
    print 'Single'
else:
    print 'More'

#条件表达式简写
minvalue = a if a &lt;= b else b</pre>
<div class="blog_h3"><span class="graybg">for循环结构</span></div>
<pre class="crayon-plain-tag">a = ['cat', 'window', 'defenestrate']
for x in a:
    print x, len(x) #逗号分隔的print可以连续打印不换行
#打印：
#cat 3
#window 6
#defenestrate 12

#迭代列表的副本，以便安全的修改列表
for x in a[:]:
    if len(x) &gt; 6: a.insert(0, x)
    elif False : continue
    elif False : break
    elif False : pass     #什么都不做，作为语法占位符

#使用range函数可以生成一个等差级数俩表：
range(10)             #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
range(5, 10)          #[5, 6, 7, 8, 9]
range(-10, -100, -30) #[-10, -40, -70] 第三个参数为步长

#迭代一个列表
a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
    print i, a[i]
#或者
for item in a:
    print item
#解包一个序列，变量个数必须与序列元素个数一致
for x,y,z in s:
    pass
#内置函数enumerate，迭代序列，返回索引、元素组成的元组
for i,x in enumerate(s):
    pass
#同时迭代两个以上的序列，使用内置的zip函数
for x,y,z in zip(r,s,t):
    pass
#迭代一个字典
c = {"GOOG": 490.10, "IBM":91.50 }
for key in c:
    print key, c[key]

#打印一个文件中所有行
f = open("foo.txt")
for line in f:
    print line</pre>
<div class="blog_h2"><span class="graybg">函数</span></div>
<pre class="crayon-plain-tag">#关键字def引入函数定义，其后必须跟有函数名、形参列表
def reminder(a, b):
#函数体必须缩进，可以添加docstring
    """docstring"""   #使用remider.__doc__可以访问文档字符串
    q = a // b        #截断除法运算符
    r = a - q * b
    return r          #如果省略return语句，自动返回None
#如果函数要返回多个值，可以使用元组
def divide(a, b):
    q = a // b
    r = a - q*b
    return (q, r)

#形参可以具有默认值
def connect(host ,port, timeout = 300):
    h0 = host   #在函数体内创建的变量，作用域是局部的，函数退出后自动销毁
    global h0 = host #使用此关键字来修改全局变量的值
    pass

#位置参数：要使用函数，只需要传入实参列表即可，参数数量、顺序必须匹配，否则引发TypeError
reminder(88, 3)
#可以省略提供默认值的参数
connect('gmem.cc', 80)
#关键字参数：可以按照任意顺序提供参数，但是需要提供形参名称
connect(port = 80, host = 'gmem.cc')

#使用可变对象作为形参默认值，会导致意外的行为：每次调用，可能使用同一个对象
def add(x, items=[]):
    items.append(x)
    return items
add(1)          #返回[1]
add(2)          #返回[1, 2]，不符合预期

#在最后一个参数前面加*，可以接受任意数量的位置参数，自动存入元组
def fprintf(file, fmt, *args):
    file.write(fmt % args)
fprintf(out,"%d %s %f", 42, "hello world", 3.45) #args == (42, "hello world", 3.45)

#在最后一个参数前加**，则所有额外的关键字参数存入字典
def make_tab(data, **params):
    fc = params.pop('fgcolor','black')
make_tab(items, fgcolor='red')

#位置参数与关键字参数可以一起使用，**必须出现在最后面
#编写代理、包装器函数时，经常需要使用*args、**kwargs
def func(*args, **kwargs):
    pass</pre>
<div class="blog_h3"><span class="graybg">类型注解</span></div>
<p>Python支持类型注解：</p>
<pre class="crayon-plain-tag"># Python 3 类型注解写法
#         参数类型
#                        返回值类型
def add(x:int, y:int) -&gt; int:
    return x + y

# Python 2写法
def add(x, y):
    return x + y</pre>
<p>类型注解的作用是，让开发人员直观的了解返回值类型，让IDE能够进行自动的类型推导。对解释器的行为不产生任何影响。 </p>
<p>对于容器类型，需要从<pre class="crayon-plain-tag">typing</pre>包引入一些类：</p>
<pre class="crayon-plain-tag">from typing import List, Dict, Tuple, Sequence

# 可以指定容器元素类型
def list() -&gt; List[float]: pass

# 可以定义类型别名
Vector = List[float]
def list() -&gt; Vector: pass


# 复杂的类型注解
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]

# 这样也可以
Sequence[Tuple[Tuple[str, int], Dict[str, str]]]</pre>
<div class="blog_h3"><span class="graybg">函数的属性</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>__doc__</td>
<td>文档字符串</td>
</tr>
<tr>
<td>__name__</td>
<td>函数名称 </td>
</tr>
<tr>
<td>__dict__</td>
<td>包含函数属性的字典</td>
</tr>
<tr>
<td>__code__</td>
<td>字节编译的代码</td>
</tr>
<tr>
<td>__defaults__</td>
<td>默认参数的元组</td>
</tr>
<tr>
<td>__globals__</td>
<td>定义函数的全局命名空间，即定义了函数的模块中所有全局变量/函数构成的字典</td>
</tr>
<tr>
<td>__closure__</td>
<td>嵌套作用域相关数据的元组</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">方法的属性</td>
</tr>
<tr>
<td>__class__</td>
<td>定义方法的类</td>
</tr>
<tr>
<td>__func__</td>
<td>实现方法的函数对象</td>
</tr>
<tr>
<td>__self__</td>
<td>与方法相关的实例，如果是非绑定方法，则为None</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用内置函数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>abs (x)</td>
<td>返回绝对值</td>
</tr>
<tr>
<td>all(s)</td>
<td>如果可迭代的s中的所有值都为True,则返回True</td>
</tr>
<tr>
<td>any(s)</td>
<td>如果可迭代的s中的任意值为True，则返回True</td>
</tr>
<tr>
<td>ascii(x)</td>
<td>类似repr()，创建对象的可打印格式，但是只使用ASCII字符，非ASCII字符使用转义序列</td>
</tr>
<tr>
<td>bin(x)</td>
<td>返回一个字符串，其中包含整数二进制形式</td>
</tr>
<tr>
<td>bool([x])</td>
<td>转换为布尔型</td>
</tr>
<tr>
<td>bytearray ([x])</td>
<td>可变字节数组，x可能是范围从0到255的可迭代整数序列、8位字符串或字节字面量</td>
</tr>
<tr>
<td>bytearray(s,encoding)</td>
<td>从字符串创建字节数组，使用指定的编码</td>
</tr>
<tr>
<td>bytes ([x])</td>
<td>表示不变字节数组</td>
</tr>
<tr>
<td>chr(x)</td>
<td>将整数值转换为单字符的字符串。在Python 2中，x必须在0 &lt;= x &lt;= 255范围内</td>
</tr>
<tr>
<td>classmethod(func)</td>
<td>用于创建类方法，@classmethod装饰器隐式调用它</td>
</tr>
<tr>
<td>cmp(x, y)</td>
<td>比较两个对象，如果x&gt;y返回正数，相等返回0</td>
</tr>
<tr>
<td>compile(string)</td>
<td>编译字符串为代码</td>
</tr>
<tr>
<td>complex(real,img)</td>
<td>创建复数 </td>
</tr>
<tr>
<td>delattr(obj, attr)</td>
<td>等同于del obj.attr</td>
</tr>
<tr>
<td>dict([m])</td>
<td>创建字典</td>
</tr>
<tr>
<td>dir([object])</td>
<td>返回属性名的有序列表。用户可以通过定义__dir__()方法改变此方法的行为</td>
</tr>
<tr>
<td>divmod(a, b)</td>
<td>返回商和余数 </td>
</tr>
<tr>
<td>enumerate(iter)</td>
<td>给定可迭代对象iter,返回新迭代器，迭代元素形式为(index,el)的元组</td>
</tr>
<tr>
<td>eval(expr)</td>
<td>计算表达式的值</td>
</tr>
<tr>
<td>exec(code)</td>
<td>执行指定的代码 </td>
</tr>
<tr>
<td>filter(function, iterable)</td>
<td>在Python2中，创建来自iterable的元素的列表，对这些元素调用function结果为True则包含在结果列表中</td>
</tr>
<tr>
<td>float([x])</td>
<td> 创建浮点数</td>
</tr>
<tr>
<td>format (val, [,fmt_spec]）</td>
<td>格式化字符串</td>
</tr>
<tr>
<td>frozensett[items])</td>
<td>不变集合对象</td>
</tr>
<tr>
<td>getattr(obj, name,default)</td>
<td>返回对象的一个命名属性的信</td>
</tr>
<tr>
<td>globals()</td>
<td>返回代表当前模块全局命名空间的字典</td>
</tr>
<tr>
<td>hasattr(object, name)</td>
<td>如果object具有属性name，则返回True</td>
</tr>
<tr>
<td>hash(object)</td>
<td>返回对象的整数散列值</td>
</tr>
<tr>
<td>hex(x)</td>
<td>根据整数x创建一个十六进制字符串</td>
</tr>
<tr>
<td>id(object)</td>
<td>返回object对象的唯一标识，通常为内存地址</td>
</tr>
<tr>
<td>input([prompt])</td>
<td>在Python 2中，该函数打印一个提示符，读取输入行并通过eval对其进行处理</td>
</tr>
<tr>
<td>int(x [,base])</td>
<td>创建整数</td>
</tr>
<tr>
<td>isinstance(obj, cls)</td>
<td>如果obj是cls或者其子类的实例</td>
</tr>
<tr>
<td>issubclass(class1, class2)</td>
<td>如果class1是class2的子类，或者class1是基于抽象基类class2册的，则返回True</td>
</tr>
<tr>
<td>iter(object [,sentinel])</td>
<td>返回object的迭代器，如果不指定sentinel，则object必须具有__iter__或者__getitem__方法</td>
</tr>
<tr>
<td>len(s)</td>
<td>返回s中包含的项数，s是列表、元组、字符串、集合或字典</td>
</tr>
<tr>
<td>list([items]) </td>
<td>根据可迭代对象items创建列表</td>
</tr>
<tr>
<td>locals()</td>
<td>返回当前函数的本地命名空间构成的字典</td>
</tr>
<tr>
<td>long([x [,base]])</td>
<td>在Python 2中表示长整数的类型。为了可移植性考虑，应当避免直接使用long</td>
</tr>
<tr>
<td>map(function, items, ...)</td>
<td>在Python 2中，该函数将function应用到items的每一项并返回结果列表，Python3则返回迭代器</td>
</tr>
<tr>
<td>max(s [, args, ...])</td>
<td>
<p>如果只有一个参数s,该函数返回s中各项的最大值，s可以是任意可迭代的对象。如果有多个参数，它返回参数中的最大值。min(s [, args, ...])类似</p>
</td>
</tr>
<tr>
<td>next(s [, default])</td>
<td>返回迭代器s中的下一项。如果该迭代器没有下一项，则引发Stopiteration异常（除非指定default）</td>
</tr>
<tr>
<td>object()</td>
<td>Python中所有对象的基类。可以调用它创建一个实例</td>
</tr>
<tr>
<td>oct (x)</td>
<td>将整数转换为一个八进制字符串</td>
</tr>
<tr>
<td>open(file [,mode[,bufsize]])</td>
<td>在Python2中，打开文件返回一个新文件对象</td>
</tr>
<tr>
<td>ord(c)</td>
<td>返回字符c的整数序值。如果是普通字符，返回范围在[0,255]内的值。如果是单个Unicode字符， 通常返回范围在[0,65535]的值</td>
</tr>
<tr>
<td>pow(x, y [, z])</td>
<td>返回x ** y。如果提供了z，则该函数返回(x ** y) % z</td>
</tr>
<tr>
<td>property ( [fget [, fset [,fdel [,doc]]]])</td>
<td>创建类的property属性。get是返回属性值的函数，fset设置属性值，而fdel删除一个属性。doc表示文档字符</td>
</tr>
<tr>
<td>range([start, ] stop [, step])</td>
<td>在Python 2中，该函数创建一个完全填充的、从start到stop的整数列表</td>
</tr>
<tr>
<td>raw_input ([prompt])</td>
<td>Python 2函数，从标准输入读取一行输入并将其作为字符串返回</td>
</tr>
<tr>
<td>repr(object)</td>
<td>返回的字符串表示形式。在大多数情况下，返回的字符串是可以传递到eval()的表达式</td>
</tr>
<tr>
<td>reversed (s)</td>
<td>创建序列s的逆序迭代器。只有当s实现了序列方法__len__()、__getitem()__才可用</td>
</tr>
<tr>
<td>round(x [, n])</td>
<td>将浮点数舍五入到最近的10的负n次幂倍数后再四舍五入 </td>
</tr>
<tr>
<td>set([items])</td>
<td>创建一个使用从可迭代对象items得到的各项来填充的桌合</td>
</tr>
<tr>
<td>setattr(object, name, value)</td>
<td>设置对象的属性。name是字符串。与object.name = value相同</td>
</tr>
<tr>
<td>slice([start,] stop [, step])</td>
<td>返回表示指定范围内整数的切片对象。等同于扩展切片语法 [ i: j: k]</td>
</tr>
<tr>
<td>staticmethod (func)</td>
<td>创建在类中使用的静态方法。通过@staticmethod装饰器隐式调用该函数</td>
</tr>
<tr>
<td>str([object])</td>
<td>表示字符串的类型。在Python 2中，一个字符串包含8位字符</td>
</tr>
<tr>
<td>sum(items,[, initial])</td>
<td>计算从可迭代对象items中得到的所有项的总数。initial是初始值，默认是0</td>
</tr>
<tr>
<td>super(type [,object])</td>
<td>返回表示type基类的对象。该对象的主要用途是调用基类中的方法</td>
</tr>
<tr>
<td>tuple([items])</td>
<td>表示元组的类型。如果提供了items,则它是用于填充该元组的可迭代对象</td>
</tr>
<tr>
<td>type(object)</td>
<td>返回对象的类型</td>
</tr>
<tr>
<td>type (name, bases, dict)</td>
<td>创建一个新type对象（相当于定义一个新类)</td>
</tr>
<tr>
<td>unichr (x)</td>
<td>将整数或长整数换为一个Unicode字符</td>
</tr>
<tr>
<td>vars([object])</td>
<td>返回object的符号表（通常在它的__dict__属性中)</td>
</tr>
<tr>
<td>xrange([start,] stop [, step])</td>
<td>表示从start到stop的整数值范围的类型，该范围不包括start和stop。step是可选的步进值</td>
</tr>
<tr>
<td>zip([s1 [, s2[,..]]])</td>
<td>在Python 2中，返回一些元组的列表，其中第n个元组是(sl[n], s2[n],…）。生成的列表被截取为最短参数序列的长度。如果没有给定参数，则返回一个空列表</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">方法</span></div>
<p>所谓方法是指<span style="background-color: #c0c0c0;">在类定义中定义的函数</span>。包含实例方法、类方法、静态方法三种：</p>
<pre class="crayon-plain-tag">class Obj(object):
    def instance_method(self,arg):  #实例会作为第一个参数传入
        pass
    @classmethod
    def class_method(cls,arg):      #类对象本身会作为第一个参数传入
        pass
    @staticmethod
    def static_method(arg):
        pass


#方法的查找
o = Obj()
method = o.instance_method          #绑定方法，绑定了o对象
method(100)                         #o作为隐含的第一个参数传入
method = Obj.instance_method        #非绑定方法，没有绑定对象
method(o,100)                       #需要手工传入作为self的对象</pre>
<div class="blog_h3"><span class="graybg">生成器（Generator）</span></div>
<p>如果一个函数里面具有yield关键字，则称为生成器。调用生成器得到返回值是一个迭代器对象，这一调用本身不会执行生成器的任何代码。</p>
<p>如果生成器中存在return语句，则执行到return时<span style="color: #222222;">抛出StopIteration并终止迭代。</span></p>
<pre class="crayon-plain-tag">#斐波那契數列的生成器
def fab(max): 
    n, a, b = 0, 0, 1 
    while n &lt; max:
        #在执行时，每次遇到yield就会返回当前迭代值，并且中断执行 
        yield b  
        #下一次调用next()，在上一次中断的下一行执行，上下文与之前一致
        a, b = b, a + b 
        n = n + 1

#调用，获取生成器实例
#虽然看起来与函数调用语法一致，但是不会执行任何函数代码
#直到执行其next()方法时，才会真正调用函数代码
for n in fab(5):  #隐含调用next()
    print n 
#手工调用next()方法
iter = fab(5)
iter.next()

#判断一个函数是否为生成器
from inspect import isgeneratorfunction 
isgeneratorfunction(fab)</pre>
<div class="blog_h3"><span class="graybg">协程</span></div>
<p>上面的生成器，实际上是一种协程，协程与普通函数（Subroutine/function）的执行方式有很大的不同：</p>
<ol>
<li>子例程的起始处是惟一的入口点，一旦退出即完成了子例程的执行，子例程的一个实例只会返回一次</li>
<li>协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系，而是一种对等关系</li>
<li>协程的起始处是第一个入口点，协程返回点之后（yield之后一句）是接下来的入口点</li>
<li>子例程的生命期遵循后进先出（最后一个被调用的子例程最先返回），而协程的生命周期完全由他们的使用的需要决定</li>
</ol>
<p>下面是一个使用协程的例子：两个吃货到餐馆用餐，每次上完菜后就马上被他们吃掉，并继续索要新的食物，一个厨师、一个侍者为他俩服务：</p>
<pre class="crayon-plain-tag">from random import randint
from time import sleep
# 贪吃鬼：
def Gourmand( name ):
    dish = []
    while True:
        if len( dish ) == 0:
            logging.debug( '%s is so hungry, food, quick!' , name )
            # 递上盘子，等待服务员给予食物
            dish.extend( ( yield ) )
        else:
            logging.debug( '%s ate: %s', name, dish.pop() )
# 大厨
all_food = ['Wine', 'Meat', 'Beer', 'Cheese']
def Cook():
    while True:
        new_food = []
        logging.debug( 'Preparing food, please wait...' )
        time = randint( 5, 30 )
        sleep( time )
        for i in range( randint( 1, len( all_food ) - 1 ) ):
            new_food.append( all_food[randint( 0, len( all_food ) - 1 )] )
        # 返回本次制作的食物
        logging.debug( '%d new food finished in %d secs', len( new_food ), time )
        yield new_food
# 服务员        
if __name__ == '__main__':
    c = Cook()
    alex = Gourmand( 'Alex' )
    meng = Gourmand( 'Meng' )
    alex.next()
    meng.next()
    while True:
        # 新菜刚上给这两吃货，他们就会马上将其吃光，并继续索要
        alex.send( c.next() )
        meng.send( c.next() )</pre>
<div class="blog_h2"><span class="graybg">类与对象</span></div>
<div class="blog_h3"><span class="graybg">新式类和旧式类</span></div>
<p>所谓新式类是指<span style="background-color: #c0c0c0;">从object或者其它新式类衍生</span>出的类，而旧式类是Python2中没有明确指定基类的类。Python3中只有新式类。</p>
<p>Python中所有变量皆为对象。使用 __func__这样形式的函数名来实现特殊方法（例如list的__add__实现了+运算符）。</p>
<pre class="crayon-plain-tag">class Stack(object):              #继承语法：继承object，所有Python类的根类
    fast_mode = True              #类变量
    def __init__(self):           #特殊方法：用于在创建对象后进行初始化
        self.stack =[]            #实例变量
    def push(self,object):        #每个实例方法的第一个参数均执行对象本身，即self
        self.stack.append(object) #涉及对象属性的操作，均必须显式使用self变量
    def pop(self):
        return self.stack.pop() 
    def length(self):
        return len(self.stack)
 
    @staticmethod                 #装饰器：定义静态方法，静态方法仅仅是定义在类的命名空间
    def create():
        pass
    
    @classmethod                  #装饰器：定义类方法，类方法对类对象本身进行操作，第一个参数cls为当前类
    def create(cls): 
        return cls()              #可以调用cls来创建合适类型的对象，静态方法做不到这一点
    
    class Inner:                  #Python支持内部类
        pass
#使用类创建对象
s = Stack()
del s         #删除对象

#类似于C++的函数对象：定义__call__方法
class FuncObj:
    def __call__(arg0):
        pass

fo = FuncObj()
fo(1)</pre>
<div class="blog_h3"><span class="graybg">类对象的属性</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>__doc__</td>
<td>文档字符串</td>
</tr>
<tr>
<td>__name__</td>
<td>类的名称</td>
</tr>
<tr>
<td>__bases__</td>
<td>基类的元组</td>
</tr>
<tr>
<td>__dict__</td>
<td>保存类方法、变量的字典</td>
</tr>
<tr>
<td>__module__</td>
<td>定义类的模块名称</td>
</tr>
<tr>
<td>__abstractmethods__</td>
<td>抽象方法的集合</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">影响对象行为的特殊方法</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>__new__(cls [,*args [,**kwargs]])</td>
<td>类方法，用于创建实例</td>
</tr>
<tr>
<td>__init__(self [,*args [,**kwargs]])</td>
<td>初始化对象属性，在创建对象后立即被调用</td>
</tr>
<tr>
<td>__del__(self)</td>
<td>销毁实例时调用</td>
</tr>
<tr>
<td>__format__(self, format_spec)</td>
<td>格式化后的表示</td>
</tr>
<tr>
<td>__repr__(self)</td>
<td>字符串表示，某些类允许使用<pre class="crayon-plain-tag">eval(repr(o))</pre> 创建对象</td>
</tr>
<tr>
<td>__str__(self)</td>
<td>
<p>返回对象的字符串表示，该方法在<span style="background-color: #c0c0c0;">Python2中返回的是“字节”</span>，在Python3中返回的是字符</p>
<p>在Python2中，print语句和str()函数会调用此方法</p>
</td>
</tr>
<tr>
<td>__unicode__(self)</td>
<td>
<p>返回对象的字符串表示，该方法返回的是“字符”。在Python2中，为了编码相关的兼容性，你应当把对象格式化代码放在该方法里，而把__str__创建为一个存根方法：</p>
<pre class="crayon-plain-tag">def __str__(self):
    return unicode(self).encode('utf-8')</pre>
<p>在Python2中，unicode()函数会调用此方法</p>
<p>在Python3中，此方法没有价值</p>
</td>
</tr>
<tr>
<td>__bool__(self)</td>
<td>真值测试</td>
</tr>
<tr>
<td>__hash__(self)</td>
<td>计算整数散列值</td>
</tr>
<tr>
<td>__lt__</td>
<td>
<p>小于，类似还有__le__、__gt__、__ge__、__eq__、__ne__<br />如果要用作字典键，或者根据==比较大小，则必须实现__eq__</p>
</td>
</tr>
<tr>
<td>__instancecheck__(cls,object)</td>
<td>修改isinstance(object,cls)的行为</td>
</tr>
<tr>
<td>__subclasscheck__(cls,sub)</td>
<td>修改issubclass(sub,cls)的行为</td>
</tr>
<tr>
<td>__getattribute__(self,name)</td>
<td>返回属性self.name时调用，调用此方法时，Python尚未查找对象的真实属性</td>
</tr>
<tr>
<td>__getattr__(self,name)</td>
<td>仅仅在常规方式找不到属性时调用</td>
</tr>
<tr>
<td>__setattr__(self, name, value)</td>
<td>设置self.name=value时调用</td>
</tr>
<tr>
<td>__delattr__(self, name)</td>
<td>删除self.name时调用</td>
</tr>
<tr>
<td>__len__(self)</td>
<td>返回长度</td>
</tr>
<tr>
<td>__getitem__(self,key)</td>
<td>获得self[key]</td>
</tr>
<tr>
<td>__setitem__(self,key,value)</td>
<td>设置self[key] = value</td>
</tr>
<tr>
<td>__delitem__(self,key)</td>
<td>删除self[key]</td>
</tr>
<tr>
<td>__contains__(self,obj)</td>
<td>如果包含，则返回真</td>
</tr>
<tr>
<td>__iter__()</td>
<td>
<p>如果对象支持迭代，从该方法返回迭代器对象，迭代器必须实现next()方法</p>
<pre class="crayon-plain-tag">it = s.__iter__()
while True :
    try:
        it.next()
    except StopIteration:
        break</pre>
</td>
</tr>
<tr>
<td>__next__()</td>
<td>Python3的迭代器方法，在Python2中为next()</td>
</tr>
<tr>
<td>__add__(self, other)</td>
<td>self + other</td>
</tr>
<tr>
<td>__sub__(self, other)</td>
<td>self - other</td>
</tr>
<tr>
<td>__mul__ (self, other)</td>
<td>self * other</td>
</tr>
<tr>
<td>__div__ (self, other)</td>
<td>self / other</td>
</tr>
<tr>
<td>__floordiv__(self, other)</td>
<td>self // other</td>
</tr>
<tr>
<td>__mod__ (self, other)</td>
<td>self % other</td>
</tr>
<tr>
<td>__pow__(self, other, [,modulo])</td>
<td>self ** other, pow(self,other,modulo)</td>
</tr>
<tr>
<td>__lshift__(self, other)</td>
<td>self &lt;&lt; other</td>
</tr>
<tr>
<td>__rshift__(self, other)</td>
<td>self &gt;&gt; other</td>
</tr>
<tr>
<td>__and__(self, other)</td>
<td>self  &amp; other</td>
</tr>
<tr>
<td>__or__(self, other)</td>
<td>self  | other</td>
</tr>
<tr>
<td>__xor__(self, other)</td>
<td>self  ^ other</td>
</tr>
<tr>
<td>__r**(self, other)</td>
<td>other ** self </td>
</tr>
<tr>
<td>__i**(self, other)</td>
<td>self **= other</td>
</tr>
<tr>
<td>__neg__(self)</td>
<td>-self  </td>
</tr>
<tr>
<td>__pos__(self)</td>
<td>+self</td>
</tr>
<tr>
<td>__abs__(self)</td>
<td>abs(self)</td>
</tr>
<tr>
<td>__invert__(self)</td>
<td>~self</td>
</tr>
<tr>
<td>__int__(self)</td>
<td>int(self)</td>
</tr>
<tr>
<td>__long__(self)</td>
<td>long(self)</td>
</tr>
<tr>
<td>__float__(self)</td>
<td>float(self)</td>
</tr>
<tr>
<td>__complex(self)</td>
<td>complex(self)</td>
</tr>
<tr>
<td>__call__(self[, *args[,*kwargs]])</td>
<td>函数对象</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">对象实例的属性</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>__class__</td>
<td>实例所属的类，可以使用tpye(o)得到</td>
</tr>
<tr>
<td>__dict__</td>
<td>所有实例变量构成的字典</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">异常</span></div>
<p>如果一个Python程序出现错误，则会引发异常，打印类似下面的信息：
<pre class="crayon-plain-tag">Traceback (most recent call last)： 
 File "foo.py", line 12, in 
IOError: [Errno 2] No such file or directory: 'file.txt'</pre>
<p>错误信息中包含了错误的类型、出错的代码位置。如果不做任何处理，异常会导致程序终止，除非使用try-except语句：</p>
<pre class="crayon-plain-tag">try:
    f = open('Readme.txt')
except IOError as e:       #如果发生IOError，则被捕获并且存放在e变量中
    print e


#raise用于手工触发异常
raise RuntimeError("Error occurred")
#如果raise没有指定任何参数，则再次引发最近一次生成的异常（仅正在处理前一个异常时）
try:
    pass
except RuntimeError as e:
    raise</pre>
<p>未捕获的异常将向上传递，如果到程序最顶级仍然没有处理，则导致解析器终止并打印消息。可以把未捕获的异常传递给用户自定义的<pre class="crayon-plain-tag">sys.excepthook()</pre> 函数进行处理。</p>
<p>可以使用<span style="background-color: #c0c0c0;">try-except-else</span>语句块，else在没有引发异常时执行；可以使用<span style="background-color: #c0c0c0;">try-except-finally</span>语句块，finally总是执行：</p>
<pre class="crayon-plain-tag">try:
    f = open('Readme.txt','r')
except IOError as e:
    error_log.write('Failed to open file:%s\n' % e)
else:
    data = f.read()
    f.close()
finally: #如果发生异常，控制权首先交给finally代码块，执行完毕后再进行异常处理
    f.close()</pre>
<div class="blog_h3"><span class="graybg">内置异常</span></div>
<p>Python预定义了以下异常：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">异常</td>
<td style="text-align: center;">描述</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>BaseException</p>
<p style="padding-left: 30px;">GeneratorExit</p>
<p style="padding-left: 30px;">Keyboardlnterrupt</p>
<p style="padding-left: 30px;">SystemExit</p>
<p style="padding-left: 30px;">Exception</p>
<p style="padding-left: 60px;">StopIteration</p>
<p style="padding-left: 60px;">StandardError</p>
<p style="padding-left: 60px;">ArithmeticError</p>
<p style="padding-left: 90px;">FloatingPointError</p>
<p style="padding-left: 90px;">ZeroDivisionError</p>
<p style="padding-left: 60px;">AssertionError</p>
<p style="padding-left: 60px;">AttributeError</p>
<p style="padding-left: 60px;">EnvironmentError</p>
<p style="padding-left: 90px;">IOError</p>
<p style="padding-left: 90px;">OSError</p>
<p style="padding-left: 60px;">EOFError</p>
<p style="padding-left: 60px;">ImportError</p>
<p style="padding-left: 60px;">LookupError</p>
<p style="padding-left: 90px;">IndexError</p>
<p style="padding-left: 90px;">KeyError</p>
<p style="padding-left: 60px;">MemoryError</p>
<p style="padding-left: 60px;">NameError</p>
<p style="padding-left: 60px;">UnboundLocalError</p>
<p style="padding-left: 60px;">ReferenceError</p>
<p style="padding-left: 60px;">RuntimeError</p>
<p style="padding-left: 60px;">NotImplementedError</p>
<p>SyntaxError</p>
<p style="padding-left: 30px;">IndentationError</p>
<p style="padding-left: 60px;">TabError</p>
<p>SystemError</p>
<p>TypeError</p>
<p>ValueError</p>
<p style="padding-left: 30px;">UnicodeError</p>
<p style="padding-left: 60px;">UnicodeDecodeError</p>
<p style="padding-left: 60px;">UnicodeEncodeError</p>
<p style="padding-left: 60px;">UnicodeTranslateError</p>
</td>
<td>
<p>所有异常的根类</p>
<p>由生成器的<pre class="crayon-plain-tag">close()</pre> 方法引发</p>
<p>由键盘中断（通常为Ctrl+C)生成</p>
<p>程序退出/终止</p>
<p>所有非退出异常的基类</p>
<p>引发后可停止迭代</p>
<p>所有内置异常的基类</p>
<p>算术异常的基类</p>
<p>浮点操作失败</p>
<p>对0进行除或取模操作</p>
<p>由assert语句引发</p>
<p>当属性名称无效时引发</p>
<p>发生在Python外部的错误</p>
<p>I/O或文件相关的错误</p>
<p>操作系统错误</p>
<p>到达文件结尾时引发</p>
<p>import语句失败</p>
<p>索引和键错误</p>
<p>超出序列索引的范围</p>
<p>字典键不存在</p>
<p>内存不足</p>
<p>无法找到局部或全局名称</p>
<p>未綁定的局部变量</p>
<p>销毁被引用对象后使用的弱引用</p>
<p>一般运行时错误</p>
<p>没有实现的特性解析错误</p>
<p>语法错误</p>
<p>缩进错误</p>
<p>使用不一致的制表符（由-tt选项生成)</p>
<p>解释器中的非致命系统错误</p>
<p>给操作传递了错误的类型</p>
<p>无效类型</p>
<p>Unicode错误</p>
<p>Unicoed解码错误</p>
<p>Unicode编码错误</p>
<p>Unicode转换错误</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">自定义异常</span></div>
<p>可以创建以Exception为父类的新类，作为自定义异常类：</p>
<pre class="crayon-plain-tag">#简单的例子
class NetworkError(Exception)： pass
#使用该异常
raise NetworkError('Cannot find host')

#自定义异常可以包含多个构造参数
class DeviceError(Exception):
    def __init__(self, errno, msg):
        self.args = (errno, msg)
#使用该异常
raise DeviceError(1,'Not Responding')</pre>
<div class="blog_h2"><span class="graybg">断言与__debug__</span></div>
<p>assert语句用于在程序中设置断言，格式为：</p>
<pre class="crayon-plain-tag">assert test [,msg]
#test为一表达式，如果为False，则触发AssertionError，并使用msg指定的消息内容</pre>
<p> 使用-O选项使解释器运行于最优模式时，不会执行断言代码。</p>
<p>除非使用-O选项，内置只读变量<pre class="crayon-plain-tag">__debug__</pre> 的值均为true，可以用于程序调试。</p>
<div class="blog_h2"><span class="graybg">上下文管理协议</span></div>
<p>该机制主要用于在Python中安全的进行资源（数据库连接、事务、文件句柄等）管理。</p>
<p>使用with语句，可以在一个“上下文管理器”对象的控制下执行一系列的语句：</p>
<pre class="crayon-plain-tag">with context [as var]:   #执行context.__enter__(self)方法，返回值存入var
    pass
#执行context.__exit__(self, type, value, traceback)方法

#举例：锁
import threading
lock = threading.Lock()
with lock:
    pass
#执行完毕后自动清除锁定

#自动关闭打开的文件
with open( sys.argv[1] ) as infile :
    for line in infile:
        print line</pre>
<p>上下文管理器对象必须实现：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 35%; text-align: center;"><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"> 方法</span></td>
<td style="text-align: center;"><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;">说明 </span></td>
</tr>
</thead>
<tbody>
<tr>
<td>__enter__(self)</td>
<td>进行一个新上下文时调用此方法，返回值存入 as 后面指定的变量 </td>
</tr>
<tr>
<td>__exit__(self, type, value, tb)</td>
<td>离开一个上下文是调用此方法，如果发生异常，则type、value、tb分别为异常类型、值、跟踪信息</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">模块</span></div>
<p>随着程序规模的扩大，有必要根据功能不同把代码分散在不同的文件中，作为单独的模块，并在需要使用时进行导入。</p>
<p>在Python中，<span style="background-color: #c0c0c0;">模块名就是相应脚本文件的basename</span>。当导入一个模块时，自动创建一个名字空间来存放模块定义的对象，默认此名字空间与模块名相同：</p>
<pre class="crayon-plain-tag">#file:  div.py
def divide(a, b):
    q = a/b
    r = a - q*b
    return (q, r)


#file:  main.py
import div                #导入div模块，创建了div名字空间
dir(div)                  #列出名字空间下的内容
q, r= div.divide(100,17)  #使用模块中的函数，需要加前缀
import div as d           #为导入的模块启用别名

from div import divide    #把某个具体的定义导入到当前名字空间
q, r = divide(100,18)     #不再需要使用名字空间作为前缀</pre>
<div class="blog_h3"><span class="graybg">模块的属性</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>__doc__</td>
<td>模块的文档字符串</td>
</tr>
<tr>
<td>__dict__</td>
<td>与模块相关的字典</td>
</tr>
<tr>
<td>__name__</td>
<td>模块的名称</td>
</tr>
<tr>
<td>__file__</td>
<td>用户加载模块的文件</td>
</tr>
<tr>
<td>__path__</td>
<td>完全限定的包名</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">基础运算和表达式</span></div>
<div class="blog_h2"><span class="graybg">运算符优先级（从高到低）</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">运算符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>(...) 、 [...] 、 {...}</td>
<td>创建元组、列表或字典</td>
</tr>
<tr>
<td>s[i]、s[i:j]</td>
<td>索引、切片</td>
</tr>
<tr>
<td>s.attr</td>
<td>属性导航符</td>
</tr>
<tr>
<td>f(...)</td>
<td>函数调用</td>
</tr>
<tr>
<td>+x、-x、~x</td>
<td>一元运算符</td>
</tr>
<tr>
<td>x**y</td>
<td>乘方（右结合性）</td>
</tr>
<tr>
<td>x*y、x/y、x//y、x%y</td>
<td>乘、除、截断除、取余</td>
</tr>
<tr>
<td>x+y、x-y</td>
<td>加、减</td>
</tr>
<tr>
<td>x&lt;&lt;y、x&gt;&gt;y</td>
<td>移位</td>
</tr>
<tr>
<td>x&amp;y</td>
<td>按位与</td>
</tr>
<tr>
<td>x^y</td>
<td>按位异或</td>
</tr>
<tr>
<td>x|y</td>
<td>按位或</td>
</tr>
<tr>
<td>x &lt; y、x &lt;= y、<br />x &gt; y、 x &gt;= y、 <br />x == y、 x != y <br />x is y、 x is not y 、<br />x in s、x not in s</td>
<td>
<p>比较、序列成员检查</p>
<p>is    用于对象同一性检查<br />==  则用于值相等性检查</p>
</td>
</tr>
<tr>
<td>not x</td>
<td>逻辑非</td>
</tr>
<tr>
<td>x and y</td>
<td>逻辑与</td>
</tr>
<tr>
<td>x or y</td>
<td>逻辑或</td>
</tr>
<tr>
<td>lambda args:expr</td>
<td>lambda表达式</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数学运算</span></div>
<p>在其它语言里面比较少见的数学运算符有：截断除法(//)、乘方(**)。</p>
<p>内置函数包括：绝对值(abs)、商与余数(divmod)、四舍五入(round)等。</p>
<div class="blog_h2"><span class="graybg">序列操作</span></div>
<p>序列包括：字符串、列表和元组。支持以下运算符或者函数：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;">运算符或函数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>s + r</td>
<td>连接两个相同类型的序列</td>
</tr>
<tr>
<td>s * n</td>
<td>生成s的n个副本，浅复制</td>
</tr>
<tr>
<td>v1,v2... = s</td>
<td>把序列解包为若干对象，变量个数必须和元素个数一致</td>
</tr>
<tr>
<td>s[i]</td>
<td>索引，返回第i+1个元素</td>
</tr>
<tr>
<td>s[i:j]</td>
<td>切片</td>
</tr>
<tr>
<td>s[i:j:stride]</td>
<td>扩展切片，stride为步进值，可以跳过若干对象，结果索引为i, i+stride, i + 2*stride直到j</td>
</tr>
<tr>
<td>x in s   以及 x not in s</td>
<td>从属关系判断</td>
</tr>
<tr>
<td>for x in s</td>
<td>迭代</td>
</tr>
<tr>
<td>all(s)</td>
<td>是否所有元素均为True，不适用于字符串</td>
</tr>
<tr>
<td>any(s)</td>
<td>是否存在元素为True，不适用于字符串</td>
</tr>
<tr>
<td>len(s)</td>
<td>长度</td>
</tr>
<tr>
<td>min(s)  max(s)</td>
<td>最值</td>
</tr>
<tr>
<td>sum(s[,initial]) </td>
<td>求和</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>以下为可变序列（即列表）支持的操作</strong></td>
</tr>
<tr>
<td>s[i] = x</td>
<td>按索引赋值，如果i为负数，则从结尾算起</td>
</tr>
<tr>
<td>s[i:j] = x</td>
<td>按切片赋值，x的个数必须与切片中元素个数一致</td>
</tr>
<tr>
<td>s[i:j:stride] = x</td>
<td>扩展切片赋值，x的个数必须与切片中元素个数一致</td>
</tr>
<tr>
<td>del s[i]</td>
<td>删除一个元素</td>
</tr>
<tr>
<td>del s[i:j]</td>
<td>删除一个切片</td>
</tr>
<tr>
<td>del [i:j:stride]</td>
<td>删除一个扩展切片</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">字典操作</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">操作 </td>
<td style="text-align: center;">描述 </td>
</tr>
</thead>
<tbody>
<tr>
<td>x = d[k]</td>
<td>通过键进行查找</td>
</tr>
<tr>
<td>d[k] = x</td>
<td>通过键进行赋值</td>
</tr>
<tr>
<td>del d[k]</td>
<td>通过键进行删除</td>
</tr>
<tr>
<td>k in d</td>
<td>测试某个键是否存在于字典中</td>
</tr>
<tr>
<td>len(d)</td>
<td>字典的条目个数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">集合操作</span></div>
<p>set和frozenset用于支持常见的集合操作：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;"> 操作</td>
<td style="text-align: center;">描述 </td>
</tr>
</thead>
<tbody>
<tr>
<td>s | t</td>
<td>并集</td>
</tr>
<tr>
<td>s &amp; t</td>
<td>交集</td>
</tr>
<tr>
<td>s - t</td>
<td>差集</td>
</tr>
<tr>
<td>s ^ t</td>
<td>对称差集</td>
</tr>
<tr>
<td>len(s)</td>
<td>集合中条目个数</td>
</tr>
<tr>
<td>max(s)</td>
<td>最大值</td>
</tr>
<tr>
<td>min(s)</td>
<td>最小值</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">类型转换</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;"> 转换函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>int(x [, base])</td>
<td>将x换为一个整数。如果x是一个字符串，base用于指定基数 </td>
</tr>
<tr>
<td>float(x)</td>
<td>将x换为一个浮点数</td>
</tr>
<tr>
<td>Complex (real [, imag])</td>
<td>创建一个复数</td>
</tr>
<tr>
<td>str(x)</td>
<td>将对象x转换为字符串表示</td>
</tr>
<tr>
<td>repr(x)</td>
<td>将对象x转换为一个表达式字符串，可以通过eval还原</td>
</tr>
<tr>
<td>format (x [, fmt_spec])</td>
<td>将对象x转换为格式化字符串，该函数调用x的__format__()方法</td>
</tr>
<tr>
<td>eval(str)</td>
<td>对字符串求值并返回对象</td>
</tr>
<tr>
<td>tuple(s)  </td>
<td>将s转换为元组</td>
</tr>
<tr>
<td>list(s)</td>
<td>将s转换为列表</td>
</tr>
<tr>
<td>set(s)</td>
<td>将s转换为集合</td>
</tr>
<tr>
<td>dict(d)</td>
<td>将d转换为字典，d是(key,value)形式的序列对象</td>
</tr>
<tr>
<td>frozenset(s)</td>
<td>将s转换为不可变集合</td>
</tr>
<tr>
<td>chr(x)</td>
<td>将整数转换为字符</td>
</tr>
<tr>
<td>unichr(x)</td>
<td>将整数转换为Unicode字符</td>
</tr>
<tr>
<td>ord (x)</td>
<td>将字符转换为其整数值</td>
</tr>
<tr>
<td>hex(x)</td>
<td>将整数转换为十六进制字符串 </td>
</tr>
<tr>
<td>bin (x)</td>
<td>将整数转换为二进制字符串</td>
</tr>
<tr>
<td>oct (x)</td>
<td>将整数转换为八进制字符串</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> 函数与函数编程</span></div>
<div class="blog_h2"><span class="graybg">变量作用域</span></div>
<ol>
<li>每次执行函数时，<span style="background-color: #c0c0c0;">自动创建局部命名空间</span>，其内包括<span style="background-color: #c0c0c0;">函数参数、函数体内定义的变量</span></li>
<li>解释器解析变量名称时，<span style="background-color: #c0c0c0;">首先从局部命名空间</span>开始；如果找不到，则搜索函数的<span style="background-color: #c0c0c0;">全局命名空间</span>（定义该函数的模块）；仍然找不到，则搜索<span style="background-color: #c0c0c0;">内置命名空间</span>；再找不到则NameError</li>
<li>除非在函数里使用<span style="background-color: #c0c0c0;">global</span>语句，否则不会<span style="background-color: #c0c0c0;">改变全局</span>命名空间变量的值，但是可以访问全局变量的值<br />
<pre class="crayon-plain-tag">i = 1
def func():
    print i   #打印1
    i = 2
    print i   #打印2
func()
print i       #仍然打印1

def func1():
    global i  #现在i位于全局名字空间
    i - 2     #修改成功</pre>
</li>
<li>Python支持嵌套函数定义，使用词法作用域来绑定嵌套函数中的变量——首先检查其<span style="background-color: #c0c0c0;">局部作用域</span>，然后检查<span style="background-color: #c0c0c0;">外部嵌套函数的作用域</span>，以此类推。在Python2中，只能对局部作用域、全局作用域进行变量赋值，<span style="background-color: #c0c0c0;">对外部嵌套函数中定义的变量进行赋值是不支持的，Python3可以使用nonlocal语句解决此问题</span></li>
<li>使用尚未赋值的局部变量，导致UnboundLocalError</li>
</ol>
<div class="blog_h2"><span class="graybg">函数对象与闭包</span></div>
<p>作为First-class对象的函数，可以当作数据传递给其他函数。把函数作为数据来处理时，它自动携带定义函数的上下文信息（变量）。</p>
<p><span style="background-color: #c0c0c0;">将函数主体语句、语句的执行环境上下文信息一起打包时，得到的对象称为闭包</span>。由于任意函数都携带定义其它的模块的全局命名空间信息（__globals__），因此本质上任何Python函数都是闭包。</p>
<div class="blog_h2"><span class="graybg">装饰器</span></div>
<p>装饰器是一个函数，用于包装一个函数或者类，目的是修改、增强被包装对象的功能。特殊符号@用于表示装饰器语法：</p>
<pre class="crayon-plain-tag">@trace
def square (x) : 
    return x*x
#上述代码等价于
def square (x) : 
    return x*x
square = trace(square)

#trace函数的定义——类似于切面，它必然返回一个函数
def trace(func)：
    def callf(*args,**kwargs):
        debug_log.write( "Calling %s： %s, %s\n" % (func.__name__, args, kwargs) )  #记录日志
        r = func(*args, **kwargs)     #调用原本的函数
        debug_log.write( "%s returned %s\n" % (func.__name__, r) )
    return callf</pre>
<p>可以声明多个装饰器，每个装饰器必须独占一行，<span style="background-color: #c0c0c0;">最后定义的装饰器，最先包装到原始函数</span>上：</p>
<pre class="crayon-plain-tag">@foo
@bar
def func(x):
    pass
#等价于
func = foo(bar(func))</pre>
<p>装饰器可以带有参数：</p>
<pre class="crayon-plain-tag">@eventhandler('BUTTON')
def handle_button(msg):
    pass
#带有参数的装饰器，装饰目标函数的步骤如下：
temp = eventhandler('BUTTON')       #使用参数调用装饰器，其应当仍然返回一个装饰器
handle_button = temp(handle_button) #使用生成的装饰器来装饰目标函数</pre>
<p>装饰器也可以应用于类，这样的<span style="background-color: #c0c0c0;">装饰器应当返回类对象作为结果</span>。 下面是一个例子：</p>
<pre class="crayon-plain-tag">def talking(cls: type):
    setattr(cls, 'talk', lambda self: print('Hello, I am %s' % str(self)))
    return cls


@talking
class person(object):
    def __init__(self, name: str):
        self.name = name

    def __str__(self):
        return self.name.upper()


if __name__ == '__main__':
    p = person('Alex Wong')
    p.talk()  #Hello, I am ALEX WONG </pre>
<div class="blog_h2"><span class="graybg">生成器与yield</span></div>
<p>使用yield关键字可以定义一个生成器对象，该对象本质上是一个函数，其生成一个值序列，在迭代中使用：</p>
<pre class="crayon-plain-tag">def countdown(n):
    while n &gt; 0:
        print('Counting down from %d' % n)
        yield n
        n -= 1
    return   #生成器不能返回除了None以外的任何值

#调用该函数，不会打印任何信息：
c = countdown(10)
#相反，返回值c是一个生成器函数，当c.next()被调用时，countdown函数被执行
#调用next()函数时，代码会正常执行，直到遇到yield语句为止，并返回yield指定的值
#通常不需要手工调用next()函数，而是在for、sum等使用序列的操作中自动调用：
for n in countdown(10):
    pass
a = sum(countdown(10))</pre>
<p>当生成器函数遇到return语句或者StopIteration异常，则其退出。</p>
<p>可选的，调用<pre class="crayon-plain-tag">close()</pre> 方法可以关闭生成器，当yield语句遭遇GeneratorExit异常时会自动调用。</p>
<div class="blog_h2"><span class="graybg">协程与yield</span></div>
<p>yield语句可以作为右值，通过该方式使用yield的函数称为协程：</p>
<pre class="crayon-plain-tag">def receiver():
    print('Prepare to receive')
    while True:
        n = (yield)
        #协程的目的是对发送给它的值做出处理
        print('Got %s' % n)

#使用协程
r = receiver()
r.next()       #执行到第一条yield语句，该调用必不可少
r.send(1)      #发送值给协程，导致其运行直到下一条yield语句
r.send('Greetings')</pre>
<p>由于协程使用时next()调用必不可少，因此可以使用类似下面的装饰器自动完成：</p>
<pre class="crayon-plain-tag">def coroutine(func):
    def start(*args,**kwargs)：
        g = func(*args,**kwargs) 
        g.next() 
        return g 
    return start</pre>
<p>协程一般是无限期运行的，可以调用close()方法显式关闭。关闭后，再调用send()会导致StopIteration异常。注意close方法会在协程内部引发GeneratorExit异常。</p>
<p>使用throw()方法可以在协程内部触发异常，该异常在yield语句处出现。使用throw()给协程发送异步信号是不安全的。</p>
<p>如果协程的yield提供值，那么将自动返回给send()方法的返回值：</p>
<pre class="crayon-plain-tag">def line_splitter(d = None):
    result = None
    while True:
        #读出line，返回result
        line = (yield result)
        result = line.split(d)

ls = line_splitter()
ls.next()                 #执行到第一次遇到yield
ls.send('X,Y,Z')          #触发继续执行，直到下一次遇到yield。因此返回['X','Y','Z']</pre>
<p>当通过使用yield返回值、throw()时需要注意，send()给协程的值将作为throw()的返回值返回。</p>
<div class="blog_h2"><span class="graybg">生成器与协程的使用场景</span></div>
<ol>
<li>数据流处理程序，类似于Unix Shell管道</li>
<li>实现某种形式的并发，例如使用某个任务管理器，把数据分发给数百个执行各种具体任务的协程</li>
</ol>
<div class="blog_h2"><span class="graybg">列表包含</span></div>
<p>Python使用“列表推导”（List comprehension）运算符，来把函数应用到列表的每个项，并根据结果创建新的列表，例如下面的操作：</p>
<pre class="crayon-plain-tag">nums = [1, 2, 3, 4, 5]
squares = []
for n in nums:
    squares.append(n * n);</pre>
<p>可以使用列表推导改写为：</p>
<pre class="crayon-plain-tag">nums = [1, 2, 3, 4, 5]
squares = [n * n for n in nums]  #列表推导</pre>
<p>列表推导的语法如下：</p>
<pre class="crayon-plain-tag">[expression for iteml in iterablel if conditionl
            for item2 in iterable2 if condition2
            ……
            for itemN in iterableN if conditionN ]

#上面的语法等价于
s = []
for item1 in iterable1:
    if condition1:
        for item2 in iterable1:  
            if condition2:
            ...
                for itemN in iterableN:  
                    if conditionN: s.append(expression)  #最终是为了计算结果列表

#举例
a = [-3,5,2,-10,7,8] 
b = 'abc'
c = [2*s for s in a]             #[-6,10,4,-20,14,16]
d = [s for s in a if s &gt;= 0]     #[5,2,7,8]
e = [(x,y) for x in a            #如果推导结果元素为元组，必须放入括号内
           for y in b            #[(5,'a'),(5,'b'),(5,'c')......]
           if x &gt; 0 ]
f = [(1,2),(3,4),(5,6)]
g = [x+y for x,y in f]           #[3,7,11]</pre>
<p>注意：Python2的列表推导迭代变量在当前作用域中求值，在列表推导完毕后，其值仍然保留。在Python3中，迭代变量是私有的，推导结束即无效。</p>
<div class="blog_h2"><span class="graybg">生成器表达式</span></div>
<p>生成器表达式与列表包含非常类似，但是它只是生成获取结果的规则，而不是生成结果本身：</p>
<pre class="crayon-plain-tag">#语法差异仅仅是把方括号换成花括号
(expression for iteml in iterablel if conditionl
            for item2 in iterable2 if condition2
            ……
            for itemN in iterableN if conditionN )

#举例
a = (1, 2, 3, 4)
b = (10*i for i in a)   #生成一个生成器
b.next()                #10

#list函数可以把生成器表达式转换为序列
list(b)</pre>
<div class="blog_h2"><span class="graybg">lambda表达式</span></div>
<p>使用lambda语句可以创建匿名函数：</p>
<pre class="crayon-plain-tag">lambda args: expression
#args为逗号分隔的参数列表
#expression为使用参数的表达式
a = lambda x,y : x + y
r = a(1,2)   #r=3
#lambda主要用于指定短小的回调函数
names.sort(key = lambda n: n.lower())</pre>
<p>lambda语句中不能出现多条语句或者非表达式语句（for、while等）。作用域规则与函数相同</p>
<div class="blog_h2"><span class="graybg">递归</span></div>
<p>sys.getrecursionlimit ()返回当前解释器对递归深度的限制，默认1000。</p>
<p>递归不能用在生成器函数、协程中。</p>
<div class="blog_h2"><span class="graybg">函数属性</span></div>
<p>可以为函数指定任意属性，这些属性会包含在__dict__属性中。</p>
<div class="blog_h2"><span class="graybg">eval()、exec()、compile()</span></div>
<pre class="crayon-plain-tag">#eval执行一个表达式字符串并返回结果
eval (str [, globals [, locals]])
#exec执行任意包含Python代码的字符串
exec (str [, globals [, locals]])</pre>
<p>eval、exec均在<span style="background-color: #c0c0c0;">调用者的变量作用域</span>中执行，可选的globals、locals用于映射全局、局部名字空间。</p>
<p>对于反复执行的代码，最好使用compile将其编译为字节码，提高性能：</p>
<pre class="crayon-plain-tag">s = "for i in range(0,10): print(i)"
c = compile(s, '', 'exec')  #编译为代码对象
exec(c)</pre>
<div class="blog_h1"><span class="graybg">类与面向对象编程</span></div>
<ol>
<li>Python没有类作用域一说，如果需要<span style="background-color: #c0c0c0;">访问对象的其它属性，必须以self.开头</span></li>
<li>派生类不会自动调用基类的__init__方法</li>
<li><pre class="crayon-plain-tag">super(cls, instance).***</pre> 用于在基类上执行属性查找，Python3直接简化为<pre class="crayon-plain-tag">super().***</pre><br />
<pre class="crayon-plain-tag">class MoreEvilAccount(EvilAccount):
    def deposit(self, amount):
        self.withdraw(5)
        super(MoreEvilAccount,self).deposit(amount) #调用父类的方法实现</pre>
</li>
<li>Python支持多重继承，示例：<br />
<pre class="crayon-plain-tag">#使用逗号分隔的基类列表
class MostEvilAccount(EvilAccount, DepositCharge, WithdrawCharge)：pass</pre>
</li>
<li>通常应当避免使用多重继承。有时，可以使用多重继承来定义混入类（mixin）。<span style="background-color: #c0c0c0;">混入类定义了需要混合到其它类中的一组方法</span>，以添加功能，<span style="background-color: #c0c0c0;">它通常假定其它方法/属性存在</span>，并以这些方法/属性为基础构建新的逻辑</li>
<li>动态绑定（多态性）：不考虑实例的类型的情况下使用实例，只要以obj.attr的形式访问属性，解释器就会按<span style="background-color: #c0c0c0;">实例本身内部 - 实例的类定义 - 基类的类定义</span>的顺序来搜索attr，返回第一个匹配。此绑定过程的关键在于它与obj的类型独立，只要它就有attr属性，就可成功绑定（鸭子类型识别）。动态绑定可以用于组件解耦，例如，可以针对具有<span style="background-color: #c0c0c0;">某个方法集的对象编写代码，而不需要考虑其属于何种类型</span>。</li>
<li>静态方法是普通的函数，只是定义在类的名字空间中；类方法的第一个参数是当前类本身；实例方法的第一个参数是当前对象本身</li>
</ol>
<div class="blog_h2"><span class="graybg">属性（property）</span></div>
<p>property是一种特殊的属性，在访问时，会计算它的值：</p>
<pre class="crayon-plain-tag">class Circle(object):
    def __init__(self,radius):
        self.radius = radius
    #属性定义
    @property  #该装饰器支持简单属性风格访问后续定义的方法
    def area(self):
        return math.pi * self.radius**2

#使用
c = Circle(5)
c.area    #访问属性，导致area()方法被调用</pre>
<p>向property添加setter、deleter方法，可以实现设置、删除属性操作：</p>
<pre class="crayon-plain-tag">class Foo(object):
    def __init__(self,name):
        self.__name = name 
    @property                        #属性读取
    def name(self):
        return self.__name           #实际存储的变量名任意，但是必须和property名不同
    @name.setter                     #属性设置
    def name(self,value):            #setter、deleter的方法名称必须与属性原始方法名一致
        if not isinstance(value,str):
            raise TypeError("string value required.")
            self.__name = value 
    @name.deleter                    #属性删除
    def name(self):
        raise TypeError("Unsupported operation")

#使用
f = Foo()
name = f.name  #调用f.name()
f.name = 50    #调用f.name(f,50)，抛出TypeError
del f.name     #调用f.name(f)，抛出TypeError</pre>
<p>另外，getter、setter、deleter风格的方法也是支持的，但是建议使用装饰器，更加简洁，不会显示大量的getter、setter等方法：</p>
<pre class="crayon-plain-tag">class Foo(object):
    def getname(self):
        return self.__name
    def setname(self,name):
        self.__name = name
    def delname(self):
        raise TypeError("Unsupported operation")</pre>
<div class="blog_h2"><span class="graybg">数据封装与私有属性</span></div>
<p>Python约定以__开头的方法为私有方法，这些方法会自动变形：<pre class="crayon-plain-tag">__func</pre> 变形为<pre class="crayon-plain-tag">__classname__func</pre> 的形式。<br /> 这不是一种严格的信息隐藏机制。</p>
<div class="blog_h2"><span class="graybg">对象内存管理</span></div>
<p>Python使用引用计数作为基础垃圾回收算法。每个对象都具有一个引用计数，将对象赋给一个新变量、将其放入容器时，均会导致该计数增加，使用<pre class="crayon-plain-tag">del</pre> 语句或者<span style="background-color: #c0c0c0;">超过变量作用范围</span>、<span style="background-color: #c0c0c0;">重新变量赋值</span>时，引用计数则减小</p>
<p>引用计数算法具有<span style="background-color: #c0c0c0;">无法处理循环引用</span>的天生缺陷，为此Python解释器会定期执行一个“循环检测器”，搜索不可访问的对象循环引用，并删除之。</p>
<p>Python类实例的创建包括两个步骤：</p>
<ol>
<li>使用特殊的<pre class="crayon-plain-tag">__new__()</pre> 方法创建实例</li>
<li>使用<pre class="crayon-plain-tag">__init__()</pre> 方法初始化实例</li>
</ol>
<p>其中类方法__new__方法很少需要用户定义，除了以下两个场景：</p>
<ol>
<li>在继承一个不变类型的基类时，用于修改对象的值</li>
<li>定义元类时使用</li>
</ol>
<p>实例创建完毕后，Python将管理其引用计数，<span style="background-color: #c0c0c0;">当引用计数为0时，实例立即被销毁</span>，其<pre class="crayon-plain-tag">__del__()</pre> 方法被调用。通常没必要定义__del__，因为无法保证解释器在退出时调用了该方法，如果需要资源清理，自定义close()方法并手工调用是最好的。</p>
<p><span style="background-color: #c0c0c0;">定义了__del__() 的对象无法被Python的循环垃圾收集器回收</span>。可以使用<pre class="crayon-plain-tag">weakref.ref()</pre> 引用解决此问题——在<span style="background-color: #c0c0c0;">不增加引用计数的情况下创建对象的引用</span>。</p>
<div class="blog_h2"><span class="graybg">__slots__</span></div>
<p>通过定义特殊变量<pre class="crayon-plain-tag">__slots__</pre> ，类可以限制设置合法属性名称：</p>
<pre class="crayon-plain-tag">class Account(object):
    #定义该变量后，只能设置列出的属性，否则抛出AttributeError
    __slots__ = ('name','balance')</pre>
<p>设置__slots__的类实例不再使用字典存储实例数据，而是改为数组，因此可以减少内存占用、执行时间。</p>
<p>没有必要在__slots__里面添加方法、property的名字，因为他们是定义在类上而不是实例级别的。</p>
<div class="blog_h2"><span class="graybg">类型、类成员测试</span></div>
<pre class="crayon-plain-tag">aid = id(a)      #内置函数id()用于获取对象的标识符，为一整数，通常为内存地址
if a is b: pass  #is运算符用于判断两个变量是否指向同一个对象（标识符相等）
if a == b: pass  #如果a与b的值相等

if type(s) is list: pass     #对象的类型也是一个对象，该对象是单例的
if type(a) is type(b): pass  #如果a与b的类型相同
#如果对象属于类cname或派生自cname的任何类，isinstance返回True
isinstance (obj, cname)
#如果A是B的子类，则issubclass()返回True
issubclass(A,B)

#考虑到鸭子类型识别的问题，可以修改isinstance、issubclass方法的行为：
class IClass(object): 
    def __init__(self):
        self.implementors = set() 
    def register(self,C):
        self.implementors.add(C) 
    def __instancecheck__(self ,x):
        return self.__subclasscheck__(type (x)) 
    def __subclasscheck__(self,sub):
        return any(c in self.implementors for c in sub.mro())</pre>
<div class="blog_h2"><span class="graybg">抽象基类</span></div>
<p>使用abc模块可以定义抽象基类，该模块由元类ABCMeta与一组装饰器组成，使用方法如下：</p>
<pre class="crayon-plain-tag">from abc import ABCMeta, abstractmethod, abstractproperty
class Foo:
    __metaclass__ =ABCMeta       # Python3使用 class Foo(metaclass=ABCMeta)
    @abstractmethod              #声明抽象方法
    def spam(self, a, b): pass
    @abstractproperty            #声明抽象属性
    def name(self): pass</pre>
<p>抽象类不能实例化，到导致TypeError，抽象的派生类只要没有全部实现抽象方法、属性，则同样不能实例化。</p>
<p>抽象基类支持对已经存在的类进行注册——使其属于该类（isinstance、issubclass返回期望的结果），但是该注册<span style="background-color: #c0c0c0;">不会检查目标子类是否实现了抽象基类的任何抽象方法、属性</span>：</p>
<pre class="crayon-plain-tag">class Bar(object): pass   #既有类
Foo.register(Bar)         #注册基类</pre>
<p>使用抽象基类注册机制，可<span style="background-color: #c0c0c0;">以重新组织已有类型的层次结构</span>，例如numbers模块把数字类型重新整理，而默认他们都是继承object类的</p>
<div class="blog_h2"><span class="graybg">元类</span></div>
<p>元类知道如何创建、管理类：</p>
<pre class="crayon-plain-tag">class Foo(object): pass
type(Foo)    #对类对象取类型，即获得其元类，默认&lt;type 'type'&gt;</pre>
<p> 使用class语句定义新类时，解释器内部事件序列可以使用下面的代码示意：</p>
<pre class="crayon-plain-tag">class_name = "Foo"                #类名
class_parents = (object,)         #基类
#类主体代码
class_body = """                  
def __init__(self, x): pass
"""
class_dict = { }
#在局部字典class_dict中执行类主体代码
exec(class_body,globals(),class_dict)

#调用元类创建类对象。这一步可以自行定义
Foo = type(class_name, class_parents, class_dict)</pre>
<p>通过指定元类，可以改变类对象创建的行为：</p>
<ol>
<li>设置类变量__metaclass__可以显式指定元类</li>
<li>如果没有显示指定元类，则使用基类元组的第一个条目的元类作为新类的元类</li>
<li>如果没有指定基类，则使用默认值：<pre class="crayon-plain-tag">types.ClassType</pre> ，即Python2.2以后的type作为元类</li>
</ol>
<p>元类通常在框架组件中使用，通常可以继承<pre class="crayon-plain-tag">type</pre> 并重新实现<pre class="crayon-plain-tag">__init__()</pre> 、<pre class="crayon-plain-tag">__new__</pre> 等方法，来扩展新的元类，下面的元类要求所有类定义必须提供文档字符串：</p>
<pre class="crayon-plain-tag">class DocMeta(type):
    def __init__(self, name, bases, dict):
        for key, value in dict.items():   #遍历所有类的元素
            # 跳过特殊方法和私有方法
            if key.startswith("__"): continue
            # 跳过不可调用的任何方法
            if not hasattr(value, "__call__"): continue 
            # 检查doc字符串
            if not getattr(value, "__doc__"):
                raise TypeError("%s must have a docstring" % key) 
        type.__init__(self, name, bases, dict)  #调用默认实现来初始化类</pre>
<div class="blog_h1"><span class="graybg">模块、包与分发</span></div>
<p>大规模的Python程序通常以模块、包的形式组织。Python的包常常和目录对应，模块则和文件对应。</p>
<div class="blog_h2"><span class="graybg">模块与import语句</span></div>
<p><span style="background-color: #c0c0c0;">任何Python源文件</span>均可以作为模块来使用，对于一个名字为util.py的源文件，可以使用import util语句来将其作为模块加载。加载模块时，Python解释器将：</p>
<ol>
<li>创建新的命名空间，用作在相应源文件中定义的所有对象的容器。源文件中定义的函数、方法在使用global语句时，将访问该命名空间</li>
<li>在新创建的命名空间中执行模块中包含的代码——import导致模块中所有语句被执行</li>
<li>在调用函数中创建名称来引用模块命名空间，该名称默认与模块名称一致：<br />
<pre class="crayon-plain-tag">#util.py
class stringutils(object):
    @classmethod
    def indexOf(string, search): pass

#main.py
import util                           #导入源文件作为模块
util.stringutils.indexOf("123","2")   #使用模块中定义的类，注意前缀</pre>
</li>
</ol>
<p>用于引用模块的名称可以使用as限定符修改，新名称只限定使用了import语句的源文件或者上下文：</p>
<pre class="crayon-plain-tag">import util as Util
Util.stringutils.indexOf</pre>
<p><span style="background-color: #c0c0c0;">模块也是Python的First class对象</span>，因此可以分配给变量，存放在列表等数据结构中。</p>
<div class="blog_h2"><span class="graybg">从模块导入指定符号</span></div>
<p>使用from语句可以将模块中指定的具体符号引入当前命名空间中，访问这些导入的符号时，不需要模块名字空间前缀：</p>
<pre class="crayon-plain-tag">from util import stringutils
i = stringutils.indexOf("123","2")

#可以同时导入多个符号
from util import stringutils, numberutils
from util import (
    stringutils,
    numberutils
)

#可以在导入的同时重命名符号
from util import stringutils as su
#星号通配符可以导入所有除下划线开头的符号
#只能在模块最顶层使用，在函数内使用非法
from util import *</pre>
<p>在模块中定义列表<pre class="crayon-plain-tag">__all__</pre> ，可以精确限制import *能导入的符号。</p>
<p>你也可以使用from<span style="background-color: #c0c0c0;">从某个包中导入模块、从包中导入（定义在包的__init__.py中的）函数</span>：</p>
<pre class="crayon-plain-tag"># 从cc/gmem/py3子包中导入HelloModule模块
from .cc.gmem.py3 import HelloModule

if __name__ == '__main__':
    HelloModule.hello()</pre>
<div class="blog_h2"><span class="graybg">相对导入</span></div>
<p>从<span style="background-color: #c0c0c0;">以点号开头</span>的路径进行导入，表示相对于当前包寻找模块或符号：</p>
<pre class="crayon-plain-tag"># 从当前包的_multiprocessing模块中导入win32
from ._multiprocessing import win32

# 导入一个兄弟模块
from . import peermodule
# 导入__init__.py中定义的变量v1
from . import v1

# 导入父包中的一个模块
from .. import parentpackagemodule</pre>
<div class="blog_h2"><span class="graybg">以主程序形式运行</span></div>
<p>模块可以在import时在自己的名字空间中运行（以库模块方式），也可以以主程序的方式运行。</p>
<p>每个模块均可以访问变量<pre class="crayon-plain-tag">__name__</pre> ，该变量用于确定当前<span style="background-color: #c0c0c0;">模块在哪个模块内部运行</span>，解释器的<span style="background-color: #c0c0c0;">顶级模块</span>的名称为<pre class="crayon-plain-tag">__main__</pre> ，在命令行指定或者直接输入的程序将在__main__模块中运行：</p>
<pre class="crayon-plain-tag">#检查模块是否以程序的形式运行 
if __name__ == '__main__':
    #是
    pass
else:
    #否，我必须以模块的形式导入
    pass</pre>
<div class="blog_h2"><span class="graybg">模块搜索路径</span></div>
<p> 加载模块时，解释器在列表sys.path中搜索字典列表，sys.path的<span style="background-color: #c0c0c0;">第一项内容是空字符串，表示当前正在使用的字典，可能包括的其他条目有：字典名称、zip归档文件、.egg文件</span>。各<span style="background-color: #c0c0c0;">条目在sys.path中出现的顺序决定了模块加载时搜索的顺序</span>。</p>
<p>egg文件只是添加了版本号、依赖项的zip文件，使用zip文件的示例如下：</p>
<pre class="crayon-plain-tag">import sys
sys.path.append("modules.zip")
#假设modules里包含foo.py、bar.py两个文件
import foo,bar

#zip文件目录层次可以与OS文件系统层次混用
sys.path.append("/tmp/modules.zip/lib/python)</pre>
<p>使用归档文件时需要注意：</p>
<ol>
<li>python不会创建.pyc、.pyo文件，应当提前创建并放在归档文件中，避免加载模块时性能下降</li>
<li>C编写的共享库、扩展模块无法从归档文件中导入</li>
</ol>
<div class="blog_h2"><span class="graybg">模块加载和编译</span></div>
<p>可使用import加载的模块包括四类：</p>
<ol>
<li>Python源代码，即.py文件</li>
<li>编译为共享库或者DLL的C/C++扩展</li>
<li>一组包含模块的包</li>
<li>使用C编写并链接到Python解释器的内置模块</li>
</ol>
<p>在加载模块foo时，在顺序sys.path下每个目录中搜索以下文件：</p>
<ol>
<li>包定义：目录foo</li>
<li>已编译扩展：foo.pyd、foo.so、foomodule.so、foomodule.dll</li>
<li>foo.pyo：使用-O、-OO选项时</li>
<li>foo.pyc</li>
<li>foo.py、foo.pyw</li>
</ol>
<p>py文件在首次被import时，会被编译为字节码并写入为.pyc文件，后续导入时，缺省直接使用.pyc文件，<span style="background-color: #c0c0c0;">除非py的修改日期更新</span>（会自动重新生成pyc）。</p>
<p>如果使用-O，则会创建pyo，并且删除行号、断言、其它调试信息。如果使用-OO，则还会删除文档字符串。</p>
<div class="blog_h2"><span class="graybg">包</span></div>
<p>使用包可以把若干模块划分为一组，可以解决不同应用程序中模块名称的命名空间冲突问题。</p>
<p>通过<span style="background-color: #c0c0c0;">创建与包名字一致的目录，并且在该目录下编写</span><pre class="crayon-plain-tag">__init__.py</pre> ，即可创建包。包内可以放入其它<span style="background-color: #c0c0c0;">源文件、编译后的扩展、子包</span>。</p>
<p>包的导入与模块导入类似，都是使用import语句。<span style="background-color: #c0c0c0;">第一次导入包的任何部分，均会执行对应的__init__.py，父包的__init__.py比子包的先执行</span>。</p>
<div class="blog_h2"><span class="graybg">基于distutils进行分发</span></div>
<p>本节以工程my-autosizer v0.1.0为例进行阐述</p>
<div class="blog_h3"><span class="graybg">步骤概述</span></div>
<p>你可以使用distutils模块来分发Python程序给其他人使用，步骤如下：</p>
<ol>
<li>相关文件有序的组织到工程目录中，这些文件包括：模块、包、脚本、支持文档等。使用Pycharm时，这个目录就是Project的根目录。工程布局示例：<br />
<pre class="crayon-plain-tag">├── my-autosizer
   ├── dist
   │   └── myautoresizer-0.1.0.tar.gz
   ├── ma_autoresize.py
   ├── MANIFEST
   ├── ma_printrect.py
   ├── myautoresizer
   │   └── __init__.py
   └── setup.py</pre>
</li>
<li>在工程目录下编写安装脚本setup.py</li>
<li>可选的，编写一个安装配置文件</li>
<li>创建源码分发包（source distribution）</li>
<li>可选的，创建更多的二进制分发包（binary distributions）</li>
</ol>
<div class="blog_h3"><span class="graybg">编写setup.py</span></div>
<p>安装脚本是构建、分发、安装等一系列活动的中心所在，最简单的例子如下：</p>
<pre class="crayon-plain-tag">from distutils.core import setup

setup(
    name="myautoresizer",                                # 软件包名称
    version="0.1.0",                                     # 字符串版本号
    description='Auto resize and move GDK windows',
    author='Alex Wong',
    author_email='alex@gmem.cc',
    url='https://py.gmem.cc/myautoresizer',
    py_modules = [],                                     # 所有单一文件的Python模块列表
    packages=['myautoresizer'],                          # 所有包目录的列表
    scripts=['ma_autoresize.py', 'ma_printrect.py']      # 脚本文件的列表
)</pre>
<div class="blog_h3"><span class="graybg">创建分发包</span></div>
<p>调用setup.py，传入不同的相应参数，可以创建分发包，这些分发包自动保存到工程目录的dist子目录下：</p>
<pre class="crayon-plain-tag">cd /home/alex/Python/projects/pycharm/my-autosizer
# 创建源码分发包，生成 dist/myautoresizer-0.1.0.tar.gz
python setup.py sdist

# 创建二进制分发程序
python setup.py bdist 
# Window下分发为安装程序.exe
python setup.py bdist_wininst 
# Redhat下分发为安装程序.prm
python setup.py bdist_rpm</pre>
<div class="blog_h3"><span class="graybg">安装包</span></div>
<p>在客户机上，可以解压并安装上面的分发包：</p>
<pre class="crayon-plain-tag">tar xzf myautoresizer-0.1.0.tar.gz
cd myautoresizer-0.1.0

# 执行安装
python setup.py install
# 在我的机器上，以下文件被安装
# /usr/local/bin/ma_autoresize.py
# /usr/local/bin/ma_printrect.py
# /usr/local/lib/python2.7/dist-packages/myautoresizer/__init__.py
# 注意：工程根目录只是容器，不会体现在安装树的任何地方</pre>
<p>调用<pre class="crayon-plain-tag">install</pre> 子命令后：</p>
<ol>
<li>模块、包通常安装到Python库的<pre class="crayon-plain-tag">site-packages</pre> 下</li>
<li>脚本在Unix下通常安装到Python解释器二进制文件所在目录</li>
<li>脚本在Windows下通常安装到<pre class="crayon-plain-tag">%PYTHON_HOME%\Scripts</pre> 目录</li>
</ol>
<p>你也可以调用pip命令，直接安装压缩包：</p>
<pre class="crayon-plain-tag">sudo pip install myautoresizer-0.1.0.tar.gz</pre>
<div class="blog_h3"><span class="graybg">发布到PyPI</span></div>
<p>Python Package Index (PyPI)存储基于distutils分发的软件包的元数据信息，如果作者愿意，软件包本身也可以存放在上面。</p>
<p>通过<pre class="crayon-plain-tag">register</pre> 和<pre class="crayon-plain-tag">upload</pre> 子命令，你可以把元数据推送到PyPI上去。PyPI允许你提交某个软件包的任意数量的版本，你还可以覆盖既有的版本。</p>
<p>执行下面的命令注册软件包的元数据：</p>
<pre class="crayon-plain-tag">python setup.py register
# 你需要根据提示，提供登录身份，或者注册新用户</pre>
<p>执行下面的命令上传软件包： </p>
<pre class="crayon-plain-tag"># 上传源码发布包、Windows二进制发布包
python setup.py sdist bdist_wininst upload
    -r https://gmem.cc/pypi   # 指定PyPI仓库地址</pre>
<p>register和upload命令会检查<pre class="crayon-plain-tag">$HOME/.pypirc</pre> 文件，从中得到用户名、密码、仓库URL信息，该文件内容格式如下：</p>
<pre class="crayon-plain-tag">[distutils]
index-servers =
    pypi

[pypi]
repository:https://pypi.python.org/pypi
username:user
password:passwd</pre>
<p>Section头中的文字，是仓库的名称，可以作为<pre class="crayon-plain-tag">-r</pre> 参数使用： </p>
<pre class="crayon-plain-tag">python setup.py sdist upload -r pypi</pre>
<div class="blog_h2"><span class="graybg">基于setuptools进行分发</span></div>
<p>Setuptools对distutils进行了一系列的增强，特别是，它能很好的处理包之间的依赖关系。下面是Setuptools的主要特性：</p>
<ol>
<li>可以在构建阶段，利用easy_install自动查找、下载、安装、升级依赖。这些依赖可以通过HTTP、HTTPS、SVN、SourceForge等方式得到</li>
<li>创建EGG——单文件的、可导入的分发格式</li>
<li>对访问位于压缩文件中的数据文件提供增强支持</li>
<li>自动包含源码树中所有包，不必在setup.py中逐个指定</li>
<li>自动包含相关的文件到源码发布包中，不需要MANIFEST.in</li>
<li>对于工程中的每一个__main__函数，自动生成包装脚本或者Windows的exe文件</li>
<li>支持上传EGG或者源码发布包到PyPI</li>
<li>创建能自动发现扩展的可扩展程序</li>
</ol>
<div class="blog_h3"><span class="graybg">简单例子</span></div>
<p>Setuptools保证了和distutils的兼容性，包括安装脚本的文件名、API风格都是一样的。Setuptools对API进行了很多扩展：</p>
<pre class="crayon-plain-tag">setup(
    name="myautoresizer",
    version="0.1.2",
    packages=find_packages(exclude=["tests.*"]),  # 自动在源码树中寻找Python包
    install_requires=['docutils&gt;=0.3'],  # 指定依赖列表
    scripts=['ma_autoresize.py', 'ma_printrect.py'],
    package_data={
        # 对于任何包，其中的*.txt、*.text文件被包含到发布树
        '': ['*.txt', '*.text'],
        # 对于myautoresizer，其中的*.ini文件被包含到发布树
        'myautoresizer': ['*.ini'],
    },
    entry_points={
        'console_scripts': [
            'ma_printrect = myautoresizer.scripts:ma_printrect',
            'ma_autoresize = myautoresizer.scripts:ma_autoresize',
        ],
    }
)</pre>
<p>执行同样的命令，可以打源码发布包：</p>
<pre class="crayon-plain-tag">python setup.py sdist</pre>
<p>源码发布包会以tar.gz格式存放在dist子目录，同时，根目录会出现一个文件夹：myautoresizer.egg-info </p>
<div class="blog_h3"><span class="graybg">调用关键字</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">关键字</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>include_package_data</td>
<td>如果设置为True，依据MANIFEST.in，包中的所有数据文件被包含到构建树中</td>
</tr>
<tr>
<td>exclude_package_data</td>
<td>一个字典，Key为包名称，Value为需要排除掉的文件名通配符的列表</td>
</tr>
<tr>
<td>package_data</td>
<td>一个字典，Key为包名称，Value为需要包含在构建树中的文件名通配符的列表<br />如果使用include_package_data，你不需要指定该选项，除非你需要包含setup脚本运行过程中生成的文件</td>
</tr>
<tr>
<td>zip_safe</td>
<td>布尔值，提示当前工程是否可以安全的安装并从一个压缩文件中运行，如果不指定此选项，那么bdist_egg子命令必须分析整个工程，来寻找可能的问题</td>
</tr>
<tr>
<td>install_requires</td>
<td>字符串或者列表，指定该包所依赖的其它包及其版本</td>
</tr>
<tr>
<td>entry_points</td>
<td>
<p>一个字典，Key为扩展点组（ entry point group）的名称，Value为定义扩展点的字符串或者列表</p>
<p>扩展点用于支持服务/插件的自动发现</p>
</td>
</tr>
<tr>
<td>extras_require</td>
<td>一个字典，Key为工程额外特性的名称，Value为支持此额外特性需要安装的依赖</td>
</tr>
<tr>
<td>python_requires</td>
<td>对Python版本的要求</td>
</tr>
<tr>
<td>setup_requires</td>
<td>一个字符串或者列表，为了能让安装脚本运行，所需要的依赖</td>
</tr>
<tr>
<td>dependency_links</td>
<td>一组URL由于搜索setup_requires、tests_require的依赖</td>
</tr>
<tr>
<td>namespace_packages</td>
<td>
<p>用于命名工程的“命名空间包”的字符串列表。这个命名空间包（例如倒写的域名）可能被多个工程使用</p>
<p>EGG运行时系统能够自动合并具有共同命名空间包的子包，只要命名空间包的__init__.py不包含任何代码</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">使用find_packages()</span></div>
<p>对于大型工程来说，手工列出packages需要很大的工作量，此时可以使用find_packages()函数：</p>
<pre class="crayon-plain-tag"># where 指定源码目录，默认setup.py所在目录
# 在Python 3.2-只有包含__init__.py才能被识别为包
# exclude 需要排除的包名通配符
# include 需要包含的包名通配符
def find( where='.', exclude=(), include=('*',)): pass

# 排除掉所有测试包
find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])</pre>
<div class="blog_h3"><span class="graybg">自动脚本创建</span></div>
<p>distutils打包、安装脚本的方式比较笨拙：</p>
<ol>
<li>脚本的名称不能很好的匹配Windows/Linux的扩展名习惯</li>
<li>你需要编写单独的脚本，仅仅为了容纳一个__main__函数。my-autosizer的v0.1.0中的ma_*.py脚本就是因此而存在</li>
</ol>
<p>要让setuptools自动创建脚本，只需要：</p>
<pre class="crayon-plain-tag">setup(
    # 其它参数
    entry_points={
        # 控制台脚本
        'console_scripts': [
            'foo = my_package.some_module:main_func',
            'bar = other_module:some_func',
        ],
        # GUI脚本
        'gui_scripts': [
            'baz = my_package_gui:start_func',
        ]
    }
)</pre>
<div class="blog_h3"><span class="graybg">让EGG可以直接执行</span></div>
<pre class="crayon-plain-tag">setup(
    entry_points = {
        'setuptools.installation': [
            'eggsecutable = my_package.some_module:main_func'
        ]
    }
)</pre>
<p> 添加此配置后，目标EGG增加可执行权限后，即可直接在Unix-like系统上执行。</p>
<div class="blog_h3"><span class="graybg">声明依赖</span></div>
<p>对于依赖版本的限制，你可以使用以下操作符：<pre class="crayon-plain-tag">&lt; &gt; &lt;= &gt;= == !=</pre> </p>
<div class="blog_h2"><span class="graybg">安装第三方库</span></div>
<p><a href="http://pypi.python.org" target="_blank">PyPI网站</a>包含大量的第三方扩展资源。对于没有依赖的简单库，可以使用脚本<pre class="crayon-plain-tag">python setup.py install</pre> 安装。</p>
<p>对于依赖关系复杂的库，最好使用setuptools提供的easy_install脚本，只需要输入<pre class="crayon-plain-tag">easy_install pkgname</pre> 即可安装指定的软件包，会自动从PyPI下载合适的软件、 依赖项。</p>
<div class="blog_h1"><span class="graybg">Python包管理器</span></div>
<div class="blog_h2"><span class="graybg">pip</span></div>
<p>pip是Python Packaging Authority (PyPA) 推荐的Python包管理器，支持从<a href="https://pypi.python.org/pypi">PyPI</a>、版本控制系统、本地工程等来源安装软件包。</p>
<div class="blog_h3"><span class="graybg">安装</span></div>
<p>从2.7.9/3.4以后，该工具集成到Python中，不需要额外安装。手工安装的步骤如下：</p>
<pre class="crayon-plain-tag">wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py
rm get-pip.py</pre>
<div class="blog_h3"><span class="graybg">常用子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>install</td>
<td>
<p>安装Python软件包，如果Wheel可用pip默认会使用之，要改变此行为，添加参数<pre class="crayon-plain-tag">--no-binary</pre> </p>
<p><span style="background-color: #c0c0c0;">举例：</span></p>
<pre class="crayon-plain-tag"># 从PyPI安装：
pip install pkgname              # 最新版本
pip install pkgname==1.0.4       # 特定版本
pip install 'pkgname&gt;=1.0.4'     # 最小版本

# 为当前用户安装
# Linux下安装到 ~/.local/；Windows下安装到%APPDATA%\Python 
pip install --user pkgname

# 从指定的“需求文件”安装
pip install -r requirements.txt

# 从Wheel安装
pip install pkgname-1.0-py2.py3-none-any.whl</pre>
</td>
</tr>
<tr>
<td>uninstall</td>
<td>卸载Python软件包</td>
</tr>
<tr>
<td>list</td>
<td>
<p>列出已经安装的Python软件包，<span style="background-color: #c0c0c0;"><span style="background-color: #c0c0c0;">举例：</span></span>
<p><pre class="crayon-plain-tag"># 列出所有软件包
pip list
# 列出过期的软件包
pip list --outdated</pre>
</td>
</tr>
<tr>
<td>show</td>
<td>显示一个已安装Python软件包的信息，包括名称、版本、安装位置、依赖关系</td>
</tr>
<tr>
<td>download</td>
<td>下载Python软件包</td>
</tr>
<tr>
<td>freeze</td>
<td>把已安装的软件包是出为需求文件格式（requirements format）</td>
</tr>
<tr>
<td>search</td>
<td>从PyPI搜索软件包，目标软件包的名称或者摘要信息必须包含指定的关键字</td>
</tr>
<tr>
<td>wheel</td>
<td>
<p>要使用该命令，必须先安装<pre class="crayon-plain-tag">pip install wheel</pre> </p>
<p>Wheel是一种归档格式，比起从源码归档安装，它的速度很快</p>
</td>
</tr>
<tr>
<td>hash</td>
<td>计算包归档文件的哈希</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">配置文件</span></div>
<p>你可以使用配置文件为pip命令提供默认选项。</p>
<p>配置文件位置如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">配置文件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>/etc/pip.conf</td>
<td>全局配置文件</td>
</tr>
<tr>
<td>~/.config/pip/pip.conf</td>
<td>个人配置文件</td>
</tr>
<tr>
<td>~/.pip/pip.conf</td>
<td>老版本使用的个人配置文件，目前仍然支持</td>
</tr>
<tr>
<td>$VIRTUAL_ENV/pip.conf</td>
<td><a href="https://virtualenv.pypa.io/en/stable/">virtualenv</a>中的配置文件</td>
</tr>
</tbody>
</table>
<p>配置文件示例如下：</p>
<pre class="crayon-plain-tag">; 这一段针对所有子命令
[global]
; 命令执行超时
timeout = 60
; 使用镜像包索引
index-url = https://pypi.doubanio.com/simple

; 这一段仅针对freeeze子命令
[freeze]
timeout = 10 </pre>
<div class="blog_h2"><span class="graybg">pip2</span></div>
<p>作为新版本的pip，目前还不成熟，安装步骤如下：</p>
<pre class="crayon-plain-tag">git clone https://github.com/osupython/pip2.git
cd pip2
python setup.py install</pre>
<div class="blog_h2"><span class="graybg">setuptools</span></div>
<p>使用该工具可以方便的下载、构建、安装、升级、卸载Python软件包，安装步骤如下：</p>
<pre class="crayon-plain-tag">wget https://bootstrap.pypa.io/ez_setup.py
python ez_setup.py</pre>
<p>该工具最常用的命令是easy_install，用来安装Python模块。</p>
<div class="blog_h1"><span class="graybg">Virtualenv</span></div>
<p>Virtualenv的目的是创建隔离的Python环境，可以解决不同软件之间<span style="background-color: #c0c0c0;">依赖包冲突、以及潜在的文件权限</span>的问题。</p>
<p>在开发Python应用的时候，依赖包默认都会安装到Python运行时的site-packages目录下。这样，如果两个应用依赖统一个包的不同版本，就会出现冲突。要解决这类问题，可以利用Virtualenv。</p>
<p>从3.3版本开始，Virtualenv的一部分功能作为标准库集成到venv模块中。不包含在其中的功能有：</p>
<ol>
<li>创建Bootstrap脚本</li>
<li>为其它Python版本创建虚拟环境</li>
</ol>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>可以使用任何版本的pip来安装和管理Virtualenv：</p>
<pre class="crayon-plain-tag">pip3 install virtualenv</pre>
<div class="blog_h2"><span class="graybg">使用</span></div>
<div class="blog_h3"><span class="graybg">创建虚拟环境</span></div>
<p>执行下面的命令，可以创建一个目录，并将其作为一个虚拟环境：</p>
<pre class="crayon-plain-tag">virtualenv  certbot-dns-aliyun</pre>
<p>上面命令创建的虚拟环境的名字是certbot-dns-aliyun，他相当于一个Python的$PREFIX目录，和Python标准的目录结构对应：</p>
<ol>
<li>lib，此虚拟环境的库文件，包被安装到lib/pythonX.X/site-packages/下</li>
<li>bin，可执行文件，包括python这个文件</li>
</ol>
<p>包括pip、setuptools在内的包，会自动安装到新创建的虚拟环境中。</p>
<div class="blog_h3"><span class="graybg">进入虚拟环境</span></div>
<p>要进入虚拟环境，执行其中的脚本：</p>
<pre class="crayon-plain-tag">source certbot-dns-aliyun/bin/activate</pre>
<p>你会发现命令提示符增加了前缀<pre class="crayon-plain-tag">(certbot-dns-aliyun)</pre>。 </p>
<p>现在，你通过pip安装的包，都会安装到当前虚拟环境中，系统的Python环境不会受到影响。</p>
<div class="blog_h3"><span class="graybg">退出虚拟环境</span></div>
<p>执行命令<pre class="crayon-plain-tag">deactivate</pre>即可退出当前虚拟环境。</p>
<div class="blog_h3"><span class="graybg">删除虚拟环境</span></div>
<p>简单的删除对应目录即可。</p>
<div class="blog_h2"><span class="graybg">命令</span></div>
<div class="blog_h3"><span class="graybg">格式</span></div>
<pre class="crayon-plain-tag">virtualenv [OPTIONS] DEST_DIR</pre>
<div class="blog_h3"><span class="graybg">选项</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-p PYTHON_EXE</td>
<td>
<p>指定使用的Python解释器：</p>
<pre class="crayon-plain-tag">virtualenv -p /usr/bin/python2.7 
virtualenv --python=python3.5</pre>
<p>将会基于该解释器（及其安装的包）来创建新的虚拟环境 </p>
</td>
</tr>
<tr>
<td>--no-site-packages</td>
<td>
<p>不将系统Python环境的第三方包复制过来，也就是说创建的是一个空的干净的环境</p>
<p>已经废弃，目前默认行为就是如此</p>
</td>
</tr>
<tr>
<td>--system-site-packages</td>
<td>允许虚拟环境访问系统Python环境的第三方包</td>
</tr>
<tr>
<td>--always-copy</td>
<td>总是拷贝而非符号链接文件</td>
</tr>
<tr>
<td>--relocatable</td>
<td>
<p>使一个即有的虚拟环境可重定位：</p>
<ol>
<li>修复脚本内容</li>
<li>使所有.pth文件使用相对路径</li>
</ol>
</td>
</tr>
<tr>
<td>--no-setuptools<br />--no-pip<br />--no-wheel</td>
<td>不在虚拟环境中安装这些软件</td>
</tr>
<tr>
<td>--extra-search-dir</td>
<td>从此额外的目录中搜索setuptools/pip，可以指定多次</td>
</tr>
<tr>
<td>--download<br />--no-download</td>
<td>从PyPI下载预安装的包</td>
</tr>
<tr>
<td>--prompt=PROMPT</td>
<td>指定命令提示符前缀</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用代码片段</span></div>
<div class="blog_h2"><span class="graybg">语言基础</span></div>
<div class="blog_h3"><span class="graybg">随机数和文本</span></div>
<pre class="crayon-plain-tag">import random

corpTypes = [22, 23, 24, 25, 26, 49, 50, 180, 181, 182, 184, 185, 186, 77, 164, 165, 220]

# 对序列进行随机的重新排序
random.shuffle(corpTypes)

# 从序列中随机选取一个
random.choice(corpTypes)

# 从序列中随机选取N个样本
random.sample([10, 20, 30, 40, 50], k=N)

# 随机浮点数
# 0-1之间随机
random.random()
# 2.5-10.0之间随机
random.uniform(2.5, 10.0)   

# 随机整数
# 0-9之间随机
random.randrange(10)  
# 3-9之间随机
random.randrange(3,10)</pre>
<div class="blog_h3"><span class="graybg">数学运算</span></div>
<pre class="crayon-plain-tag">#decimal模块实现了IBM通用十进制算法标准，能够准确表示十进制值
import decimal 
x = decimal.Decimal('3.4')
y = decimal.Decimal('4.5')
a = x * y  #15.30
b = x / y  #0.7555555555555555555555556
#改变精度并计算
decimal.getcontext().prec = 3  #三位有效数字
a = x * y  #15.3
b = x / y  #0.756
#只改变语句块的精度
with decimal.localcontext(decimal.Context(prec=10)):
    a = x * y  #15.30
    b = x / y  #0.7555555556

#Context对象用于控制十进制数的各种属性
Context(
    prec = None,       #精度的位数，即有效数字数
    rounding = None,   #四舍五入方式
    traps = None,      #信号列表，在发生算术异常时有用
    flags = None,      #环境初始状态的信号列表
    Emin = None,       #指数最小值
    EMax = None,       #指数最大值
    capitals = 1       #指数使用'E'还是'e'，默认'E'
)


#math模块定义了很多标准算术运算函数
#numbers模块对数值类的层次进行梳理</pre>
<div class="blog_h3"><span class="graybg"> 命令行参数</span></div>
<pre class="crayon-plain-tag"># Python使用列表sys.argv存放命令行参数，第一个元素是程序的名称，后续为命令行参数
import sys
if len( sys.argv ) != 3 :
    sys.stderr.write( "Invalid arguments" )  # 访问标准输出
    raise SystemExit( 1 )  # 以非零退出
inputfile = sys.argv[1]



# 对于复杂的命令行参数，可以使用optparse模块进行处理
import optparse
p = optparse.OptionParser()
p.add_option( 
             "-o" ,  # 命令行选项
             action = "store",   #store表示把值存放在Options中
             dest = "outfile",   #在处理结果Options中的键
             default = "out.log" #默认值
            )
p.add_option( "--output" , action = "store", dest = "outfile" )
p.add_option( "-o" , "--output" , action = "store", dest = "outfile" )  # 同时指定长短选项
# 布尔选项，在命令行中只指定命令选项，而不指定值
p.add_option( "-d" , action = "store_true", dest = "debug" )       #store_true表示存储为True
p.add_option( "--debug" , action = "store_true", dest = "debug" )
# 设置一个或者多个选项的默认值
p.set_defaults( debug = False )
# 解析命令行
# opts为包含所有选项值的字典
# args为为解析为选项的命令行项的列表
(opts, args) = p.parse_args()
outfile = opts.outfile
debugmode = opts.debug</pre>
<div class="blog_h3"><span class="graybg">环境变量</span></div>
<pre class="crayon-plain-tag">#可以通过字典os.environ访问环境变量
import os
path = os.environ ["PATH" ] 
user = os.environ["USER"]

#写入的环境变量会影响正在运行的程序、Python创建的子进程
os.environ ["PATH" ] = ""</pre>
<div class="blog_h3"><span class="graybg">如何使用弱引用</span></div>
<pre class="crayon-plain-tag">#弱引用不会导致引用计数的增加，因而可以避免循环引用
class A: pass
a = A()
ar = weakref.ref(a)  #创建弱引用
#只要把弱引用作为函数调用，即可获得底层的对象，如果对象已经不存在，则返回None
a = ar()</pre>
<div class="blog_h3"><span class="graybg">访问标准输入输出</span></div>
<p>Python在sys模块提供stdin、stdout、stderr三个文件对象，用于访问标准输入、输出、错误。</p>
<pre class="crayon-plain-tag">import sys
sys.stdout.write("Enter password: ")  #写到标准输出，通常在屏幕上显示
sys.stdin.readline()                  #读取标准输入，通常映射到键盘
#可以如下从stdin读取一行文本（不包括结尾换行符）
name = raw_input ("Enter password: ")</pre>
<p>键盘中断会引发KeyboardInterrupt异常。  </p>
<div class="blog_h3"><span class="graybg">深复制/浅复制</span></div>
<pre class="crayon-plain-tag">import copy
ls = [[], []]
lsc = copy.copy(ls)
lsdc = copy.deepcopy(ls)</pre>
<p>实现<pre class="crayon-plain-tag">__copy__(self)</pre>、<pre class="crayon-plain-tag">__deepcopy__(self)</pre>可以控制复制行为。</p>
<div class="blog_h3"><span class="graybg">退出钩子</span></div>
<p>在解释器退出时执行某个函数：</p>
<pre class="crayon-plain-tag">def exithook():
    print 'Exiting...'
import atexit
atexit.register(exithook)</pre>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">调用Shell</span></div>
<div class="blog_h3"><span class="graybg">执行命令</span></div>
<pre class="crayon-plain-tag"># 执行指定的命令，并等待其结束
# args：字符串，或者程序参数的序列。如果传递字符串则要求shell=True或者字符串仅仅是不带参数的被调用程序的名称
# stdin, stdout, stderr 对应被调用命令的标准输入、输出、错误
# shell 如果设置为True则目标程序通过Shell执行
# subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

from subprocess import call
call(['ls','-l'])
call(["ma_autoresize.py"])</pre>
<div class="blog_h3"><span class="graybg">读取输出</span></div>
<pre class="crayon-plain-tag">internal_ip = subprocess.check_output('''
    kubectl get node %s -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}'
''' % (node.node_name()), shell=True)
#                         不设置shell为True导致No such file or directory

ipstr = str(internal_ip, encoding='utf8')</pre>
<div class="blog_h3"><span class="graybg">重定向</span></div>
<pre class="crayon-plain-tag"># 丢弃
subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

# 重定向到文件
with open('sysout.log', 'w') as out, open('syserr.log', 'w') as err:
    subprocess.Popen(cmd, shell=False, stdout=out, stderr=err)</pre>
<div class="blog_h3"><span class="graybg">强杀子进程</span></div>
<pre class="crayon-plain-tag">import subprocess
import os

#若要使用kill()方法终结进程，则shell必须为False
p = subprocess.Popen(['D:/PATH_TO_BAT.bat','ARG0'], shell=False)
p.kill()

#使用psutil亦可
import psutil
import os
def kill_proc_tree(pid, including_parent=True):    
    parent = psutil.Process(pid)
    for child in parent.children(recursive=True):
        child.kill()
    if including_parent:
        parent.kill()
#杀死当前进程的所有子进程
kill_proc_tree(os.getpid(), False)</pre>
<div class="blog_h2"><span class="graybg">文件系统</span></div>
<div class="blog_h3"><span class="graybg">目录操作</span></div>
<pre class="crayon-plain-tag">#获取上级目录
import posixpath
import ntpath
print ntpath.abspath(ntpath.join('D:\\JavaEE', '..'))  # D:\
print posixpath.abspath(posixpath.join('/home', '..')) # /</pre>
<div class="blog_h3"><span class="graybg">路径判断与操控</span></div>
<pre class="crayon-plain-tag"># 是否是文件，不存在或者不是普通文件，返回False
os.path.isfile('/swapfile')
# 是否是目录
os.path.isdir('/tmp')
# 路径是否对应文件或目录
os.path.exists('~')

# 展开~开头的路径
os.path.expanduser('~')  # /home/alex
# 展开Shell变量替换
os.path.expandvars("$PWD")

# 得到文件所在目录
os.path.dirname('/home/alex')   # /home
# 得到绝对路径
os.path.abspath('.')
# 得到当前脚本所在目录
os.path.dirname(os.path.abspath(__file__))
# 得到当前工作目录
os.getcwd()
# 改变当前工作目录
os.chdir('/tmp')

# 链接子目录、文件路径
os.path.join('/home','alex')  # /home/alex
os.path.join('home','alex')   # home/alex
os.path.join('/home','/alex') # /alex</pre>
<div class="blog_h3"><span class="graybg">文件复制和移动</span></div>
<pre class="crayon-plain-tag">import os
import shutil

os.chdir('/tmp')

# 实现touch操作
def touch(path):
    with open(path, 'a'):
        os.utime(path, None)

# 创建目录
os.makedirs('src/sub1')
os.makedirs('src/sub2')
os.makedirs('dest')

# 创建文件
touch('src/sub1/file11')
touch('src/sub1/file12')
touch('src/sub1/file13')
touch('dest/file22')

# 复制文件
shutil.copy('src/sub1/file11','dest')          # 复制单个文件到目录
# 删除单个文件
os.remove('dest/file11')
shutil.copy('src/sub1/file11','dest/file22')   # 复制到文件，覆盖既有文件

# 复制目录
shutil.copytree('src/sub1','dest/sub2')        # 第二个参数必须是不存在的路径，用来作为目标目录
# 删除整个目录，包括指定的目标本身
shutil.rmtree('dest/sub1')

# 移动目录
shutil.move('src/sub1','dest/sub1')
# 移动文件
shutil.move('dest/sub1/file11','src')</pre>
<div class="blog_h3"><span class="graybg">文件的读写</span></div>
<p>打开文件读写时，可以指定r、w、a标记位。后面可以附加b、t表示二进制还是文本模式。在二进制模式下，不会在Windows下对\n与\r\n进行转换。</p>
<pre class="crayon-plain-tag">#内置函数open(name [,inode [, bufsize]])用于打开和创建文件对象
f = open('sys.log', 'r')  #以读模式打开文件，可以省略第二个参数
f = open('sys.log', 'w')  #以写模式打开文件</pre>
<p>在Python2中，所有读操作均返回二进制字符串。在Python3中，文本模式打开读取到Unicode字符串，二进制模式打开返回字节字符串。</p>
<p>如果要处理Unicode字符文件的读写，则需要考虑字节序的问题（Unicode字符在内部使用多字节整数表示），例如，需要决定把U+HHLL以Little-ending方式写为LL HH，还是Big-ending方式写为HH LL。使用codecs提供的函数是最直接的处理Unicode文件的方法：</p>
<pre class="crayon-plain-tag"># codecs.open (filename [, mode [, encoding [, errors]]]
codecs.open("var.log",'r','utf-8', 'strict')  #读取
codecs.open("var.log",'w','utf-8')  #写入

# 可以使用codecs.EncodedFile来包装已经存在的文件对象
# codecs.EncodedFile(file, inputenc [, outputenc [,errors]])
f0 = codecs.EncodedFile(f, 'utf-8')</pre>
<p>Unicode文件可能包含特殊的BOM（字节顺序标记）用于指示字节编码的方式，它作为文件的第一个字符写入：\xff\xfe表示UTF-16-LE，\xfe\xff表示UTF-16-BE。</p>
<div class="blog_h2"><span class="graybg">网络编程</span></div>
<div class="blog_h3"><span class="graybg">ICMP请求</span></div>
<p>要发送ICMP报文，可以使用ping3包：</p>
<pre class="crayon-plain-tag">ping3.ping(ip, timeout / float(1000))</pre>
<div class="blog_h3"><span class="graybg">TCP连接</span></div>
<pre class="crayon-plain-tag">with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    try:
        sock.connect((ip, port))
    except ConnectionError:
        pass</pre>
<div class="blog_h3"><span class="graybg">UDP数据报</span></div>
<pre class="crayon-plain-tag">from IN import IP_RECVERR
sock = socket.socket(socket.AF_INET, sock_type)
sock.setsockopt(socket.IPPROTO_IP, IP_RECVERR, 1)
sock.settimeout(timeout / 1000.0)
sock.sendto(bytes(1024), (ip, port))
try:
    sock.recvfrom(1024)
except socket.timeout as e:
    pass</pre>
<div class="blog_h3"><span class="graybg">静态HTTP服务</span></div>
<pre class="crayon-plain-tag">from werkzeug.serving import run_simple
from werkzeug.wrappers import Request, Response
import os
@Request.application
def application(request):
    return Response('')
run_simple('0.0.0.0', 1010, application, static_files={"/" : os.path.dirname(__file__)})</pre>
<div class="blog_h2"><span class="graybg">压缩与解压</span></div>
<pre class="crayon-plain-tag">#bz2模块用于根据bzip2压缩算法读取和写入压缩数据

#打开一个名为filename的bz2文件，压缩级别0-9越大压缩比越高
BZ2File(filename [, mode [, buffering [, compress_level]]])
#返回字符串data中数据的一个压缩版
compress(data [, compress_level])
#返回一串包含字符串data解压数据的字符串
decompress (data)


#创建用于顺序压缩数据块序列的压缩器对象
c = BZ2Compressor([compress_level])
c.compress(data)   #添加数据到压缩器对象
c.flush            #刷新内部缓冲区并返回一串包含全部剩余数据的压缩字符串

#创建一个解压缩器对象
d.BZ2Decompressor()
d.decompress()     #解压缩

#其他压缩模块
#gzip模块提供了一个类，即Gzipfile。它可以用来读取和写入与GNU gzip程序兼容的文件，其使用与普通文件相同
GzipFile([filename [, mode [, compress_leve1 [, fileobj] ]]])
open(filename [, mode [, compress_leve1 ]])
#tarfile模块用于操作tar归档文件。利用这一模块，无论tar文件压缩与否，都可以读取和写入tar文件
#zipfilee模块用于操作zip格式编码的文件
#zlib块通过提供对zlib库的访问支持数据压缩</pre>
<div class="blog_h2"><span class="graybg">数据库</span></div>
<p>Python Database API Specification V2.0是Python社区制定的一套数据库访问标准，MySQL、Oracle等数据库可以通过该标准的API访问。</p>
<pre class="crayon-plain-tag"># 连接对象：Connection
# 使用connect(dsn="hostname:DBNAME",user="",password="")函数创建Connection对象
# Connection对象的方法：
# close()      关闭数据库连接
# commit()     提交未完成的事务
# rollback()   回滚到事务开始前状态
# cursor()     创建一个游标对象，用于进行SQL查询
# Cursor对象的方法和属性：
# callproc(procname [, parameters])   调用存储过程
# close()                             关闭游标
# execute(sql [, parameters])         执行查询语句或者命令
# executemany(sql [, parameterseqs])  重复执行查询或者命令，sql是一个查询语句，parameterseqs是一个查询参数的序列，其每一项是序列或者映射
# fetchone()                          返回execute、executemany生成的结果集的下一行
# fetchonemany([size])                返回execute、executemany生成的结果集若干行
# fetchall()                          返回剩余结果集行的序列
# rowcount                            结果集的行数

#数据库类型
#Date(year, month, day)                            日期
#Time(hour, minute, second)                        时间
#Timestamp(year, month, day, hour, minute second)  时间戳
#DateFromTicks(ticks)                              根据系统时间创建日期对象。ticks是秒数，就像函数time.time()返回的一样
#TimeFromTicks(ticks)                              根据系统时间创建时间对象
#TimestampFromTicks(ticks)                         根据系统时间创建时间戳对象
#Binary(s)                                         根据字符串创建二进制对象

#数据访问异常的超类为Error，包括以下子类
#InterfaceError    与数据库界面相关的错误。但是不是数据库本身
#DatabaseError     与数据库本身相关的错误
#DataError         与处理的数据相关的错误。例如，类型转换错误，除零等
#OperationalError  与数据库本身的运行相关的错误。例如，丢失连接
#IntegrityError    当数据库的关系完整性被破坏时出现的错误
#IntenalError      数据库内部错误。例如，如果是一个失效指针
#ProgrammingError  SQL査询中的错误
#NotSupportedError 不受底层数据库支持的数据库API方法导致的错误

if __name__ == '__main__':
    import sqlite3
    conn = sqlite3.connect("dffile")
    cur = conn.cursor()
    # 执行查询
    cur.execute("select corp_name, reg_capi from t_corp where org_id = 1000")
    # 遍历结果集
    while True:
        row = cur.fetchone()
        if not row:break
        # 处理行
        corp_name, reg_capi = row
    # 另一种处理结果集的方法
    for corp_name, reg_capi in cur: pass
    
    # 预编译语句
    pstmt = "select reg_no,addr from t_corp where org_id = ? and status = ?"
    cur.execute(pstmt, (10001, 'A'))  #使用元组填充占位符?
    #注意，并不是所有数据库模块都使用?占位符，模块的paramstyle变量描述其占位符风格，例如：
    #qmark    where id = ? and status = ?           元组填充
    #numeric  where id = :0 and status = :1         元组填充
    #named    where id = :id and status = :status   字典填充</pre>
<div class="blog_h3"><span class="graybg">数据结构类</span></div>
<pre class="crayon-plain-tag">#array模块定义了新类型array，与列表类似，但是元素必须是单一类型
#array(typecode [, initializer])
#其中typecode：
# 'c'   8位字符
# 'b'   8位整型
# 'B'   8位无符号整型
# 'u'   Unicode字符
# 'h'   16位整型
# 'H'   16位无符号整型
# 'i'   整型
# 'I'   无符号整型
# 'l'   长整型
# 'L'   无符号长整型
# 'f'   单精度浮点型
# 'd'   双精度浮点型

#支持的方法、属性列表
# typecode	  用于创建数组的类型编码字符
# itemsize	  存储在数组中的项目大小（以字节为单位）
# append (x)	  将x附加到数组末尾
# buffer_info()   返回（address, length)，提供用于存储数组的缓冲区的内存位置和长度
# byteswap()	  在大尾与小尾之间切换数组中所有项目的字节顺序。仅支持整型值
# count (x)	  返回a中出现X的次数
# extend(b)	  将b附加到数组a的末尾。b可以是一个数组，也可以是一个元素类型与a中相同的可迭代对象
# fromfile(f, n)  从文件对象中读取n个项目（二进制格式），并附加到数组末尾。f必须是一个文件对象。如果可读取的项目少于n，则抛出EOFError
# fromlist(list)  将list中的项目附加到数组末尾。list可以是任何可迭代对象
# fromstring(s)	  附加字符串s中的项目，其中s是一个由二进制值组成的字符串，与使用fromfile()进行读取相同
# index(x)	  返回x在a中首次出现的位置索引。如果未找到，则抛出ValueError
# insert(i, x)	  在位置i前插入x
# pop([i])	  从数组中删除项目i并将其返回。如果i已被删除，则删除最后一个元素
# remove (x)	  从数组中删除第一个x。如果未找到，则抛出ValueError
# reverse()       反转数组的顺序
# tofile(f)       将所有项目写入文件f。数据保存为本机二进制格式
# tolist ()       将数组转换为普通的值列表
# tostring()      转换为由二进制数据组成的字符串，与使用tofile()写入的数据相同
# tounicode()     将数组转换为Unicode字符串。如果数组类型不为'u'则抛出ValueError

a = array.array ('i', [1,2,3 ,4,5])
b = array.array(a.typecode, (2*x for x in a) )  #从a中创建新数组


#collections模块包含一些有用容器类型的高能实现、各种容器的抽象基类
deque([iterable [, maxlen]])                   #双端队列
defaultdict([default_factory], ...)            #类似于基本字典，但是再查找不存在键时，会使用default_factory提供默认值
namedtuple(typename, fieldnames, [,verbose])   #命名元组，相比起字典，效率更高
IAddr =  collections.namedtuple('InternetAddress',['hostname','port'])
a = InternetAddress('gmem.cc',80)
a.hostname</pre>
<div class="blog_h2"><span class="graybg">文本处理</span></div>
<div class="blog_h3"><span class="graybg">字符串格式化</span></div>
<p>参考：<a href="/text-processing-with-python#format-string">格式化字符串</a></p>
<p>打印可以使用print语句或者print函数</p>
<pre class="crayon-plain-tag">f = open('log', 'w')
print &gt;&gt; f,"Error"    #可以改变print的目标</pre>
<div class="blog_h3"><span class="graybg">处理Unicode字符</span></div>
<p>对于原始字节字符串s，如果其包含已编码的Unicode字符串，则可以使用s.decode(encoding, errors)方法进行转换。<br /> 对于Unicode字符串u，可以使用u.encode(encoding, errors)方法进行编码。</p>
<p>如果包含目标encoding不支持的字符，默认引发UnicodeError，可以指定errors参数以修改此行为。</p>
<p>通过sys.getdefaultencoding()可以获取默认编码。</p>
<div class="blog_h2"><span class="graybg">对象序列化</span></div>
<pre class="crayon-plain-tag">#使用pickle模块进行对象序列化
if __name__ == '__main__':
    import pickle
    users = {
        10001:'Alex',
        10002:'Meng'
    }
    fname = "F:/temp/user.dmp"
    # 第三个参数表示协议，默认0，为文本协议；1为二进制协议；2为较新的协议；3只能用于Python3
    pickle.dump(users, open(fname, 'w'), 1)  # 类似的dumps方法返回包含已序列化数据的字符串
    loaded_users = pickle.load(open(fname, 'r'))  # 从文件反序列化，自动检测协议
    print loaded_users  #类似的loads从字符串反序列化
    
    #如果存在复杂的对象关系，例如循环引用，应当使用Pickle对象进行序列化
    p = pickle.Pickler(open(fname, 'w'),1)
    p.dump(users) #把users对象写入文件，并记住其唯一标识，后续再写，自动进行引用处理
    
    up = pickle.Unpickler(open(fname, 'f'))</pre>
<div class="blog_h2"><span class="graybg">日志</span></div>
<p>logging模块为Python提供了日志记录的功能。类似于Log4j。logging包含以下组件</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">组件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>logger</td>
<td>
<p>提供日志记录接口，可以执行一些配置，或者执行日志记录：</p>
<pre class="crayon-plain-tag">#根据名称获取日志记录器，不指定name返回root记录器，此方法调用具有幂等性
logging.getLogger(name)
#默认logging提供的方法是使用root记录器</pre>
</td>
</tr>
<tr>
<td>handler</td>
<td>用于将日志发往相应的目的地，例如文件、控制台、套接字。常用的包括：StreamHandler、FileHandler 、 RotatingFileHandler、TimedRotatingFileHandler、SocketHandler、DatagramHandler</td>
</tr>
<tr>
<td>filter</td>
<td>用来过滤日志内容，决定是否将其发送给handler</td>
</tr>
<tr>
<td>formatter</td>
<td>用于指定日志输出格式 </td>
</tr>
</tbody>
</table>
<p>logging支持5种记录级别：DEBUG、INFO、WARNING、ERROR、CRITICAL，在不指定的情况下，<span style="background-color: #c0c0c0;">默认记录级别为WARNING</span>，也就是说，低于这个级别的信息不会被记录到控制台或文件。
<p>利用basicConfig函数可以配置日志记录的参数，例如下面的例子将日志记录到文件：</p>
<pre class="crayon-plain-tag">import logging
#记录到文件，日志级别为DEBUG
#使用命令行参数 --log=DEBUG 也可以指定日志级别
logging.basicConfig( filename='pyapp.log', filemode='w', level=logging.DEBUG )
logging.debug( 'This message should be written to pyapp.log' )</pre>
<p>logging支持使用C语言printf风格的格式化：</p>
<pre class="crayon-plain-tag">import logging
logging.warning( '%s is %d years old.', 'Alex', 24 )</pre>
<p>为basicConfig指定format参数，可以详细定制日志输出格式，例如：</p>
<pre class="crayon-plain-tag">import logging
#日志输出格式为：时间 日志内容
#详细规定日期时间的显示格式，datefmt的参数参考time.strftime()
#format中占位符的格式为%(key)[-][n][s|d...]，-表示右补白，n表示显示的最小宽度，s表示格式化为字符串
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')</pre>
<p> format可以使用下表列出的占位符：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">占位符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>%(name)s</td>
<td>Logger的名称</td>
</tr>
<tr>
<td>%(levelno)s</td>
<td>数字形式的日志级别</td>
</tr>
<tr>
<td>%(levelname)s</td>
<td>文本形式的日志级别</td>
</tr>
<tr>
<td>%(pathname)s</td>
<td>发起日志函数调用的源代码文件的详细路径</td>
</tr>
<tr>
<td>%(filename)s</td>
<td>发起日志函数调用的源代码文件的名称</td>
</tr>
<tr>
<td>%(module)s</td>
<td>模块名称，即filename的基名部分</td>
</tr>
<tr>
<td>%(funcName)s</td>
<td>发起日志函数调用的函数</td>
</tr>
<tr>
<td>%(lineno)d</td>
<td>发起日志函数调用的行号</td>
</tr>
<tr>
<td>%(created)f</td>
<td>LogRecord被创建的时间</td>
</tr>
<tr>
<td>%(relativeCreated)d</td>
<td>LogRecord创建相对于logging模块被加载的时间</td>
</tr>
<tr>
<td>%(asctime)s</td>
<td>人类可阅读的LogRecord创建时间</td>
</tr>
<tr>
<td>%(msecs)d</td>
<td>LogRecord被创建时间的毫秒部分</td>
</tr>
<tr>
<td>%(thread)d</td>
<td>线程ID</td>
</tr>
<tr>
<td>%(threadName)s</td>
<td>线程名称</td>
</tr>
<tr>
<td>%(process)d</td>
<td>进程ID</td>
</tr>
<tr>
<td>%(message)s</td>
<td>日志主体内容，由msg参数传入</td>
</tr>
</tbody>
</table>
<p>除了通过编程方式配置logging以外，还可以支持配置文件方式：</p>
<pre class="crayon-plain-tag">logging.config.fileConfig('logging.conf')</pre>
<p>logging.conf文件的格式类似于INI，下面是一个示例：</p>
<pre class="crayon-plain-tag">;声明使用的日志记录器
[loggers]
keys=root,corelogger
;声明使用的日志处理器
[handlers]
keys=consoleHandler
;声明日志输出格式
[formatters]
keys=defaultFormatter
;每个日志记录器的详细配置：使用什么级别、处理器
[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_corelogger]
level=DEBUG
handlers=consoleHandler
qualname=corelogger
propagate=0

;每个处理器的详细配置：处理器的类（相对logging模块的类名或是全限定类名）、格式以及构造器入参
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=defaultFormatter
args=(sys.stdout,)

[formatter_defaultFormatter]
format=[%(levelname)-7s] [%(process)d-%(threadName)s] %(asctime)s %(module)s.%(funcName)s:%(lineno)d %(message)s
datefmt=%Y-%m-%d %I:%M:%S</pre>
<p>在多模块中使用logging时，典型做法是在main模块中初始化日志配置，其他模块引用Logger对象即可。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">Python 2.7.6报错：UnicodeDecodeError: utf8 codec can’t decode byte 0xb0 in position 1: invalid start byte</span></div>
<div>这是一个BUG，删除下面三行即可。</div>
<pre class="crayon-plain-tag">ctype = ctype.encode(default_encoding) # omit in 3.x!
except UnicodeEncodeError:
    pass</pre>
<div class="blog_h3"><span class="graybg">运行脚本报错：SyntaxError: Non-ASCII character...</span></div>
<p>类似的报错信息还可能是“but no encoding declared...”。需要指定源代码的编码方式：</p>
<pre class="crayon-plain-tag"># 在源文件开始出添加类似下面的语句
# -*- coding: cp-1252 -*-
# -*- coding: UTF-8 -*-</pre>
<div class="blog_h3"><span class="graybg">easy_install提示：*** is already the active version in easy-install.pth</span></div>
<p>如果安装了两个Python环境A、B，其中A已经安装了软件包P，在运行B的easy_install再次安装P时会出现该提示，并且Pydev不能识别路径匹配的软件包。</p>
<p>打开文件：%PYTHON_B_HOME%/site-packages/easy-install.pth，删除与P相关的行，然后拷贝%PYTHON_A_HOME%/site-packages/P-version.egg到B的对应目录即可。</p>
<div class="blog_h3"><span class="graybg">Python 2.6下pip安装模块时提示：SNIMissingWarning: An HTTPS request has been made, but the SNI ...</span></div>
<p>基于HTTPS访问PyPI时会出现此警告：SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform。</p>
<p>安装模块时还会出现证书错误提示：Certificate did not match expected hostname，导致无法完成安装。</p>
<p>解决办法：<pre class="crayon-plain-tag">pip install pyopenssl ndg-httpsclient pyasn1</pre> </p>
<div class="blog_h3"><span class="graybg">使用pip报错：ImportError: cannot import name 'HTTPSHandler' </span></div>
<p>可能是因为你构建Python时，机器上缺少OpenSSL支持，先安装OpenSSL：</p>
<pre class="crayon-plain-tag"># CentOS
yum install openssl openssl-devel
# Ubuntu
apt-get install openssl libssl-dev</pre>
<p>然后重新构建Python即可。</p>
<div class="blog_h3"><span class="graybg">ValueError: Attempted relative import in non-package</span></div>
<p>考虑如下目录结构：</p>
<pre class="crayon-plain-tag">.
└── myautosizer
    ├── __init__.py
    ├── script.py</pre>
<p>以及Python脚本文件： </p>
<pre class="crayon-plain-tag">all_usrs = []</pre><br />
<pre class="crayon-plain-tag">from . import all_usrs
if __name__ == '__main__':
    pass</pre>
<p>执行<pre class="crayon-plain-tag">python ./myautosizer/scrpit.py</pre> 时就会出现该错误。原因是：相对导入是相对于“当前包”的 ，而直接执行脚本的时，<span style="background-color: #c0c0c0;">不存在当前包的概念</span>。</p>
<p>解决办法：</p>
<ol>
<li>改变调用命令，搜索模块，作为脚本运行：<pre class="crayon-plain-tag">python -m  myautosizer.scrpit</pre> </li>
<li>或者，不使用相对导入，修改导入语句为 <pre class="crayon-plain-tag">from myautosizer.scrpit import all_usrs</pre> </li>
<li>或者，从外部脚本调用script.py，把script.py最为模块看待</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-study-note">Python学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/python-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
