argparse 模块

一个可执行文件或脚本都可以接受参数

$ ls -l /etc
/etc 是位置参数
-l 是短选项

argparse 模块就提供了参数分析的功能

参数分类

  • 位置参数:参数放在那里,就要对应一个参数位置。例如/etc/ 就是对应一个参数的位置
  • 选项参数:必须通过前面是-的短选项或–的长选项,然后后面的才算该选项的参数,当然选项后面也可以没有参数

上例子中,/etc/对应的就是位置参数,-l对应的是选项参数

基本解析

import argparse
parser = argparse.ArgumentParser() #获得一个参数解析器
args = parser.parse_args() #分析参数
parser.print_help() #打印帮助

运行结果

usage: parser-test.py [-h]

optional arguments:
  -h, --help  show this help message and exit

Process finished with exit code 0

argparse不仅仅做了参数的定义和解析,还自动帮助生成了帮助信息。尤其是usage,可以看到现在定义的参数是否是自己想要的

解析器的参数

参数名称 说明
prog 程序的名字,缺省使用sys.argv[0]的basename
add_help 自动为解析器增加-h 和–help 选项,默认为True
description 为程序功能添加描述

parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')

usage: ls [-h]

list directory contents

optional arguments:
  -h, --help  show this help message and exit

位置参数解析

ls基本功能应该解决目录内容的打印

打印的时候应该指定目录路径,需要位置参数

import argparse
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path')
args = parser.parse_args()  # 分析参数
parser.print_help()  # 打印帮助
# 运行结果,出现了错误,提示需要输入path对应的位置参数
usage: ls [-h] path
ls: error: the following arguments are required: path

程序定义为:

ls [-h] path

-h为帮助选项,可有可无

path为位置参数,必须提供

传参

parse_args(args=None, namespace=None)

args 参数列表,一个可迭代对象。内部会把可迭代对象转换成list。如果None则使用命令行传入参数,非None则使用args参数的可迭代对象

import argparse
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path') # 位置参数
args = parser.parse_args(('/etc',))  # 分析参数,同时传入可迭代的参数
print(args, args.path) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
#运行结果
Namespace(path='/etc') /etc
usage: ls [-h] path
list directory contents
positional arguments:
  path
optional arguments:
  -h, --help  show this help message and exit

Namespace(path=’/etc’) 里面的path 参数存储在了一个Namespace对象内的属性上,可以通过Namespace对象属性来访问,例如args.path

非必须位置参数

上面的代码必须输入位置参数,否则会报错

usage: ls [-h] path
ls: error: the following arguments are required: path

但有时候,ls命令不输入任何路径的话就表示列出当前目录的文件列表

import argparse
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help="path help") # 位置参数,可有可无,缺省值,帮助
args = parser.parse_args()  # 分析参数,同时传入可迭代的参数
print(args) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
# 运行结果
Namespace(path='.')
usage: ls [-h] [path]
list directory contents
positional arguments:
  path        path help
optional arguments:
  -h, --help  show this help message and exit

可以看出path也变成可选的位置参数,没有提供就使用默认值 . 点号 表示当前路径

  • help表示帮助文档中这个参数的描述
  • nargs表示这个参数接受结果参数
    • ?表示可有可无
    • +表示至少一个
    • * 可以任意个
    • 数字表示必须是指定数目个
  • default 表示如果不提供该参数,就使用这个值。一般和?、* 配合,因为他们都可以不提供位置参数,不提供就用缺省值

选项参数

-l 的实现

parser.add_argument('-l') 就增加了选项参数,参数定义为

ls [-h][-l L ] [path] 和我们要的形式有出入,期望的是[-l],如何解决?

nargs能否解决?

parser.add_argument(‘-l’,nargs=’?’)

ls [-h] [-l [L]] [path]

-l 还不是可选参数

那么,直接吧nargs=0,意思就是让这个选项接受0个参数,如下:

parser.add_argument('-l',nargs=0)

结果,抛出异常

raise ValueError(‘nargs for store actions must be > 0; if you ‘ValueError: nargs for store actions must be > 0; if you have nothing to store, actions such as store true or store const may be more appropriate

看来nargs是控制位置参数和选项参数的,不能控制选项参数的参数

这个问题使用action解决

parser.add_argument('-l',action="store_true")

看到命令定义变成了 ls [-h] [-l] [path]

提供-l 选项,例如

ls -l 得到Namespace(l=True, path=’.’),提供-l值是True

ls 得到Namespace(l=False, path=’.’),未提供-l值是False

这样同True、False来判断用户是否提供了该选项

parser.add_argument('-l', action='store_const', const = 20)
# 提供-l选项,属性值为20;否则,对应值为None

-a 的实现

parser.add_argument('-a', '--all', action='store_true') # 长短选项同时给

属性名称

参数都是Namespace 对象的属性,如果想指定这些属性名,可以使用dest

parser.add_argument('-l', action='store_true', dest='longfmt')
args = parser.parse_args(['/etc'])
print(args)
#输出
Namespace(longfmt=False, path='/etc')
import argparse
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list 
directory contents')
parser.add_argument('path', nargs='?', default='.', help="directory") # 位置参数,可有可无,缺省值,帮助
parser.add_argument('-l', action='store_true', dest='longfmt', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with .')
args = parser.parse_args()  # 分析参数,同时传入可迭代的参数
print(args) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
# 运行结果
Namespace(all=False, longfmt=False, path='.')
usage: ls [-h] [-l] [-a] [path]
list directory contents
positional arguments:
  path        directory
optional arguments:
  -h, --help  show this help message and exit
  -l          use a long listing format
  -a, --all   show all files, do not ignore entries starting with .
# parser.parse_args('-l -a /tmp'.split())运行结果
Namespace(all=True, longfmt=True, path='/tmp')

练习:

ls业务功能的实现

实现ls命令功能,实现-l、-a和–all、-h选项

  • 实现显示路径下的文件列表
  • -a 和–all显示包含.开头的文件
  • -l 详细列表显示
  • -h 和 -l配合,人性化显示文件大小,例如1K,1G等
  • 类型字符
    • c字符
    • d目录
    • 普通文件
    • l软连接
    • b块设备
    • s socket文件
    • p pipe文件,即FIFO
参看Linux、Unix命令ls -lah
-rw-rw-r--   1     python python     5     Oct 25 00:07       test4
mode       硬链接 属主   属组       字节   时间               文件名
按照文件名排序输出,可以和ls的顺序不一样,但要求文件名排序
要求详细列表显示时,时间可以按照“年--日 时::秒” 格式显示,例如2015-06-17 17:05:00

实现:

import argparse
from pathlib import Path
from datetime import datetime
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help="directory") # 位置
参数,可有可无,缺省值,帮助
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with .')
args = parser.parse_args()  # 分析参数,同时传入可迭代的参数
print(args) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
def listdir(path, all=False):
    """列出本目录文件"""
    p = Path(path)
    # for f in p.iterdir():
    # if not all and f.name.startswith('.'): # 不显示隐藏文件
    # continue
    # yield f.name
    #yield from filter(lambda f:not(not all and f.name.startswith('.')), p.iterdir())
    #yield from filter(lambda f: all or not f.name.startswith('.'), p.iterdir())
    yield from map(lambda x: x.name, filter(lambda f:all or not f.name.startswith('.'), p.iterdir())) # 路径对象转成路径字符串
print(*(listdir(args.path)))
# -rw-rw-r-- 1 python python     5 Oct 25 00:07 test4
def listdirdetail(path, all=False):
    """详细列出目录"""
    p = Path(path)
    for f in p.iterdir():
        if not all and f.name.startswith('.'): # 不显示隐藏文件
           continue
# mode 硬链接 属主 属组 字节 时间 name
        stat = f.stat()
        mtime = datetime.fromtimestamp(stat.st_mtime).strftime('%Y %m %d %H:%M:%S')
        yield (stat.st_mode, stat.st_nlink, stat.st_uid, stat.st_gid, st.st_size, mtime, f.name)
print(*(listdirdetail(args.path)))

mode是整数,八进制描述的权限,最终显示为rwx的格式。

import stat
mode = stat.filemode(st.st_mode) # 利用stat的函数

合并列出文件函数

listdirdetail 和listdir几乎一样,重复太多,合并

import argparse
from pathlib import Path
from datetime import datetime
import stat
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help="directory") # 位置参数,可有可无,缺省值,帮助
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with .')
args = parser.parse_args()  # 分析参数,同时传入可迭代的参数
print(args) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
def listdir(path, all=False, detail=False):
    """详细列出本目录"""
    p = Path(path)
    for i in p.iterdir():
        if not all and i.name.startswith('.'): # 不显示隐藏文件
            continue
        if not detail:
            yield (i.name,)
        else:
            # -rw-rw-r-- 1     python python     5 Oct 25 00:07 test4
            # mode       硬链接 属主   属组       字节 时间         name
            st = i.stat()
            mode = stat.filemode(st.st_mode)
            mtime = datetime.fromtimestamp(st.st_atime).strftime('%Y/%m/%d %H:%M:%S')
            yield (mode, st.st_nlink, st.st_uid, st.st_gid, st.st_size, mtime, i.name)
print(*listdir(args.path, detail=True), sep='\n')

排序

实现-r 选项,按文件名倒排

import argparse
from pathlib import Path
from datetime import datetime
import stat
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help="directory") # 位置参数,可有可无,缺省值,帮助
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with .')
parser.add_argument('-r', '--reverse', action='store_true', help="reverse order while sorting")
args = parser.parse_args()  # 分析参数,同时传入可迭代的参数
print(args) # 打印名词空间中收集的参数
parser.print_help()  # 打印帮助
def listdir(path, all=False, detail=False, reverse=False):
    """详细列出本目录"""
    def _listdir(path, all, detail, reverse):
        p = Path(path)
        for i in p.iterdir():
            if not all and i.name.startswith('.'): # 不显示隐藏文件
                continue
            if not detail:
                yield (i.name,)
            else:
                # -rw-rw-r-- 1     python python     5 Oct 25 00:07 test4
                # mode       硬链接 属主   属组       字节 时间         name
                st = i.stat()
                mode = stat.filemode(st.st_mode)
                mtime = datetime.fromtimestamp(st.st_atime).strftime('%Y/%m/%d %H:%M:%S')
                yield (mode, st.st_nlink, st.st_uid, st.st_gid, st.st_size, mtime, i.name)
    return sorted(_listdir(path, all, detail, reverse), key=lambda x: x[len(x)-1], reverse=reverse)
print(*listdir(args.path, detail=True, reverse=True), sep='\n')

-h 的实现

-h ,–human-readable ,如果-l存在,-h有效。

1、增加选项参数

parser = argparse.ArgumentParser(prog='ls', description='list directory contents', add_help=False)
parser.add_argument('-h', '--human-readable', action='store_true', help='with -l, print sizes in human readable format')

2、增加一个函数,能够解决单位转换的

def _gethuman(size: int):
    units = " KMGTP"
    depth = 0
    while size > 1000 and depth < len(units) - 1:
     # 当前size大于1000,且depth不是最后一个进入循环
         depth += 1
         size //= 1000
    return "{}{}".format(size, units[depth] if depth else '')

3、在-l逻辑部分增加处理

size = stat.st_size if not human else _gethuman(stat.st_size)

其他的完善

uid、gid的转换

pwd模块,The password database,提供访问Linux、Unix 的 password文件的方式。windows没有。

pwd.getpwuid(Path().stat().st_uid).pw_name

grp模块,Linux、Unix获取组信息的模块。windows没有

grp.getgrgid(Path().stat().st_gid).gr_name

pathlib模块,Path().group() 或者 Path().owner()也可以,本质上它们就是调用pwd模块和grp模块。

由于windows不支持,这次可以不加这个uid、gid的转换

最终代码

import argparse
from pathlib import Path
from datetime import datetime
import stat
# 获得一个参数解析器
parser = argparse.ArgumentParser(prog='ls', add_help=False, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help="directory") # 位置参数,可有可无,缺省值,帮助
parser.add_argument('-l', action='store_true', dest='long', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with .')
parser.add_argument('-r', '--reverse', action='store_true', help="reverse order while sorting")
parser.add_argument('-h', '--human-readable', action='store_true',dest='human', help='with -l, print sizes in human readable format')
def _gethuman(size: int):
    units = " KMGTP"
    depth = 0
    while size > 1000 and depth < len(units) - 1:
        # 当前size大于1000,且depth不是最后一个进入循环
        depth += 1
        size //= 1000
    return "{}{}".format(size, units[depth] if depth else '')
def _listdir(path, all, detail, reverse, human):
    p = Path(path)
    for i in p.iterdir():
        if not all and i.name.startswith('.'):  # 不显示隐藏文件
            continue
        if not detail:
            yield (i.name,)
        else:
            # -rw-rw-r-- 1     python python     5 Oct 25 00:07 test4
            # mode       硬链接 属主   属组       字节 时间         name
            st = i.stat()
            mode = stat.filemode(st.st_mode)
            mtime = datetime.fromtimestamp(st.st_atime).strftime('%Y/%m/%d %H:%M:%S')
            size = st.st_size if not human else _gethuman(st.st_size)
            yield (mode, st.st_nlink, st.st_uid, st.st_gid, size, mtime, i.name)
            
def listdir(path, all=False, detail=False, reverse=False, human=False):
    """详细列出本目录"""
    return sorted(_listdir(path, all, detail, reverse, human), key=lambda x: x[len(x)-1], reverse=reverse)
if __name__ == '__main__':
    #args = parser.parse_args('-lrha'.split()) # 分析参数,同时传入可迭代的参数
    args = parser.parse_args()
    print(args)  # 打印名词空间中收集的参数
    parser.print_help()  # 打印帮助
    files = listdir(args.path, args.all, args.long, args.reverse, args.human)
    print(*files, sep='\n')

测试

$ python xxx.py -lah -r
$ python xxx.py /etc/ -lahr