本文目录
- 基本用法
- 内置函数
- 内置数据类型
- 面向对象
- 装饰器
- pip用法
- os模块:处理文件和目录
- sys模块:系统层级的一些信息
- shutil模块:对文件、文件夹操作
- numpy库用法
- matplotlib库用法
- PIL库用法
- random库的用法
- pandas库的用法
- signal库的用法
- sklearn库用法
- tqdm库用法(进度条)
- unittest库:单元测试\
- ipdb库:断点调试
- argparse库:命令行调用时接收传入参数
- setuptools库:构建自己的python包
- pyinstaller:将py程序打包成exe
- h5py库(.h5文件格式)
- struct库用法
- Socket库:程序间的通信
- multiprocessing库的用法
- 网络爬虫
- selenium库:模拟浏览器行为的爬虫
- Flask库:用于建立网站
- 其他库的部分语句
- 出错信息收录箱
基本用法
在Windows开始菜单选择“命令提示符”,就进入到命令行模式,它的提示符类似C:\>
在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>
。
在Python交互式模式下,可以直接输入代码,然后执行,并立刻得到结果。
在命令行模式下,可以直接运行.py文件。
如何运行多个版本的python:把每个python.exe
改一下名字,比如python3, python2,然后在环境变量里都要添加进去,然后在cmd分别输入python3
或python2
就可以了。更简单的办法是py -2
启动python2.x,py -3
启动python3.x。ubuntu里是通过python2
和python3
分别运行两个版本的python。
文本编辑器用Sublime Text或Notepad++都行,但是绝对不能用Word和Windows自带的记事本。Word保存的不是纯文本文件,而记事本会自作聪明地在文件开始的地方加上几个特殊字符(UTF-8 BOM),结果会导致程序运行出现莫名其妙的错误。
关于import的一些理解
模块及包的导入
Python引用(import)文件夹下的py文件的方法
如何在某.py文件中调用其他.py内的函数
当前目录是指的你运行 .py 文件时 shell 的当前目录; import 进行搜索的时候并不会默认对当前路径进行搜索, 只会对运行的.py文件所在的目录进行搜索。 举个例子,python utils/image/xx.py
,其中xx.py
里import
了跟utils
同层的model
下的包,此时sys.path
中会有运行的.py文件所在的目录,也即utils/image/
,但不会有shell的当前目录,因此无法找到model,遇到这种情况要记得加上一行sys.path.append('./')
或者.append('')
,把shell的当前目录加进来,才不会出现找不到包的情况。
如果想要import一个data/目录下的xx.py,需要先把这个目录添加到import的搜索路径下:
import sys
sys.path.append('data/')
import xx
import同样也有作用域,在函数func里面import的函数,对外
需要调用其他py文件时才用import
,否则在IDLE里直接按F5就能运行了。
import module_name
后可以查看模块在本地的位置:module_name.__file__
当需要进行多层import时,如果不是"每一层的目录下面都有一个__init__.py:,则必须将模块层级写满,例如:
目录结构是a/b/c/cc.py,前面三层目录都没有__init__.py,那么要导入cc.py中的一个类Test
就只能把层级写满,对导入上层的模块也是如此:
from a.b.c.cc import Test
from a.b.c import cc
from a.b import c
from a import b
而如果有__init__.py,这个文件就像是一座桥梁,可以越过中间的模块名,直接导入底层的模块。当c/有一个__init__.py,内容为
# 注意不能写成 from cc import Test,原因下文会讲
`from .cc import Test`
时,导入语句可以缩减为:
from a.b.c import Test
from a.b.c import cc
from a.b import c
from a import b
在此基础上,如果b/有一个__init__.py,内容为
from .c import cc, Test
那么以下语句将是可行的:
from a.b import Test
from a.b import cc
from a.b import c
from a import b
在此基础上,如果a/有一个__init__.py,内容为
from .b import c, cc, Test
那么以下语句将是可行的:
from a import Test
from a import cc
from a import c
from a import b
from .cc import
和from cc import
的区别:from .cc
,是尝试在进行导入操作的py文件同级目录下的cc.py文件或cc/模块中导入东西,而from cc
则是尝试导入一个cc模块,python解释器只会在sys.path
当中寻找这个模块。二者的搜索位置是不同的,一般如果是自己写的包,想要导入自己项目目录下的文件,__init__.py
里头都是用的from .cc
,只有已经通过pip
安装好的库才可以通过from cc
的方式进行导入。
import进来的模块,不能再基于它进行import,比如:
from a import b
from b import c
如果目录结构是a/b/c,在a的同级目录下运行python,输入这两个语句,第一句不会报错,第二句会报错:No module named ‘b’,因为python不把这两个b当成同一个东西,它想去导入一个新的名为b的模块,结果没找到,就报错了。所以如果要导入c,只能从头再import一次:
from a import b
from a.b import c
接下来讲一下相对引用:如果目录结构是a/b/,在a/和b/的目录下都有__init__.py
,如果b/目录下有一个xx.py,里面有一句from . import yy
,这个.
意指xx.py的同级目录对应的package,也就是包b,所以这句from . import yy
就类似于from b import yy
(“类似于”的意思是,可以这么理解,但是直接写成from b import yy
可能是会有问题的);而如果是from .. import yy
,这个..
意指xx.py的同级目录的上级目录对应的package,也就是包a,所以from .. import yy
就类似于from a import yy
。还有一个容易被忽视的问题是,如果一个py文件被当做主程序入口(也就是被python直接执行,python xx.py
),那么这个文件的同级目录将不被当作package,不可以再导入这个包,因此才会出现在xx.py里写了from . import yy
(yy是与xx.py同在b/目录下的py文件),看起来明明没有问题,而且在其他文件导入xx.py也是正常的,可是直接python xx.py
的时候却会报类似"ValueError: attempted relative import beyond top-level package"的错误(参考链接)。
importlib
有时候我们会有重新导入某个模块以重新加载更新的需求,例如模块中某个过程依赖了其它文件中的变量,我们在外部修改这个变量,不会直接影响这个模块,因为它是在修改之前就导入的,要想应用对变量的更改,则需要重新进行导入。
使用importlib
包可以执行与导入模块相关的内容,如importlib.reload(module_name)
就可以重新加载某个模块。需要注意的是,对于基于unittest的单元测试,若在文件开头有:
import A.B
from A.B import C
在SetUp()
中有:
importlib.reload(A.B)
该操作并不能影响test_xx()
中的C的值,若要更新C的值,还需在test_xx()
函数中单独执行一次reload()
。
r’字符串’
表示字符串里的\不转义
空值用None
表示
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件。
u’...’
表示以Unicode表示的字符串
Unicode是默认编码,从Unicode转换为其他编码使用encode
方法,从其他编码转换为Unicode则用decode
方法:
把u'xxx'
转换为UTF-8编码的’xxx’用encode('utf-8')
方法:u'ABC'.encode('utf-8')
显示’ABC’
, u'中文'.encode('utf-8')
显示'\xe4\xb8\xad\xe6\x96\x87'
。
反过来,把UTF-8编码表示的字符串'xxx'
转换为Unicode字符串的u'xxx'
用decode('utf-8')
方法:'abc'.decode('utf-8')
得到u’abc’
。 直接 '\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
还不能输出汉字,得到的是u'\u4e2d\u6587'
,需要再print
一下:
print ‘%s’ %
(这里可以是gbk什么的字符串) print u‘%s’ %
(这里也得是u’×’或者decode一下)
接收输入:raw_input(‘提示内容’)
,内容只能是str类型,如果要输出中文(Unicode)则需要进行一波encode。不加u’’
时是ascii码,无法表示中文会出乱码;加u’’
时raw_input
不能接收Unicode,直接报错,所以需要:u’中文’.encode(‘gbk’)
,注意到u’中文’
相当于’中文’.decode(‘utf-8’)
格式化字符串的格式是 ’balabala’ % (格式化参数表)
,如果想输出%
则用%%
转义来表示
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
关于函数:
查看函数介绍 或者通过help(函数名)来查看帮助
把函数名赋给一个变量,就相当于给这个函数起了一个别名,a=abs
后就可以a(-2)
取绝对值了
定义函数:def 函数名(参数表):
下一行要缩进
没有return
语句,函数将返回None
。return None
可以简写为return
pass
语句可以用在函数体或者一般语句里,作为一个什么都不做的占位符。有些地方不可以放空,如if:
后面的语句,放空会报错,如果现在还没想好怎么写,可以先放个pass
可变参数
关键字参数:同可变参数链接
四种参数可以组合使用,在参数表中的顺序为:必选参数、默认参数、可变参数和关键字参数
默认参数
原文链接
调用函数报错:
TypeError:abs() takes exactly one argument (2 given) 该函数只有1个参数,但给出了2个。Python解释器会自动检查出参数个数不匹配错误
TypeError: bad operand type for abs(): ‘str’ 参数类型不对,不应是给出的str类型
高级特性之列表生成式
函数式编程:map和reduce
闭包
示例如下:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
内部函数不能修改外部定义的某些局部变量,比如整数,但可以修改list:
# UnboundLocalError: local variable 'x' referenced before assignment
def func_out():
x = 0
def func_in():
x += 1
func_in()
func_out()
# 正常, func_in()中修改了func_out()中定义的x
def func_out():
x = [0]
def func_in():
x.append(1)
func_in()
func_out()
# 正常, func_in()中修改了func_out()中定义的x
def func_out():
x = [0]
def func_in():
x[0] += 1
func_in()
func_out()
匿名函数:lambda x: x * x
实际上就是:
def f(x):
return x * x
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
错误处理:使用try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()
调用foo()
,foo()
调用bar()
,结果bar()
出错了,这时,只要main()
捕获到了,就可以处理。也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally
的麻烦。
程序文件在类和方法定义的后面一般有几句例程语句,如果写成
if __name__ == '__main__':
执行例程
则能使得文件作为库导入时不会执行例程。
关于字符串对齐
在中英文混合的字符串s
中,len(s)
表示s所包含的字符数,中文英文都算1个字符,而中文在print
的时候实际上是占了两个英文字符的宽度。len(s.encode("gbk")) - len(s)
可以得到s中中文字符的数量,因为编码成gbk之后,一个英文字符长度为1,一个中文字符长度为2,所以多出来的长度刚好就是中文字符数量。字符串的ljust
方法补足的是字符数,也即len(s)
,因此直接s.ljust(30)
是不行的,含有中文字符多的字符串,最后print
出来就会更胖,因此需要做一些处理:如果是中文符号,那么要补至的长度就减1,因为这个中文符号最后一个顶俩,print
出来的长度又加1加了回去。例子如下:
# 这种写法可以使得每个-->都是对齐的
for query_text in query_texts:
print(f"{query_text + ' ' * (25 - len(query_text.encode('gbk')))} --> {answer}\n")
Python如何读写大字典比较快(参考链接):
方法1:yaml模块,文件格式:.yaml,大小:46.3M,文件行数:573189
台式机读取耗时333秒,写入耗时148秒
容器中读取耗时380秒,写入耗时174秒
方法2:json模块,文件格式:.json,大小:44.2M,文件行数:1
台式机读取耗时1.3秒,写入耗时7秒
容器中读取耗时1.0秒,写入耗时9.8秒
方法3:pickle模块,文件格式:.pkl,大小:57.8M,文件行数:二进制文件,无行数概念
台式机读取耗时0.32秒,写入耗时0.12秒
容器中读取耗时0.46秒,写入耗时1.6秒
结论:yaml读写速度不可接受,pickle比json稍快,json可以直接预览,推荐使用json读写大字典
但是VSCode读写yaml文件速度是很快的,10秒以内可以完成读或者写操作,有点好奇它是怎么做到的。
内置函数
dir(class)
函数可以查看一个对象拥有的所有属性和方法
若for a, b in xx
可以运行,则a, b in xx
也可以运行,表示对生成器xx取一次值
hasattr(e,"code")
判断e
是否含有属性code
,以防直接访问时出错(当然也可以用try..catch
结构,二者是等效的)
zip()
函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同. 利用 * 号操作符,可以将元组解压为列表。
zip 方法在 Python 2 和 Python 3 中的不同:在 Python 3.x 中为了减少内存,zip()
返回的是一个对象。如需展示列表,需手动list()
转换. 实例如下:
a = [1,2,3] b = [4,5,6]
zipped = zip(a,b) #输出为 [(1, 4), (2, 5), (3, 6)]
zip(*zipped) #则还原为 [(1, 2, 3), (4, 5, 6)]
i = input(prompt)
用于接收用户输入,prompt为提示语,一般为str类型
int函数:将给定的字符串转成十进制数,如int(‘10000’, 2)
返回16
super(type(self), self)
可以返回一个实例的父类,在多态继承时,类名.__bases__
属性可以访问到每一个直接父类。如果要访问父类的父类,可以通过type(self).__base__.__base__
来实现(后记:发现super(父类名, self)
就可以访问到爷爷类了,不需要长长的__base__
,可读性太差)
with
关键字实际上是对类实例中__enter__()
跟__exit__()
方法的调用,在执行with
内部的语句库之前调用__enter__()
,执行结束后调用__exit__()
,详细内容看参考链接。如果有个需求需要if
做判断,来选择需不需要执行with
操作,正常写法是:
if use_with:
with autocast():
# do something
else:
# do the same thing
如果with
内部语句块很大,看起来就会很臃肿,同样的内容写了两遍。此时可以选择把with
拆开:
if use_with:
cast = autocast()
cast.__enter__()
# do something
if use_with:
cast.__exit__()
这样就还是只写一次代码块就可以了。
内置数据类型
list
有成员函数sort
,不需要通过内置sorted
函数来实现排序。同时sort()
也有key
参数可以选择参与比较的元素。更精细的比较函数则需要用到functool.cmp_to_key
(参考链接):
from functool import cmp_to_key
def func(x, y):
# 优先第一元素排序,如果相同就第二元素排序
# 注意返回值是个int型,根据它<0 >0 =0来判断两个数的大小关系
# 之前在C++里返回值是bool型,True表示x >= y,注意区分
if x[0] == y[0]:
return x[1] - y[1]
else:
return x[0] - y[0]
nums = [[2, 3], [2, 7], [3, 5], [4, 6], [3, 4], [2, 5]]
nums.sort(key=cmp_to_key(func))
# 排序后的nums = [[2, 3], [2, 5], [2, 7], [3, 4], [3, 5], [4, 6]]
不过,上述的双键值排序其实不用cmp_to_key就能实现:
# 返回一个tuple用于排序,x[0]在前表示第一关键字是x[0]且为升序,后面的-x[1]表示次要关键字是x[1]且为降序
people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
people.sort(key=lambda x: (x[0], -x[1]))
面向对象
运算符重载
在类型标注时使用类本身:python3.7及以上只需多加一行from __future__ import annotations
,参考链接
元类metaclass可以在构造类时根据传入参数的不同构造出不同的类,相当于是在比__init__()
更高的层级上做处理,它可以改变类方法的名字使其实际名字与代码中所写的不同,当然也有其他更高级的功能,详情请看廖雪峰老师对元类的讲解。
类定义的方法中__new__()
和__init__()
的区别:__init__()
比较常见,在我们初始化一个类实例的时候,参数会传给__init__()
,对这个实例中的属性做设置;但其实还有一个__new__()
函数可以重写,它的执行顺序在__init__()
之前,它必须返回一个对象作为self
,也就是说__init__(self)
里的self
是__new__()
的返回值。这两个函数的形参表很接近,一个是__new__(cls, param1, param2)
,一个是__init__(self, param1, param2)
,参考链接。
装饰器
详细讲解
一个基本的装饰器的写法是:
import functools
def log(func):
# @functools.wraps(func)固定搭配,保持装饰后的函数名__name__依然是原来的名字
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
一个需要传参的装饰器的写法是(多一层嵌套):
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
下面介绍一些花里胡哨的装饰器:
switch装饰器原文地址
class switch:
def __init__(self, func):
self.func = func
self.registry = {}
def case(self, value, func=None):
if func is None:
return lambda f: self.case(value, f)
self.registry[value] = func
return func
def __call__(self, *args, **kwargs):
return self.registry.get(args[0], self.func)(*args, **kwargs)
这个装饰器提供了一个switch分支的作用,它允许用户创建自己的switch分支方式,往里面添加case和对应的操作,举个例子:
@switch
def color(name): # Default when no match.
print('loser!')
@color.case('pink')
def _pink_power(name):
print("wow")
@color.case('blue')
@color.case('red')
def _powerpower(name):
print("god!!!!")
def input(name):
color(name)
input('pink') # -> wow
input('blue') # -> god!!!!
input('red') # -> god!!!!
input('lavender') # -> loser!
这个装饰器的神奇之处在于,第一装饰器是一个类不是一个函数,第二当它装饰了一个函数之后,这个函数的名字所指的便不再是是函数,而是一个switch实例,拥有了case()
方法,可以用这个方法再接着当装饰器修饰其他函数。具体来说,@switch
的时候执行了color = switch(color)
,所以原本的函数color
就变成了switch
类对象color
。类的__call__
方法使类对象可以像函数一样通过变量名调用:color("pink")
。最后讲一下color.case(“pink”)
的流程,首先调用情况是color.case("pink", func=None)
,会返回一个lambda
函数作为新的装饰器,它接收被装饰函数_pink_power
作为入参,第二次调用case
方法,调用情况是color.case("pink", _pink_power)
,此时才真正地注册了这个函数。
接下来讲一个__get__
函数的骚操作,这个操作的目的是在类调用__call__
的时候,既能支持普通函数,又能支持类的普通方法要多传入一个self。假设有一个类A,实例为a,有一个方法func(self, x1, x2)
,如果直接@switch
注册,它就变成普通函数的样子,只是参数表里多了一个self参数,后续a.func(x1, x2)的时候,由于此时a已经被装饰过,变成了switch类,它是不会传self给到func的,这就有了问题,接下来讲一下下面这个__get__
函数是怎么解决这个问题的:
class switch:
def __get__(self, obj, objtype):
"""Support instance methods.
当类对象a拥有一个值为switch对象的属性b时,a.b就会触发b.__get__(a, type(a))
因此这个函数不是只服务于self.__call__的,但由于此处switch对象对外的使用方式就只有a.b(x1, x2)这一种,
因此把__get__函数做成专门为了调__call__而使用是被允许的
"""
# inspect.signature获取函数的参数表,用于判断被装饰的函数是普通函数还是类的方法
params = inspect.signature(self.orig_func).parameters
if "self" in params or "cls" in params:
# classmethod or instancemethod need to pass the class instance
return functools.partial(self.__call__, obj)
else: # function or staticmethod
return self.__call__
也就是说,如果是普通函数,当外部a.func想要拿这个func做点什么的时候,直接给它一个__call__
就可以了,而如果是类的方法,除了给__call__
,还要实现加上来要方法的这个类本身也就是代码里的obj
,这个__get__
很神奇,还可以从被调用对象中获取调用对象的值。__get__讲解
在进一步实现的过程中,我们又遇到了传递局部变量的问题:
def func(x1, x2):
def func1():
a = 1
def func2():
# how to get a?
pass
func1()
func2()
func(x1, x2)
假如一个流程有多个部分,对外只展示整体的接口,内部的func1()
生成了一些局部变量,func2()
需要使用,但因为某种原因不能直接把两个函数合为一个,那有没有什么办法做到呢?还真有,这里再来一个骚操作,sys.setprofile()
,参考链接1,setprofile讲解。
from copy import copy
class switch:
# 最开始的函数还是使用原本的参数表
def func(self, x1, x2):
print(x1, x2)
# 其他函数都只有一个**kwargs参数接收所有关键字参数
def func1(self, **kwargs):
a = 1
def func2(self, **kwargs):
kwargs["a"] += 1
def __call__(self, *args, **kwargs):
def tracer(frame, event, arg):
"""在函数执行结束后提取其局部变量的辅助函数
Args:
frame (frame): 函数视角下的调用堆栈
event (str): 时间,"call"表示即将被调用,"return"表示刚被调用完
arg (None or builtin_function_or_method): 不知道是啥
"""
if event == "return":
self._locals = frame.f_locals
sys.setprofile(tracer)
self.func(*args, **kwargs)
# 按照目前的设计,第一次调用结束后可以直接使用self._locals
self.func1(**self._locals)
_locals = copy(self._locals)
self._unpack_locals(_locals)
# 不能直接使用self._locals,因为每个函数调用结束后这个值都会变,进出_unpack_locals()后就又被刷新了,所以我们应当在进去之前就对它的值进行拷贝
self.func2(**_locals)
# 清空,以免影响下一次__call__
self._locals = {}
@staticmethod
def _unpack_locals(_locals):
item = _locals.pop("kwargs")
# inplace update
_locals.update(item)
sys.setprofile()
的作用是设置一个函数,这个函数会在每个函数的调用前和调用后都执行一遍,就像是函数调用堆栈门口的保安。frame.f_locals
可以提取出局部变量,我们在tracer
函数里把局部变量抽出来,这是一个dict
,我们把它解开就可以传给下一个函数了。在调用完func1
之后,局部变量里会多出一个kwargs
,需要把它的参数解开来放回外层的字典,才能接着传给func2
,相当于解了一层套娃,也就是self._unpack_locals()
在干的事情。
其他杂项
当多个装饰器同时修饰一个函数的时候,内层装饰器先生效,先进行装饰,最后返回的函数再作为外层装饰器的入参。
类实例的.__dict__
是一个包含所有属性attribute的名称和值的字典,不包含方法method。
pip用法
pip install package_name
安装库
pip check
检查包的依赖关系
pip freeze > requirements.txt
生成依赖文件,有了这个,到了新的环境之后无需再一个个库重新pip install
,只需执行pip install -r requirements.txt
,它就会自动安装上去。(如果需要生成在虚拟环境下的依赖关系,需要先进入虚拟环境)
添加源:可以在使用pip
的时候加上-i 网址
,也可以直接修改配置文件:
Linux下,修改 ~/.pip/pip.conf (没有就创建一个文件夹及文件。文件夹要加“.”,表示是隐藏文件夹);windows下,直接在user目录中创建一个pip目录,如:C:\Users\xx\pip,新建文件pip.ini。内容如下:
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
[install]
trusted-host=mirrors.aliyun.com
pip安装旧版本的pytorch
pip可以直接安装whl文件:pip install xx.whl
pip免密码下载:在一些自动化脚本中会有这样的需求,全程无人操作,自动完成python包的拉取和安装,平时遇到有密码认证的pypi服务器都是要人工输入的,不利于自动化。在原有的pip install --index-url http://ip:port
命令中把--index-url
参数值改成http://username:password@ip:port
,就可以了,参考链接
pip免密码上传:修改一下~/.pypirc
:
[distutils]
index-servers = local
[local]
repository: http://ip:port
username: username
password: passwd
然后上传的时候使用python setup.py sdist upload -r local
命令就可以了。
sys模块:系统层级的一些信息
# 返回当前所在位置的函数名
sys._getframe().f_code.co_name
shutil模块:对文件、文件夹操作
复制文件:
shutil.copyfile("oldfile","newfile")
#oldfile和newfile都只能是文件
shutil.copy("oldfile","newfile")
#oldfile只能是文件夹,newfile可以是文件,也可以是目标目录
复制文件夹:shutil.copytree("olddir","newdir")
#olddir和newdir都只能是目录,且newdir必须不存在
移动文件(目录):shutil.move("oldpos","newpos")
删除目录:shutil.rmtree("dir")
#空目录、有内容的目录都可以删
matplotlib库用法
matplotlib.pyplot (以下简称plt) :
基本命令与matlab无异。区别在于,plt进行plot之后不会立即显示出图像,在plt.show()
之后才显示,显示之后plt就被清空了,无法像matlab一样重复显示图像。
plt.plot(x, y)
画图,产生一个lines对象,但不显示出图像来。Tensor不能直接拿来画图,要通过Tensor.numpy()
转换成ndarray才能画图。颜色、线型的设置同matlab
line, = plt.plot(x, y)
可以获得这条线的句柄,获得句柄之后可以做很多事情,比如
line.set_dashes((4,1))
自定义虚线的长度样式
plt.xlabel(‘balabala’)
plt.ylabel类似
plt.xlabel(r"$\alpha_i > \beta_i$")
在label中使用上下标,模仿LaTeX语法([原文地址]
plt.xlabel(‘balabala’, fontsize=fontsize)
# fontsize设置字体大小
plt.xticks([1, 2, 3, 4, 5], [0.333, 0.5, 1.0, 2.0, 3.0])
# 修改坐标轴上的标示,第一个参数是在什么位置打标示,第二个参数(可选)是打上什么标示,不填就打出打第一个参数的值
plt.tick_params(labelsize=25)
# 设置坐标轴上的数字大小
plt.axis('off')
# 隐藏坐标轴
plt.title(‘balabala’)
# 设置标题
plt.legend([‘bala’, ‘laba’])
比matlab多了一对中括号
plt.legend([‘bala’, ‘laba’], loc="upper right")
# 有9个string可以控制图例的位置
plt.legend([‘bala’, ‘laba’], loc=[0.7, 0.3])
# 可以用数字更加具体地指定位置
plt.subplot(m, n, p)
# 画m行n列个图,现在是第p个
plt.subplots_adjust()
# 用于调整调整子图的间距,参考链接
plt.suptitle("rgb_*.jpg")
# 给多个子图添加一个总标题
plt.pyplot.hold()
与matlab的hold相似,hold(True)
是开,False
是关,不带参数是切换。但不被推荐使用,因为在python中只要你不show,前面的多个plot就相当于是开了hold。
plt.savefig(imgName)
# 保存图片
plt.savefig(imgName, bbox_inches = 'tight')
# 有时候保存下来的图片四周会有比较大的空白,加个参数去掉这些空白;如果plt.show()正常但保存时某个地方比如下方缺了一小块,也是加上这个参数就正常了
plt.savefig("rescaled_P.png", dpi=300)
# 有时候保存下来的图片有点糊,需要加大dpi
plt.savefig("rescaled_P.pdf", dpi=300)
# 据说保存成pdf保真度会高一些,LaTeX可用
plt.rcParams['savefig.dpi'] = 300
# 类似于全局变量,一次设置每次savefig都有效
plt.rcParams['figure.dpi'] = 300
# 控制plt.show()的dpi
有时候图片视觉效果不好不是因为dpi不够,而是因为图片的物理尺寸不够大:
plt.rcParams['figure.figsize'] = (12.0, 8.0) # set figure size, default (6.0, 4.0)
# 图片双倍大小,空间一下子变得宽
plt.axhline(y=0.0, c="r", ls="--", lw=2)
# plt有专门画水平线跟竖直线的函数,可以不用自己用plot画。参数分别代表位置、颜色、线型和线宽
plt.axvline()
#画竖直线,用法同axhline
去掉图的某个边框
实现坐标轴截断
用matplotlib绘制gif图:样例代码;详细剖析原理
绘制柱状图
绘制双Y轴坐标图
绘制双Y轴的时候需要获取axis对象,每个axis对象就是一条轴,后面都是对axis对象进行操作,用的ax1.xx
而不是plt.xx
。需要注意的是plt里面的一些函数到axis对象中换了名字,都加上了set_
, get_
前缀,如plt.ylabel
, plt.ylim
, plt.title
,到了axis对象就变成了ax1.set_ylabel
, ax1.set_ylim
, ax1.set_title
Matplotlib:点、实虚线条、柱状图阴影填充
在新环境刚安装matplotlib时有时候会出现_tkinter.TclError: couldn’t connect to display "localhost:14.0"报错,此时需要修改matplotlibrc文件,给它加上一句backend: Agg
就可以了。配置文件的路径可以通过
import matplotlib
, matplotlib.get_configdir()
来获得,参考链接
PIL库用法
用PILj将多张图片合并成一个gif图(参考链接):
from PIL import Image
from tqdm import tqdm
imgs = []
for _ in tqdm(range(30), desc="running"):
img = Image.open(f"tmp/{i}.png")
imgs.append(img)
#对一个list中的第一张图片进行save,并令save_all=True,append_images=这个list,就可以将整个列表保存到同一张gif图中。
# duration表示单帧图像停留的时间(ms)
imgs[0].save("tmp.gif", save_all=True, append_images=imgs, duration=500)
random库用法
random.sample(seq, k)
从序列seq(一般是个迭代器,如range(xx)
)中随机选取k个互不相同的数,适合用来生成随机整数序列
random.randrange方法:返回指定递增基数集合中的一个随机数,基数缺省值为1。
用法:
import random
random.randrange([start,] stop [,step])
(先看上面numpy的seed部分讲解再来看random会发现其实它们很像)random.seed(123)
设置随机数种子,random.getstate()
可以获得当前随机数生成器的状态,它是一个长度为3的tuple,下记为state。state[0]第一个是一个int值3,可能是表示使用了MT随机数生成算法,state[2]貌似一直是None不变,state[1]是一个长度为625的类型为uint32的tuple,前624个数的范围是[0, 4294967295],最后一个数是个从1变化到624的int。每次进行取随机数操作比如random.randint(xxx)
,state[1][-1]就会增加1,有时候会增加2或3,到624之后重新回到1,此时state[1][:-1]的数据全部发生了变化。我猜这624个数可能对应这一个阶段的624个随机数的计算依据。执行random.seed(xxx)
时,会把state[1][0]赋值为2147483648,把state[1][-1]赋值为624,其余元素值与设置的seed有关
总结一下,如果是想要控制某个位点的输出一致,使用random.seed()
即可;如果是前面已经设置了seed了,现在想要核对中间的状态或者是从中间位置继续生成随机数,且想要保持一致性,那就要用如下的方法:
state = random.getstate()
random.setstate(state)
pandas库用法(表格处理)
处理xlsx文件
十分钟搞定pandas
pandas数据结构和基本操作
写入不需要索引号的数据到csv文件:
# 注意键值要用[]括起来
info = pd.DataFrame({'angle': ['{:.2f}'.format(3.1415)],
'scale': ['{:.2f}'.format(3.0)],
'x_bias': ['{:.2f}'.format(0.0)],
})
try:
df = pd.read_csv('logs/summary.csv')
# concat连接两个列标签相同的dataframe
df = pd.concat([df, info])
except: # had no infos, create an empty dataframe
df = info
# index=None参数表示写入时不带index,如果带上index在下次读取时可能会出现"多出一个UnName列“的现象
df.to_csv('logs/summary.csv', index=None)
创建一个空DataFrame,并逐行插入数据(原文链接):
#创建一个空的Dataframe
result =pd.DataFrame(columns=('idx','degree','weight','diameter'))
#将计算结果逐行插入result,注意变量要用[]括起来,同时ignore_index=True,否则会报错,ValueError: If using all scalar values, you must pass an index
for i in idx:
degree=
weight=
diameter=
result=result.append(pd.DataFrame({'idx':[i],'degree':[degree],'weight':[weight],'diameter':[diameter]}),ignore_index=True)
ValueError: If using all scalar values, you must pass an index.的解决方案
插入新列:
df.insert(df.shape[1], 'd', 0) # 新列名为d,整列的值都是0
合并dataframe:同样的列名,两个df合并成一个新df:df3 = pd.concat([df1, df2])
;append
将一个df接在另一个df的后面:df3 = df1.append(df2)
,但它也是生成了新的df,所以没有必要,直接用concat
就行了。
只读文件某几行,以作为后续逻辑的判断依据,参考链接:
df = pd.read_csv(path, header=None, nrows=1, encoding='utf-8') # 只读第1行且没有标题行
将dataframe保存为csv文件:
# 写入时不要表头
df.to_csv('logs/summary.csv', header=None)
保存成csv文件时中文变成乱码的解决方案:encoding="utf_8_sig"
,参考链接
pandas读取csv文件,有时候会发现数字前面的0没了,解决方案:
# 不使用DataFrame,指定dtype=object即可,其他用法同DataFrame
data1 = pd.read_excel(filename, dtype=object)`
signal库用法
signal.convolve2d (待补充)
矩阵的广播:向量可以以某种规则与矩阵做运算(如相加)
3×2的矩阵在和1×2的向量v做相加时,向量v会自动扩充成np.tile(v,(3,1)) ,与矩阵同维度后再相加。
Notepad++设置用空格取代TAB键:设置(T) ⇒ 首选项… ⇒ 语言 ⇒ 标签设置(有的版本叫做制表符设置),勾选 “以空格取代”。以后输入Tab键的时候就会自动以所设置的4个空格代替。
sklearn库用法(以下简称skl)
skl.model_selection.KFold 将数据分成K份,用于K折交叉验证。返回的是一个对象,调用对象的spilt方法来实现分割
kf = skl.KFold(n_split=2) 设置分割份数,其他参数略
for train_index, test_index in kf.split(X): 循环一共是n_split次,每一轮循环中的train_index与test_index就是一种分割方式大小之比约为K-1: K,test_index之间不会重复
skl.model_selection.StratifiedKFold 与KFold类似,但更优秀,它不仅将数据分成K份,同时保证训练集和验证集的比例在每一份中基本相同。
kf = skl.StratifiedKFold(n_split=2) 设置分割份数,其他参数略
for train_index, test_index in kf.split(X, Y) X是数据,Y是标签,split将根据Y来区分训练集和验证集
tqdm库用法
详见地址:https://blog.csdn.net/langb2014/article/details/54798823
scipy库用法
scipy.cluster.vq.kmeans(obs, k_or_guess, iter=20, thresh=1e-05, check_finite=True) 输入obs是数据矩阵,行代表数据数目,列代表特征维度; k_or_guess表示聚类数目;iter表示循环次数,最终返回损失最小的那一次的聚类中心; 输出有两个,第一个是聚类中心codebook,一个k×N矩阵,第二个是损失distortion,即聚类后各数据点到其聚类中心的距离的加和。只求质心不分类
scipy.cluster.vq(obs, code_book, check_finite=True) 根据聚类中心将所有数据进行分类。obs为数据,code_book则是kmeans产生的聚类中心。输出同样有两个:第一个是各个数据属于哪一类的label,第二个和kmeans的第二个输出是一样的,都是distortion
scipy.cluster.vq.whiten(obs) 对数据进行白化(漂白)。数据白化可以减少特征之间的相关性,同时使得特征具有相同的方差(协方差阵为1)。obs是输入数据,输出是一个同维度的被漂白后的矩阵
百度语音API的用法:
安装:pip install aip
python实现matlab中find函数的方法:
①idx = [idx for (idx, val) in enumerate(a) if val > 2] 再 vals = [val for (idx, vals) in enumerate(a) if val > 2]
②idx = np.where(a > 2) 则 a[idx] 即为所求
③a[a>2] 最简单
_符号与matlab中的~用法相同,用于不接收某个返回值
torch.nn.functional.relu6(input, inplace=False) → Tensor[source]
Applies the element-wise function (\text{ReLU6}(x) = \min(\max(0,x), 6)).
See ReLU6 for more details.
ipdb库:断点调试
刚接触python的时候只知道用print来查看程序运行的中间结果,但是那样不够高效,定位不准调试很麻烦。后来接触了jupyter notebook跟ipython,发现了交互式编程的好处。但是在大型项目里头不可能靠一个jupyter notebook来运行,还是得回到.py文件,那么在.py文件中怎么进行交互式编程调试呢?这就要靠ipdb库了。
from ipdb import set_trace
# do something
set_trace()
# do some other things
一个简简单单的set_trace()
就可以插入断点,当程序运行到该位置时就会触发交互式编程。
ipdb各指令详解
n
单步执行,s
步入函数,r
从当前函数返回,c
运行至下个断点
当n
触发报错时,w
查看当前报错堆栈,d
往下一层,u
往上一层,ll
显示报错行以上的本层堆栈代码
之前n
触发报错时不知道怎么复现,还得手动在内层函数set_trace()
然后重新运行代码,原来只要d
几下就能看到详细内容了,哈哈。
argparse库(命令行调用时接收传入参数)
该库的一般使用方法是:
parser = argparse.ArgumentParser(description='xxxxx', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
# 添加一个参数, 可以通过--data_path_source xxxx 或者-d xxxx来传入参数, type指定类型, 可以是int或str, default指定默认值, help写明在help这个.py文件或者传入参数格式不合法时, 会弹出的信息
parser.add_argument('--data_path_source', '-d', type=str, default='data/Office-31/webcam', help='Root of the source dataset (default: webcam in Office-31)')
# bool类型的参数比较特殊, 不是用type=bool来设定的, 而是用action属性来指定, 当action='store_true'时, 传入--mixup(后面不用带一个值)即使得该参数为真, 不传则使用默认值(因此default跟action必须是相反的, True就要搭配store_false)
parser.add_argument('--mixup', default=False, action='store_true', help='The indicator of whether use mix-up trick (default: no)')
# choices关键字可以限定输入的范围
parser.add_argument("--mode", type=str, default='edge', choices=['edge', 'negative'], help="Training mode for training, 'edge' or 'negative' (default: 'edge')")
# nargs='+'的设定可以通过python arg.py --nargs 1234 2345 3456 4567 的方式传入一个list
parser.add_argument('--nargs', nargs='+')
# argparse可以接收各种类型格式的数据,也包括json数据(使用时注意传入参数在原来的json数据两端要加个单引号):
parser.add_argument('-d', '--my-dict', type=json.loads)
# 最后面可以加一个nargs=argparse.REMAINDER类型的参数,它接收剩下的所有参数并保存为一个list,
# 可以通过args.others来访问这个list,命令行中所有不在前面定义里面的参数都会保存到这里。
# 不过这样一来参数表意就不明确了,所以我不用这个东西
parser.add_argument("others", default=None, nargs=argparse.REMAINDER)
args = parser.parse_args()
# 至此, args就可以被用到main.py中了. 通过args.mixup来获取从命令行传入的值
pyinstaller:将py程序打包成exe
首先安装pyinstaller:pip install pyinstaller
然后进行编译:Pyinstaller -F -w main.py
然后进到代码目录下的dist/
目录,里面的main.exe
就是可执行文件了
pyinstaller会打包我们import
到的模块,不会打包那些pip list
能看到但实际没有import
的模块。
pyinstaller只适合打包小程序,它体积太大了,如果import numpy
,编译出来就会有200M,编译一次超级久,不要numpy的话一般在10M以内,其他更大的库比如pytorch就更不要说了。
打包教程,优化教程
打包之后发现__file__
变了,exe执行的时候好像是在系统的临时目录下放的py文件,所以__file__
天差地别。那么如何获取到一个在调试时的py文件的路径以及发布时的exe文件的路径呢?答案是os.path.realpath(sys.argv[0])
,python真强,啥鬼需求都能做。参考链接
h5py库(.h5文件格式)
struct库用法
struct
库常用于利用socket
传送长报文(如图片)的时候,对数据段长度进行计算。
对struct.pack的讲解
struct.pack()
就是把当前的数据变换成能直接传输的二进制流,以struct.pack(">L", 1)
为例,L
是无符号长整型,跟python中的int
相对应,占用4
个字节,因此报头长度为4
;>
表示大端对齐,也即高位在左低位在右,1
表示为b"\x00\x00\x00\x01"
有时候某个字节会变成符号,这是因为那个字节刚好小于128,跟ascii码表里的某个码相对应,因此显示为字符,如b"\x00\x83\xd6\x00"
=8640000,而b"\x00\x83\xd6&"
=8640038,因为ord("&")=38
。
Socket库:程序间的通信
Python 网络编程入门——用 Socket 做一个风花雪月服务器
记sock
为socket.socket()
函数返回的对象
sock.send()
与sock.sendall()
的区别:sock.send(data)
一次只发送n
个字节,不一定完全发完,其返回值的含义是成功发出的字节数,如果网络繁忙,这个数可能会小于data
的字节数;sock.sendall(data)
通过重复调用send()
来把所有的数据发送出去,该函数如果报错,无法得知已成功发送的字节长度。
multiprocessing库的用法
Python 进阶必备:进程模块 multiprocessing
import multiprocessing as mp
q = mp.Queue()
# 这个q只能用于mp.Process(), 不能用于mp.Pool的apply跟apply_async,原因见下面链接
p = mp.Process(target=func, args=(q,))
# 如果又想用Pool又想用Queue,还有另一个东东可以用:
m= mp.Manager()
q = m.Queue()
p = mp.Process(target=func, args=(q,))
python multiprocess.Queue - RuntimeError: Queue objects should only be shared between processes through inheritance的解决方案
用进程池生成训练数据的时候,要使用pool.apply_async
而不是pool.apply()
,apply
相当于主进程一个一个送进去,一直在外面等,效率跟用一个进程是一样的,async
才能做到放进去然后继续往后走,继续for循环不断往里塞参数去生成数据(所以我也不是很懂apply
会适用于什么情况,感觉用apply
跟单进程没有差别呀)。而因为async
是放完就走,如果不加限制就很可能在数据还没生成完的时候主代码已经来到读取数据的地方,此时自然读不到数据,因此async
放好之后要来个pool.close()
跟pool.join()
,等进程们把数据生成完再往下读数据
from multiprocessing.dummy import Pool
# 开8个 worker,没有参数时默认是 cpu 的核心数
pool = Pool(processes=8)
# 在线程中执行 urllib2.urlopen(url) 并返回执行结果,urls是一个list,每个元素是一个url
results2 = pool.map(urllib2.urlopen, urls)
subprocess库
call
函数:rc = subprocess.call(["ls","-l"]) # rc接收调用子进程后的returncode
也可以这样写:rc = subprocess.call("ls -l", shell=True)
,我们使用了shell=True
这个参数。这个时候,我们使用一整个字符串,而不是一个表来运行子进程。Python将先运行一个shell,再用这个shell来解释这整个字符串(原文链接)
网络爬虫
URL:Uniform Resource Locator,统一资源定位符,也就是我们常说的网址。互联网上的每个文件都有一个唯一的URL,它指出了文件的位置以及浏览器应该怎么处理它。URL由三部分组成:协议+所在主机IP地址+目录和文件名
静态网页:查看服务器里的东西
动态网页(现在多是动态):用户传参数给服务器(如用户名和密码),服务器给出对应的响应。传参数有两种方法:①GET方法:把请求的数据附在URL之后,以?分割URL和传输的数据,参数之间以&相连,如果数据是英文字母/数字则原样发送,如果是空格则以+代替,如果是中文或其他字符则直接对字符串进行加密。 ②POST方法:把提交的数据放在HTTP包的包体中,用户无法在网址上看到传送的数据。返回的Response对象直接print不能读出内容,需要print(xx.content)
Python爬虫开发与项目实战(电子书)
Python爬虫入门(3):Urllib库的基本使用
Windows 10 Linux子系统 (wsl)学习手记
python3爬虫问题 POST data should be bytes or an iterable of bytes
,解决方法:data = urllib.parse.urlencode(values).encode(encoding='UTF8')
,即在后面加一段.encode(encoding=’UTF8’)
字符串连接如果用+
不行的话,就采用格式化字符串:str=”%s str2 %s” %(str1,str3)
Vim命令合集
详解:Python2中的urllib、urllib2与Python3中的urllib以及第三方模块requests
python3使用cookie
Python3爬虫实战:爬取大众点评网某地区所有酒店相关信息
Python3常见问题和解决方案(Python2 和 Python3的区别)[持续更新]
POST请求传送正文的4种方式
requests.post()
里有一个参数名叫json
,一个参数名叫data
,一开始容易搞不明白这两个是要干嘛,其实就是,网络上传输的时候都是JSON字符串,如果我们已经json.dumps()
自己转换好了,那就走requests.post(json=xx)
,如果还是dict
的形式,那就走requests.post(data=xx)
,函数里头会帮我们转换成json
格式的字符串
python 一个.py文件如何调用另一个.py文件中的类和函数
在浏览器上如何查看或更改页面编码模式
Python中的str与unicode处理方法
Python: 在Unicode和普通字符串之间转换
\u672c\u52a8\u753b\u300a\u8d85\u65f6
这种码是Unicode编码,python中可以直接显示Unicode编码(Unicode是16位的Utf-8是8位的,两者不一样):
s=u'\u9738\u738b\u522b\u59ec'
print (s)
s.encode('gbk')
s.encode('utf8')
print (s.encode('gbk').decode('gbk'))
print (s.encode('utf8').decode('utf8'))
最基本的爬法(爬下整页html代码):
import sys
import urllib.request
res=urllib.request.urlopen('https://baike.baidu.com/item/SDF-1/7849075')
htmlBytes=res.read()
print(htmlBytes.decode('utf-8'))
str转bytes叫encode,bytes转str叫decode,上面的代码就是将抓到的字节流给decode成unicode数组
#-*- coding:utf-8 -*-
import sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改变标准输出的默认编码
r’hello’ 前面的r的意思是“原生(原始)字符串”,如 r’\t’ 等价于 \t
aaa="Hello world"
print(aaa)
unicodestring = u"Hello world"
print(unicodestring)
utf8string = unicodestring.encode("utf-8")
print(utf8string)
asciistring = unicodestring.encode("ascii")
print(asciistring)
isostring = unicodestring.encode("ISO-8859-1")
print(isostring)
utf16string = unicodestring.encode("utf-16")
print(utf16string)
Python的三种代码续行书写方法
# 第一种:三个单引号
print (''' 我是一个程序员
我刚开始学习python''')
# 也可以加上...表示连接,可加可不加
print ('''line1
... line2
... line3''')
# 第二种:三个双引号
print (""" 我是一个程序员
我刚开始学习python""")
# 第三种:\结尾
print ("我是一个程序员,\
我刚开始学python")
python的注释:单行#,多行’’’或””” 快捷操作见下图:
清屏:ctrl+L
python str与bytes之间的转换:
# bytes object
b= b"example"
# str object
s = "example"
# str to bytes
bytes(s, encoding = "utf8")
# bytes to str
str(b, encoding = "utf-8")
# an alternative method
# str to bytes
str.encode(s)
# bytes to str
bytes.decode(b)
Python 有办法将任意值转为字符串:将它传入repr()
或str()
函数。函数str()
用于将值转化为适于人阅读的形式,而repr()
转化为供解释器读取的形式。在终端中,print(a)
调用的是对象的__str__()
方法,a
调用的是对象的__repr__()
方法;__str__
的返回结果应当可读性强,__repr__
的返回结果应当更准确(参考链接)。
两个字符串可以用+
实现相加。
将字符串a中的’bb’替换成’cc’: a.replace(’bb’,’cc’)
a.strip()
:把头和尾的空格去掉
a.lstrip()
:把左边的空格去掉
a.rstrip()
:把右边的空格去掉
a.replace('c1','c2')
:把字符串里的c1替换成c2。故可以用replace(' ','')
来去掉字符串里的所有空格
a.split(b)
:根据指定分隔符b对字符串a进行切片,如果参数num 有指定值,则仅分隔 num 个子字符串。split支持正则表达式。
a.join(b)
:根据指定分隔符a把包含多个字符串的列表b连接成一个字符串
删除字符串中的连续空格只保留一个:不区分tab的话,
这样就行了: ' '.join(s.split())
1.使用re.compile
re模块中包含一个重要函数是compile(pattern [, flags])
,该函数根据包含的正则表达式的字符串创建模式对象。可以实现更有效率的匹配。在直接使用字符串表示的正则表达式进行search,match和findall操作时,python会将字符串转换为正则表达式对象。而使用compile完成一次转换之后,在每次使用模式的时候就不用重复转换。当然,使用re.compile()函数进行转换后,re.search(pattern, string)的调用方式就转换为 pattern.search(string)
的调用方式。findall依然可用:search_result = re.findall(pattern, message)
匹配中文:加个u表示Unicode编码即可 pattern = re.compile(u'歌|音乐')
其中,后一种调用方式中,pattern是用compile创建的模式对象。如下:
>>> import re
>>> some_text = 'a,b,,,,c d'
>>> reObj = re.compile('[, ]+')
>>> reObj.split(some_text)
['a', 'b', 'c', 'd']
2.不使用re.compile
在进行search,match等操作前不适用compile函数,会导致重复使用模式时,需要对模式进行重复的转换。降低匹配速度。而此种方法的调用方式,更为直观。如下:
>>> import re
>>> some_text = 'a,b,,,,c d'
>>> re.split('[, ]+',some_text)
['a', 'b', 'c', 'd']
格式化输出:
s = "%05d" % num # 将num转成字符串赋给s,如果num少于五位则在左边补0
正则表达式:(a和b是两个任意符号):
a.b贪婪搜索,以a开头b结尾,能拉多远拉多远
a.?b 懒惰搜索,以a开头b结尾,遇到第一个b就停下来
a|b a或b,用于实现类似switch的功能(不过python里面没有switch)
比如搜索一个标题,用正则表达式搜索到之后返回的是整段字符串:
如,表达式为<h3 class="core_title_txt.*?>(.*?)</h3>
,得到的结果是
<h3 class="core_title_txt pull-left text-overflow " title="纯原创我心中的NBA2014-2015赛季现役50大" style="width: 396px">纯原创我心中的NBA2014-2015赛季现役50大</h3>
但我们只想要标题文字,这个时候就要用到分组了:
分组的符号是(),使用之后中间的内容会被保存在一组中,如:
<h3 class="core_title_txt.*?>(.*?)</h3>
此时我们就将标题部分保存在了第一组里面,最后通过group把它调出来就能用啦!
print(result.group(1))
如果想显示所有分组,则group中不要带参数或者给0,如果正则表达式中没有括号,也即没有分组,使用group(1)
将会报错,此时只能用group()
或group(0)
。
如果表达式中有多个括号,按左括号的顺序编号,如:
<h3 class="co(re_tit)le_txt.*?>(.*?)</h3>
会得到两组分组,re_tit是第1组,标题内容是第二组。
为什么要使用Cookie呢?Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的。那么我们可以利用Urllib2库保存我们登录的Cookie,然后再抓取其他页面就达到目的了。
selenium库:模拟浏览器行为的爬虫
driver.execute_script("window.open('');")
运行JS脚本新开一个窗口
模拟点击页面的空白处后进行按键(如Ctrl+End滚动到底部):
driver.find_element_by_tag_name('html').send_keys(Keys.CONTROL + Keys.END)
在input框中输入Keys.RETURN或Keys.ENTER相当于打回车,用于更新页面
填表单需要处理下拉框,如何选择下拉框的值呢?selenium提供了一个类,专门用于处理选择框:selenium.webdriver.support.select.Select
大部分有文本的按钮可以通过driver.find_element_by_link_text("按钮文本")
进行定位,不过<span>
不行,它可以通过driver.find_element_by_xpath("//span[text()='文本内容']")
进行定位,参考链接
selenium.webdriver遇到的报错(故障)及原因:
Element could not be scrolled into view. : 有可能是因为浏览器窗口太小,看不到该元素了(跟我们直接看浏览器一样,窗口小有的东西是点不到的);二级下拉框如果没有先选中其上级,就不会显示出来,直接click就会报这个错
在页面上能看到的元素,在page_source里却看不到:可能是因为html文档里面还有<iframe> </iframe>
,即多个页面,selenium一开始只能看到最外面的页面,在iframe标签里的就看不到了。需要用driver.switch_to.frame(id或name)
切进frame里才能看到,看完切回原来的主页面使用driver.switch_to_default_content()
方法。
一个可以规避网站selenium检测的driver写法(参考链接):
def get_driver():
options = webdriver.ChromeOptions()
options.add_argument("--disable-extensions")
options.add_argument("--disable-gpu")
#options.add_argument("--no-sandbox") # linux only
# 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Selenium
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome("chromedriver.exe", options=options)
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {"headers": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36"}})
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
return driver
driver = get_driver()
selenium有时候也会被反爬,如果实在找不到解决方案,还有这一招:人工打开浏览器访问网页后再交由selenium接管,参考链接
Flask库:用于建立网站
下文适用于基于Flask 1.1.2。
Flask中的数据库操作
flask配合git hook实现Git push后自动编译rst文件,这里展示flask一端的代码:
import os
import threading
from flask import Flask, request
LOG_FILE = "/data/maintainer/post_receive_log"
SPHINX_LOG_FILE = "/data/maintainer/sphinx_build_log"
app = Flask(__name__)
def pull_and_build(repos_path, build_path, timestamp):
r"""拉取远程git仓库并编译
Args:
repos_path (str): 本地的git仓库路径
build_path (str): 放置编译结果的路径
timestamp (str): 随http请求一同发送的时间戳
"""
# 每一个操作的输出都写进log,后面有问题才有东西可查。把操作的时间写上,不然log太乱了看不清楚
os.system(f"echo receive update request at {timestamp}, processing... >> {LOG_FILE}")
# 把更新的版本拉到这边的仓库目录下
os.system(f"git -C {repos_path} pull --recurse-submodules origin")
# 最好把整个编译结构目录都rm掉再重新编译,时间会长一点,但可以确保不会有奇怪的bug
os.system(f"rm -rf {build_path} >> {LOG_FILE}")
# 单次编译页面,使得编译结果能在浏览器上访问到,该路径是http://ip/mixedai,-Q是不显示warning信息
os.system(f"sphinx-build -Q {repos_path}/docs {build_path} >> {LOG_FILE}")
print("Successfully update repository and build latest documentation.")
os.system(f"echo Successfully update repository and build latest documentation. >> {LOG_FILE}")
# 最后空两行,方便分割上下文
os.system(f"echo >> {LOG_FILE}")
os.system(f"echo >> {LOG_FILE}")
# 函数的返回值将发回给发起http请求的一方
@app.route("/git_post_receive", methods=["POST"])
def result():
r"""在其他git仓库完成一次提交后,向这边发送一个请求,这边将去同步git仓库的更新并完成后续的工作(比如把rst编译成html)
发起请求的一个样例:
# -s静默模式,-X设定http方法,-d要发送的数据,最后一个参数是url
curl -s -X POST -d "repos=mixedai×tamp=`date '+%Y-%m-%d %H:%M:%S'`" "http://ip:port/git_post_receive"
目前要求repos跟timestamp键值都是必填,其他选填
目前是要自己先手动git clone --recurse-submodules 一次到项目目录,后面再让脚本自动化pull
"""
if request.form["repos"] == "mixedai":
# NOTE: mixedai仓库目前pull好像不需要账号密码,后面如果需要账号密码了这段代码可能会失效
repos_path = "/data/maintainer/git_latest/MixAIPlatform"
build_path = "/var/www/html/mixedai"
t = threading.Thread(target=pull_and_build, args=(repos_path, build_path, request.form["timestamp"]))
# 因为处理http请求的函数要先返回值给git push的人,所以这里不能t.join(),只能放子线程自己在那跑
# NOTE: 子线程里不能使用request的东西,外层return之后request就没了,会报RuntimeError: Working outside of request context.错误
t.start()
return f"{request.form['timestamp']}: Successfully update repository and build latest documentation.\n" # response to your request.
return f"Received at {request.form['timestamp']} but this repos is not mixedai!\n" # response to your request.
if __name__ == "__main__":
# 这边监听所有ip,ip过滤由阿里云控制台的安全组完成
app.run(host="0.0.0.0", port=4999, debug=True)
python与myspl的连接
安装mysql(我现在用的是8.0.12版本,需要VS2019)参考链接
配置环境变量:把mysql的安装目录C:\Program Files\MySQL\MySQL Server 8.0\bin
加入Path
环境变量中
测试:mysql -u root -p
,输入密码,能进入mysql命令行(命令行出现mysql>
字符前缀)则为安装成功
mysql忘记root密码如何重置
使用pymysql库:import pymysql
连接数据库:mysql = pymysql.connect(host="localhost", user="root", passwd="xxxx", db="mysql", charset="utf8")
python数据库操作pymysql
Python简单爬虫导出CSV文件
python远程登录服务器(paramiko模块安装和使用
python读写文件,和设置文件的字符编码比如utf-8
python连接远程主机
关于CSV文件的读写问题(特别是有双引号和逗号的情况)
Python2 --> Python3
为了提高稳定性,如果有时会在Python2环境下工作的话,不要直接替换掉原Imoprt语句,而是采用如下形式:
try:
# python3 的import 语句
except:
# python2 的import 语句
python3之后urllib2和urllib合并成urllib了。
urllib2.urlopen()
变成了urllib.request.urlopen()
urllib2.Request()
变成了urllib.request.Request()
urllib2.HTTPCookieProcessor
变成了urllib.request.HTTPCookieProcessor
urllib.urlencode
变成了urllib.parse.urlencode
cookielib
变成了http.cookiejar
urllib2.opener()
变成了urllib.request.build_opener
StringIO
和cStringIO
被io.StringIO
和io.BytesIO
替代:
from cStringIO import StringIO
--> from io import StringIO
*
操作符还可以是一元操作符,对list或tuple类型的变量*
一下,可以将该变量按元素拆分,如:b=[1,2,3],print(b)
输出[1 2 3],而print(*b)
则输出1 2 3
该操作一般用于函数,如函数cc定义为cc(a,b,c)
,则可以通过cc(*b)
的方式来调用
Python的一些需要容易引发出错的特性:
Python中对象的赋值都是进行对象引用(内存地址)传递,修改了被赋值的对象相当于是修改了原对象。这称为浅拷贝。深拷贝就是新创建一个对象,修改时不会影响原对象的内容。可以使用copy.deepcopy()进行深拷贝。
如果pip3 install 库名
等了很长时间都没安装成,可以尝试手动下载:
https://pypi.python.org/pypi ,下载到的一般是whl文件。whl格式本质上是一个压缩包,里面包含了py文件,以及经过编译的pyd文件。使得可以在不具备编译环境的情况下,选择合适自己的python环境进行安装。
安装下载好的库:pip3 install 库文件名.whl
如果下载了对应版本的库,pip install
时仍然提示不支持当前平台(not a supported wheel on this platform),可以先看看本地的pip支持哪些类型的库:在cmd中进入python
,输入import pip; print(pip.pep425tags.get_supported())
,便可以获取到pip支持的文件名还有版本。把下载下来的库改一下名字,改成符合要求的文件名,就可以下载啦~不过也请注意库的依赖哦。
python字符串格式化方法 format函数的使用(可代替原来的%方式)
如果要输出百分数则把格式字符中的f
替换成%
,比如.3f
替换成.3%
functools库
partial
函数的功能:固定函数参数,返回一个新的函数。当函数参数太多,需要固定某些参数时,可以使用partial
创建一个新的函数。如funcb = partial(funca, y=2)
,funcb相当于funca中固定了y=2的版本。参考自这篇文章
其他库的部分语句
sys.executable
可以查看python编译器的路径,用于辨认自己到底用的是哪一个编译器
自己做了一个库,要编译需要python setup.py install
,具体怎么玩请看这里
出错信息收录箱
- SyntaxError: (unicode error) ‘unicodeescape’ codec can’t decode bytes in position 2-3: truncated \uXXXX escape
换了个awrrpt.html的文件名也是如此。。。。
原来是由于\u之类的存在,导致了python进行转义,从而找不到目录下面对应的文件了。多加个\即可:
os.path.getsize(‘c:\user_weblogic.dmp’)
SyntaxError: (unicode error) ‘utf-8’ codec can’t decode byte 0xb4 in position 0: invalid start byte
基本知识:在python中默认的编码格式是 utf-8。所以怎么会报不能按 utf-8来解码嘞?一头雾水啊。
问题的解决:使用notepad++打开test.py发现文件存储的格式是ANSI
只要将保存文件的格式换成UTF-8就好了
使用notepad++打开test.py >> 菜单栏Encoding(编码)>> Convert to UTF-8(转化成utf-8)再运行test.py问题解决
csv.writer().writerow()保存的csv文件,打开时每行后都多一行空行:在with open里面增加一个newline=’’ 解释:参数newline是用来控制文本模式之下,一行的结束字符。可以是None,’’,\n,\r,\r\n等。
当在读取模式下,如果新行符为None,那么就作为通用换行符模式工作,意思就是说当遇到\n,\r或\r\n都可以作为换行标识,并且统一转换为\n作为文本输入的换行符。当设置为空’’时,也是通用换行符模式工作,但不作转换为\n,输入什么样的,就保持原样全输入。当设置为其它相应字符时,就会判断到相应的字符作为换行符,并保持原样输入到文本。
当在输出模式时,如果新行符为None,那么所有输出文本都是采用\n作为换行符。如果设置为’’或者\n时,不作任何的替换动作。如果是其它字符,会在字符后面添加\n作为换行符。
解决html文件乱码现象
html文件打开后出现乱码一般是因为编码格式不对
- urllib.request.urlopen()时抛出"HTTP Error 403: Forbidden"异常:
有些网站为了防止爬虫之类的非正常的访问,会验证请求信息中的UserAgent(它的信息包括硬件平台、系统软件、应用软件和用户个人偏好),如果UserAgent存在异常或者是不存在,那么这次请求将会被拒绝(如上错误信息所示)
解决方案:尝试在请求中加入UserAgent的信息
相关链接
解决python3 UnicodeEncodeError: ‘gbk’ codec can’t encode character ‘\xXX’ in position XX
ValueError
在enumerate的时候报错:
例如for i, (img1, _, _) in enumerate(dataloaders[‘test’]):
ValueError: not enough values to unpack (expected 3, got 2),意思是dataloaders[‘test’]它enumerate出来只有2个值,但是in的左边的tuple里想要接收3个值
而如果是for i, (img1, _, _, _) in enumerate(dataloaders[‘target_u’]):
ValueError: too many values to unpack (expected 4),意思是in左边的tuple里想要接收4个值,但dataloaders[‘target_u’]它enumerate出来有不止4个值,超过了(too many)。