Python最迷糊的很多时候都是字符串,不像其他高深的魔法语法糖和技巧,可以不用就不用,但字符串却无法避免。避无可避的时候只有直面困难才能解决问题,知己知彼方能百战百胜。如果是初学者可以跳过,后面再回来比较好,这是深入探究Python字符串的课题。

  1. Python2.x的字符串究竟是什么?

Python的字符串str其实是一个字节数组,一个字节一个单位长度,用len('我')去获取长度就可以看出长度是3,并不是我们肉眼所看到的1,惯性思维都会认为上面长度应该是1,恰恰str其实不是惯性思维的字符串概念。而unicode却是我们惯性思维上面的字符串,len(u'我')(unicode字符串是前面加个u)。所以在这里就可以知道,其实主要分清楚str和unicode这两大阵营就好了。

转换关系如下,但什么时候该encode什么时候该decode,如果把str看成加密的报文,unicode看成解密后的报文就比较容易理解,同样他们的意义也非常贴近,

str.decode() <=> unicode.encode() 密文.解码 <=> 明文.编码

写一些例子看看

str1 = "我" print(str1.decode("utf-8")) unicode1 = u"我" print(unicode1.encode("utf-8")) print(repr(str1)) '\xe6\x88\x91' print(repr(unicode1)) u'\u6211'

str类型是不能进行encode的,utf-8是推荐的字符编码,一般都使用这个,除非系统全都是用GBK,建议还是utf-8
unicode类型是不能进行decode,要特别记得的。
repr函数返回的是解释器看到的样子,我们用print打印出来,发现都是中文字我,但其实对于Python解释器看到的并不是这样,而是实际上的编码。可以看到str1返回的是十六进制数字表达形式,可以和后面的\u6211互转。6211是”我“这个字符的unicode编码。最后,如果你的时间不是很紧张,并且又想快速的python提高,最重要的是不怕吃苦,建议你可以架尉♥信(同音):276 3177 065 ,那个真的很不错,很多人进步都很快,需要你不怕吃苦哦!大家可以去添加上看一下~

从上面可以看出两者的本质,往往用print出来看到的只是一个便于阅读的字符串,并不是解释器看到的,就是因为这个就很容易出现问题。 2. 有哪些坑值得我们注意的? 2.1. print报错或者log方法的时候报错

这个问题出现过好几次,如果是碰上重要业务来输出日志的时候,丢了就会很麻烦。首先我们先重现一遍:

str1 = '我' unicode1 = u'我' print(str1+' ' + unicode1) UnicodeDecodeError Traceback (most recent call last) <ipython-input-93-b94b95f8ee8e> in <module>() 1 str1 = '我' 2 unicode1 = u'我' ----> 3 print(str1+' ' + unicode1)

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)

这个也会报错

fmt = '我试试{0}'.format(unicode1)

没错,就是字符串连接的时候特别容易出错,恰好我们想在这里输出一些重要日志,却因为字符串连接的时候报错了,导致重要日志丢失就会踩坑了。首先str和unicode混用的时候相连接会出现转换导致了报错。

解决思路:在相连接之前,如果发现不是unicode类型就转换成unicode类型,然后再连接,同理在格式化方面,但格式化的字符串统一用unicode。例子就是用Tornado的一个函数。

def to_unicode(value): """Converts a string argument to a unicode string.

If the argument is already a unicode string or None, it is returned
unchanged.  Otherwise it must be a byte string and is decoded as utf8.
"""
if isinstance(value, _TO_UNICODE_TYPES):
    return value
if not isinstance(value, bytes_type):
    raise TypeError(
        "Expected bytes, unicode, or None; got %r" % type(value)
    )
return value.decode("utf-8")

2.2.标准输出作为管道参数是报错

这个问题也是很经常出现,往往Python作为一个快捷的脚本语言,用来从数据库导出数据并按策划要求写到文件中是很方便的。虽然可以直接操作文件io来写,但直觉习惯用print函数会更快,通过linux管道或者重定向到文件中,在测试阶段可以直观地在屏幕看到输出,待到生产环境运行的时候也可以写入到文件,体现了一个快字。但往往放到生产环境一跑就出现了以下的异常:

UnicodeEncodeError: 'ascii' codec can't encode character u'\u6211' in position 0: ordinal not in range(128)

脚本比较简单,名字是string_print.py

#! /usr/bin/env python

-- coding: utf-8 --

print(u"我")

运行命令:

python string_print.py |tee /tmp/output.dat

运行后就会报错,假如把u"我"改成"我"str类型,那就不会报错。相信很多人都看过unicode和str的文章,有些论调不是说,尽量采用unicode吗?那这里使用unicode了反而报错,这让人的确更混乱了。

报错原因其实很简单:print会自动对会对unicode进行encode,如果输出目标是一个终端,就会使用终端的编码,但输出是一个管道就无法得知了,使用系统的默认编码,这时候就会报Unicode错误。最后,如果你的时间不是很紧张,并且又想快速的python提高,最重要的是不怕吃苦,建议你可以架尉♥信(同音):276 3177 065 ,那个真的很不错,很多人进步都很快,需要你不怕吃苦哦!大家可以去添加上看一下~

由于一般数据库返回来的字符串都是以unicode类型,我们可以写一个新的print函数来代替旧的,但对于这种快餐式的脚本语言来说,轻量才是他真正的优点。所以我的习惯还是要对字符串是什么类型需要把握好,这里就要说到函数返回值的问题了。函数有时候返回str有时候返回unicode,如果无法记忆清楚这些使用的函数就多试,也不会有多次编译的成本。如果是在业务进程编码的话,最好还是使用文件IO,这就不是本节所讨论的了。

那现在来解决问题,如果要输出到管道或者文件的,都需要对unicode进行encode。

print(u"我") => print(u"我".encode("utf-8"))

同理如果把标准输出导入到文件也是一样的:

python string_print.py 1>//tmp/output.dat 2>&1

  1. 总结

上面只是举了两个简单的例子,往往会问有没有一个终极大招或者一招鲜,吃遍天的方法,我认为是没有的。但可以看到提倡所有都用unicode的也会有可能遇到输出的异常,不用unicode也会遇到问题。我总结是:

把str和unicode的意义和特点以及转化死记硬背。
多用unicode,但要正确把握什么时候该用什么时候不应该用。
unicode和str混用的话,注意提前转换。
在业务进程里面可以写好更加安全的方法,脚本里面使用就要多测试,多动手。

什么是安全的方法,我简单提一个关于字符串转为整型的conv_int的方法:

def conv_int(int_str, default=None): try: return int(int_str) except Exception, e: return default

比一般int()多了两个功能,这个函数不会因为传入非法参数而导致报错,第二个是可以设置默认返回值,例如设置为0,假如0是作为否定的意思,转换为0刚好就不需要再额外处理了。对于unicode和str都可以类似的方式去处理,这样就不会再常常看到UnicodeError了。