编写高质量代码--改善python程序的建议(七)
原文发表在我的博客主页,转载请注明出处!
建议三十四:掌握字符串的基本用法
编程有两件事,一件是处理数值,另一件是处理字符串,在商业应用编程来说,处理字符串的代码超过八成,所以需要重点掌握。
首先有个小技巧,python遇到未闭合的小括号时会自动将多行代码拼接为一行,同时把相邻的两个字符串字面量拼接再议,比如下面的两个代码是等价的。
s = ('SELECT *'
'FROM atable'
'WHERE afield = "value"')
'SELECT *FROM atableWHERE afield = "value"'
除此之外,python的字符串有str和unicode两种,判断一个变量s是不是字符串应使用isinstance(s, basestring)。
#input
a = "hi"
b = u"Hi"
print isinstance(b, str)
print isinstance(b, basestring)
print isinstance(b, unicode)
print isinstance(a, unicode)
#output
False
True
True
False
通过上面的例子可以总结:
- isinstance(a, str)用于判断一个字符串是不是普通字符串,也就是说其类型是否为str
- isinstance(a, unicode)用来判断一个字符串是不是Unicode
- 因此判断一个变量是不是字符串,应该使用isinstance(a, basestring),basestring是str和Unicode的基类
下面简单来列举下字符串的使用 - 性质判定
str对象有以下几个方法:isalnum(), isalpha(), isdigit(), islower(), isupper(), isspace(), istitle(), startwith(prefix[,start[,end]]),endwith(suffix[,start[,end]]) - 查找替换
有以下函数:count(sub[,start[,end]]),find(sub[,start[,end]]), index(sub[,start[,end]]), rfind(sub[,start[,end]]), rindex(sub[,start[,end]]),replace(old,new[,count]),其中find()函数族找不到时返回-1,index()函数族则抛出异常。 - 分切与连接
主要掌握两个:partition(sep), split([sep[,maxsplit]]),前者接受一个字符串参数,并返回一个3个元素的元组对象,如果sep没有出现,返回值是(sep,","),否则返回值的第一个元素是sep左边的部分,第二个元素是sep本身,第三个元素是sep右端的部分。而split()的参数maxsplit是分切的次数,即最大的分切次数,所以返回值做多有maxsplit+1个元素。 - 变形
主要是一些常用函数,lower(),upper(),capitalize(),swapcase(),title() - 填充与删除
如果strip([chars]),lstrip([chars]),rstrip([chars])中的chars参数没有指定,就是删除空白符。填充则用于字符串的输出,借助于它们能够排出漂亮的版面。center(width[,fillchar]),ljust(width[,fillchar]),rjust(width[,fillchar]),zfill(width),expandtabs([tabsize])这些完成的居中,左对齐,右对齐,零填充等功能。
建议三十五:按需选择sort()或者sorted()
两者的函数形式如下:
sorted(iterable[,cmp[,key[,reverse]]])
s.sort([cmp[,key[,reverse]]])
#cmp为用户定义的任何比较函数,函数的参数为两个可比较的元素(来自iterable或者s),函数根据第一个和第二个参数的关系返回-1,0,1,默认值为None
#key是带一个参数的函数,用来为每个元素提取比较值,默认为None(即直接比较每个元素)
#reverse表示排序结果是否反转
#exampe
p = [{'name':'Jon','age':32},{'name':'Alan','age':50},{'name':'Jon','age':23}]
print sorted(p,key=lambda x: (x['name'], -x['age']))
两者区别如下:
- sorted()用于任意可迭代对象,而sort()一般作用于列表
- 当排序对象为列表的时候两者适用的场景不同,sorted()会返回一个排序后的列表,原有列表保持不变,而sort()函数会直接修改原有列表,函数返回None
- 不论用哪个,传入参数key都要比传入参数cmp效率高,cmp传入的函数在整个排序过程中会调用多次,函数开销较大,而key针对每个元素仅作一次处理。
建议三十六:使用copy模块深拷贝对象
浅拷贝:构造一个新的复合对象并将从原对象中发现的引用插入该对象中。浅拷贝的实现方式主要有:工厂函数,切片操作,copy模块中的copy操作
深拷贝:构造一个新的复合对象,遇到引用会继续递归拷贝其所指向的具体内容,它会针对引用所指向的对象继续执行拷贝,产生的对象不受其他引用对象操作的影响。
在包含引用的数据结构中,浅拷贝并不能进行彻底的拷贝,当存在列表、字典等对象的时候,它仅仅拷贝其引用地址,修改拷贝的对象可能会改变原对象的值,要解决这个问题需要用到深拷贝,深拷贝不仅拷贝引用也拷贝引用所指向的对象,深拷贝得到的对象和原对象是相互独立的。使用如下:
import copy
copy.deepcopy(x)
建议三十七:使用Counter进行计数统计
计数统计具有非常广泛的用途,逐一来看实现方式:
- 使用dict
data = ['a','2',2,3,4,'2','b','a','b',3,4,'a']
count_frq = {}
for item in data:
if item in count_frq:
count_frq[item] += 1
else:
count_frq[item] = 1
print count_frq
- 使用defaultdict
from collections import defaultdict
data = ['a','2',2,3,4,'2','b','a','b',3,4,'a']
count_frq = defaultdict(int)
for item in data:
count_frq[item] += 1
print count_frq
- 使用set和list
data = ['a','2',2,3,4,'2','b','a','b',3,4,'a']
count_set = set(data)
count_frq = []
for item in count_set:
count_frq.append((item,data.count(item)))
print count_frq
上面的方法都比较naive,更加pythonic的如下:
from collections import Counter
data = ['a','2',2,3,4,'2','b','a','b',3,4,'a']
print Counter(data)
Counter属于字典类的子类,是一个容器对象,主要用来统计散列对象,支持集合操作,提供了三种初始化方式:
Counter('success')
Counter(s=3,c=2,e=1,u=1)
Counter('s':3,'c':2,'u':1,'e':1)
可以使用elements()方法来获取Counter中的key值,利用most_common(N)方法获取N个出现频率最高的元素以及对应的次数,当访问元素不存在时候,默认返回0。
from collections import Counter
data = ['a','2',2,3,4,'2','b','a','b',3,4,'a']
print list(Counter(data).elements())
print Counter(data).most_common(3)
print Counter(data)['z']
建议三十八:深入掌握ConfigParser
几乎所有的应用程序真正运行起来的时候,都会读取一个或几个配置文件,配置文件的意义在于用户不需要修改代码,就可以改变应用程序的行为。常见的配置文件格式有XML和ini等。这里强调几点用法:
- getboolean()函数,其根据一定的规则将配置项的值转化为布尔值,如:
[section1]
option1 = 0
当调用getboolean('section1','option1')时,将返回False。不过他的真值规则值得一说:处了0以外,no, false, off都会转义为False,对应的1, yes, true, on都会转义为True,其他值会抛出异常。
- 配置项的查找规则
首先,在ConfigParser支持的配置文件格式里,有一个[DEFAULT]节,当读取的配置项不在指定的节里时,ConfigParser将会到[DEFAULT]节中查找。
#cyj.conf
[DEFAULT]
in_default = 'if you have a dream, you have to protect it'
[section1]
#test.py
import ConfigParser
conf = ConfigParser.ConfigParser()
conf.read('cyj.conf')
print conf.get('section1','in_default')
#output
'if you have a dream, you have to protect it'
除此之外,还有一些机制导致项目配置的查找更复杂,这就是class ConfigParser构造函数中的defaults形参以及其get(section,option[,raw[,vars]])中的全名参数vars。如果把这些机制全部用上,查找规则如下:
- 如果找不到节名,就抛出NoSectionError
- 如果给定的配置项出现在get()方法的vars参数中,则返回vars参数中的值
- 如果在指定的节中含有给定的配置项,则返回值
- 如果在[DEFAULT]中含有指定的配置项,返回值
- 如果在构造函数的defaults参数中有指定的配置项,则返回值
- 抛出NoOptionError
- ConfigParser其他配置方法
python字符串的格式化可以使用如下语法:
site = {'protocol':'http','server':'115.159.48.140','port':80}
print '%(protocol)s://%(server)s:%(port)s/' %site
ConfigParser支持类似的用法,所以在配置文件中可以使用,比如:
#cyj.conf
[DEFAULT]
conn_str = %(dbn)s://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s
dbn = mysql
user = root
host = localhost
port = 3306
[db1]
user = aaa
pw = ppp
db = example
[db2]
host = 192.168.0.110
pw = www
db = example
#test.py
import ConfigParser
conf = ConfigParser.ConfigParser()
conf.read('cyj.conf')
print conf.get('db1','conn_str')
print conf.get('db2','conn_str')
#output
mysql://aaa:ppp@localhost:3306/example
mysql://root:www@192.168.0.110:3306/example
建议三十九:使用argparse处理命令行参数
尽管应用程序通常能够通过配置文件在不修改代码的情况下改变行为,但提供灵活易用的命令行参数依然非常有意义,比如:减轻用户的学习成本,通常命令行参数的用法只需要在应用程序名后加--help参数就能获得。
现在比较流行的库为argparse*库,他子optparse脱胎而来,先生成一个parser实例,然后增加参数声明。比如:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-o','--output')
parser.add_argument('-v',dest='verbose',action='store_true')
args = parser.parse_args()
add_argument()方法用以增加一个参数声明,他支持的类型比较多,语法直观,type参数的值不再是一个字符串,而是一个可调用对象。此外,add_argument()提供了对必填参数的支持,只要把required参数设置为True传递进去,当缺失这一参数时,argparser就会自动退出程序,并提示用户。
ArgumentParser还支持参数分组,add_argument_group()可以在输出帮助信息时更加清晰。
test.py
import argparse
parser = argparse.ArgumentParser(prog='PROG',add_help=False)
group1 = parser.add_argument_group('group1','python')
group1.add_argument('foo',help='foo help')
group2 = parser.add_argument_group('group2','nice')
group2.add_argument('--bar',help='bar help')
print parser.print_help()
建议四十:使用pandas处理大型CSV文件
CSV(Comma Separated Values)作为一种逗号分隔型值的纯文本文件,在实际应用中经常用到,如数据库数据的导入导出,数据分析中记录的存储等。首先来看下python提供的CSV处理相关的API:
- csv.reader(csvfile[,dialect='excel'][,fmtparam]),用于CSV文件的读取,返回一个reader对象用于在CSV文件内容上进行行迭代
- csv.writer(csvfile,dialect='excel',**fmtparams)用于写入CSV文件
import csv
with open('data.csv','wb') as csvfile:
csvwriter = csv.writer(csvfile, dialect='excel', delimiter='|', quotechar='"', quoting=csv.QUOTE_MINIMAL)
csvwriter.writerow(["1/3/09 14:44","'Product1'","1200''","Visa","Gouya"])
- csv.DictReader(csvfile, fieldnames=None, restkey=None, restval=None, dialect='excel',*args,**kwargs),和reader()类似,不同的是将读入的信息映射到一个字典中去,其中字典的key由fieldnames指定,省略的话CSV文件第一行的数据作为key值。如果读入行的字段的个数大于fieldnames中指定的个数,多余的字段名将会存放在restkey中,而restval主要用于当读取行的域的个数小于fieldnames的时候,它的值将会被用作剩下的key对应的值
- csv.DictWriter(csvfile, fieldnames, restval='',extrasaction='raise',dialect='excel',*args,**kwds)用于支持字典写入
上面的使用可以满足大部分需求,但是如果处理大文件会抛出MemoryError异常,这种情况下可以使用pandas模块。
Pandas,支持多种文件格式处理,包括CSV,HDF5,HTML等,两种数据结构:Series和DataFrame。
Series:是一种类似数组的带索引的一维数据结构,支持的类型与NumPy兼容,如果不指定索引,默认从0到N-1,通过obj.values()和obj.index()分别获取值和索引,当给Series传递一个字典的时候,Series的索引将根据字典中的键排序,如果传入字典的时候重新制订了index参数,当index与字典中的键不匹配的时候,会出现数据丢失的情况,标记为NaN。
DataFrame:类似于电子表格,其数据结构为排好序的数据列的集合,每一列都可以是不同的数据类型,类似于二维数组,支持行和列的索引。可以通过colums指定序列的顺序。import pandas
data = {'OrderDate': ['1-6-10', '1-23-10', '2-9-10', '2-26-10', '3-15-10'],
'Region': ['East', 'Central', 'Central', 'West', 'East'],
'Rep': ['Jones', 'Kivell', 'Jardine', 'Gill', 'Sorvino']}
print pandas.DataFrame(data,columns=['OrderDate','Rep','Region'])
pandas中处理CSV文件的主要函数为read_csv()和to_csv(),前者读取文件的内容返回DataFrame,后者为其逆过程。下面简单讲述其用法:
- 指定读取部分列和文件的行数
df = pd.read_csv('data.csv',nrows=5,usecols=[])
#nrows指定读取文件的行数
#usecols指定索要读取的列的列名,没有列名可用索引
- 设置CSV文件和excel兼容
如果文件格式改为使用“|”分隔符,则需要设置dialect相关的参数,error_bad_lines设置为False,当记录不符合要求的时候,可直接忽略。 - 对文件进行分块处理并返回一个可迭代的对象
为了防止内存消耗过大,可以分批载入内存,参数chunksize设置分块的文件行数,将参数iterator设置为True时,返回值为TextFileReader,是一个可迭代对象
reader = pd.read_table("data.csv",chunksize=10,iterator=True)
iter(reader).next()
- 当文件格式相似的时候,支持多个文件合并处理
dfs = [pd.read_csv(f) for f in fileset]
totol_fg = pd.concat(dfs)
参考:编写高质量代码--改善python程序的91个建议