通过文件名查找文件
你需要写一个涉及到文件查找操作的脚本,比如对日志归档文件的重命名工具, 你不想在Python脚本中调用shell,或者你要实现一些shell不能做的功能。
查找文件,可使用os.walk()函数,传一个顶级目录名给它。 下面是一个例子,查找特定的文件名并答应所有符合条件的文件全路径:
#!/usr/bin/env python3.3
import os
def findfile(start, name):
for relpath, dirs, files in os.walk(start):
if name in files:
full_path = os.path.join(start, relpath, name)
print(os.path.normpath(os.path.abspath(full_path)))
if __name__ == '__main__':
findfile(sys.argv[1], sys.argv[2])
保存脚本为文件findfile.py,然后在命令行中执行它。 指定初始查找目录以及名字作为位置参数,如下:
os.walk() 方法为我们遍历目录树, 每次进入一个目录,它会返回一个三元组,包含相对于查找目录的相对路径,一个该目录下的目录名列表, 以及那个目录下面的文件名列表。
对于每个元组,只需检测一下目标文件名是否在文件列表中。如果是就使用 os.path.join() 合并路径。 为了避免奇怪的路径名比如 ././foo//bar ,使用了另外两个函数来修正结果。 第一个是 os.path.abspath() ,它接受一个路径,可能是相对路径,最后返回绝对路径。 第二个是 os.path.normpath() ,用来返回正常路径,可以解决双斜杆、对目录的多重引用的问题等。
尽管这个脚本相对于UNIX平台上面的很多查找来讲要简单很多,它还有跨平台的优势。 并且,还能很轻松的加入其他的功能。 我们再演示一个例子,下面的函数打印所有最近被修改过的文件:
#!/usr/bin/env python3.3
import os
import time
def modified_within(top, seconds):
now = time.time()
for path, dirs, files in os.walk(top):
for name in files:
fullpath = os.path.join(path, name)
if os.path.exists(fullpath):
mtime = os.path.getmtime(fullpath)
if mtime > (now - seconds):
print(fullpath)
if __name__ == '__main__':
import sys
if len(sys.argv) != 3:
print('Usage:{}dir seconds'.format(sys.argv[0]))
raise SystemExit(1)
modified_within(sys.argv[1], float(sys.argv[2]))
读取配置文件
configparser模块能被用来读取配置文件。例如,假设你有如下的配置文件:
; config.ini
; Sample configuration file
[installation]
library=%(prefix)s/lib
include=%(prefix)s/include
bin=%(prefix)s/bin
prefix=/usr/local
# Setting related to debug configuration
[debug]
log_errors=true
show_warnings=False
[server]
port: 8080
nworkers: 32
pid-file=/tmp/spam.pid
root=/www/root
signature:
=================================
Brought to you by the Python Cookbook
=================================
下面是一个读取和提取其中值的例子:
>>> from configparser import ConfigParser
>>> cfg = ConfigParser()
>>> cfg.read('config.ini')
['config.ini']
>>> cfg.sections()
['installation', 'debug', 'server']
>>> cfg.get('installation','library')
'/usr/local/lib'
>>> cfg.getboolean('debug','log_errors')
True
>>> cfg.getint('server','port')
8080
>>> cfg.getint('server','nworkers')
32
>>> print(cfg.get('server','signature'))
\=================================
Brought to you by the Python Cookbook
\=================================
>>>
如果有需要,你还能修改配置并使用cfg.write()方法将其写回到文件中。例如:
>>> cfg.set('server','port','9000')
>>> cfg.set('debug','log_errors','False')
>>> import sys
>>> cfg.write(sys.stdout)
配置文件作为一种可读性很好的格式,非常适用于存储程序中的配置数据。 在每个配置文件中,配置数据会被分组(比如例子中的“installation”、 “debug” 和 “server”)。 每个分组在其中指定对应的各个变量值。
对于可实现同样功能的配置文件和Python源文件是有很大的不同的。 首先,配置文件的语法要更自由些,下面的赋值语句是等效的:
prefix=/usr/local
prefix: /usr/local
或许配置文件和Python代码最大的不同在于,它并不是从上而下的顺序执行。 文件是安装一个整体被读取的。如果碰到了变量替换,它实际上已经被替换完成了。 例如,在下面这个配置中,prefix变量在使用它的变量之前或之后定义都是可以的
ConfigParser有个容易被忽视的特性是它能一次读取多个配置文件然后合并成一个配置。 例如,假设一个用户像下面这样构造了他们的配置文件:
; ~/.config.ini
[installation]
prefix=/Users/beazley/test
[debug]
log_errors=False
仔细观察下 prefix 变量是怎样覆盖其他相关变量的,比如 library 的设定值。 产生这种结果的原因是变量的改写采取的是后发制人策略,以最后一个为准。 你可以像下面这样做试验:
>>> cfg.get('installation','library')
'/Users/beazley/test/lib'
>>> cfg.set('installation','prefix','/tmp/dir')
>>> cfg.get('installation','library')
'/tmp/dir/lib'
>>>
给脚本增加日志功能
打印日志最简单方式是使用logging模块。例如:
import logging
def main():
# Configure the logging system
logging.basicConfig(
filename='app.log',
level=logging.ERROR
)
# Variables (to make the calls that follow work)
hostname = 'www.python.org'
item = 'spam'
filename = 'data.csv'
mode = 'r'
# Example logging calls (insert into your program)
logging.critical('Host%sunknown', hostname)
logging.error("Couldn't find%r", item)
logging.warning('Feature is deprecated')
logging.info('Opening file%r, mode=%r', filename, mode)
logging.debug('Got here')
if __name__ == '__main__':
main()
上面五个日志调用(critical(), error(), warning(), info(), debug())以降序方式表示不同的严重级别。basicConfig() 的 level 参数是一个过滤器。 所有级别低于此级别的日志消息都会被忽略掉。 每个logging操作的参数是一个消息字符串,后面再跟一个或多个参数。 构造最终的日志消息的时候我们使用了%操作符来格式化消息字符串。
运行这个程序后,在文件 app.log 中的内容应该是下面这样:
CRITICAL:root:Host www.python.org unknown
ERROR:root:Could not find 'spam'
如果你想改变输出等级,你可以修改 basicConfig() 调用中的参数。例如:
logging.basicConfig(
filename='app.log',
level=logging.WARNING,
format='%(levelname)s:%(asctime)s:%(message)s')
最后输出变成如下:
CRITICAL:2012-11-20 12:27:13,595:Host www.python.org unknown
ERROR:2012-11-20 12:27:13,595:Could not find 'spam'
WARNING:2012-11-20 12:27:13,595:Feature is deprecated
上面的日志配置都是硬编码到程序中的。如果你想使用配置文件, 可以像下面这样修改basicConfig()调用:
import logging
import logging.config
def main():
# Configure the logging system
logging.config.fileConfig('logconfig.ini')
尽管对于 logging 模块而已有很多更高级的配置选项, 不过这里的方案对于简单的程序和脚本已经足够了。 只想在调用日志操作前先执行下basicConfig()函数方法,你的程序就能产生日志输出了。
如果你想要你的日志消息写到标准错误中,而不是日志文件中,调用 basicConfig() 时不传文件名参数即可。例如:
logging.basicConfig(level=logging.INFO)
参考书目:
《Python CookBook》作者:【美】 David Beazley, Brian K. Jones
Python 3.0.0文档:Python Cookbook 3rd Edition Documentationpython3-cookbook.readthedocs.io