❶ Elasticsearch性能优化
注:文本整理自《ELKstack权威指南》
在 CRUD 章节,我们已经知道 ES 的数据写入是如何操作的了。喜欢自己动手的读者可能已经迫不及待的自己写了程序开始往 ES 里写数据做测试。这时候大家会发现:程序的运行速度非常一般,即使 ES 服务运行在本机,一秒钟大概也就能写入几百条数据。
这种速度显然不是 ES 的极限。事实上,每条数据经过一次完整的 HTTP POST 请求和 ES indexing 是一种极大的性能浪费,为此,ES 设计了批量提交方式。在数据读取方面,叫 mget 接口,在数据变更方面,叫 bulk 接口。mget 一般常用于搜索时 ES 节点之间批量获取中间结果集,对于 Elastic Stack 用户,更常见到的是 bulk 接口。
bulk 接口采用一种比较简朴的数据积累格式,示例如下:
格式是,每条 JSON 数据的上面,加一行描述性的元 JSON,指明下一行数据的操作类型,归属索引信息等。
采用这种格式,而不是一般的 JSON 数组格式,是因为接收到 bulk 请求的 ES 节点,就可以不需要做完整的 JSON 数组解析处理,直接按行处理简短的元 JSON,就可以确定下一行数据 JSON 转发给哪个数据节点了。这样,一个固定内存大小的 network buffer 空间,就可以反复使用,又节省了大量 JVM 的 GC。
事实上,产品级的 logstash、rsyslog、spark 都是默认采用 bulk 接口进行数据写入的。对于打算自己写程序的读者,建议采用 Perl 的 Search::Elasticsearch::Bulk 或者 python 的 elasticsearch.helpers.* 库。
在配置 bulk 数据的时候,一般需要注意的就是请求体大小(bulk size)。
这里有一点细节上的矛盾,我们知道,HTTP 请求,是可以通过 HTTP 状态码 100 Continue 来持续发送数据的。但对于 ES 节点接收 HTTP 请求体的 Content-Length 来说,是按照整个大小来计算的。所以,首先,要确保 bulk 数据不要超过 http.max_content_length 设置。
那么,是不是尽量让 bulk size 接近这个数值呢?当然不是。
依然是请求体的问题,因为请求体需要全部加载到内存,而 JVM Heap 一共就那么多(按 31GB 算),过大的请求体,会挤占其他线程池的空间,反而导致写入性能的下降。
再考虑网卡流量,磁盘转速的问题,所以一般来说,建议 bulk 请求体的大小,在 15MB 左右,通过实际测试继续向上探索最合适的设置。
注意:这里说的 15MB 是请求体的字节数,而不是程序里里设置的 bulk size。bulk size 一般指数据的条目数。不要忘了,bulk 请求体中,每条数据还会额外带上一行元 JSON。
以 logstash 默认的 bulk_size => 5000 为例,假设单条数据平均大小 200B ,一次 bulk 请求体的大小就是 1.5MB。那么我们可以尝试 bulk_size => 50000 ;而如果单条数据平均大小是 20KB,一次 bulk 大小就是 100MB,显然超标了,需要尝试下调至 bulk_size => 500 。
gateway 是 ES 设计用来长期存储索引数据的接口。一般来说,大家都是用本地磁盘来存储索引数据,即 gateway.type 为 local 。
数据恢复中,有很多策略调整我们已经在之前分片控制小节讲过。除开分片级别的控制以外,gateway 级别也还有一些可优化的地方:
注意:gateway 中说的节点,仅包括主节点和数据节点,纯粹的 client 节点是不算在内的。如果你有更明确的选择,也可以按需求写:
虽然 ES 对 gateway 使用 NFS,iscsi 等共享存储的方式极力反对,但是对于较大量级的索引的副本数据,ES 从 1.5 版本开始,还是提供了一种节约成本又不特别影响性能的方式:影子副本(shadow replica)。
首先,需要在集群各节点的 elasticsearch.yml 中开启选项:
同时,确保各节点使用相同的路径挂载了共享存储,且目录权限为 Elasticsearch 进程用户可读可写。
然后,创建索引:
针对 shadow replicas ,ES 节点不会做实际的索引操作,而是单纯的每次 flush 时,把 segment 内容 fsync 到共享存储磁盘上。然后 refresh 让其他节点能够搜索该 segment 内容。
如果你已经决定把数据放到共享存储上了,采用 shadow replicas 还是有一些好处的:
但是请注意:主分片节点还是要承担一个副本的写入过程,并不像 Lucene 的 FileReplicator 那样通过复制文件完成,所以达不到完全节省 CPU 的效果。
shadow replicas 只是一个在某些特定环境下有用的方式。在资源允许的情况下,还是应该使用 local gateway。而另外采用 snapshot 接口来完成数据长期备份到 HDFS 或其他共享存储的需要。
我们都知道,ES 中的 master 跟一般 MySQL、Hadoop 的 master 是不一样的。它即不是写入流量的唯一入口,也不是所有数据的元信息的存放地点。所以,一般来说,ES 的 master 节点负载很轻,集群性能是可以近似认为随着 data 节点的扩展线性提升的。
但是,上面这句话并不是完全正确的。
ES 中有一件事情是只有 master 节点能管理的,这就是集群状态(cluster state)。
集群状态中包括以下信息:
这些信息在集群的任意节点上都存放着,你也可以通过 /_cluster/state 接口直接读取到其内容。注意这最后一项信息,之前我们已经讲过 ES 怎么通过简单地取余知道一条数据放在哪个分片里,加上现在集群状态里又记载了分片在哪个节点上,那么,整个集群里,任意节点都可以知道一条数据在哪个节点上存储了。所以,数据读写才可以发送给集群里任意节点。
至于修改,则只能由 master 节点完成!显然,集群状态里大部分内容是极少变动的,唯独有一样除外——索引的映射。因为 ES 的 schema-less 特性,我们可以任意写入 JSON 数据,所以索引中随时可能增加新的字段。这个时候,负责容纳这条数据的主分片所在的节点,会暂停写入操作,将字段的映射结果传递给 master 节点;master 节点合并这段修改到集群状态里,发送新版本的集群状态到集群的所有节点上。然后写入操作才会继续。一般来说,这个操作是在一二十毫秒内就可以完成,影响也不大。
但是也有一些情况会是例外。
在较大规模的 Elastic Stack 应用场景中,这是比较常见的一个情况。因为 Elastic Stack 建议采用日期时间作为索引的划分方式,所以定时(一般是每天),会统一产生一批新的索引。而前面已经讲过,ES 的集群状态每次更新都是阻塞式的发布到全部节点上以后,节点才能继续后续处理。
这就意味着,如果在集群负载较高的时候,批量新建新索引,可能会有一个显着的阻塞时间,无法写入任何数据。要等到全部节点同步完成集群状态以后,数据写入才能恢复。
不巧的是,中国使用的是北京时间,UTC +0800。也就是说,默认的 Elastic Stack 新建索引时间是在早上 8 点。这个时间点一般日志写入量已经上涨到一定水平了(当然,晚上 0 点的量其实也不低)。
对此,可以通过定时任务,每天在最低谷的早上三四点,提前通过 POST mapping 的方式,创建好之后几天的索引。就可以避免这个问题了。
如果你的日志是比较严重的非结构化数据,这个问题在 2.0 版本后会变得更加严重。 Elasticsearch 从 2.0 版本开始,对 mapping 更新做了重构。为了防止字段类型冲突和减少 master 定期下发全量 cluster state 导致的大流量压力,新的实现和旧实现的区别在:
也就是说,一旦你日志中字段数量较多,在新创建索引的一段时间内,可能长达几十分钟一直被反复锁死!
这是另一种常见的滥用。在使用 Elastic Stack 处理访问日志时,为了查询更方便,可能会采用 logstash-filter-kv 插件,将访问日志中的每个 URL 参数,都切分成单独的字段。比如一个 "/index.do?uid=1234567890&action=payload" 的 URL 会被转换成如下 JSON:
但是,因为集群状态是存在所有节点的内存里的,一旦 URL 参数过多,ES 节点的内存就被大量用于存储字段映射内容。这是一个极大的浪费。如果碰上 URL 参数的键内容本身一直在变动,直接撑爆 ES 内存都是有可能的!
以上是真实发生的事件,开发人员莫名的选择将一个 UUID 结果作为 key 放在 URL 参数里。直接导致 ES 集群 master 节点全部 OOM。
如果你在 ES 日志中一直看到有新的 updating mapping [logstash-2015.06.01] 字样出现的话,请郑重考虑一下自己是不是用的上如此细分的字段列表吧。
好,三秒钟过去,如果你确定一定以及肯定还要这么做,下面是一个变通的解决办法。
用 nested object 来存放 URL 参数的方法稍微复杂,但还可以接受。单从 JSON 数据层面看,新方式的数据结构如下:
没错,看起来就是一个数组。但是 JSON 数组在 ES 里是有两种处理方式的。
如果直接写入数组,ES 在实际索引过程中,会把所有内容都平铺开,变成 Arrays of Inner Objects 。整条数据实际类似这样的结构:
这种方式最大的问题是,当你采用 urlargs.key:"uid" AND urlargs.value:"0987654321" 语句意图搜索一个 uid=0987654321 的请求时,实际是整个 URL 参数中任意一处 value 为 0987654321 的,都会命中。
要想达到正确搜索的目的,需要在写入数据之前,指定 urlargs 字段的映射类型为 nested object。命令如下:
这样,数据实际是类似这样的结构:
当然,nested object 节省字段映射的优势对应的是它在使用的复杂。Query 和 Aggs 都必须使用专门的 nested query 和 nested aggs 才能正确读取到它。
nested query 语法如下:
nested aggs 语法如下:
ES 内针对不同阶段,设计有不同的缓存。以此提升数据检索时的响应性能。主要包括节点层面的 filter cache 和分片层面的 request cache。下面分别讲述。
ES 的 query DSL 在 2.0 版本之前分为 query 和 filter 两种,很多检索语法,是同时存在 query 和 filter 里的。比如最常用的 term、prefix、range 等。怎么选择是使用 query 还是 filter 成为很多用户头疼的难题。于是从 2.0 版本开始,ES 干脆合并了 filter 统一归为 query。但是具体的检索语法本身,依然有 query 和 filter 上下文的区别。ES 依靠这个上下文判断,来自动决定是否启用 filter cache。
query 跟 filter 上下文的区别,简单来说:
所以,选择也就出来了:
不过我们要怎么写,才能让 ES 正确判断呢?看下面这个请求:
在这个请求中,
需要注意的是,filter cache 是节点层面的缓存设置,每个节点上所有数据在响应请求时,是共用一个缓存空间的。当空间用满,按照 LRU 策略淘汰掉最冷的数据。
可以用 indices.cache.filter.size 配置来设置这个缓存空间的大小,默认是 JVM 堆的 10%,也可以设置一个绝对值。注意这是一个静态值,必须在 elasticsearch.yml 中提前配置。
ES 还有另一个分片层面的缓存,叫 shard request cache。5.0 之前的版本中,request cache 的用途并不大,因为 query cache 要起作用,还有几个先决条件:
以 Elastic Stack 场景来说,Kibana 里几乎所有的请求,都是有 @timestamp 作为过滤条件的,而且大多数是以 最近 N 小时/分钟 这样的选项,也就是说,页面每次刷新,发出的请求 JSON 里的时间过滤部分都是在变动的。query cache 在处理 Kibana 发出的请求时,完全无用。
而 5.0 版本的一大特性,叫 instant aggregation。解决了这个先决条件的一大阻碍。
在之前的版本,Elasticsearch 接收到请求之后,直接把请求原样转发给各分片,由各分片所在的节点自行完成请求的解析,进行实际的搜索操作。所以缓存的键是原始 JSON 串。
而 5.0 的重构后,接收到请求的节点先把请求的解析做完,发送到各节点的是统一拆分修改好的请求,这样就不再担心 JSON 串多个空格啥的了。
其次,上面说的‘拆分修改’是怎么回事呢?
比如,我们在 Kibana 里搜索一个最近 7 天( @timestamp:["now-7d" TO "now"] )的数据,ES 就可以根据按天索引的判断,知道从 6 天前到昨天这 5 个索引是肯定全覆盖的。那么这个横跨 7 天的 date range query 就变成了 5 个 match_all query 加 2 个短时间的 date_range query。
现在你的仪表盘过 5 分钟自动刷新一次,再提交上来一次最近 7 天的请求,中间这 5 个 match_all 就完全一样了,直接从 request cache 返回即可,需要重新请求的,只有两头真正在变动的 date_range 了。
注1: match_all 不用遍历倒排索引,比直接查询 @timestamp:* 要快很多。
注2:判断覆盖修改为 match_all 并不是真的按照索引名称,而是 ES 从 2.x 开始提供的 field_stats 接口可以直接获取到 @timestamp 在本索引内的 max/min 值。当然从概念上如此理解也是可以接受的。
响应结果如下:
和 filter cache 一样,request cache 的大小也是以节点级别控制的,配置项名为 indices.requests.cache.size ,其默认值为 1% 。
字段数据(fielddata),在 Lucene 中又叫 uninverted index。我们都知道,搜索引擎会使用倒排索引(inverted index)来映射单词到文档的 ID 号。而同时,为了提供对文档内容的聚合,Lucene 还可以在运行时将每个字段的单词以字典序排成另一个 uninverted index,可以大大加速计算性能。
作为一个加速性能的方式,fielddata 当然是被全部加载在内存的时候最为有效。这也是 ES 默认的运行设置。但是,内存是有限的,所以 ES 同时也需要提供对 fielddata 内存的限额方式:
Elasticsearch 在 total,fielddata,request 三个层面上都设计有 circuit breaker 以保护进程不至于发生 OOM 事件。在 fielddata 层面,其设置为:
但是相比较集群庞大的数据量,内存本身是远远不够的。为了解决这个问题,ES 引入了另一个特性,可以对精确索引的字段,指定 fielddata 的存储方式。这个配置项叫: doc_values 。
所谓 doc_values ,其实就是在 ES 将数据写入索引的时候,提前生成好 fielddata 内容,并记录到磁盘上。因为 fielddata 数据是顺序读写的,所以即使在磁盘上,通过文件系统层的缓存,也可以获得相当不错的性能。
注意:因为 doc_values 是在数据写入时即生成内容,所以,它只能应用在精准索引的字段上,因为索引进程没法知道后续会有什么分词器生成的结果。
由于在 Elastic Stack 场景中, doc_values 的使用极其频繁,到 Elasticsearch 5.0 以后,这两者的区别被彻底强化成两个不同字段类型: text 和 keyword 。
等同于过去的:
而
等同于过去的:
也就是说,以后的用户,已经不太需要在意 fielddata 的问题了。不过依然有少数情况,你会需要对分词字段做聚合统计的话,你可以在自己接受范围内,开启这个特性:
你可以看到在上面加了一段 fielddata_frequency_filter 配置,这个配置是 segment 级别的。上面示例的意思是:只有这个 segment 里的文档数量超过 500 个,而且含有该字段的文档数量占该 segment 里的文档数量比例超过 10% 时,才加载这个 segment 的 fielddata。
下面是一个可能有用的对分词字段做聚合的示例:
这个示例可以对经过了 logstash-filter-punct 插件处理的数据,获取每种 punct 类型日志的关键词和对应的代表性日志原文。其效果类似 Splunk 的事件模式功能:
[图片上传失败...(image-b0b69f-1511752650964)]
如果经过之前章节的一系列优化之后,数据确实超过了集群能承载的能力,除了拆分集群以外,最后就只剩下一个办法了:清除废旧索引。
为了更加方便的做清除数据,合并 segment,备份恢复等管理任务,Elasticsearch 在提供相关 API 的同时,另外准备了一个命令行工具,叫 curator 。curator 是 Python 程序,可以直接通过 pypi 库安装:
注意,是 elasticsearch-curator 不是 curator。PyPi 原先就有另一个项目叫这个名字
和 Elastic Stack 里其他组件一样,curator 也是被 Elastic.co 收购的原开源社区周边。收编之后同样进行了一次重构,命令行参数从单字母风格改成了长单词风格。新版本的 curator 命令可用参数如下:
Options 包括:
--host TEXT Elasticsearch host.
--url_prefix TEXT Elasticsearch http url prefix.
--port INTEGER Elasticsearch port.
--use_ssl Connect to Elasticsearch through SSL.
--http_auth TEXT Use Basic Authentication ex: user:pass
--timeout INTEGER Connection timeout in seconds.
--master-only Only operate on elected master node.
--dry-run Do not perform any changes.
--debug Debug mode
--loglevel TEXT Log level
--logfile TEXT log file
--logformat TEXT Log output format [default|logstash].
--version Show the version and exit.
--help Show this message and exit.
Commands 包括:
alias Index Aliasing
allocation Index Allocation
bloom Disable bloom filter cache
close Close indices
delete Delete indices or snapshots
open Open indices
optimize Optimize Indices
replicas Replica Count Per-shard
show Show indices or snapshots
snapshot Take snapshots of indices (Backup)
针对具体的 Command,还可以继续使用 --help 查看该子命令的帮助。比如查看 close 子命令的帮助,输入 curator close --help ,结果如下:
在使用 1.4.0 以上版本的 Elasticsearch 前提下,curator 曾经主要的一个子命令 bloom 已经不再需要使用。所以,目前最常用的三个子命令,分别是 close , delete 和 optimize ,示例如下:
这一顿任务,结果是:
logstash-mweibo-nginx-yyyy.mm.dd 索引保存最近 5 天, logstash-mweibo-client-yyyy.mm.dd 保存最近 10 天, logstash-mweibo-yyyy.mm.dd 索引保存最近 30 天;且所有七天前的 logstash-* 索引都暂时关闭不用;最后对所有非当日日志做 segment 合并优化。
profiler 是 Elasticsearch 5.0 的一个新接口。通过这个功能,可以看到一个搜索聚合请求,是如何拆分成底层的 Lucene 请求,并且显示每部分的耗时情况。
启用 profiler 的方式很简单,直接在请求里加一行即可:
可以看到其中对 query 和 aggs 部分的返回是不太一样的。
query 部分包括 collectors、rewrite 和 query 部分。对复杂 query,profiler 会拆分 query 成多个基础的 TermQuery,然后每个 TermQuery 再显示各自的分阶段耗时如下:
我们可以很明显的看到聚合统计在初始化阶段、收集阶段、构建阶段、汇总阶段分别花了多少时间,遍历了多少数据。
注意其中 rece 阶段还没实现完毕,所有都是 0。因为目前 profiler 只能在 shard 级别上做统计。
collect 阶段的耗时,有助于我们调整对应 aggs 的 collect_mode 参数选择。目前 Elasticsearch 支持 breadth_first 和 depth_first 两种方式。
initialise 阶段的耗时,有助于我们调整对应 aggs 的 execution_hint 参数选择。目前 Elasticsearch 支持 map 、 global_ordinals_low_cardinality 、 global_ordinals 和 global_ordinals_hash 四种选择。在计算离散度比较大的字段统计值时,适当调整该参数,有益于节省内存和提高计算速度。
对高离散度字段值统计性能很关注的读者,可以关注 https://github.com/elastic/elasticsearch/pull/21626 这条记录的进展。
(本文完)
文本整理自《ELKstack权威指南》
❷ python 基础教程
运算
a = 21
b = 10
c = 0
c = a + b
print "1 - c 的值为:", c
c = a - b
print "2 - c 的值为:", c
c = a * b
print "3 - c 的值为:", c
c = a / b
print "4 - c 的值为:", c
c = a % b
print "5 - c 的值为:", c
a = 2
b = 3
c = a**b
print "6 - c 的值为:", c
a = 10
b = 5
c = a//b
print "7 - c 的值为:", c
python比较
a = 21
b = 10
c = 0
if ( a == b ):
print "1 - a 等于 b"
else:
print "1 - a 不等于 b"
if ( a != b ):
print "2 - a 不等于 b"
else:
print "2 - a 等于 b"
if ( a <> b ):
print "3 - a 不等于 b"
else:
print "3 - a 等于 b"
if ( a < b ):
print "4 - a 小于 b"
else:
print "4 - a 大于等于 b"
if ( a > b ):
print "5 - a 大于 b"
else:
print "5 - a 小于等于 b"
a = 5
b = 20
if ( a <= b ):
print "6 - a 小于等于 b"
else:
print "6 - a 大于 b"
if ( b >= a ):
print "7 - b 大于等于 a"
else:
print "7 - b 小于 a"
赋值
a = 21
b = 10
c = 0
c = a + b
print "1 - c 的值为:", c
c += a
print "2 - c 的值为:", c
c *= a
print "3 - c 的值为:", c
c /= a
print "4 - c 的值为:", c
c = 2
c %= a
print "5 - c 的值为:", c
c **= a
print "6 - c 的值为:", c
c //= a
print "7 - c 的值为:", c
逻辑运算符:
a = 10
b = 20
if ( a and b ):
print "1 - 变量 a 和 b 都为 true"
else:
print "1 - 变量 a 和 b 有一个不为 true"
if ( a or b ):
print "2 - 变量 a 和 b 都为 true,或其中一个变量为 true"
else:
print "2 - 变量 a 和 b 都不为 true"
a = 0
if ( a and b ):
print "3 - 变量 a 和 b 都为 true"
else:
print "3 - 变量 a 和 b 有一个不为 true"
if ( a or b ):
print "4 - 变量 a 和 b 都为 true,或其中一个变量为 true"
else:
print "4 - 变量 a 和 b 都不为 true"
if not( a and b ):
print "5 - 变量 a 和 b 都为 false,或其中一个变量为 false"
else:
print "5 - 变量 a 和 b 都为 true"
in,not in
a = 10
b = 20
list = [1, 2, 3, 4, 5 ];
if ( a in list ):
print "1 - 变量 a 在给定的列表中 list 中"
else:
print "1 - 变量 a 不在给定的列表中 list 中"
if ( b not in list ):
print "2 - 变量 b 不在给定的列表中 list 中"
else:
print "2 - 变量 b 在给定的列表中 list 中"
a = 2
if ( a in list ):
print "3 - 变量 a 在给定的列表中 list 中"
else:
print "3 - 变量 a 不在给定的列表中 list 中"
条件
flag = False
name = 'luren'
if name == 'python': # 判断变量否为'python'
flag = True # 条件成立时设置标志为真
print 'welcome boss' # 并输出欢迎信息
else:
print name
num = 5
if num == 3: # 判断num的值
print 'boss'
elif num == 2:
print 'user'
elif num == 1:
print 'worker'
elif num < 0: # 值小于零时输出
print 'error'
else:
print 'roadman' # 条件均不成立时输出
循环语句:
count = 0
while (count < 9):
print 'The count is:', count
count = count + 1
print "Good bye!"
i = 1
while i < 10:
i += 1
if i%2 > 0: # 非双数时跳过输出
continue
print i # 输出双数2、4、6、8、10
i = 1
while 1: # 循环条件为1必定成立
print i # 输出1~10
i += 1
if i > 10: # 当i大于10时跳出循环
break
for letter in 'Python': # 第一个实例
print '当前字母 :', letter
fruits = ['banana', 'apple', 'mango']
for fruit in fruits: # 第二个实例
print '当前水果 :', fruit
print "Good bye!"
获取用户输入:raw_input
var = 1
while var == 1 : # 该条件永远为true,循环将无限执行下去
num = raw_input("Enter a number :")
print "You entered: ", num
print "Good bye!"
range,len
fruits = ['banana', 'apple', 'mango']
for index in range(len(fruits)):
print '当前水果 :', fruits[index]
print "Good bye!"
python数学函数:
abs,cell,cmp,exp,fabs,floor,log,log10,max,min,mod,pow,round,sqrt
randrange
访问字符串的值
var1 = 'Hello World!'
var2 = "Python Runoob"
print "var1[0]: ", var1[0]
print "var2[1:5]: ", var2[1:5]
转义字符
格式化输出
print "My name is %s and weight is %d kg!" % ('Zara', 21)
字符串函数:
添加元素
list = [] ## 空列表
list.append('Google') ## 使用 append() 添加元素
list.append('Runoob')
print list
删除元素
list1 = ['physics', 'chemistry', 1997, 2000]
print list1
del list1[2]
print "After deleting value at index 2 : "
print list1
列表操作
列表方法
删除字典
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'};
del dict['Name']; # 删除键是'Name'的条目
dict.clear(); # 清空词典所有条目
del dict ; # 删除词典
print "dict['Age']: ", dict['Age'];
print "dict['School']: ", dict['School'];
字典的函数:
当前时间戳:
import time
time.time()
格式化日期输出
import time
print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print time.strftime("%a %b %d %H:%M:%S %Y", time.localtime())
a = "Sat Mar 28 22:24:24 2016"
print time.mktime(time.strptime(a,"%a %b %d %H:%M:%S %Y"))
获取某个月日历:calendar
import calendar
cal = calendar.month(2016, 1)
print "以下输出2016年1月份的日历:"
print cal
当前日期和时间
import datetime
i = datetime.datetime.now()
print ("当前的日期和时间是 %s" % i)
print ("ISO格式的日期和时间是 %s" % i.isoformat() )
print ("当前的年份是 %s" %i.year)
print ("当前的月份是 %s" %i.month)
print ("当前的日期是 %s" %i.day)
print ("dd/mm/yyyy 格式是 %s/%s/%s" % (i.day, i.month, i.year) )
print ("当前小时是 %s" %i.hour)
print ("当前分钟是 %s" %i.minute)
print ("当前秒是 %s" %i.second)
不定长参数:*
lambda:匿名函数
def....
python模块搜索路径
获取用户输入
str = raw_input("请输入:")
print "你输入的内容是: ", str
input可以接收表达式
open参数
write要自己添加换行符
读取10个字符
重命名:os.rename
os.remove
os.mkdir os.chdir
os.getcwd
os.rmdir
open参数
file的方法
异常:
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
print "Error: 没有找到文件或读取文件失败"
else:
print "内容写入文件成功"
fh.close()
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "Error: 没有找到文件或读取文件失败"
用户自定义异常:
os 模块提供了非常丰富的方法用来处理文件和目录。常用的方法如下表所示:
| 序号 | 方法及描述 |
| 1 |
os.access(path, mode)
检验权限模式 |
| 2 |
os.chdir(path)
改变当前工作目录 |
| 3 |
os.chflags(path, flags)
设置路径的标记为数字标记。 |
| 4 |
os.chmod(path, mode)
更改权限 |
| 5 |
os.chown(path, uid, gid)
更改文件所有者 |
| 6 |
os.chroot(path)
改变当前进程的根目录 |
| 7 |
os.close(fd)
关闭文件描述符 fd |
| 8 |
os.closerange(fd_low, fd_high)
关闭所有文件描述符,从 fd_low (包含) 到 fd_high (不包含), 错误会忽略 |
| 9 |
os.p(fd)
复制文件描述符 fd |
| 10 |
os.p2(fd, fd2)
将一个文件描述符 fd 复制到另一个 fd2 |
| 11 |
os.fchdir(fd)
通过文件描述符改变当前工作目录 |
| 12 |
os.fchmod(fd, mode)
改变一个文件的访问权限,该文件由参数fd指定,参数mode是Unix下的文件访问权限。 |
| 13 |
os.fchown(fd, uid, gid)
修改一个文件的所有权,这个函数修改一个文件的用户ID和用户组ID,该文件由文件描述符fd指定。 |
| 14 |
os.fdatasync(fd)
强制将文件写入磁盘,该文件由文件描述符fd指定,但是不强制更新文件的状态信息。 |
| 15 |
os.fdopen(fd[, mode[, bufsize]])
通过文件描述符 fd 创建一个文件对象,并返回这个文件对象 |
| 16 |
os.fpathconf(fd, name)
返回一个打开的文件的系统配置信息。name为检索的系统配置的值,它也许是一个定义系统值的字符串,这些名字在很多标准中指定(POSIX.1, Unix 95, Unix 98, 和其它)。 |
| 17 |
os.fstat(fd)
返回文件描述符fd的状态,像stat()。 |
| 18 |
os.fstatvfs(fd)
返回包含文件描述符fd的文件的文件系统的信息,像 statvfs() |
| 19 |
os.fsync(fd)
强制将文件描述符为fd的文件写入硬盘。 |
| 20 |
os.ftruncate(fd, length)
裁剪文件描述符fd对应的文件, 所以它最大不能超过文件大小。 |
| 21 |
os.getcwd()
返回当前工作目录 |
| 22 |
os.getcw()
返回一个当前工作目录的Unicode对象 |
| 23 |
os.isatty(fd)
如果文件描述符fd是打开的,同时与tty(-like)设备相连,则返回true, 否则False。 |
| 24 |
os.lchflags(path, flags)
设置路径的标记为数字标记,类似 chflags(),但是没有软链接 |
| 25 |
os.lchmod(path, mode)
修改连接文件权限 |
| 26 |
os.lchown(path, uid, gid)
更改文件所有者,类似 chown,但是不追踪链接。 |
| 27 |
os.link(src, dst)
创建硬链接,名为参数 dst,指向参数 src |
| 28 |
os.listdir(path)
返回path指定的文件夹包含的文件或文件夹的名字的列表。 |
| 29 |
os.lseek(fd, pos, how)
设置文件描述符 fd当前位置为pos, how方式修改: SEEK_SET 或者 0 设置从文件开始的计算的pos; SEEK_CUR或者 1 则从当前位置计算; os.SEEK_END或者2则从文件尾部开始. 在unix,Windows中有效 |
| 30 |
os.lstat(path)
像stat(),但是没有软链接 |
| 31 |
os.major(device)
从原始的设备号中提取设备major号码 (使用stat中的st_dev或者st_rdev field)。 |
| 32 |
os.makedev(major, minor)
以major和minor设备号组成一个原始设备号 |
| 33 |
os.makedirs(path[, mode])
递归文件夹创建函数。像mkdir(), 但创建的所有intermediate-level文件夹需要包含子文件夹。 |
| 34 |
os.minor(device)
从原始的设备号中提取设备minor号码 (使用stat中的st_dev或者st_rdev field )。 |
| 35 |
os.mkdir(path[, mode])
以数字mode的mode创建一个名为path的文件夹.默认的 mode 是 0777 (八进制)。 |
| 36 |
os.mkfifo(path[, mode])
创建命名管道,mode 为数字,默认为 0666 (八进制) |
| 37 |
os.mknod(filename[, mode=0600, device])
创建一个名为filename文件系统节点(文件,设备特别文件或者命名pipe)。
|
| 38 |
os.open(file, flags[, mode])
打开一个文件,并且设置需要的打开选项,mode参数是可选的 |
| 39 |
os.openpty()
打开一个新的伪终端对。返回 pty 和 tty的文件描述符。 |
| 40 |
os.pathconf(path, name)
返回相关文件的系统配置信息。 |
| 41 |
os.pipe()
创建一个管道. 返回一对文件描述符(r, w) 分别为读和写 |
| 42 |
os.popen(command[, mode[, bufsize]])
从一个 command 打开一个管道 |
| 43 |
os.read(fd, n)
从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd对应文件已达到结尾, 返回一个空字符串。 |
| 44 |
os.readlink(path)
返回软链接所指向的文件 |
| 45 |
os.remove(path)
删除路径为path的文件。如果path 是一个文件夹,将抛出OSError; 查看下面的rmdir()删除一个 directory。 |
| 46 |
os.removedirs(path)
递归删除目录。 |
| 47 |
os.rename(src, dst)
重命名文件或目录,从 src 到 dst |
| 48 |
os.renames(old, new)
递归地对目录进行更名,也可以对文件进行更名。 |
| 49 |
os.rmdir(path)
删除path指定的空目录,如果目录非空,则抛出一个OSError异常。 |
| 50 |
os.stat(path)
获取path指定的路径的信息,功能等同于C API中的stat()系统调用。 |
| 51 |
os.stat_float_times([newvalue])
决定stat_result是否以float对象显示时间戳
|
| 52 |
os.statvfs(path)
获取指定路径的文件系统统计信息 |
| 53 |
os.symlink(src, dst)
创建一个软链接 |
| 54 |
os.tcgetpgrp(fd)
返回与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组 |
| 55 |
os.tcsetpgrp(fd, pg)
设置与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组为pg。 |
| 56 |
os.tempnam([dir[, prefix]])
返回唯一的路径名用于创建临时文件。 |
| 57 |
os.tmpfile()
返回一个打开的模式为(w+b)的文件对象 .这文件对象没有文件夹入口,没有文件描述符,将会自动删除。 |
| 58 |
os.tmpnam()
为创建一个临时文件返回一个唯一的路径 |
| 59 |
os.ttyname(fd)
返回一个字符串,它表示与文件描述符fd 关联的终端设备。如果fd 没有与终端设备关联,则引发一个异常。 |
| 60 |
os.unlink(path)
删除文件路径 |
| 61 |
os.utime(path, times)
返回指定的path文件的访问和修改的时间。 |
| 62 |
os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
输出在文件夹中的文件名通过在树中游走,向上或者向下。 |
| 63 |
os.write(fd, str)
写入字符串到文件描述符 fd中. 返回实际写入的字符串长度 |
❸ Python os模块
Python2.7.8(default,Jun302014,16:03:49)[MSCv.150032bit(Intel)]onwin32
Type"help","right","credits"or"license"formoreinformation.
>>>importos
>>>dir(os)
['F_OK','O_APPEND','O_BINARY','O_CREAT','O_EXCL','O_NOINHERIT','O_RANDOM',
'O_RDONLY','O_RDWR','O_SEQUENTIAL','O_SHORT_LIVED','O_TEMPORARY','O_TEXT',
'O_TRUNC','O_WRONLY','P_DETACH','P_NOWAIT','P_NOWAITO','P_OVERLAY','P_WAIT',
'R_OK','SEEK_CUR','SEEK_END','SEEK_SET','TMP_MAX','UserDict','W_OK','X_OK',
'_Environ','__all__','__builtins__','__doc__','__file__','__name__',
'__package__','__reg','_execvpe','_exists','_exit','_get_exports_list',
'_make_stat_result','_make_statvfs_result','_pickle_stat_result',
'_pickle_statvfs_result','abort','access','altsep','chdir','chmod','close',
'closerange','curdir','defpath','devnull','p','p2','environ','errno',
'error','execl','execle','execlp','execlpe','execv','execve','execvp',
'execvpe','extsep','fdopen','fstat','fsync','getcwd','getcw','getenv',
'getpid','isatty','kill','linesep','listdir','lseek','lstat','makedirs',
'mkdir','name','open','pardir','path','pathsep','pipe','popen','popen2',
'popen3','popen4','putenv','read','remove','removedirs','rename','renames',
'rmdir','sep','spawnl','spawnle','spawnv','spawnve','startfile','stat',
'stat_float_times','stat_result','statvfs_result','strerror','sys','system',
'tempnam','times','tmpfile','tmpnam','umask','unlink','unsetenv',
'urandom','utime','waitpid','walk','write']
>>>
首先,根据报错信息,可以得到os没有 getlogin()
然后,dir(os)看到确实没有
使用dir可以查看当前模块的所有属性 及方法
❹ 使用python写文件时,如何做到写入文件由于外力删掉了之后可以新创建一个同名文件并继续写入
你的试验很详细。不过这个现象在linux下可能与windows下不一样。 通常改名或者是删除后文件就失效了。写入操作也是无效的。
为了防止别人修改你的文件,通常在写入时,会加上一个锁。使用操作系统特有的open方法才可以加锁。
可以使用portalocker,filelock 也可以使用posixfile,
os.open能不能成呢?按理可以。不过C语言里使用fopen没有这个功能,不过使用fcntl里的open可以。
你加了锁后,别人就不能写。文件处于占用状态。
另外操作系统都有一种文件监控机制的消息通知。具体忘记了。在unix与windows都有这个功能。当别人程序修改了某个文件,你会立刻得到消息通知。
补充一些教程。os.open还是可以用的。
os.open(file, flags[, mode]);
Parameters
file -- File name to be opened.
flags -- This is the following constants are options for the flags. They can be combined using the bitwise OR operator |. Some of them are not available on all platforms.
os.O_RDONLY: open for reading only
os.O_WRONLY: open for writing only
os.O_RDWR : open for reading and writing
os.O_NONBLOCK: do not block on open
os.O_APPEND: append on each write
os.O_CREAT: create file if it does not exist
os.O_TRUNC: truncate size to 0
os.O_EXCL: error if create and file exists
os.O_SHLOCK: atomically obtain a shared lock
os.O_EXLOCK: atomically obtain an exclusive lock
os.O_DIRECT: eliminate or rece cache effects
os.O_FSYNC : synchronous writes
os.O_NOFOLLOW: do not follow symlinks
mode -- This work in similar way as it works for chmod() method.
❺ C#数据写成txt文本数据丢失问题。代码如下。本机测试正常。XP系统。在服务器会偶尔丢失几条数据。03系统
难道是和我刚总结的,别人写python时遇到的问题类似?
别人的那个问题,是需要保证关闭文件时,如何确保文件写入的数据真正已生效了。
python中是
file.Flush()
然后调用
os.fsync()
去强制操作系统写回的。
具体帖子标题为:
【整理】Python中写完文件再关闭后,不知道需要sleep多长时间才是安全的(才能保持数据真正写入了)
不知道你这里,是不是也要调用MS下相关的_commit()方面的函数,才可以避免数据丢失的。
注:
python的相关解释:
os.fsync(fd)
Force write of file with filedescriptor fd to disk. On Unix, this calls the native fsync() function; on Windows, the MS _commit() function.
(此处不给贴地址,想要看帖子的全部内容的话,请自己google搜帖子标题,即可找到帖子地址)
❻ python如何查看一个模块下所属的函数如下图所示
使用dir函数
>>> import os
>>> dir(os)
['F_OK', 'MutableMapping', 'O_APPEND', 'O_BINARY', 'O_CREAT', 'O_EXCL', 'O_NOINHERIT', 'O_RANDOM', 'O_RDONLY', 'O_RDWR', 'O_SEQUENTIAL', 'O_SHORT_LIVED', 'O_TEMPORARY', 'O_TEXT', 'O_TRUNC', 'O_WRONLY', 'P_DETACH', 'P_NOWAIT', 'P_NOWAITO', 'P_OVERLAY', 'P_WAIT', 'R_OK', 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'W_OK', 'X_OK', '_Environ', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_execvpe', '_exists', '_exit', '_get_exports_list', '_putenv', '_unsetenv', '_wrap_close', 'abort', 'access', 'altsep', 'chdir', 'chmod', 'close', 'closerange', 'cpu_count', 'curdir', 'defpath', 'device_encoding', 'devnull', 'p', 'p2', 'environ', 'errno', 'error', 'execl', 'execle', 'execlp', 'execlpe', 'execv', 'execve', 'execvp', 'execvpe', 'extsep', 'fdopen', 'fsdecode', 'fsencode', 'fstat', 'fsync', 'ftruncate', 'get_exec_path', 'get_handle_inheritable', 'get_inheritable', 'get_terminal_size', 'getcwd', 'getcwdb', 'getenv', 'getlogin', 'getpid', 'getppid', 'isatty', 'kill', 'linesep', 'link', 'listdir', 'lseek', 'lstat', 'makedirs', 'mkdir', 'name', 'open', 'pardir', 'path', 'pathsep', 'pipe', 'popen', 'putenv', 'read', 'readlink', 'remove', 'removedirs', 'rename', 'renames', 'replace', 'rmdir', 'scandir', 'sep', 'set_handle_inheritable', 'set_inheritable', 'spawnl', 'spawnle', 'spawnv', 'spawnve', 'st', 'startfile', 'stat', 'stat_float_times', 'stat_result', 'statvfs_result', 'strerror', 'supports_bytes_environ', 'supports_dir_fd', 'supports_effective_ids', 'supports_fd', 'supports_follow_symlinks', 'symlink', 'sys', 'system', 'terminal_size', 'times', 'times_result', 'truncate', 'umask', 'uname_result', 'unlink', 'urandom', 'utime', 'waitpid', 'walk', 'write']
>>>
❼ python,如何在shell下查看一个模块有哪些方法
如:
>>>importos
>>>dir(os)
['F_OK','O_APPEND','O_BINARY','O_CREAT','O_EXCL','O_NOINHERIT','O_RANDOM','O_RDONLY','O_RDWR','O_SEQUENTIAL','O_SHORT_LIVED','O_TEMPORARY','O_TEXT','O_TRUN
OWAIT','P_NOWAITO','P_OVERLAY','P_WAIT','R_OK','SEEK_CUR','SEEK_END','SEEK_SET','TMP_MAX','UserDict','W_OK','X_OK','_Environ','__all__','__builtins__','_
'__package__','__reg','_execvpe','_exists','_exit','_get_exports_list','_make_stat_result','_make_statvfs_result','_pickle_stat_result','_pickle_statvfs_r
ep','chdir','chmod','close','closerange','curdir','defpath','devnull','p','p2','environ','errno','error','execl','execle','execlp','execlpe','execv
,'extsep','fdopen','fstat','fsync','getcwd','getcw','getenv','getpid','isatty','kill','linesep','listdir','lseek','lstat','makedirs','mkdir','name',
ep','pipe','popen','popen2','popen3','popen4','putenv','read','remove','removedirs','rename','renames','rmdir','sep','spawnl','spawnle','spawnv','spawn
float_times','stat_result','statvfs_result','strerror','sys','system','tempnam','times','tmpfile','tmpnam','umask','unlink','unsetenv','urandom','utime',
>>>
❽ 如何安装和使用Beanstalkd工作队列
使用aptitude安装:
下载并安装Beanstalkd运行以下命令:
aptitude install -y beanstalkd
编辑默认配置文件让随着系统启动
vim /etc/default/beanstalkd
打开文件后,向下滚动并找到底部线#开始= yes。将其更改为:
START=yes
下面介绍源码安装
我们需要从源代码安装过程的一个关键工具- Git。
运行以下获取Git在你系统上:
aptitude install -y git
下载必要的开发工具软件包:
aptitude install -y build-essential
使用Git克隆(下载)官方库:
git clone https://github.com/kr/beanstalkd
进入到下载目录:
cd beanstalkd
从源代码构建应用程序:
make
安装:
make install
再介绍一下centos下源码安装:
下载地址:
wget http://cloud.github.com/downloads/kr/beanstalkd/beanstalkd-1.4.6.tar.gz
解压:
tar xzf beanstalkd-1.4.6.tar.gz
cd beanstalkd-1.4.6
/configure
make
make install
默认安装路径 :/usr/local/bin/
查看版本:
/usr/local/bin/beanstalkd -v
1.4.6
再附加一个启动脚本,从Fedora下挖来的 startup 脚本:
#!/bin/sh
#
# beanstalkd - a simple, fast workqueue service
#
# chkconfig: - 57 47
# description: a simple, fast workqueue service
# processname: beanstalkd
# config: /etc/sysconfig/beanstalkd
#
### BEGIN INIT INFO
# Provides: beanstalkd
# Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs
# Default-Stop: 0 1 2 6
# Short-Description: start and stop beanstalkd
# Description: a simple, fast work-queue service
### END INIT INFO
# Source function library.
/etc/rc.d/init.d/functions
# Source networking configuration.
/etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit
exec="/usr/local/bin/beanstalkd"
prog=$(basename $exec)
# default options, overruled by items in sysconfig
BEANSTALKD_ADDR=127.0.0.1
BEANSTALKD_PORT=11300
BEANSTALKD_USER=beanstalkd
[ -e /etc/sysconfig/beanstalkd ] && . /etc/sysconfig/beanstalkd
lockfile=/var/lock/subsys/beanstalkd
start() {
[ -x $exec ] || exit 5
echo -n $"Starting $prog: "
# if not running, start it up here, usually something like "daemon $exec"
options="-l ${BEANSTALKD_ADDR} -p ${BEANSTALKD_PORT} -u ${BEANSTALKD_USER}"
if [ "${BEANSTALKD_MAX_JOB_SIZE}" != "" ]; then
options="${options} -z ${BEANSTALKD_MAX_JOB_SIZE}"
fi
if [ "${BEANSTALKD_BINLOG_DIR}" != "" ]; then
if [ ! -d "${BEANSTALKD_BINLOG_DIR}" ]; then
echo "Creating binlog directory (${BEANSTALKD_BINLOG_DIR})"
mkdir -p ${BEANSTALKD_BINLOG_DIR} && chown ${BEANSTALKD_USER}:${BEANSTALKD_USER} ${BEANSTALKD_BINLOG_DIR}
fi
options="${options} -b ${BEANSTALKD_BINLOG_DIR}"
if [ "${BEANSTALKD_BINLOG_FSYNC_PERIOD}" != "" ]; then
options="${options} -f ${BEANSTALKD_BINLOG_FSYNC_PERIOD}"
else
options="${options} -F"
fi
if [ "${BEANSTALKD_BINLOG_SIZE}" != "" ]; then
options="${options} -s ${BEANSTALKD_BINLOG_SIZE}"
fi
fi
daemon $exec -d $options
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
# stop it here, often "killproc $prog"
killproc $prog -INT
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
stop
start
}
reload() {
restart
}
force_reload() {
restart
}
rh_status() {
# run checks to determine if the service is running or use generic status
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
restart
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
exit 2
esac
exit $?
使用Beanstalkd
在安装之后,您就可以开始使用Beanstalkd服务器。以下是运行守护进程的选项:
-b DIR wal directory
-f MS fsync at most once every MS milliseconds (use -f0 for "always fsync")
-F never fsync (default)
-l ADDR listen on address (default is 0.0.0.0)
-p PORT listen on port (default is 11300)
-u USER become user and group
-z BYTES set the maximum job size in bytes (default is 65535)
-s BYTES set the size of each wal file (default is 10485760)
(will be rounded up to a multiple of 512 bytes)
-c compact the binlog (default)
-n do not compact the binlog
-v show version information
-V increase verbosity
-h show this help
使用例子:
# Usage: beanstalkd -l [ip address] -p [port #]
# For local only access:
beanstalkd -l 127.0.0.1 -p 11301 &
管理服务:
如果安装包管理器(i.e. aptitude),你将能够管理Beanstalkd作为服务守护进程。
# To start the service:
service beanstalkd start
# To stop the service:
service beanstalkd stop
# To restart the service:
service beanstalkd restart
# To check the status:
service beanstalkd status
获得Beanstalkd客户端库
Beanstalkd配有一长串的支持客户端库来处理许多不同的应用程序部署。这个列表的支持语言和框架,包括:
●Python
●Django
●Go
●Java
●Node.js
●Perl
●PHP
●Ruby
●and more.
查看完整列表支持,寻找你最喜欢的语言和安装说明,查看客户端库页面Beanstalkd Github上。
使用Beanstalkd
在本节之前,完成这篇文章,让我们快速Beanstalkd的基本用法。在我们的示例中,我们将使用Python语言和Beanstald Python bindings ——beanstalkc。
安装beanstalkc,运行以下命令:
pip install pyyaml
pip install beanstalkc
基本操作
在所有Python文件你想处理Beanstalkd时,需要导入beanstalkc并连接:
import beanstalkc
# Connection
beanstalk = beanstalkc.Connection(host='localhost', port=11301)
To enqueue a job:
beanstalk.put('job_one')
To receive a job:
job = beanstalk.reserve()
# job.body == 'job_one'
To delete a job after processing it:
job.delete()
To use a specific tube (i.e. queue / list):
beanstalk.use('tube_a')
To list all available tubes:
beanstalk.tubes()
# ['default', 'tube_a']
Final example (nano btc_ex.py):
import beanstalkc
# Connect
beanstalk = beanstalkc.Connection(host='localhost', port=11301)
# See all tubes:
beanstalk.tubes()
# Switch to the default (tube):
beanstalk.use('default')
# To enqueue a job:
beanstalk.put('job_one')
# To receive a job:
job = beanstalk.reserve()
# Work with the job:
print job.body
# Delete the job:
job.delete()
当您运行上面的脚本时,您应该会看到工作的主体被打印:
python btc_ex.py
# job_one