Python写脚本的一些常用操作

总是在刷新自己写出Dirty代码的下限

Posted by tianchen on April 21, 2020

杂项

  • Patch Forward 方法
  • 一个py文件中下面定义name == __main__内部的import和内容和上面文件中的import不共享
  • Python List Sum itertools.accumulate(l)
  • 安装一个库(指从git上拉下来的,比如pt_adv这种自写库)
    • 不建议用内部的setup.py build以及setup.py install,可能会引起路径错误
    • 进入项目根目录,用pip install -e ./`
      • 注意一定要加-e,表示editable,这样代码是可以修改
    • 并且在其他目录检查 import pytorch_pt_adv; pytorch_pt_adv.__path__
  • 使用Python的super init
    • 对于基类的__init__方法都是直接赋值的内容
    • 对于一些比如load checkpoint需要判断的东西应该放在init()当中
    • 然后派生类基本会重写init()方法,如果需要基类init中的内容应该调用一下
      • 在派生类的init当中super().init()
  • ipdb在进行补全的时候报了很多关于DEBUG级别的信息
    • 原因是因为logging被设置为了输出到控制台,在设置为DEBUG级别,导致debug级别以上的东西全部被打印了
    • logging的baseconfig里面还是写成info级别比较好
  • logging同时输出到文件和控制台
  • 免warning输出
    • python -W ignore main.py
  • 不用arg来指定输入文件
    • 这样调用 python test.py $FILENAME
    • sys.argv的[0]是本文件名字后面的就是后面附加的文件名
  • 不用logging直接把print的内容打印到文件中
    • python t.py > t.txt
  • 一个python obj,快速列举其所有attr的方法 dir(self.trainer)
  • generator以及yield,这样用可以省内存,创建batch
  • np.concatenate对应list的加法…… list的+与append
  • 下划线命名,self._foo,不是强制的,只是约定俗成 - (表示类的内部私有变量)
    • 比如说在from XXX import * python默认不会导入带前导下划线的方法
    • 单末尾下划线,则是用在当一个变量名是被关键字占用的时候
  • 对于dict的key可能不存在的时候 d.get("key")
  • import 当前目录下的所有文件
import os
import glob
import importlib

_here = os.path.dirname(os.path.abspath(__file__))
_pyfiles = glob.glob(os.path.join(_here, "*.py"))
for f in _pyfiles:
  mod_name = os.path.basename(f).split(".")[0]
  if mod_name == "__init__":
    continue
  importlib.import_module("." + mod_name, __name__)
  • 匿名函数 lambda x: x**2 returns the function
  • setattr(object,name,value) 给某个obj增加一个属性
  • 从dict中取数,当可能不存在这个key的时候,不需要用 if key in d.keys(): XXX else: return X
    • 直接用d.get(key,X)
  • SyntaxError: import * only allowed at module level 导入函数*不能放在def函数里面
  • 直接import XXX会在项目的根目录下寻找,所以找不到
    • 如果改成模块化的import from . import utils相对导入,在当前.py文件的目录下
      • .的就是相对导入,是从当前py文件的路径下
    • py3默认绝对导入
      • 是从执行文件夹下
  • TypeError: visualize_image_prediction() got multiple values for keyword argument 'label_id_mapping'
    • 检查kwargs内部是否与前面的重合
    • 可能与方法没有register成self有关
  • ipython
    • CTRL+D退出
  • 出现了很多的warning,不想看到
    • import warnings; warnings.filterwarning("ignore")
  • 创建多个一样元素的list - [3]*4
  • 有的时候莫名报错Cannot import xxx - 有可能是循环引用的问题
    • 用lazy import可以解决,就是当用到它的时候再import,而不是在文件的一开始进行import

List Comprehension

  • 这样比for循环形式简洁而且比ForLoop快
  • 形式为 [expr for i in XXX if XXXX] [x for x in range(20) if not x%5]

ipdb 断点调试

  • import ipdb
  • ipdb.set_trace() 就会在这里停下像ipython一样可调试

  • 或者使用python -m ipdb xxx.py 单步调试

  • ipdb当中调试界面指到哪一行说明还没有执行这一行
  • 如果需要在内部执行多行指令,可以选择再开一个ipython
    • 在ipdb的窗口中执行 from IPython import embed; embed()
  • 内部指令
    • n(下一个)
    • ENTER(重复上次命令)
    • q(退出)
    • p<变量>(打印变量)
    • c(继续)
    • l(查找当前位于哪里)
    • s(进入子程序)
    • r(运行直到子程序结束)
  • 在某次的ipdb运行的时候,发现方向键被映射为了]]A导致不能进行正常的调试
    • 搜索过后有人表示要pip install readline测试了没有用,以及换ipdb版本也没有用
    • 参考了这个StackOverflow Post发现这个问题是与2>&1 | tee output.txt当把控制台输出重定向到文件的时候用ipdb导致的错误,将训练脚本中的该部分注释之后回复了正常

Conda虚拟环境

  • 首先建立一个conda虚拟环境conda create -n $ENV_NAME
  • 然后进入conda activate $ENV_NAME
  • 在这里用conda装的包是独立的,使用conda install pip之后就可以一直用pip了,其缩调用的仍然是~/.pip/pip.conf
  • 如果需要退出的话 source deactivate

  • conda换源

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge 
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/

# 设置搜索时显示通道地址
conda config --set show_channel_urls yes

  • 或者写到config里

channels:
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/main/
  - https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
  - defaults
show_channel_urls: true


  • 用pip会发现很多package不能satisfy,比如torch,然后发现是python版本3.8的原因 conda install python==3.7.3解决问题

OS

Reference

Often Used

  • 如果不存在目录,就建立
if not os.path.exists('./imgs/'):
    os.makedirs('./imgs/')
  • 递归列举所有文件(filelist中是文件名,filelist_full是完整路径)
filelist = []
for fpath, dirname, fname  in os.walk(os.getcwd()):
    for file in os.listdir(fpath):
        filelist.append(os.path.join(fpath, file))
  • os.listdir() 列举当前目录
  • os.getcwd() 获取当前目录
  • os.mkdir() 创建目录
  • os.remove(file_name) 删除文件
  • COMPLETE_PATH = os.path.abspath('./XXX')+"/"+os.listdir('./XXX')[0] 获取某一目录下文件的绝对路径

  • 对于输入arg可能带’./logs’可能不是’./logs/’的时候,为了避免出错,建立目录不用”+”而用os.path.join()

Python Files

  • 读取文件
with open(FILE_DIR, 'r') as f:
    lines = f.readline()
    line_num = len(lines)
    for line in lines:
        print(line)
  • 写文件 (注意w是创建文件或者清空文件重新写,如果想要继续的话用wa)
with open(FILE_DIR, 'w') as f:
    f.write("...")
  • 复制文件(利用shutil)

shutils使用

  • shutil.copy(file, dst_path) 拷贝单个文件
  • shutil.copytree(src_path, dst_path_with_name) 递归拷贝整个目录,注意这里后面一个不能是母目录了,要带上新的名字
  • shutil.rmtree
import shutil

# Given That You use S to get fileulist

k = 0
for i,j in zip(filelist, filelist_full):
    shutil.copyfile(j,"logs/"+str(k)+"_"+i)
    k = k+1
print(i,j)

Logging

  • 直接将日志打印到屏幕上 ````logging.info(“XXX)```

  • 写入文件

    • 其中诸如asctime等等内容是内建字

import logging

logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filename='myapp.log',
                filemode='w')
    
logging.debug('This is debug message')

  • 同时打印到控制台和文件之中,借助FileHandler,或者是对控制台的StreamHandler

console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)


logger = logging.getLogger('mylogger') 
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('test.log') 
fh.setLevel(logging.DEBUG) 
formatter = logging.Formatter("")
fh.setFormatter(formatter) 
logger.addHandler(fh) 
logger.info("$STRING")

String Operation

  • s.find('a') 返回字符串中该字符的第一个的开始index
  • "a" in s 是否含有
  • s.split('.') 按照’.’分割,返回一个list
  • s.strip('.') 删除字符串的某个元素
    • 这种只能删除头尾的数据
    • 采用重新赋值的方法s.replace('.'.'')

Matplotlib

这里有一个简要上手教程

  • 在未配置好X11 Forwarding的服务器上可能在import pyplot 一步出现错误导致链接异常,需要首先取消GraphicBackend
    • import matplotlib matplotlib.use(“Agg”)
  • 解释
  • 一些机制
    • (不建议用plt.XXX会不是很明确)
    • figure可以看作是一个画布 fig = plt.figure()
    • 然后建立ax,ax并不是指的axes ax = fig.add_subplot(1,1,1)
      • 认为是各个物体,axes能表示各个元素 (axes.xlabel; axes.ylabel)
    • fig,ax = plt.subplots(111,figsize=[10.,8.])
      • ax.plot(x,y)
      • ax.set_title('Title',fontsize=18)
      • ax.set_xlabel('xlabel')
      • ax.legend()
      • ax.set_xlim(0,16)
      • ax.grid()
  • matplotlib force 显示画图 %matplotlib inline
  • jupyter当中shitf+tab批量缩进

  • plt.figure()新建一张新的图
  • plt.plot([1,2,3,4,5]) 直接画图,作为y,x轴为0,1,2,3
    • 如果我想隔点画图,画在0,2,4,6,8 x轴的位置,plt.plot([1,2,3,4,5],[1,3,5,7,9])
  • plt.title("TITLE") 指定标题
  • plt.legend(["A","B"], loc = "upper left") 如果不指定loc会自动安排,注意中间没有下划线
    • 另外一种给图形打上label的方式是: 当对一个figure需要调用多次plot的时候
    • 调用plt.plot(X, label=name)
    • 然后调用plt.legend()
  • plt.savefig("./img/XXX.jpg") 注意该指令不能创建新的文件夹,所以需要提前建立
  • 默认绘图是Matlab风格(针对列表)
import matplotlib.pyplot as plt
plt.plot([1,2,3,4], [1,4,9,16], 'ro')
plt.axis([0, 6, 0, 20])
plt.show()
  • 或者使用Numpy-based
# evenly sampled time at 200ms intervals
t = np.arange(0., 5., 0.2)

# red dashes, blue squares and green triangles
plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^')
plt.show()
  • numpy related
    • numpy indice 间隔索引 x[::5,:]
    • numpy swith rows micros[:,:,:,[0,1]] = micros[:,:,:,[1,0]]
  • 绘图颜色tips

argparse

可参考这个博客

 parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training') # 首先建立一个Parser
 parser.add_argument('--lr', default=0.1, type=float, help='learning rate') # 然后加上arg
 parser.add_argument('--resume', '-r', action='store_true', help='resume from checkpoint') 
 args = parser.parse_args() # 然后parse_arg
  • 完成parseargs()之后,args是一个命名空间(NameSpace)可以利用vars(args)来获得一个dict

click

  • Python中为命令行准备的接口,以修饰器的形式修饰函数
  • ``` py import click

@click.command() @click.option(‘–name’, default = “Nobody” , help = “The Name 2 Print”) @click.option(‘–age’, type = int, help = “The Age”) @click.option(‘–gender’, default = “?”, type = click.Choice([“Male”,”FeMale”,”?”])) def hello(name, age, gender): print(“Hello there, {} {} {}”.format(name,age, gender)) click.echo(“Click Triggered..”)

if name == “main”: hello()


# yaml

* install by ```pip3 install pyaml```
  * 建议安装一下oyaml (优点在于对dict保序了)

``` py
import oyaml as yaml

with open("FILE_DIR",'a') as f:
    yaml.safe_dump(dict, f) # 注意这里不要忘记加f
  • 注意对于1e-10 yaml会把它当成string,但是1.0e-10就可以

tqdm

  • 一个很实用的进度条库
  • 注意要用from tqdm import *这样来import哦
t = tqdm(range(10))
for i in t:
  t.set_description("WWW{}".format(i))
  • 对于enumerate的case就把enumerate里的对象套上tqdm
    • for i in enumerate(tqdm(list))

Graphviz

  • 一个很好的绘图库

pytest

  • args
    • -x: 遇到错误就停下来不进行下面的
    • -s: 打印输入输出,要用ipdb必须要
    • -k: filter名字,只进行某些名字的test


把当时学Python的一些笔记也Merge进来了

Python

  • 我居然又记不住格式控制输出了?!
    • print("this is %d" % 20)
    • print("%d---%d"%(10,20))
    • Format() - 用“{}”来代替“%”
      • {}里面带数字可以替换顺序 print("{0}>{1}".format(1,2))
      • {}里面“:x”可以进行进制转换
      • {}里面”:.2f”表示保留小数点后几位
      • {:4e} 科学计数法并且保留四位
  • enumerate()返回一个枚举对象
    • 输入参数是以一个iterable的对象,比如list
    • 返回的是一个tuple (index, content_of_iter)
  • 迭代器
    • Python内置的iter(list)创建了一个iter对象
    • Next(iter对象)
  • 生成器-使用了yield的函数(本职需要是一个函数)
    • 是一个返回迭代器的函数,只是运行到yield的时候都保存下所有当前的状态,并返回yield的值
      • 下一次从当前位置开始继续运行!
      • 由于用到了yield,所以虽然返回了,但是下一次也是在while循环里面的
  • zip
    • [iter, (等待被batchup的元素)]
    • 返回的是一个tuple的迭代器
      l = ['a', 'b', 'c', 'd', 'e','f']
      print zip(l[:-1],l[1:])
      
    • 或者需要从两个迭代器交替取出
    for i,j in zip(l1,l2):
      print(i,j)
    
  • assert
    • 断言
import traceback

try:
  assert a == 1
except AssertionError as er:
  print ("Whatever you Want")

try:
	assert a == 1
except:
	raise Exception("XXX")
  • 最直接的判断式的exception(会直接抛出,不会执行后面的代码)
    • raise Exception("XXX")
  • 想类似的当某行代码会产生bug(已知bug类型),而希望在产生bug的同时开始debug的话
try:
  something
except RuntimeError:
  import ipdb; ipdb.set_trace()
  • Python使用”:”进行索引的时候,和直接索引不一样
    • 比如说对于一个长为32的数组,取后16个 [15:31]并不是取了最后16个,而[16:32]才是
  • range(0,10,2)返回[0,2,4,6,8,10]
  • ipython reload模块
    • 对于ipython,可以支持动态热重载
      %load_ext autoreload
      %autoreload 2
      
    • 对于一般python,3.4之后
      import importlib
      importlib.reload(MODULE)
      
    • reload不支持from A import B只能先reload A,然后用A.B调用 **
  • *args**kwargs
    • 目的是让不定量的参数输入给函数(指的调用的时不确定输入几个参数)
    • args通过将参数放到一个tuple里面,通过*索引来做到输入一个参数来传入多个参数,但是默认顺序
    • kwargs将一个不定长的键值对(以一个dict的形式输入)传送给函数
    • 比较常用的一种应用 - 猴子补丁 Monkey Patch
      • 在runtime的时候修改某些代码
    • 使用修饰器的时候也用到
  • Python中的变量和方法,可以直接后面加问号查看帮助,比如torch里的一些方法,可以不用每次都查手册了
  • dict的操作
    • 如果需要修改甚至新增keys直接 d['b'] = 3
  • sys库中的argv中有当前正在运行的文件名
  • 制表符
      print("Hello \t you")
      print("Hi \t yours")
    
    • 遇到了一个问题就是超过一个制表符的位数之后会跳到下一个制表符,变得太远了
      • 目前想到的解决方案是给那个短的再来一个制表符
  • importfrom XXX import *
    • 最直接的区别就是后者可以直接import文件里的”公有”class和method,而第一种需要MODULE.METHOD()
    • 比如说后者对_开头的方法(_generate_fix_cfgs)就不会被import进来
  • set_attr(OBJ,'ATTR_NAME',VALUE)
    • 可以用来新增属性
    • 比如说可以用来建立网络中的各层,对于self.conv1_1 = nnf.Conv2d(XXX)改成set_attr(self, name, nnf.Conv2d)
  • 想要获得一个obj的所有attribute
    • instance.__dict__或者是instance.__dir__
    • vars(instance)
    • inspect库的get_members
  • __init____new__
    • 前者其实是初始化方法,而后者才是真正的构造方法
    • 一般写一个新的类,都是继承object类,由于我们没有重写(Override)一个__new__方法,所以实例化类的对象的时候会默认调用object类(父类)的__new__,返回值是一个self
    • 前者返回一个实例,而后者不能有返回值
    • MetaClassExample
      • type也可以创建一个类(和用Class定义等价)
  • 调用函数的时候多个参数通过一个List来喂进去 func(*l)
  • something.dict() 类似 dir(Something)
  • pprint - 更美观的print,可以调用一下(对list和dict有比较好的支持)

引用相关

ref 关于Python导入模块,你可能没学透?! - Wayne的文章 - 知乎 [深入探讨 Python 的 import 机制:实现远程导入模块 - 王炳明的文章 - 知乎([(https://zhuanlan.zhihu.com/p/269633294)

  • 绝对引用是一种相对比较保险的方式,会查询sys.path所以当涉及到比较深的库的时候,可以用sys.path.append(PATH)再对那个目录进行绝对引用
    • 但是存在一个很蛋疼的问题就是当sys path之后可能会存在多个一样的包,貌似会优先从执行程序的目录下去寻找,而不会去找append出来的: [Solution]: 改名字…或者回退到PATH的前面一层来用两层引用 比如from test.models import xxx
  • from .ops import * 是一种显示的相对引用的写法,表示从当前库中进行下一层的引用
    • 但是对于一般的import,不应该使用import .ops,而是应该是from . import ops
  • 当import一个目录的时候,会优先查看并执行其中的__init__.py文件,注意这点,比如如果init文件中进行了重定向的话 from modes import resnet可能会没有import到想要import的文件
    • 如果涉及到访问某个目录下的子目录,那么需要在该目录下的__init__.py下写好相对引用,才能把当前目录下的子模块给引入到当前模块下
  • 有一种比较强制的引用方式,就是如果你想要引用其他位置的一个包,可以直接把它的上层目录,直接通过sys.path給加进去,然后再import $PACKAGE_NAME

  • when 绝对 when 相对?
    • 绝对引用一般更保险,更具有唯一性,不会错(在我们已知一定会在某个目录下运行代码的时候)可以看作是相对于一个固定的Path; 如果是绝对导入,一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块
    • 相对引用在可能会重定向的模块中会常用到; 如果是相对导入,一个模块必须有包结构且只能导入它的顶层模块内部的模块
    • 如果一个模块被直接运行,则它自己为顶层模块,不存在层次结构,所以找不到其他的相对路径,如果一个模块是被其他模块导入的,则它存在层次结构,但也无法导入与顶层模块同级别的模块
  • 出现问题的时候可以去检查一下是否有循环引用

importlib

  1. importlib.import_module(name, package=None) 绝对导入直接输路径即可,相对导入的话name要包含比如../的形式,并且要传入package参数

Exmaple:

|-- ($HOME)
  |-- models
     |-- resnet
  • 在$HOME目录下执行python,调用 import modelsfrom . import models本质是一样的,分别是绝对和相对引用
  • 可以执行import models.resnet as resnet 或者是from models import resnet,本质上都是绝对引用
  • 而调用from .models import resnet就会报错: ModuleNotFoundError: No module named '__main__.models'; '__main__' is not a package
    • 这种相对引用的方式需要在parent module is imported 的情况下才能work,这里没有import,所以唯一可以base的目录是main函数,就报错了
  • 上述情况中,你可以正确的引用models了,但是如果你想访问models.resnet仍然是没有东西的
    • 我们希望在models当中管理下面的包,在models中添加__init__.py并在其中执行import models.resnet as resnet,这样之后我们只要在main中import models就可以直接调用models下的子模块了(否则要用哪个还必须要从某个具体文件中import)
    • 与上方同理,如果用相对引用(这里感觉这种更符合实际意义),在init中加入from . import resnet(你如果足够自信甚至可以直接from . import *),这样也会自动将所有子模块都import到parent module - models下,就可以正常访问了

iPython

  • 从文件运行完打开ipython进行调试 ipython -i XXX.py
  • 有一些莫名奇妙的bug可能来自于ipython,reload模块,尝试重启内核

OOP

  • 一个类的__init__(self, )是构造函数
  • 类的方法第一个参数要填self!
  • self.相当于this指针
  • Python没有New关键字,直接使用类的名称构造对象,相当于调用构造函数p1=Person(18,0)
  • 内置了一些类的属性
    • __dict__ 包含了一个类的属性所表示的dict
    • __name__
    • __module__类定义所在的模块
  • 约定俗称的规律__foo代表的是private变量
  • 而super方法则是通过调用父类方法来显示调用父类(可以避免需要用父类的名字来调用父类,需要将self传进去)
    • super(BasicBlock, self).__init__()
  • 本身是不能直接执行一个对象的
    • 需要对类中添加__call__()方法,这样就可以直接执行对象 ```

    t = T() t()

    ```

装饰器

参考了谈谈Pyhton装饰器

  • 使用装饰器把函数注册为事件的处理程序
  • 其实本质上是一种设计模式
    • 对已经存在的对象加上一些功能
    • 装饰器本身是一个函数,它的参数是另外一个函数,参数列表与原函数一致
    • 一个简单的例子

import time  
 
def foo():  
  print 'in foo()'  
 
# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法  
def timeit(func):  
     
  # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装  
  def wrapper():  
      start = time.clock()  
      func()  
      end =time.clock()  
      print 'Time Elapsed:', end - start  
     
  # 将包装后的函数返回  
  return wrapper  
 
foo = timeit(foo)   #可以直接写成@timeit + foo定义,python的"语法糖"
foo()

  • 相当于在装饰的前后对原本的foo函数加上了timeit的功能,做到了调用函数同时增加执行代码(指timeit的部分)
  • 顺序的作用 **“Hamburg Model”

  def bread(func) :  
    def wrapper() :  
        print "</'''       '''\>"  
        func()  
        print "<\______/>"  
    return wrapper  
   
  def ingredients(func) :  
      def wrapper() :  
          print "#tomatoes#"  
          func()  
          print "~salad~"  
      return wrapper  

  @bread  
  @ingredients  
  def sandwich(food="--ham--") :  
      print food  
     
  sandwich()

      #</''''''\>  
      # #tomatoes#  
      # --ham--  
      # ~salad~  
      #<\______/>  

  • 内置装饰器
    • staticmethod 转变为静态
    • classmethod 转变为类方法
    • property 转变为类属性
  • 对于有返回值的函数,在装饰器的wrapper里面也要把返回值返回回去