Back to prev

Python Hard Way

Jan 21, 2021
Linkang Chan
@Jesse Chan

some posts about python

[Asterisks in Python]: 讲解 Python 中有关*的使用

command line argument parse

使用 [click] 作为解析库,可以方便的实现子命令操作。整理一些使用中遇到的问题。

  • 对 argument 进行注释

使用多行注释的方式""" xxx """。但是 click 默认是现在在统一行,去除掉了换行的操作。所以在有多参数的情况下显示比较乱。解决方式是:

@click.command()
@click.argument('gt', type=click.Path(exists=True))
@click.argument('prefix', type=click.Path(exists=True))
def execute(gt, prefix):
    """
       \b 
       explain the command usage
       gt: xxxx
       prefix: xxxxx
    """

progess bar

使用 [tqdm] 搭配各种场景使用,比如在 requests 中显示现在的进度时,可以有如下的方式:

r = requests.get(url, stream=True, allow_redirects=True)
... # status code check 
path = pathlib.Path(filename).expanduser().resolve()
path.parent.mkdir(parents=True, exist_ok=True)

desc = filename.ljust(22, ' ') # 22 is the lenght bigger than filename, should change
r.raw.read = functools.partial(r.raw.read, decode_content=True)  # Decompress if needed
with tqdm.tqdm.wrapattr(r.raw, "read", total=file_size, desc=desc) as r_raw:
     with path.open("wb") as f:
            shutil.copyfileobj(r_raw, f)

同时也可以在命令行中使用:

find . -name '*.py' -type f -exec cat \{} \; \
  | tqdm --unit loc --unit_scale --total 857366 >> /dev/null
100%|█████████████████████████████████| 857K/857K [00:04<00:00, 246Kloc/s]

更多使用参考文档 [tqdm documention] 。

requirements.txt

这个文件可以用于python项目初始化时安装依赖使用。可以通过两种方式获取到:

# 获取完整的依赖环境
$ pip3 freeze > requirements.txt

# 获取必要的依赖
$ pip install pipreqs
$ pipreqs .

# 使用
$ pip install -r requirements.txt

tar file

获取 tar.gz 文件中的顶层目录的名称,使用下面简单的方式:

archive = tarfile.open(filepath, mode='r')
print os.path.commonprefix(archive.getnames())

zip file

使用 zipfile 库,更加灵活的打包 zip 包。

from zipfile import ZipFile

with ZipFile('target.zip', 'w') as newzip:
    newzip.write('directory_name')
    newzip.write('file_name')

使用 shutil 中的 make_archive 函数生成的包有点奇怪(?),不如使用 zipfile 来的灵活。

subprocess

使用 subprocess 时,往往需要添加子进程中的环境变量,可以使用:

import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

sh

sh is a full-fledged subprocess replacement for Python 2.6 - 3.8, PyPy and PyPy3 that allows you to call any program as if it were a function.

import sh
# like command run in bash. $ sed -i 's/a/A/g' filename
sh.sed(['-i', f's/a/A/g', filename]}'])

format

python 中用于格式的操作,在格式化数字的时候,可以方便的控制小数点后的面位数。具体的可以参考[string format]

"{:.2f}".format(13.949999999999999)

listdir

python 中遍历目录有好几种方式,不同的方式满足于不同的场景。

  • os.listdir

列举出当前目录下所有的文件,同时我们可以通过文件类型去进行过滤。比如:

import os 

files = [f for f in os.listdir('.') if os.path.isfile(f)]
dirs = [d for d in os.listdir('.') if os.path.isdir(d)]
  • os.walk

walk 一般会递归去获取当前目录下所有的文件包含子目录,需要通过指定一些配置来满足我们的需求。简单的使用如下:

import os

for root, dirs, files in os.walk('.', topdown=True):
    dirs.clear()
    for file in files:
        print(file)

dirs.clear()用于不递归遍历当前目录下的子目录。 如果不删除,则会显示当前目录下所有的文件包含子目录里面的文件。这需要结合具体的场景。

  • find(shell command with subprocess)

这种方式是结合了 shell 的一些特性,一般不得以的情况下才使用。

chunk

def chunk(iterable, n):
    d = {}
    for i, x in enumerate(iterable):
        d.setdefault(i//n, []).append(x)
    
    return list(d.values())

或者使用more_itertools这个库, 可以更加简单的实现。

import more_itertools as mit

list(mit.chunked(iterable, n))

[stackoverflow answer]: 不造轮子,使用对应的库

atexit

atexit 在注册时传递参数:


def goodbye(name, adjective):
    print('Goodbye %s, it was %s to meet you.' % (name, adjective))

import atexit

atexit.register(goodbye, adjective='nice', name='Donny')

OrderedDict

将字典 key 转换成 list,可以按照如下的方式:

>>> from collections import OrderedDict
>>> a = OrderedDict({'a': 1, 'b':2})
>>> a.keys()
odict_keys(['a', 'b'])
>>> b = [*a]
>>> b
['a', 'b']
>>> 

单纯的 keys 是odict_keys类型,而不是list类型。所以通过解引用直接获取生成 key 的 list 。

logging

在使用 logging 时,我们在自定义输出格式时,使用如下的方式:

logging.basicConfig(format='%(asctime)s,%(msecs)03d %(levelname)-8s [%(pathname)s:%(lineno)d in function %(funcName)s] %(message)s', datefmt='%Y-%m-%d:%H:%M:%S', level=logging.DEBUG)

注意这边的%(msecs)03d这个是用来保留 3 位毫秒数的,方便后续的处理。

list 间隔差

t = [1,2,4,6]
v = [t[i+1]-t[i] for i in range(len(t)-1)]
# [1,2,2]

统计数据分布

from itertools import groupby

def histogram(data, step):
    for k, g in groupby(sorted(data), key=lambda x: x//step):
        print('{}-{}: {}'.format(k*step, (k+1)*step-1, len(list(g))))

随机采样

import random
import glob

pbfiles = [f for f in glob.glob("trt_data_pb/*.pb")]

random_pb = random.sample(pbfiles, 1000)
print(random_pb[:10])

采用random.sample这个可以在一组 list 中,随机获取指定数量的内容。 如果只是想选一个,可以使用random.choice

pprint

正常使用 print 的打印效果一般,不够 pretty, 所以我们可以使用 pprint 库来完成更好的打印,正常情况下我们会使用到一些基本的参数来控制打印的效果。同时可以通过自定义统一的打印风格。

>>> from pprint import PrettyPrinter
>>> custom_printer = PrettyPrinter(
...     indent=4,
...     width=100,
...     depth=2,
...     compact=True,
...     sort_dicts=False
... )
...
>>> user={"name": "jesse", "age": 1, "address": {"street": "xxx", "city": "xxxxx"}}
>>> custom_printer.pprint(user)
{'name': 'jesse', 'age': 1, 'address': {'street': 'xxx', 'city': 'xxxxx'}}

实际上我们可以通过单独设置的方式来打印这些内容,比如:

>>> from pprint import pprint
>>> pprint(users, indent=4, depth=2)