2022年 11月 5日

Python工程代码学习笔记(随时更新)

1.python格式化输出print的format用法

“{被替换变量0}……{被替换变量1}……”.format(变量0,变量1)变量可以是任意类型的

  1. name = "Jack"
  2. num = 1
  3. print("{0}有{1}个书包".format(name,num))

当然format不仅仅可以用于print,还可以直接作用在字符串,例如:

"cfgs/{}_ls.yml".format(args.net)

2.assert函数:判断条件是否为真,如果为真则继续运行程序,如果为假则结束程序并抛出异常AssertionError

用法:assert 表达式 [, 参数]    判断表达式是否为真,如果为假则抛出AssertionError,参数为AssertionError的参数,例如:

  1. a = 1
  2. b = 2
  3. assert a==b,"a不等于{}".format(b)

如果a不等于b,则抛出AssertionError并且其参数为:a不等于b(2),并且程序中断

  1. Traceback (most recent call last):
  2. File "C:/Pycharm/PycharmProjects/Project_test_1/test.py", line 35, in <module>
  3. assert a==b,"a不等于{}".format(b)
  4. AssertionError: a不等于2

3.Python的装饰器与静态装饰器

当我们定义一个类class的时候,第一个参数总是self,类的方法method也有self这个参数。当我们通过实例调用方法obj.method时,我们默认把实例obj本身作为参数传到了方法内。举个例子

 在haha这个类中,x方法有self这个参数,在使用obj.method的时候会自动把obj当做参数来输入x方法。如果我们运行下面这个代码,结果如下:

 

我们实例化c之后,调用c.x()的时候自动把c这个实例作为参数给传到x方法内了。因此相当于多传入了一个参数

解决方法:在方法前面添加@staticmethod

 再添加静态装饰器后,方法x就相当于普通的函数,使用obj.method时不会把实例c当作参数传入方法x内。除了使用静态方法之外,还可以直接用类直接调用方法来做:通过类直接访问方法,即haha.x()。这样也不会把实例当作参数传入到方法x内部。

下面我们来聊一聊装饰器(Decorator):

Python中装饰器是用来修饰或装饰一个已经定义好的函数,例如它可以让你在函数的运行前后做一些其它的事情。而装饰器的本质是一个二阶函数。

首先在Python中所有元素都是看作是对象,甚至是函数。我们可以在其它的函数内定义新的函数,也可以在其它函数返回另一个函数,也可以将其它函数作为参数传到另一个函数内。我们来看一个例子:

  1. def func_1():
  2. print("你正在函数1内")
  3. def func_2(func_1):
  4. def func_3():
  5. print("你正在函数3内")
  6. print("你正在函数2内")
  7. func_1()
  8. return func_3
  9. a = func_2(func_1)
  10. print("执行完了函数2")
  11. print(a)
  12. print(a())
  13. 输出为:
  14. 你正在函数2
  15. 你正在函数1
  16. 执行完了函数2
  17. <function func_2.<locals>.func_3 at 0x00000190459BF700>
  18. 你正在函数3

在执行函数2的时候,按照顺序输出函数2与函数1,然后将返回的函数3的地址赋值给a,我们输出a发现输出的是函数的地址func_3,只有输出a()的时候才相当于输出func_3()才会执行函数内容。

下面我们通过例子来说明一下装饰器的作用:让函数执行前后做一些其它的事情

  1. def func():
  2. print("执行函数")
  3. def decorator(func):
  4. def func_1():
  5. print("执行函数前要做的")
  6. func()
  7. print("执行函数后要做的")
  8. return func_1
  9. func()
  10. func = decorator(func)
  11. func()
  12. 输出为:
  13. 执行函数
  14. 执行函数前要做的
  15. 执行函数
  16. 执行函数后要做的

我们定义了一个装饰器decorator,它传入的参数是func。在装饰器内部我们定义了一个新的函数func_1:在func执行前后做事情,并且装饰器返回新的函数。通过装饰器修饰前后,我们可以发现输出也会相应地改变。实际上在经过修饰后,func已经变成了func_1。装饰器必须返回一个新的函数,否则无法幅值给func。当然,上面这段代码可以用一种更加简洁的方式:

  1. def decorator(func):
  2. def func_1():
  3. print("执行函数前要做的")
  4. func()
  5. print("执行函数后要做的")
  6. return func_1
  7. @decorator
  8. def func():
  9. print("执行函数")
  10. func()
  11. 输出:
  12. 执行函数前要做的
  13. 执行函数
  14. 执行函数后要做的

我们在func前面加了一个@decorator符号,其作用就是代替语句:func = decorator(func)。这样func输出的直接就是被装饰器修饰后的函数。这就是装饰器的作用啦。

Python中还有一个装饰器叫作@property 其作用是创建一个只读的属性。没错,property是一个用于类内方法的装饰器,其作用是创建一个属性。它会自动把一个方法变成一个只读的属性,例:

  1. class DataSet(object):
  2. @property
  3. def method_with_property(self): ##含有@property
  4. return 15
  5. def method_without_property(self): ##不含@property
  6. return 15
  7. l = DataSet()
  8. print(l.method_with_property) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
  9. print(l.method_without_property()) #没有加@property , 必须使用正常的调用方法的形式,即在后面加()

与所定义的属性配合使用,这样可以防止属性被修改

  1. class DataSet(object):
  2. def __init__(self):
  3. self._images = 1
  4. self._labels = 2 #定义属性的名称
  5. @property
  6. def images(self): #方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
  7. return self._images
  8. @property
  9. def labels(self):
  10. return self._labels
  11. l = DataSet()
  12. #用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
  13. print(l.images) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。

4.from __future__ import

这样的做法的作用就是将新版本的特性引进当前版本中。比如我的代码的运行环境时python2的版本,但是我又想体验python3版本的某些功能或者函数,那么我就可以使用这个功能。例如print函数在Python2与Python3的写法不同:

  1. # python 2.x
  2. print "Hello World"
  3. # python 3.x
  4. print("Hello World")

如果我们想提前体验Python3的print函数,就可以采用这个写法:

  1. # python 2.x
  2. from __future__ import print_function
  3. print("Hello World")

有的时候我们还会看到absolute_import,这主要是针对Python2.4版本之前的:在Python2.4之前,当我们import一个模块时会优先从当前目录进行导入(比如我们在当前目录自己实现了一个函数print,那么我们导入print的时候就不会导入python自带的print函数而是导入我们自己定义的)。如果我们想用python自带的就必须使用:

from __future__ import absolute_import

5.关键字参数与非关键字参数

我们在python的代码中经常可以看到函数的参数是(*args,**kw)

  • *args是可变参数,args接收的是一个tuple
  • **kw是关键字参数,kw接收的是一个dict

args代表的是可变参数:参数的个数是不确定的可变的,可以是0个、1个、2个……

  1. 定义:可变参数就是传入的参数个数是可变的,可以是0个,1个,2个,……很多个。
  2. 作用:就是可以一次给函数传很多的参数
  3. 特征:*args
  1. 首先定义一个可变参数的求和函数:
  2. def cout(numbers):
  3. sum = 0
  4. for n in numbers:
  5. sum = sum + n * n
  6. return sum
  7. 显然numbers是一个元组或者列表,我们通过元组或列表的方式实现可变参数,但是使用的时候必须要加中括号或者小括号
  8. 输出:
  9. >>> cout([1, 2, 3]) 参数为列表
  10. 14
  11. >>> cout((1, 3, 5, 7)) 参数为元组
  12. 84

如果我们不想输出的时候还要写小括号或者中括号,可以使用非关键字参数:

  1. 在函数的参数前面加上*代表可变参数
  2. def cout(*numbers):
  3. sum = 0
  4. for n in numbers:
  5. sum = sum + n * n
  6. return sum
  7. 这样输出就可以不需要采用元组或者列表,变为:
  8. >>> cout(1, 2, 3)
  9. 14
  10. >>> cout(1, 3, 5, 7)
  11. 84
  1. 如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:把列表拆成元素
  2. >>> nums = [1, 2, 3]
  3. >>> cout(nums[0], nums[1], nums[2])
  4. 14
  5. 这种写法当然是可行的,问题是太繁琐,所以Python允许你在listtuple前面加一个*号,把listtuple的元素变成可变参数传进去:
  6. >>> nums = [1, 2, 3]
  7. >>> calc(*nums)
  8. 在这里nums还是一个列表,因此体会我们在最开头所说的args接受一个列表或者元组的含义。实际上args还是一个列表,采用*args把这个列表转换成了可变的非关键字参数,传递到函数中

*nums表示把nums这个list的所有元素转换成可变参数传给*args,args获得一个列表。*运算符的这个作用非常常见。注意函数内部的numbers与函数参数的nums都是列表,只有*nums以及*numbers是可变非关键字参数。

kw代表的是关键字参数(keywords):参数的个数也是可变的,但是必须与字典的形式相同:key:value

  1. 定义:关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。在调用函数时,可以只传入必选参数。
  2. 作用:扩展函数的功能
  3. 特征:**kw

实例:

  1. def person(name, age, **kw):
  2. print('name:', name, 'age:', age, 'other:', kw)
  3. 函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:
  4. >>> person(‘Michael’, 30)
  5. name: Michael age: 30 other: {}
  6. 也可以传入任意个数的关键字参数:
  7. >>> person('Adam', 45, gender='M', job='Engineer')
  8. name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

  1. 和可变参数*args类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
  2. >>> extra = {'city': 'Beijing', 'job': 'Engineer'}
  3. >>> person('Jack', 24, city=extra['city'], job=extra['job'])
  4. name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
  5. 当然,上面复杂的调用可以用简化的写法:使用**extra
  6. >>> extra = {'city': 'Beijing', 'job': 'Engineer'}
  7. >>> person('Jack', 24, **extra)
  8. name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra与*args对应,表示把extra这个dict的所有key-value用关键字参数转换成关键字参数,再传入到函数的kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

6.Python的zip函数

zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

举个例子:在pytorch训练中,dataset所构成的数据集是由N个元组构成的,每个元组含有两个元素:图片以及其对应的标签(image,label)。我们在训练的时候希望把图片单独放在一起,把标签单独放在一起,构成两个元组[(images),(labels)]放在一个列表内,就可以使用zip函数。

  1. 以下实例展示了 zip 的使用方法:
  2. >>>a = [1,2,3]
  3. >>> b = [4,5,6]
  4. >>> c = [4,5,6,7,8]
  5. >>> zipped = zip(a,b) # 打包为元组的列表
  6. [(1, 4), (2, 5), (3, 6)]
  7. >>> zip(a,c) # 元素个数与最短的列表一致
  8. [(1, 4), (2, 5), (3, 6)]
  9. >>> zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
  10. [(1, 2, 3), (4, 5, 6)]
  11. *zipped是将zipped列表转化为可变非关键字参数(1,4),(2,5),(3,6)共3个元素
  12. 相当于zip((1,4),(2,5),(3,6)) zip把所有列表或者元组的第一个元素取出来拼在一起构成新列表的第一个元素,把所有列表或者元组的第二个元素取出来拼在一起构成新列表的第二个元素......以此类推
  13. 因此把1,2,3单独取出来构成第一个元素,再把4,5,6单独取出来构成第二个元素
  14. 即[(1, 2, 3), (4, 5, 6)]

考虑我们的例子,设batch由N个元组构成,每个元组含有两个元素,分别为图片以及标签,我们就可以使用zip(*batch):首先将batch构成可变非关键字参数,即分解为N个元组,zip将每个元组的第一个元素(图片)取出来,构成第一个元素(N个图片构成的元组);再将每个元组的第二个元素(标签)取出来,构成第二个元素(N个标签构成的元组)。

7.系统的路径sys.path与__file__

__file__就是当前运行的py文件的绝对路径

例如:我们运行Project_test_1下面的path.py文件,并打印__file__:

输出结果为:”C:/Pycharm/PycharmProjects/Project_test_1/path.py”是path.py的绝对路径

我们也经常可以在工程代码中看到sys.path。sys.path输出的是一个列表,列表内的每一个元素都是一个路径点的字符串形式,其作用就是帮助python搜索我们要import的包。搜索包的路径就是sys.path的全部路径。导入一个叫 mod1 的模块时,解释器先在当前目录中搜索名为 mod1.py 的文件。如果没有找到的话,接着会到 sys.path 变量中给出的目录列表中查找

sys.path包括了三种路径:

(1)当前路径:即所运行的py文件的目录,是sys.path列表的第一个元素

(2)PYTHONPATH:通常是空的,可以自己设置

(3)site-packages的路径:我们在pip 安装包的时候都会自动储存到site-pacages文件内

如果是本地的miniconda,site-packages在我们自己创建的环境文件夹下的lib内:

‘C:\\miniconda\\miniconda\\envs\\test_env_1\\lib\\site-packages’ (test_env_1是我们自己创建的环境文件夹)

如果是本地的虚拟环境,site-packages就在工程文件夹的下方的venv虚拟环境文件夹内的lib下:

C:\\Users\\褚峤松\\Desktop\\迁移学习与小样本学习\\code\\HTCN-master\\venv\\lib\\site-packages

如果是服务器的conda环境,site-packages在我们自己创建的环境文件夹下的lib下面的python文件夹内:

/home/chuqs/.conda/envs/test/lib/python3.8/site-packages/

(4)一些其它路径比如python的安装路径,环境文件夹路径等,通常我们的包不在这里面

如果我们需要导入的包不在这里面,则我们需要把我们的包(py文件)的路径放到sys.path内。我们可以借助os.path工具以及__file__(当前路径)来手动添加包的绝对路径

我们以一个实际的工程为例,工程的代码结构如下:

 我们实现代码所自己定义的py文件都储存在lib文件夹中,由于这是本地的虚拟环境作为解释器,因此pip 安装的所有工具都储存在venv下面的site-packages文件夹中。

我们在import文件时,sys.path包含了当前路径以及环境文件夹下site-packages的路径。因此pip安装的包都可以在site-packages下被正确找到,可以正确import。但是lib文件夹并不在sys.path中,因此无法正确导入。因此我们在工程文件夹下方添加了一个_init_paths.py文件来把lib加入到sys.path中。下面来看_init_paths.py的代码:

  1. import os.path as osp
  2. import sys
  3. #定义函数add_path,将路径加入到sys.path中
  4. def add_path(path):
  5. if path not in sys.path:
  6. sys.path.insert(0, path)
  7. print(sys.path)
  8. print(__file__) #借助_file__打印当前_init_paths.py文件的路径
  9. >>>C:/Users/褚峤松/Desktop/迁移学习与小样本学习/code/HTCN-master/_init_paths.py
  10. this_dir = osp.dirname(__file__) #通过__file__找到当前路径(工程文件的路径)
  11. print(this_dir) #dirname是返回_init_paths.py的上一级目录,即当前目录
  12. >>>C:/Users/褚峤松/Desktop/迁移学习与小样本学习/code/HTCN-master
  13. # 将lib文件夹的路径添加到sys.path中
  14. lib_path = osp.join(this_dir, 'lib')
  15. add_path(lib_path)
  16. print(lib_path)
  17. >>>C:/Users/褚峤松/Desktop/迁移学习与小样本学习/code/HTCN-master\lib

通过代码我们可以发现为什么要单独写一个_init_paths.py文件。原因在于:这个工程在每个人的电脑或者服务器存放的位置都是不同的,因此我们需要用_file__来找到_init_paths.py的路径,并通过dirname来找到工程文件的绝对路径,而lib就在工程文件夹下,出于这个考虑,才把_init_paths.py与lib放在一个目录下。这样就可以添加lib的绝对路径到sys.path下,import操作才不会出错。因此我们必须首先运行_init_paths.py修改sys.path,后续的代码才可以正常运行。

8.argsparse

argsparse是python的命令行解析的标准模块,内置于python,不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。

传入一个参数:我们先来看一个例子:

  1. import argparse
  2. #首先创建一个ArgumentParser对象
  3. parser = argparse.ArgumentParser(description='命令行中传入一个数字')
  4. #再添加参数,type是要传入的参数的数据类型 help是该参数的提示信息
  5. parser.add_argument('integers', type=str, help='传入的数字')
  6. args = parser.parse_args()
  7. #获得传入的参数
  8. print(args)

我们在命令行输入:python file.py args(以空格隔开)

  1. python demo.py 5
  2. >>>Namespace(integers='5')

注意intergers是str字符串类型的,但是我们输入参数的时候不需要输入”5″而是直接输入5

python会根据type=str自动转化成字符串,因此输出为intergers=’5′

我们也可以用args.参数名来获取参数的值:

  1. print(args.integers)
  2. >>>5

传入多个参数:

现在在命令行中给demo.py 传入多个参数,例如传入1,2,3,4四个数字

python demo.py 1 2 3 4

运行会报错,这时我们需要添加nargs=’+’参数:代表传入不只1个参数

  1. import argparse
  2. parser = argparse.ArgumentParser(description='命令行中传入一个数字')
  3. parser.add_argument('integers', type=str, nargs='+',help='传入的数字')
  4. args = parser.parse_args()
  5. print(args.integers)

输出结果为:

['1', '2', '3', '4']

可选参数:

如果我们要传入多个参数,参数输入的顺序就很重要,必须与代码设置的一样,这就很会麻烦:

  1. import argparse
  2. parser = argparse.ArgumentParser(description='姓名')
  3. parser.add_argument('param1', type=str,help='姓')
  4. parser.add_argument('param2', type=str,help='名')
  5. args = parser.parse_args()
  6. #打印姓名
  7. print(args.param1+args.param2)
  8. >>>张三
  9. 如果我们交换参数行的顺序(第三行和第四行)
  10. >>>三张

这时我们就可以借鉴python函数的关键字参数:为其设置一个主键,与参数的值相对应,需要在关键词前面加'--'

  1. import argparse
  2. parser = argparse.ArgumentParser(description='姓名')
  3. parser.add_argument('--family', type=str,help='姓')
  4. parser.add_argument('--name', type=str,help='名')
  5. args = parser.parse_args()
  6. #打印姓名
  7. print(args.family+args.name)

这时我们的命令行要输入:(还是不需要输入”张”,只需要输入张,自动转化成字符串类型)

python demo.py --family=张 --name=三

就可以得到结果:张三

我们来看一个工程代码实际的例子:(对学习率的参数设置)

  1. parser.add_argument('--lr', dest='lr',
  2. help='starting learning rate',
  3. default=0.001, type=float)

 其采用了可选参数的做法,其中含有参数dest=’lr’,目的是:可以通过args.lr来调用参数的值

(若dest=’a’,则args.a可以调用学习率的值)

命令行需要输入的是:

python parser_func.py --lr=0.01

如果不输入lr这个参数,默认将lr设置为0.001,类型为浮点数。

应用:我们在pycharm自己定义一个函数用于接收参数:

  1. import argparse
  2. def parse_args():
  3. parser = argparse.ArgumentParser(description='Input arguments')
  4. parser.add_argument('--name', dest='name',
  5. help='user name',
  6. default='chuqiaosong', type=str)
  7. parser.add_argument('--age', dest='age',
  8. help='user age',
  9. default=23, type=int)
  10. args = parser.parse_args()
  11. return args
  12. args = parse_args()
  13. print(args.name)
  14. print(args.age)

主函数在调用这个函数,调用的过程就是接受参数的过程。如果我们直接运行不给参数,那么输出的参数args就是默认的default参数。我们不一定非要在命令行输入参数,pycharm可以帮助我们解决这个问题:

点击右上角的:Edit Configurations

在parameters内输入我们的参数:–name=chu –age=18(与默认值不同)

 再点击OK,再点击运行即可输出我们输入的参数:

可以看到下面命令行里pycharm自动帮我们写了argparse_test.py –name=chu –age=18