专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »Python » python:如何编写Unix管道风格的Python代码 »正文

python:如何编写Unix管道风格的Python代码

来源: 发布时间:星期二, 2009年2月3日 浏览:25次 评论:0
  看过 SICP 就知道其实式编程中map, filter 都可以看作是管道思想应用但其实管道思想不仅可以在式语言中使用只要语言支持定义有能够存放组数据数据结构就可以使用管道思想

  个日志处理任务

  应用场景如下:

  ◆ 某个目录及子目录下有些 web 服务器日志文件日志文件名以 access-log 开头

  ◆ 日志格式如下

81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238
81.107.39.38 - ... "GET /ply HTTP/1.1" 304 -
  其中最后列数字为发送字节数若为 ‘-’ 则表示没有发送数据

  ◆目标是算出总共发送了多少字节数据实际上也就是要把日志记录最后列数值加起来

  我不直接展示如何用 Unix 管道风格来处理这个问题而是先给出些“不那么好”代码指出它们问题最后再展示管道风格代码并介绍如何使用 generator 来避免效率上问题

  问题并不复杂几个 for 循环就能搞定:

sum = 0
for path, dirlist, filelist in os.walk(top):
  for name in fnmatch.filter(filelist, "access-log*"):
    # 对子目录中每个日志文件进行处理
    with open(name) as f:
      for line in f:
         line[-1] '-':
          continue
        :
          sum (line.rsplit(None, 1)[1])
  利用 os.walk 这个问题解决起来很方便由此也可以看出 python for 语句做遍历是多么方便不需要额外控制循环次数变量省去了设置值、更新、判断循环结束条件等工作相比 C/C/Java 这样语言真是太方便了看起来切都很美好

  然而设想以后有了新统计任务比如:

  1.统计某个特定页面访问次数

  2.处理另外些日志文件日志文件名字以 error-log 开头

  完成这些任务直接拿上面代码过来改改就可以了文件名 pattern 改处理每个文件代码改其实每次任务处理中找到特定名字为特定 pattern 文件代码是直接修改的前代码其实就引入了重复

  如果重复代码量很大我们很自然会注意到然而 python for 循环实在太方便了像这里找文件代码共就两行哪怕重写遍也不会觉得太麻烦for 循环方便使得我们会忽略这样简单代码重复然而再如何方便好用for 循环无法重用只有把它放到中才能进行重用

  (先考虑下是你会如何避免这里代码重复下面马上出现代码并不好是“误导性”代码我会在的后再给出“更好”代码)

  因此我们把上面代码中不变部分提取成个通用可变部分以参数形式传入得到下面代码:

def generic_process(topdir, filepat, processfunc):
  for path, dirlist, filelist in os.walk(top):
    for name in fnmatch.filter(filelist, filepat):
      with open(name) f:
        processfunc(f)
sum = 0
# 很遗憾python 对 closure 中变量不能进行赋值操作
# 因此这里只能使用全局变量
def add_count(f):
  global sum
  for line in f:
     line[-1] '-':
      continue
    :
      sum (line.rsplit(None, 1)[1])
generic_process('logdir', 'access-log*', add_count)
  看起来不变和可变部分分开了然而 generic_process 设计并不好它除了寻找文件以外还了日志文件处理因此在其他任务中很可能就无法使用另外 add_count 参数必须是 file like object因此测试时不能简单直接使用

  管道风格

  下面考虑用 Unix 工具和管道我们会如何完成这个任务:

find logdir -name "access-log*" |
xargs cat |
grep '[^-]$' |
awk '{ total $NF } END { pr total }'


  find 根据文件名 pattern 找到文件cat 把所有文件内容合并输出到 stdoutgrep 从 stdin 读入过滤掉行末为 ‘-’ awk 提取每行最后将数值相加最后打印出结果(省掉 cat 是可以但这样来 grep 就需要直接读文件而不是只从标准输入读)

  我们可以在 python 代码中模拟这些工具Unix 工具通过文本来传递结果在 python 中可以使用 list

def find(topdir, filepat, processfunc):
  files =
  for path, dirlist, filelist in os.walk(top):
    for name in fnmatch.filter(filelist, filepat):
      files.append(name)
   files
def cat(files):
  lines =
  for file in files:
    with open(file) as f:
      for line in f:
        lines.append(line)
   lines
def grep(pattern, lines):
  result =
  import re
  pat = re.compile(pattern)
  for line in lines:
     pat.search(line):
      result.append(line)
  resurn result
lines = grep('[^-]$', cat(find('logdir', 'access-log*')))
col = (line.rsplit(None, 1)[1] for line in lines)
pr sum((c) for c in col)




  有了 find, cat, grep 这 3个只需要连续就可以像 Unix 管道样将这些组合起来数据在管道中变化如下图(简洁起见过滤器直接标在箭头上 ):



  看起来现在代码行数比最初直接用 for 循环代码要多但现在代码就像 Unix 那些小工具个都更加可能被用到我们可以把更多常用 Unix 工具用 Python 来模拟从而在 Python 代码中以 Unix 管道风格来编写代码



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: