Python黑魔法手册 2.0 文档第六章:良好编码习惯【1-4】
Python黑魔法手册 2.0 文档第六章:良好编码习惯【1-4】
6.1 不要直接调用类的私有方法
大家都知道,类中可供直接调用的方法,只有公有方法(protected类型的方法也可以,但是不建议)。也就是说,类的私有方法是无法直接调用的。
这里先看一下例子
class Kls():
def public(self):
print('Hello public world!')
def __private(self):
print('Hello private world!')
def call_private(self):
self.__private()
ins = Kls()
# 调用公有方法,没问题
ins.public()
# 直接调用私有方法,不行
ins.__private()
# 但你可以通过内部公有方法,进行代理
ins.call_private()
既然都是方法,那我们真的没有方法可以直接调用吗?
当然有啦,只是建议你千万不要这样弄,这里只是普及,让你了解一下。
# 调用私有方法,以下两种等价
ins._Kls__private()
ins.call_private()
6.2 默认参数最好不为可变对象
函数的参数分三种
- 可变参数
- 默认参数
- 关键字参数
当你在传递默认参数时,有新手很容易踩雷的一个坑。
先来看一个示例
def func(item, item_list=[]):
item_list.append(item)
print(item_list)
func('iphone')
func('xiaomi', item_list=['oppo','vivo'])
func('huawei')
在这里,你可以暂停一下,思考一下会输出什么?
思考过后,你的答案是否和下面的一致呢
['iphone']
['oppo', 'vivo', 'xiaomi']
['iphone', 'huawei']
如果是,那你可以跳过这部分内容,如果不是,请接着往下看,这里来分析一下。
Python 中的 def 语句在每次执行的时候都初始化一个函数对象,这个函数对象就是我们要调用的函数,可以把它当成一个一般的对象,只不过这个对象拥有一个可执行的方法和部分属性。
对于参数中提供了初始值的参数,由于 Python 中的函数参数传递的是对象,也可以认为是传地址,在第一次初始化 def 的时候,会先生成这个可变对象的内存地址,然后将这个默认参数 item_list 会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。
6.3 增量赋值的性能更好
诸如 += 和 *= 这些运算符,叫做 增量赋值运算符。
这里使用用 += 举例,以下两种写法,在效果上是等价的。
# 第一种
a = 1 ; a += 1
# 第二种
a = 1; a = a + 1
+= 其背后使用的魔法方法是 __iadd__ ,如果没有实现这个方法则会退而求其次,使用__add__ 。
这两种写法有什么区别呢?
用列表举例 a += b,使用 __add__ 的话就像是使用了a.extend(b),如果使用 __add__ 的话,则是 a = a+b,前者是直接在原列表上进行扩展,而后者是先从原列表中取出值,在一个新的列表中进行扩展,然后再将新的列表对象返回给变量,显然后者的消耗要大些。
所以在能使用增量赋值的时候尽量使用它。
6.4 别再使用 pprint 打印了
1. 吐槽问题
pprint 你应该很熟悉了吧?
随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 。
比如这下面这个 json 字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。
[{"id":1580615,"name":"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934 走过路过不要错过,这里有最好的鸡儿"}]
如果你不想看到一堆密密麻麻的字,那就使用大伙都极力推荐的 pprint 看下什么效果(以下在 Python 2 中演示,Python 3 中是不一样的效果)。
>>> info=[{"id":1580615,"name":"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934 走过路过不要错过,这里有最好的鸡儿"}]
>>>
>>> from pprint import pprint
>>> pprint(info)
[{'des': '2011-2017
\xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\
'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
'id': 1580615,
'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b',
'packageName': 'com.renren.mobile.android',
'size': 21803987,
'stars': 2},
{'des': '\xe6\x96\x97\xe9\xb1\xbc271934
\xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\
'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
'iconUrl': 'app/com.ct.client/icon.jpg',
'id': 1540629,
'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84',
'packageName': 'com.ct.client',
'size': 4794202,
'stars': 2}]
好像有点效果,真的是 “神器”呀。
但是你告诉我, :raw-latex:`xe`4:raw-latex:`xbd`:raw-latex:`xa`0:raw-latex:`xe`7:rawlatex:`x`9a 这些什么玩意?本来想提高可读性的,现在变成完全不可读了。
好在我懂点 Python 2 的编码,知道 Python 2 中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte 存储的。
行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。
>>> info = [{"id":1580615,"name":u"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
>>>
>>> from pprint import pprint
>>> pprint(info)
[{'des': u'2011-
2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7
'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
'id': 1580615,
'name': u'\u76ae\u7684\u561b',
'packageName': 'com.renren.mobile.android',
'size': 21803987,
'stars': 2},
{'des':
u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u670
'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
'iconUrl': 'app/com.ct.client/icon.jpg',
'id': 1540629,
'name': u'\u4e0d\u5b58\u5728\u7684',
'packageName': 'com.ct.client',
'size': 4794202,
'stars': 2}]
确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀?
u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u670
除此之外,我们知道 json 的严格要求必须使用 双引号,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 单引号?我也太难了吧,我连自己的代码都无法控制了吗?
到这里,我们知道了 pprint 带来的两个问题:
1. 没法在 Python 2 下正常打印中文
2. 没法输出 JSON 标准格式的格式化内容(双引号)
2. 解决问题
打印中文
如果你是在 Python 3 下使用,你会发现中文是可以正常显示的。
# Python3.7
>>> info = [{"id":1580615,"name":u"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
>>>
>>> from pprint import pprint
>>> pprint(info)
[{'des': '2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青',
'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk',
'iconUrl': 'app/com.renren.mobile.android/icon.jpg',
'id': 1580615,
'name': '皮的嘛',
'packageName': 'com.renren.mobile.android',
'size': 21803987,
'stars': 2},
{'des': '斗鱼271934走过路过不要错过,这里有最好的鸡儿',
'downloadUrl': 'app/com.ct.client/com.ct.client.apk',
'iconUrl': 'app/com.ct.client/icon.jpg',
'id': 1540629,
'name': '不存在的',
'packageName': 'com.ct.client',
'size': 4794202,
'stars': 2}]
但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 Python,本来我可以选择不用的,因为有更好的替代方案(这个后面会讲)。
但是我出于猎奇,正好前两天不是写过一篇关于 编码 的文章吗,我自认为自己对于 编码还是掌握比较熟练的,就想着来解决一下这个问题。
索性就来看下 pprint 的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。
以下是我的解决方案,供你参考:
写一个自己的 printer 对象,继承自 PrettyPrinter(pprint 使用的printer)
并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str 类型,而是unicode 类型,就用 utf8编码str类型。
# coding: utf-8
from pprint import PrettyPrinter
# 继承 PrettyPrinter,复写 format 方法
class MyPrettyPrinter(PrettyPrinter):
def format(self, object, context, maxlevels, level):
if isinstance(object, unicode):
return (object.encode('utf8'), True, False)
return PrettyPrinter.format(self, object, context, maxlevels, level)
info = [{"id":1580615,"name":u"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
MyPrettyPrinter().pprint(info)
输出如下,已经解决了中文的显示问题:
打印双引号
解决了中文问题后,再来看看如何让 pprint 打印双引号。
在实例化 PrettyPrinter对象的时候,可以接收一个 stream 对象,它表示要将内容输出到哪
里,默认是使用 sys.stdout 这个 stream,也就是标准输出。
现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。
那我们完全可以自己定义一个 stream 类型的对象,该对象不需要继承任何父类,只要你实现write 方法就可以。
有了思路,就可以开始写代码了,如下:
# coding: utf-8
from pprint import PrettyPrinter
class MyPrettyPrinter(PrettyPrinter):
def format(self, object, context, maxlevels, level):
if isinstance(object, unicode):
return (object.encode('utf8'), True, False)
return PrettyPrinter.format(self, object, context, maxlevels, level)
class MyStream():
def write(self, text):
print text.replace('\'', '"')
info = [{"id":1580615,"name":u"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
MyPrettyPrinter(stream=MyStream()).pprint(info)
尝试执行了下,我的天,怎么是这样子的。
[
{
"des"
:
2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青
,
"downloadUrl":
"app/com.renren.mobile.android/com.renren.mobile.android.apk"
,
"iconUrl":
"app/com.renren.mobile.android/icon.jpg"
,
"id":
1580615
,
"name":
皮的嘛
,
"packageName":
"com.renren.mobile.android"
,
"size":
21803987
,
"stars":
2
}
,
{
"des"
:
斗鱼271934走过路过不要错过,这里有最好的鸡儿
,
"downloadUrl":
"app/com.ct.client/com.ct.client.apk"
,
"iconUrl":
"app/com.ct.client/icon.jpg"
,
"id":
1540629
,
"name":
不存在的
,
"packageName":
"com.ct.client"
,
"size":
4794202
,
"stars":
2
}
]
经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 换行符。
那如何将使 print 函数打印的内容,不进行换行呢?
方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 逗号 就行。
就像下面这样。
知道了问题所在,再修改下代码
# coding: utf-8
from pprint import PrettyPrinter
class MyPrettyPrinter(PrettyPrinter):
def format(self, object, context, maxlevels, level):
if isinstance(object, unicode):
return (object.encode('utf8'), True, False)
return PrettyPrinter.format(self, object, context, maxlevels, level)
class MyStream():
def write(self, text):
print text.replace('\'', '"'),
info = [{"id":1580615,"name":u"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
MyPrettyPrinter(stream=MyStream()).pprint(info)
终于成功了,太不容易了吧。
3. 何必折腾
通过上面的一番折腾,我终于实现了我 梦寐以求 的需求。
代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。
所以我想说的是,Python 2 下的 pprint ,真的不要再用了。
为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了Python ,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗?
如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 pprint 了。
打印中文
其实无法打印中文,是 Python 2 引来的大坑,并不能全怪 pprint 。
但是同样的问题,在 json.dumps 这里,却只要加个参数就好了,可比 pprint 简单得不要太
多。
具体的代码示例如下:
>>> info = [{"id":1580615,"name":"皮的
嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg",
2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在
的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202
鱼271934走过路过不要错过,这里有最好的鸡儿"}]
>>>
>>> import json
>>>
>>>
>>> print json.dumps(info, indent=4, ensure_ascii=False)
[
{
"downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk",
"iconUrl": "app/com.renren.mobile.android/icon.jpg",
"name": "皮的嘛",
"stars": 2,
"packageName": "com.renren.mobile.android",
"des": "2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青",
"id": 1580615,
"size": 21803987
},
{
"downloadUrl": "app/com.ct.client/com.ct.client.apk",
"iconUrl": "app/com.ct.client/icon.jpg",
"name": "不存在的",
"stars": 2,
"packageName": "com.ct.client",
"des": "斗鱼271934走过路过不要错过,这里有最好的鸡儿",
"id": 1540629,
"size": 4794202
}
]
>>>
json.dumps 的关键参数有两个:
- indent=4:以 4 个空格缩进单位
- ensure_ascii=False:接收非 ASCII 编码的字符,这样才能使用中文
与 pprint 相比 json.dumps 可以说完胜:
- 两个参数就能实现所有我的需求(打印中文与双引号)
- 就算在 Python 2 下,使用中文也不需要用 u’中文’ 这种写法
- Python2 和 Python3 的写法完全一致,对于这一点不需要考虑兼容问题
4. 总结一下
本来很简单的一个观点,我为了证明 pprint 实现那两个需求有多么困难,花了很多的时间去
研究了 pprint 的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。
本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点
- 核心观点:Python2 下不要再使用 pprint
- 若真要使用,且有和一样的改造需求,可以参考我的实现
- Python 2 中的 print 语句后居然可以加 逗号