python使用小结

把之前的python使用方法全部汇总到这里

转载记录

python自动给数字前面补0的方法

python中有一个zfill方法用来给字符串前面补0,非常有用

# zfill补0
n = "123"
s = n.zfill(5)
assert s == "00123"

# 也可以给负数补0
n = "-123"
s = n.zfill(5)
assert s == "-0123"

# 对于纯数字,我们也可以通过格式化的方式来补0
n = 123
s = "%05d" % n
assert s == "00123"

浅析python 中__name__ = ‘main’ 的作用

很多新手刚开始学习python的时候经常会看到python 中__name__ = ‘main’ 这样的代码,可能很多新手一开始学习的时候都比较疑惑,python 中__name__ = ‘main’ 的作用,到底干嘛的?
有句话经典的概括了这段代码的意义:
“Make a script both importable and executable”
意思就是说让你写的脚本模块既可以导入到别的模块中用,另外该模块自己也可执行。
这句话,可能一开始听的还不是很懂。下面举例说明:
先写一个模块:

#module.py
def main():
  print "we are in %s"%__name__
if __name__ == '__main__':
  main()

这个函数定义了一个main函数,我们执行一下该py文件发现结果是打印出”we are in main“,说明我们的if语句中的内容被执行了,调用了main():
但是如果我们从另我一个模块导入该模块,并调用一次main()函数会是怎样的结果呢?

#anothermodle.py
from module import main
main()

其执行的结果是:we are in module
但是没有显示”we are in main“,也就是说模块__name__ = ‘main’ 下面的函数没有执行。
这样既可以让“模块”文件运行,也可以被其他模块引入,而且不会执行函数2次。这才是关键。
总结一下:
如果我们是直接执行某个.py文件的时候,该文件中那么”name == ‘main’“是True,但是我们如果从另外一个.py文件通过import导入该文件的时候,这时__name__的值就是我们这个py文件的名字而不是__main__。
这个功能还有一个用处:调试代码的时候,在”if name == ‘main’“中加入一些我们的调试代码,我们可以让外部模块调用的时候不执行我们的调试代码,但是如果我们想排查问题的时候,直接执行该模块文件,调试代码能够正常运行!

python排序方法

转载自这里

C/C++与Python

C调用python接口方法

主要是参考下面两篇博文
使用c语言调用python小结
Python实例浅谈之三Python与C/C++相互调用
需要注意的是,
1.如果提示undefined reference to Py_XXXX
说明没有正确加载python的库,根据python的版本,在makefile里加入-lpython2.7
2.如果PyImport_ImportModule返回的指针为空,一定是python模块的路径搞错了,这里我开始直接给
PyImport_ImportModule的参数传的是python模块的绝对路径,返回为空。后来使用下面两句解决了问题,
应该是需要先调用第一句加入python模块的路径,PyImport_ImportModule只负责加载模块名称,不包括路径

PyRun_SimpleString("import sys");     
PyRun_SimpleString("sys.path.append('./')");   
PyImport_ImportModule('voc_eval');  

python与c++混合编程调试方法

这里主要是调试py-faster-rcnn,其他类似
单纯使用pdb可以调试python代码,但是无法调试其调用的c++代码。
如果要调试c++代码,使用如下指令
gdb --args python test.py
具体步骤:
1.使用指令,并且r进去,让进程跑起来
2.由于此时caffe.so并没有加载进来,所以,可能会出现即使在cpp里下断点也无法跟踪的情况,这里可以先手动ctrl+c把进程断开,再设置断点,让caffe.so加载进来。
所以,这一步是下断点,如b caffe/src/caffe/solver.cpp:228
3.c,进去cpp里即可

关于命名中的下划线

变量

主要存在四种情形
1. object # public
2. object # special, python system use, user should not define like it
3. __object # private (name mangling during runtime)
4. _object # obey python coding convention, consider it as private
核心风格:避免用下划线作为变量名的开始。

函数

  1. 前带_的变量: 标明是一个私有函数, 只用于标明
  2. 前带两个_ ,后带两个_ 的函数: 标明是特殊函数
    #多层目录模块调用#
    转载自这里
  3. 将导入模块所在目录(…/model/模块)添加到系统环境变量path下,可添加多个
import sys
sys.path.append("../model")  # 模块父目录下的model文件中
from model import a

注意:不管什么样的引用,建议直接使用这种方法,避免发生引用模块里import的问题。

(所有被引用模块的调用会优先查找当前执行目录下的文件,找不到再去找所有环境变量的目录!)

程序结构如下:

– src
|-- model
| |-- a.py
| |-- b.py
|-- test.py

a模块 调用了同级目录的b --> import b

test模块 调用a模块时出错 --> from model import a

出错:在test当前目录运行时找不到b模块。

2.引用模块在 当前目录的同级以及所包含的子目录中:
2.1. 直接import 同级文件.子文件.引用模块
2.2. from 同级文件.子文件 import 引用模块

列表等分

转载自这里
这里主要借助itertools模块里的izip和iziplongest方法,代码如下

from itertools import zip_longest    # zip_longest -> Python 3, izip_longest -> Python 2  
chunk_list = lambda a_list, n: zip_longest(*[iter(a_list)]*n)  
result_groups = list(chunk_list([x for x in range(100)], 8))  
  
Out[12]:   
[(0, 1, 2, 3, 4, 5, 6, 7),  
 (8, 9, 10, 11, 12, 13, 14, 15),  
 (16, 17, 18, 19, 20, 21, 22, 23),  
 (24, 25, 26, 27, 28, 29, 30, 31),  
 (32, 33, 34, 35, 36, 37, 38, 39),  
 (40, 41, 42, 43, 44, 45, 46, 47),  
 (48, 49, 50, 51, 52, 53, 54, 55),  
 (56, 57, 58, 59, 60, 61, 62, 63),  
 (64, 65, 66, 67, 68, 69, 70, 71),  
 (72, 73, 74, 75, 76, 77, 78, 79),  
 (80, 81, 82, 83, 84, 85, 86, 87),  
 (88, 89, 90, 91, 92, 93, 94, 95),  
 (96, 97, 98, 99, None, None, None, None)]  

这里借助了迭代器,关于*[iter(a_list)]*n ,参考了stackoverflow里的精彩说明,如下

iter() is an iterator over a sequence. [x]

  • n produces a list containing n quantity of x, i.e. a list of length n, where each element is x. *arg unpacks a sequence into arguments for a function call. Therefore you’re passing the same iterator 3 times to zip(), and it pulls an item from the iterator each time.
x = iter([1,2,3,4,5,6,7,8,9])  
print zip(x, x, x)  

最终产生了一个等分list的方法。
等分list可以用来批量处理数据,如等分目录下的文件等。
#数字前补0#
转载自这里
python中有一个zfill方法用来给字符串前面补0,非常有用

n = "123"  
s = n.zfill(5)  
assert s == "00123"

zfill()也可以给负数补0

n = "-123"  
s = n.zfill(5)  
assert s == "-0123"

对于纯数字,我们也可以通过格式化的方式来补0

n = 123  
s = "%05d" % n  
assert s == "00123"

生成voc数据集的脚本代码


# !/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'xiaobanderui'

from xml.dom import minidom
import numpy as np
import cv2

imgpath = u'e:/VOC2007/JPEGImages/' # 图片存放路径
txtpath = u'e:/data.txt' # 保存标注信息的文件,每一行的内容为[filename objectname xmin ymin xmax ymax]
xml_path = u'e:/VOC2007/Annotations/'

lastname = u'begin'
foldername = u'VOC2007'

num = 0 # 总样本数
valid_num = 0 # 有效样本数

cv2.namedWindow('img', cv2.WINDOW_NORMAL)

with open(txtpath, 'r') as fr:
    for item in fr.readlines():
        num += 1
        print 'processing %d' % num
        item = item.strip().decode('gbk')
        str_item = item.split(' ')
        img =cv2.imread((imgpath + str_item[0]).encode('gbk'), cv2.IMREAD_UNCHANGED)
        ht. wd. cn = img.shape

        xmin = int(float(str_item[2]))
        xmax = int(float(str_item[3]))
        ymin = int(float(str_item[4]))
        ymax = int(float(str_item[5]))

        cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2)
        cv2.imshow('img', img)
        cv2.waitKey()
     
        # 过滤掉标注信息错误的样本
        # 框的位置越界
        if xmin >= xmax or ymin >= ymax or xmin < 0 or ymin < 0 or xmax < 0 or ymax < 0 or xmin >= wd or xmax >= wd or ymin >= ht or ymax >= ht
	        continue

        # 框的位置不对,比如翻转时
	    oldx1 =xmin
	    oldx2 = xmax
	    newxmin = wd - oldx2 - 1
	    newxmax = wd - oldx1 - 1

	    if newxmin >= newxmax
	        continue

	     valid_num += 1

	    if str_item[0] == last_name:
    	    object_node = doc.createElem('object')
    	    rootNode = doc.appendChild(object_node)

   	        node = doc.createElem('name')
    	    node_text = doc.createTextNode(str_item[1])
    	    node.appendChild(node_text)
    	    object_ode = doc.appendChild(node)

   	    '''
    	    其他类似添加,不再赘述
    	    '''

	    else:
    	    if vars().has_key('rootNode'):
        	    fw_xml = open(xml_path + file_full_name[0] + '.xml', 'w')
        	    fw_xml.write(doc.toprettyxml(encoding='utf-8'))
        	    fw_xml.close()

	        node = doc.createElem('name')
    	    node_text = doc.createTextNode(str_item[1])
    	    node.appendChild(node_text)
    	    object_ode = doc.appendChild(node)

    fw_xml = open(xml_path + file_full_name[0] + '.xml', 'w')
    fw_xml.write(doc.toprettyxml(encoding='utf-8'))
    fw_xml.close()

编译错误总结

代码缩进问题,错误信息如下

IndentationError: unindent does not match any outer indentation level

类型转换问题

如下字串转为int会有错误

str = '123.456'  
x = int(str) 

错误信息如下

invalid literal for int() with base 10

百度了一下,发现python里的int()只接受纯数字的转换,这里有的字串有‘.’,所以会报错,做如下更改即可

str = '123.456'  
x = round(float(str)) 

numpy

savetxt

使用numpy的savetxt保存的数据维度>=3时,会出现错误,比如保存一个三通道的数组就会出错,这里可以采用每个通道分别保存的方式

np.savetxt(filename, array[0,:,:])  
np.savetxt(filename, array[1,:,:])  
np.savetxt(filename, array[2,:,:])  

目录、文件操作

判断目录、文件是否存在

转载自这里
这篇文章主要介绍了Python判断文件和文件夹是否存在的方法,本文还讲解了判断是否为文件或者目录的方法、os.path.lexist的作用、FTP中判断文件或目录是否存在等内容,需要的朋友可以参考下
1.python判断文件和文件夹是否存在、创建文件夹,代码如下

>>> import os  
>>> os.path.exists('d:/assist')  
True  
>>> os.path.exists('d:/assist/getTeacherList.py')  
True  
>>> os.path.isfile('d:/assist')  
False  
>>> os.path.isfile('d:/assist/getTeacherList.py')  
True  
>>> os.makedirs('d:/assist/set')  
>>> os.path.exists('d:/assist/set')  
True  

2.python判断文件是否存在,代码如下

import os  
   
filename = r'/home/tim/workspace/test.txt'  
if os.path.exists(filename):  
    message = 'OK, the "%s" file exists.'  
else:  
    message = "Sorry, I cannot find the "%s" file."  
print message % filename 

3.如何用Python判断文件是否存在
使用os.path.exists()方法可以直接判断文件是否存在,代码如下

>>> import os  
>>> os.path.exists(r'C:\1.TXT')  
False  
>>>  

如果存在返回值为True,如果不存在则返回False
4.python判断文件夹是否存在,代码如下

$ python  
Python 2.7.3 (default, Jan  2 2013, 16:53:07)   
[GCC 4.7.2] on linux2  
Type "help", "copyright", "credits" or "license" for more information.  
>>> import os  
>>>   
>>>   
>>> tobecheckdir = r'/home/tim/workspace'  
>>> os.path.isdir(tobecheckdir)  
True  
>>>  

5.python检查文件是否存在,以及路径是否为文件

from os import path, access, R_OK  # W_OK for write permission.  
PATH='./file.txt'  
if path.exists(PATH) and path.isfile(PATH) and access(PATH, R_OK):  
    print "File exists and is readable"  
else:  
    print "Either file is missing or is not readable"  

你也可以通过下面的方式实现:

def file_exists(filename):  
    try:  
        with open(filename) as f:  
            return True  
    except IOError:  
        return False  

6.python判断文件和文件夹是否存在

import os   
os.path.isfile('test.txt') #如果不存在就返回False   
os.path.exists(directory) #如果目录不存在就返回False  

7.os.path.lexist
还有os.path.lexists(path)
对broken的link file也返回True.

8.python FTP判断文件夹是否存在
python怎样判断文件夹是否存在?广大网友给出了答案:
使用ftp库就可以了,下面是Python核心编程上的例子:

>>> from ftplib import FTP  
>>> f = FTP('ftp.python.org')  
>>> f.login('anonymous', 'guido@python.org')  
'230 Guest login ok, access restrictions apply.'  
>>> f.dir() 

dir结果中无此文件,就是不存在,或者如下:

try:  
f.retrbinary('RETR %s' % FILE,open(FILE, 'wb').write)  
except ftplib.error_perm:  
print 'ERROR: cannot read file "%s"' % FILE 40 os.unlink(FILE)  

不能读此文件,也视为不存在。

命名规范

转载自这里

常量

常量名所有字母大写,由下划线连接各个单词,如

WHITE = 0XFFFFFF
THIS_IS_A_CONSTANT = 1

变量

普通变量

全部小写,由下划线连接各个单词,如:

color = WHITE  
this_is_a_variable = 1 

保护成员变量

单下划线作前缀,意思是只有类对象和子类对象自己能访问到这些变量,且不能用’from module import *'导入。如:

_name=name

私有成员变量

双下划线作前缀,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。

__ha=h

全局变量

大写字母,单词之间用_分割。
对于from M import *导入语句,如果想阻止导入模块内的全局变量可以使用旧有的规范,在全局变量上加一个前导的下划线

NUMBER  
COLOR_WRITE

注意:
(1) 不论是类成员变量还是全局变量,均不使用 m 或 g 前缀
(2)变量名不应带有类型信息,因为 Python 是动态类型语言。如 iValue、names_list、dict_obj 等都是不好的命名。

函数

普通函数

与普通变量一致,函数名应该为小写,可以用下划线风格单词以增加可读性。
混合大小写仅被允许用于这种风格已经占据优势的时候,以便保持向后兼容

myfunctio()  
my_example_function()  
myName()  

私有函数

以双下划线开头。如:

__get_name()

函数的参数

总使用“self”作为实例方法的第一个参数。总使用“cls”作为类方法的第一个参数。
如果一个函数的参数名称和保留的关键字冲突,通常使用一个后缀下划线好于使用缩写或奇怪的拼写。
模块:
应该是简短的、小写的名字,单词之间用_分割。模块就是指文件

hello.py  
ad_stats.py

类名单词首字母大写,不使用下划线连接单词,也不加入 C、T 等前缀
实例用小写字母

class MyClass(object)  

命名方式同模块,是文件夹
特定命名方式:xxx
主要是指 xxx 形式的系统保留字命名法。项目中也可以使用这种命名,它的意义在于这种形式的变量是只读的,这种形式的类成员函数尽量不要重载。如

class Base(object):
def __init__(self, id, parent = None):
self.__id__ = id
self.__parent__ = parent
def __message__(self, msgid):

其中 idparentmessage 都采用了系统保留字命名法。
#编码问题#
目前遇到的最头疼的问题,没有之一
先是一篇简要介绍,链接在这里http://in355hz.iteye.com/blog/1860787
最近业务中需要用 Python 写一些脚本。尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。
很快,我就遇到了异常:

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)   

为了解决问题,我花时间去研究了一下 Python 的字符编码处理。网上也有不少文章讲 Python 的字符编码,但是我看过一遍,觉得自己可以讲得更明白些。

下面先复述一下 Python 字符串的基础,熟悉此内容的可以跳过。

对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:

# -*- coding: utf-8 -*-    
# file: example1.py    
import string    
    
# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
print isinstance(s, str)      # True    
print isinstance(u, unicode)  # True    
    
print s.__class__   # <type 'str'>    
print u.__class__   # <type 'unicode'> 

前面的申明:# -- coding: utf-8 -- 表明,上面的 Python 代码由 utf-8 编码。

为了保证输出不会在 linux 终端上显示乱码,需要设置好 linux 的环境变量:export LANG=en_US.UTF-8

如果你和我一样是使用 SecureCRT,请设置 Session Options/Terminal/Appearance/Character Encoding 为 UTF-8 ,保证能够正确的解码 linux 终端的输出。

两个 Python 字符串类型间可以用 encode / decode 方法转换:

# 从 str 转换成 unicode    
print s.decode('utf-8')   # 关关雎鸠    
    
# 从 unicode 转换成 str    
print u.encode('utf-8')   # 关关雎鸠  

为什么从 unicode 转 str 是 encode,而反过来叫 decode?

因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字符的二进制(字节)编码形式。把字符从 unicode 转换成二进制编码,当然是要 encode。

反过来,在 Python 中出现的 str 都是用字符集编码的 ansi 字符串。Python 本身并不知道 str 的编码,需要由开发者指定正确的字符集 decode。

(补充一句,其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -- coding: utf-8 --,这表明代码中的 str 都是用 utf-8 编码的,我不知道 Python 为什么不这样做。)

如果用错误的字符集来 encode/decode 会怎样?

# 用 ascii 编码含中文的 unicode 字符串    
u.encode('ascii')  # 错误,因为中文无法用 ascii 字符集编码    
                   # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)    
    
# 用 gbk 编码含中文的 unicode 字符串    
u.encode('gbk')  # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示    
                 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'    
                 # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的    
    
# 用 ascii 解码 utf-8 字符串    
s.decode('ascii')  # 错误,中文 utf-8 字符无法用 ascii 解码    
                   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)    
    
# 用 gbk 解码 utf-8 字符串    
s.decode('gbk')  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码    
                 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'    

这就遇到了我在本文开头贴出的异常:UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-3: ordinal not in range(128)

现在我们知道了这是个字符串编码异常。接下来, 为什么 Python 这么容易出现字符串编/解码异常?

这要提到处理 Python 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

# -*- coding: utf-8 -*-    
# file: example2.py    
    
# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
s + u  # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

简单的字符串连接也会出现解码错误?

陷阱一:在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。

由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 ‘ascii’ ——显然,如果需要转换的 str 有中文,一定会出现错误。

除了字符串连接,% 运算的结果也是一样的:

# 正确,所有的字符串都是 str, 不需要 decode    
"中文:%s" % s   # 中文:关关雎鸠    
    
# 失败,相当于运行:"中文:%s".decode('ascii') % u    
"中文:%s" % u  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)    
    
# 正确,所有字符串都是 unicode, 不需要 decode    
u"中文:%s" % u   # 中文:关关雎鸠    
    
# 失败,相当于运行:u"中文:%s" % s.decode('ascii')    
u"中文:%s" % s  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)   

我不理解为什么 sys.getdefaultencoding() 与环境变量 $LANG 全无关系。如果 Python 用 $LANG 设置 sys.getdefaultencoding() 的值,那么至少开发者遇到 UnicodeDecodeError 的几率会降低 50%。

另外,就像前面说的,我也怀疑为什么 Python 在这里不参考 # -- coding: utf-8 -- ,因为 Python 在运行前总是会检查你的代码,这保证了代码里定义的 str 一定是 utf-8 。

对于这个问题,我的唯一建议是在代码里的中文字符串前写上 u。另外,在 Python 3 已经取消了 str,让所有的字符串都是 unicode ——这也许是个正确的决定。

其实,sys.getdefaultencoding() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

# -*- coding: utf-8 -*-    
# file: example3.py    
import sys    
    
# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
# 使得 sys.getdefaultencoding() 的值为 'utf-8'    
reload(sys)                      # reload 才能调用 setdefaultencoding 方法    
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'    
    
# 没问题    
s + u  # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20'    
    
# 同样没问题    
"中文:%s" % u   # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'    
    
# 还是没问题    
u"中文:%s" % s  # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'    

可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。

另一个陷阱是有关标准输出的。

刚刚怎么来着?我一直说要设置正确的 linux $LANG 环境变量。那么,设置错误的 $LANG,比如 zh_CN.GBK 会怎样?(避免终端的影响,请把 SecureCRT 也设置成相同的字符集。)

显然会是乱码,但是不是所有输出都是乱码。

# -*- coding: utf-8 -*-    
# file: example4.py    
import string    
    
# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
# 输出 str 字符串, 显示是乱码    
print s   # 鍏冲叧闆庨笭    
    
# 输出 unicode 字符串,显示正确    
print u  # 关关雎鸠    

为什么是 unicode 而不是 str 的字符显示是正确的? 首先我们需要了解 print。与所有语言一样,这个 Python 命令实际上是把字符打印到标准输出流 —— sys.stdout。而 Python 在这里变了个魔术,它会按照 sys.stdout.encoding 来给 unicode 编码,而把 str 直接输出,扔给操作系统去解决。

这也是为什么要设置 linux $LANG 环境变量与 SecureCRT 一致,否则这些字符会被 SecureCRT 再转换一次,才会交给桌面的 Windows 系统用编码 CP936 或者说 GBK 来显示。

通常情况,sys.stdout.encoding 的值与 linux $LANG 环境变量保持一致:

# -*- coding: utf-8 -*-    
# file: example5.py    
import sys    
    
# 检查标准输出流的编码    
print sys.stdout.encoding  # 设置 $LANG = zh_CN.GBK,  输出 GBK    
                           # 设置 $LANG = en_US.UTF-8,输出 UTF-8    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
# 输出 unicode 字符串,显示正确    
print u  # 关关雎鸠    

但是,这里有 陷阱二:一旦你的 Python 代码是用管道 / 子进程方式运行,sys.stdout.encoding 就会失效,让你重新遇到 UnicodeEncodeError。

比如,用管道方式运行上面的 example4.py 代码:

python -u example5.py | more    
    
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)    
None    

可以看到,第一:sys.stdout.encoding 的值变成了 None;第二:Python 在 print 时会尝试用 ascii 去编码 unicode.

由于 ascii 字符集不能用来表示中文字符,这里当然会编码失败。

怎么解决这个问题? 不知道别人是怎么搞定的,总之我用了一个丑陋的办法:

# -*- coding: utf-8 -*-    
# file: example6.py    
import os    
import sys    
import codecs    
    
# 无论如何,请用 linux 系统的当前字符集输出:    
if sys.stdout.encoding is None:    
    enc = os.environ['LANG'].split('.')[1]    
    sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替换 sys.stdout    
    
# 这个是 unicode 的字符串    
u = u'关关雎鸠'    
    
# 输出 unicode 字符串,显示正确    
print u  # 关关雎鸠    

这个方法仍然有个副作用:直接输出中文 str 会失败,因为 codecs 模块的 writer 与 sys.stdout 的行为相反,它会把所有的 str 用 sys.getdefaultencoding() 的字符集转换成 unicode 输出。

# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 输出 str 字符串, 异常    
print s   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)   

显然,sys.getdefaultencoding() 的值是 ‘ascii’, 编码失败。

解决办法就像 example3.py 里说的,你要么给 str 加上 u 申明成 unicode,要么通过“后门”去修改 sys.getdefaultencoding():

# 使得 sys.getdefaultencoding() 的值为 'utf-8'    
reload(sys)                      # reload 才能调用 setdefaultencoding 方法    
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'    
    
# 这个是 str 的字符串    
s = '关关雎鸠'    
    
# 输出 str 字符串, OK    
print s   # 关关雎鸠  

总而言之,在 Python 2 下进行中文输入输出是个危机四伏的事,特别是在你的代码里混合使用 str 与 unicode 时。

有些模块,例如 json,会直接返回 unicode 类型的字符串,让你的 % 运算需要进行字符解码而失败。而有些会直接返回 str, 你需要知道它们的真实编码,特别是在 print 的时候。

为了避免一些陷阱,上文中说过,最好的办法就是在 Python 代码里永远使用 u 定义中文字符串。另外,如果你的代码需要用管道 / 子进程方式运行,则需要用到 example6.py 里的技巧。

后来,为了统一Windows和Linux上的编码,不管什么字符串,我都会在前面加上u,把它们全部强制转化为Unicode。但是,在打印类似list和dict这些数据结构的时候,显示中文字符遇到了问题,只能显示unicode编码,无法显示中文。
解决办法转载自这里http://blog.csdn.net/xiaobei4929/article/details/38734047
在从元组转换到字符串时,中文字符前会出现u’例子’类似这种,具体可参考第二个文章,需要手动去除u一切就都正常了

[u'\u773c', u'\u8179\u90e8', u'\u4e94\u5b98', u'\u53e3\u8154', u'\u8179\u90e8',  
u'\u53e3\u8154'] 
str_symptom = str(all_symptom).replace('u\'','\'')  
str_symptom.decode("unicode-escape") 
['眼', '腹部', '五官', '口腔', '腹部', '口腔']  #处理后的显示情况

另外,对于dict这种无序表,可以使用OrderedDict来代替,具体可以参考廖雪峰的python教程,非常棒

#其他的一些小结#
这里有一个爱可可老师的分享python-snippets
##类与属性##
参考这篇博客
主要是这句话有用

getattr(object, name[,default])
获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。
需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,
可以在后面添加一对括号。

multiprocessing里的Process模块,多线程调用非常的恶心,有些错误,即使添加了print还是无法达到追踪的效果,有时只能改掉多线程的调用方式
#列表、字典#
使用list初始化dict的方法是

d=dict.fromkeys(l, 0)

得到一个值全为0的字典
对于字典而言,dict[‘x’] =1的操作,如果dict中没有’x’这个键,那么这时就会创建1个新的item

python2与python3的切换

raise ImportError('This package should not be accessible on Python 3. ’ ImportError 报错解决
当电脑上安装了Python2 和 python3时,在python3下执行help()报如下错误:

raise ImportError('This package should not be accessible on Python 3. ’
ImportError: This package should not be accessible on Python 3.
Either you are trying to run from the python-future src folder or your installation of python-future is corrupted

这是因为环境变量中包含PYTHONPATH=/usr/local/lib/python2.7/site-packages,而这条不适用于python3。

继承

关于super().init()的用法,转载自这里Python super初始化理解过程

python lmdb

python lmdb
lmdb documents
leveldb issue

并发

一个简单的多卡并行方法 – Python代码

  • 应用场景
    1.Python代码调用C/C++库(已用Pybind或者其他工具封装成python接口,该库内部参数已经固定住,如只允许单卡运行,固定batch_size)
    2.有多卡环境下
    3.需要使用该库处理大量数据
  • 解决办法
    1.将输入数据根据卡数进行chunk,代码如下:
import pynvml
pynvml.nvmlInit()
# 获取GPU卡数
dev_count = pynvml.nvmlDeviceGetCount()

#根据卡数chunk输入数据
from itertools import zip_longest
chunk_list = lambda a_list, n: zip_longest(*[iter(a_list)]*n)

len_samples = len(samples_fullpaths)
if len_samples % dev_count == 0:
   len_each_dev = int(len_samples / dev_count)
else:
   len_each_dev = int(len_samples / dev_count) + 1
result_groups = list(chunk_list([x for x in samples_fullpaths], len_each_device))

2.使用python shell命令调用.py

# 这里参数要写成*,来搜集传入的元组可变参数
def subprocess(*kwargs):
    # 传入的元组个数为1
    kwargs = kwargs[0]
    r_q = kwargs[1]
    id = kwargs['id']
    """
    解析其他输入参数
    """
    # 注意这里第2个参数格式化时,因为是可变参数,把字典kwargs格式化成字符串(前后都加上"),后边直接用eval解析即可
    cmd = 'export CUDA_VISIBLE_DEVICES = {} && python proc.py \"{}\"'.format(id, kwargs)
    ret = subprocess.call(cmd, shell=True)
    r_st = '{}+{}'.format(id, ret)
    r_q.put(r_st)

3.主处理单元

#假定启动py文件是main.py,处理数据的py文件是proc.py

# main.py
from multiprocessing import Process, Queue
def main_proc(params, input_fullpaths, output_fullpaths):
    """
    这里粘贴1中代码,来对input_fullpaths进行chunk
    """
    len_groups = len(result_groups)
    try:
        assert(len_groups <= device_count)
    except:
        return
    for id, group in enumerate(results_groups):
        with open('sample{}.txt'.format(id)) as f_w:
            for it in group:
                if it:
                    f_w.write(it + '\n')
    kwargs = []
    for gpu_id in range(len_groups):
        dic_args = dict()
        dic_args['id'] = params['id']
        """
        其他外部配置参数
        """
        kwargs.append(dic_args)
    r_q = Queue()
    r_q_count = len_groups
    
    # 使用多进程或者线程封装主处理函数subprocess,完成并发
    multiproc_list = [Process(target=subprocess, args=(kwargs[i], r_q)) for i in range(len_groups)]
    for mp in multiproc_list:
        mp.start()

    while r_q_count:
    	r_sp = r_q.get().split('+')
    	id = eval(r_sp[0])
    	ret = eval(r_sp[1])
    	if ret != 0:
    		print('error in task {}'.format(id))
    		sys.exit(-1)
    	r_q_count -= 1
    #去掉join,主进程不等待子进程,可先退出
    #for mp in multiproc_list:
    #    if mp.is_alive():
    #        mp.join()
    

if __name__ == '__main__'
    parser = getArgs()
    args = parser.parse_args()
    main_proc(args.params, args.input_fullpaths, args.output_fullpaths)

# proc.py
def main_subprocess(**kwargs):
    id = kwargs['id']
    """
    解析其他参数
    """

    """
    以下代码是处理代码,比如加在模型,调用算法C/C++库的python接口获取结果等
    """

if __name__ == '__main__':
    kwargs = eval(sys.argv[1])
    # 传入的是字典,加上**
    main_subprocess(**kwargs)

4.总结

1.多卡并行首先对输入数据进行chunk,然后使用subprocess.call或者os.system通过运行shell命令调用,实现在不同的卡上运行py脚本,这样就达到了简单的并发目的。
2.在具体实施的时候,还需要用进程或者线程来封装subprocess.call,因为subprocess.call本身并不能很好地并发。
3.一般需要2个脚本,一个是启动脚本main.py,一个是主处理脚本proc.py。通过main.py对proc.py传参时,考虑使用可变参数(字典、元组)等形式,这样更灵活。

5.参考资料
Python多进程代码调试工具

import sys
import pdb

class ForkedPdb(pdb.Pdb):
    """
    PDB Subclass for debugging multi-processed code
    Suggested in: https://stackoverflow.com/questions/4716533/how-to-attach-debugger-to-a-python-subproccess
    """
    def interaction(self, *args, **kwargs):
        _stdin = sys.stdin
        try:
            sys.stdin = open('/dev/stdin')
            pdb.Pdb.interaction(self, *args, **kwargs)
        finally:
            sys.stdin = _stdin

# 用法
ForkedPdb().set_trace()

因为GIL的存在,python的多线程是伪多线程

参考Python多线程详解
在这里插入图片描述

有了GIL为什么还需要Lock

https://stackoverflow.com/questions/49859287/what-is-the-need-of-threading-lock-when-cpython-has-gil
https://stackoverflow.com/questions/40072873/why-do-we-need-locks-for-threads-if-we-have-gil
https://en.wikipedia.org/wiki/Global_interpreter_lock
https://www.zhihu.com/question/23030421/answer/93789486 **通俗解释
https://softwareengineering.stackexchange.com/questions/186889/why-was-python-written-with-the-gil

该如何选择并行的方式

最近在进行数据处理任务,发现几个问题:

  1. 不要使用corrent.futures里的进程池方法。之前一直使用的corrent.futures里的进程池方法,
with concurrent.futures.ProcessPoolExecutor(N) as pool:
    pool.map(func, data)

发现会有数据丢失的问题,改为多进程/多线程方式不会有数据丢失问题

from threading import Thread
from multiprocessing import Process
# 前处理代码
# 如果要用多进程,将Thread改为Process
thread_list = [Thread(target=func, args=(i,) for i in range(N)]
for t in thread_list:
    t.start()
for t in thread_list:
    if t.is_alive():
        t.join()
# 后处理代码

猜测原因,当进程池方式里的N能被len(data)整除时,不会有数据丢失,无法被整除时,会出现丢失现象,可能与进程池里的并行实现方式有关。
2. 对于IO密集型或者有更多的IO操作,使用多线程更好。因为多线程在读写数据时,可以不阻塞,将GIL释放出来给空闲线程。如果使用多进程,进程间的数据通信(python多进程间存在数据的序列化和反序列化),这个尤其在处于大存储数据,比如图片时,是非常耗时的。
3. 对于计算密集型,使用多进程。

join方法用与不用的区别

在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况
情况一:
在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。 这种是没有join方法
情况二:
如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用

以下是一个使用join和不使用join的对比例子:

from multiprocessing import Process
import time
import os
def task(name):
    print('%s is running' %(name))
    time.sleep(2)
    print('%s is end' %(name))
if __name__ == '__main__':
    start = time.time()
    p1 = Process(target=task, args=('子进程1',))
    p2 = Process(target=task, args=('子进程2',))
    p3 = Process(target=task, args=('子进程3',))
    p4 = Process(target=task, args=('子进程4',))
    process_list = [p1,p2,p3,p4]
    for p in process_list:
      p.start()
    for p in process_list:
      p.join()
    print('主',time.time() - start)

打印如下

#屏蔽p.join代码
子进程1 is running
子进程2 is running
子进程3 is running
主 25061 25052
子进程4 is running
子进程1 is end
子进程2 is end
子进程3 is end
子进程4 is end

#使用p.join
子进程1 is running
子进程2 is running
子进程3 is running
子进程4 is running
子进程1 is end
子进程2 is end
子进程3 is end
子进程4 is end
主 25226 25217

让主进程等着,所有子进程执行完毕后,主进程才继续执行

注意,join要单独写循环调用,不能和start方法在同一层循环里调用,否则子进程无法并发。原因解释在这里【Python】多线程中阻塞(join)使用误区详解。如果在同一层调用start和join,那在第一层循环调用join后,主进程会被子进程1阻塞,无法调用子进程2的start,直到子进程1结束。

装饰器使用方法(处理并行时用)

Decoration in Python done right: Decorating and pickling

from functools import wraps
def with_print(func):
    """ Decorate a function to print its arguments.
    """
    @wraps(func)
    def my_func(*args, **kwargs):
        print args, kwargs
        return func(*args, **kwargs)
    return my_func

@with_print
def f(x):
    print 'f called'

为啥进程池封装在装饰器中不能生效,而多进程可以

logging模块

import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(filename)s:%(lineno)d] - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
logger = logging.getLogger(__name__)

metaclass的使用

参考资料
cookbook里8.12例子,定义一个接口或抽象基类

from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta):
    @abstractmethod
    def read(self, maxbytes=-1):
        pass
    @abstractmethod
    def write(self, data):
        pass

打印堆栈信息

import inspect
 
def function_print_stack():
    stack = inspect.stack()
    for frm in stack:
        print(f"Function: {frm.function}, Line: {frm.lineno}, Code: {frm.code_context}, Filename: {frm.filename}")
 
function_print_stack()

其它问题

  1. os.walkglob.glob获取目录列表时,即使使用镜像一样,在不同的机器上跑出来的结果顺序也不一致。
  2. 并发时常用到Queue来保存数据,保存的数据如果是list,那么保存后不能使用clear()方法来清空list
dataload_queue = Queue()
item1 = []
item2 = []
batch_size = 16
for idx, data in enumerate(datasets):
    item1.append(idx)
    item2.append(data)

    if idx % 16 == 0 and idx != 0:
        dataload_queue.put((item1, item2))
		# 下面是错误的方式,使用clear()后,已经压入dataload_queue里的数据也会被clear
		#item1.clear()
		#item2.clear()

		# 给item1和item2重新赋值是正确的方式
		item1 = []
		item2 = []

新的知识

Annotated,Python3.9支持

可用于类型提示

from typing import Annotated
UserID = Annotated[int, "This is a user ID", "Must be int and positive"]

def get_user_id(user_id: UserID) -> None:
    print(f"Fetching user with ID: {user_id}")

get_user_id(123)

更多使用方法参考Pydantic, Field和Annotated

pydantic,提供友好的数据校验,广泛用于FastAPI中

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值