读源码 - stagesepx

背景

近段时间笔者在GitHub 发现一个有意思的工具:stagesepx ,经过这段时间的学习和研究,也基本将其应用于实际项目中,并产生了不错的效果,而本文旨在于记录stagesepx源码中诸多优雅的用法和思路,作以学习、积累

一、概述

stagesepx

轻量化的、基于图像处理与机器学习的、全自动的视频分析工具。它提供了丰富的可定制性,能够根据你的实际需求分析视频并将其拆分为一系列阶段。在此之后,你可以清晰地得知视频包含了几个阶段、以及每个阶段发生了什么。而这一切都是自动完成的。

更多内容请移步GitHub项目地址,此外stagesepx作者也很贴心的给出了开箱即用的实例:work_with_stagesepx

二、源码中优雅的用法

优雅的赋值/返回

# 赋值/返回,当输入为空时,赋一个固定值,否则将输入赋值/返回
def foo(input):
    return input or ‘null'
    # return input if input else ’null'

递归方法新建多级目录

import os
# target_dir - 目标目录
# exist_ok - 默认为False,表示当新建目录存在时抛异常
os.makedirs(target_dir, exist_ok=True)

更简洁的日志输出

from loguru import logger

logger.debug('this is a debug log')
logger.info('this is a info log')
logger.warning('this is a warning log')
logger.error('this is a error log')
logger.success('this is a success log')

>>> output
2020-02-26 17:55:12.681 | DEBUG    | __main__:<module>:38 - this is a debug log
2020-02-26 17:55:12.681 | INFO     | __main__:<module>:39 - this is a info log
2020-02-26 17:55:12.681 | WARNING  | __main__:<module>:40 - this is a warning log
2020-02-26 17:55:12.681 | ERROR    | __main__:<module>:41 - this is a error log
2020-02-26 17:55:12.681 | SUCCESS  | __main__:<module>:42 - this is a success log

格式化输出的另一种姿势

s = 'hello'
print(f'{s}, world !')

基于生成器读取多个文件

上下文管理的生成器常常与with联合使用

import contextlib

# 上下文管理生成器
@contextlib.contextmanager
def op(fp):
    res = open(fp, 'rb')
    try:
        yield res
    finally:
        res.close()

for ff in ['./ss.py', './test_adbutils.py']:
    with op(ff) as rf:
        res = rf.readline()
        while res:
            print(res)
            res = rf.readline()

sorted进阶

# key - 可自定义排序的基准,通常与lambda结合
# reverse - bool类型,默认为False表示不颠倒(升序)
l = [(1,3), (-1, 0), (3, 7)]
l0 = sorted(l, key=lambda x: x[0])
l1 = sorted(l, key=lambda x: x[1])

>>> output
l
Out[21]: [(1, 3), (-1, 0), (3, 7)]
l0
Out[22]: [(-1, 0), (1, 3), (3, 7)]
l1
Out[23]: [(-1, 0), (1, 3), (3, 7)]

入参类型限制

python3.5引入
通常方法的入参类型限制的格式如下

# 不带初始值
def func(a: int, b: str, c: bool):
   ......

# 带初始值
def func_with_args(
    a: int = 0,
    b: str = None,
    ):
    ......

可引入typing,提供更多的类型

# typing.Union[a, b, c] - 表示path入参类型为a/b/c其中一种即可,否则IDE warn提示

# target_size: typing.Tuple[int, int] = None - 表示target_size为元组类型,且只能有两个整形

# _hook_list: typing.List[BaseHook] = list() - 表示_hook_list为BaseHook的列表类型
# As follows: 
import typing

class VideoObject(object):
    def __init__(
        self,
        path: typing.Union[bytes, str, os.PathLike],
        pre_load: bool = None,
        fps: int = None,
        *_,
        **__,
    ):
        self.path: str = str(path)
        self.data: typing.Optional[typing.Tuple[VideoFrame]] = tuple()
   ......

绝对安全的路径

# method 1
import os

PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)

# methon 2
from pathlib import Path

P = lambda p: Path(p).resolve()

__file__ - 返回当前python文件所在的路径
os.path.dirname(__file__) - 返回当前python文件所在的目录
os.path.dirname/basename() - 分别为获取目标所在的目录和目标名
os.path.split() - 拆分路径为:[目录/路径的头, 目录/路径的尾]
os.path.splitext('./demo.log') - ('./demo', '.log')

# os.path.dirname/basename()实现
def basename(p):
"""Returns the final component of a pathname"""
return split(p)[1]

# Return the head (dirname) part of a path.
def dirname(p):
    """Returns the directory component of a pathname"""
    return split(p)[0]

josn.dumps/dump()进阶


d = {'1': 2, '3': 4, '0': 3, '中国': 'ss', 'b': 'inf'}
res = json.dumps(
    d,
    ensure_ascii=False, # 是否使用ascii
    skipkeys=True,  # 跳过非int str float bool None的key
    indent=None,    # 可设置缩紧层级
    separators=(', ', ': '),    # key vaules的分离格式
    sort_keys=True  # 是否按照key排序,注意排序时要求key类型统一
)
print(res)

pathlib库

参考:https://www.cnblogs.com/huangm1314/p/11318514.html

from pathlib import Path

Path.cwd()
Out[23]: PosixPath('/Users/ssfanli/Myfolder/pyproj/owner/stagesepx')
Path.home()
Out[24]: PosixPath('/Users/ssfanli')
res = Path('./demo.mp4')
print(res, type(res))
demo.mp4 <class 'pathlib.PosixPath'>

# 属性
# .nane - 文件名,包含拓展名
# .stem - 仅文件名,不包含拓展名
# .parent - 上一级目录名
# .suffix - 获取文件的拓展名
# .anchor - 哪个盘

abs_p = p.resolve()
abs_p
PosixPath('/Users/ssfanli/Myfolder/pyproj/work/YSPTimeConsuming/main.py')
abs_p.name
'main.py'
abs_p.stem
'main'
abs_p.parent
PosixPath('/Users/ssfanli/Myfolder/pyproj/work/YSPTimeConsuming')
abs_p.suffix
'.py'
abs_p.anchor
'/'

# iterdir(‘./’) - 返回'./'路径下的所有内容的生成器
p = Path('./')
all_p = p.iterdir()
for i in all_p:
    print(i)
>>> output
pyrightconfig.json
LICENSE
test
......

any & all

any - 检查可迭代对象,只要有一个为真则返回True,否则返回False
all - 检查可迭代对象,全部为真则返回True,否则返回False

# 源码
def all(*args, **kwargs): # real signature unknown
    """
    Return True if bool(x) is True for all values x in the iterable.
    
    If the iterable is empty, return True.
    """
    pass

def any(*args, **kwargs): # real signature unknown
    """
    Return True if bool(x) is True for any x in the iterable.
    
    If the iterable is empty, return False.
    """
    pass

while...continue <=> for...if

# while循环
a = 1
while a < 10:
    if a % 2:
        print(a)
        a += 1
        continue
    print(f'{a} is even number')
    a += 1

# for循环
    for a in range(10):
        if a % 2:
            print(a)
        else:
            print(f'{a} is even number')
        
>>> output
1
2 is even number
3
4 is even number
5
6 is even number
7
8 is even number
9

# 注意⚠️
# 无continue while循环依然继续,但是if循环之后会继续往下走,产生bug
# continue,在此处的作用时结束if循环,重新回到while大循环中
a = 1
while a < 10:
    if a % 2:
        print(a)
        a += 1
        # continue
    print(f'{a} is even number')
    a += 1

python私有变量

“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量
“双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据,但依然可以通过如:a._A__a方式访问

参考
https://blog.csdn.net/debugm/article/details/8179482?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

class A:
    def __init__(self):
        self.a = 'a'
        self._a = '_a'
        self.__a = '__a'
        
a = A()
a.a
Out[66]: 'a'
a._a
Out[67]: '_a'
a.__a
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3331, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-68-52cb5731dd97>", line 1, in <module>
    a.__a
AttributeError: 'A' object has no attribute '__a'
a._A__a
Out[69]: '__a'

获取当前类名和方法名

参考:https://www.cnblogs.com/yoyoketang/p/9231320.html

import sys
import inspect

# inspect模块动态获取当前运行的函数名(或方法名称)
def get_cur_func_name_2():
    return inspect.stack()[1][3]

class A:
    def get_cul_cls_name(self):
        print(self.__class__.__name__)
    
    @staticmethod
    def get_cul_func_name():
        print(sys._getframe().f_code.co_name)
        print(f'cur_func_name: {get_cur_func_name_2}')

父类调用子类重写的方法

import inspect

def get_cur_func_name():
    return inspect.stack()[1][3]


class A:
    def __init__(self):
        self.a = 'a'
        self._a = '_a'
        self.__a = '__a'

    @staticmethod
    def t():
        print(f'cul_func_name_by_method 1: {sys._getframe().f_code.co_name}: ')
        print(f'cul_func_name_by_method 2: {get_cur_func_name()}')

    def tt(self):
        # TODO: 此时父类直接调用的是子类中重写的t()方法?
        self.t()
        print(self.__class__.__name__)


class B(A):
    def __init__(self):
        super().__init__()
        self.b = 'b'
        self._b = '_b'
        self.__b = '__b'

    def t(self):
        super().t()
        ab_a_b = self.a + self.b + self._a + self._b
        # 子类无法访问父类中`__`开头的属性或方法,除非这样访问`self._A__a`
        # __a__b = self.__a + self.__b
        print(
            f'ab_a_b = {ab_a_b}\n',
            # f'__a__b = {__a__b}'
        )


if __name__ == '__main__':
    b = B()
    b.tt()

python图像比较

参考:https://www.jianshu.com/p/0c3ac46af8fd
SSIM的值范围[-1, 1],1代表完全相同

hook特性

非常nice的逻辑

__str__ & __repr__

https://baijiahao.baidu.com/s?id=1596817611604972751&wfr=spider&for=pc

posted @ 2020-03-21 12:04  葛木瓜  阅读(721)  评论(0编辑  收藏  举报