前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >59个Python使用技巧,从此你的Python与众不同(二)

59个Python使用技巧,从此你的Python与众不同(二)

作者头像
1480
发布2020-02-19 13:28:56
4570
发布2020-02-19 13:28:56
举报
文章被收录于专栏:数据分析1480数据分析1480

11. Python代码微优化之加快查找

collections.OrderedDict类:

代码语言:javascript
复制
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
if key not in self:
root = self.__root
last = root[0]
last[1] = root[0] = self.__map[key] = [last, root, key]
return dict_setitem(self, key, value)

注意最后一个参数:dict_setitem=dict.setitem。如果你仔细想就会感觉有道理。将值关联到键上,你只需要给__setitem__传递三个参数:要设置的键,与键关联的值,传递给内建dict类的__setitem__类方法。等会,好吧,也许最后一个参数没什么意义。最后一个参数其实是将一个函数绑定到局部作用域中的一个函数上。具体是通过将dict.__setitem__赋值为参数的默认值。这里还有另一个例子:

代码语言:javascript
复制
def not_list_or_dict(value):
return not (isinstance(value, dict) or isinstance(value, list))

def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list):
return not (_isinstance(value, _dict) or _isinstance(value, _list))

这里我们做同样的事情,把本来将会在内建命名空间中的对象绑定到局部作用域中去。因此,python将会使用LOCAL_FAST而不是LOAD_GLOBAL(全局查找)。那么这到底有多快呢?我们做个简单的测试:

代码语言:javascript
复制
$ python -m timeit -s 'def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list))' 'not_list_or_dict(50)'
1000000 loops, best of 3: 0.48 usec per loop
$ python -m timeit -s 'def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))' 'not_list_or_dict(50)'
1000000 loops, best of 3: 0.423 usec per loop

换句话说,大概有11.9%的提升 [2]。比我在文章开始处承诺的5%还多!

12. 包管理

Python世界最棒的地方之一,就是大量的第三方程序包。同样,管理这些包也非常容易。按照惯例,会在 requirements.txt 文件中列出项目所需要的包。每个包占一行,通常还包含版本号。

代码语言:javascript
复制
pelican==3.3
Markdown
pelican-extended-sitemap==1.0.0

13. Python函数参数默认值的陷阱和原理深究

代码语言:javascript
复制
Python 2.7.9 (default, Dec 19 2014, 06:05:48)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def generate_new_list_with(my_list=[], element=None):
... my_list.append(element)
... return my_list
...
>>> list_1 = generate_new_list_with(element=1)
>>> list_1
[1]
>>> list_2 = generate_new_list_with(element=2)
>>> list_2
[1, 2]
>>>

可见代码运行结果并不和我们预期的一样。list_2在函数的第二次调用时并没有得到一个新的list并填入2,而是在第一次调用结果的基础上append了一个2。为什么会发生这样在其他编程语言中简直就是设计bug一样的问题呢?

可见如果参数默认值是在函数编译compile阶段就已经被确定。之后所有的函数调用时,如果参数不显示的给予赋值,那么所谓的参数默认值不过是一个指向那个在compile阶段就已经存在的对象的指针。如果调用函数时,没有显示指定传入参数值得话。那么所有这种情况下的该参数都会作为编译时创建的那个对象的一种别名存在。

如果参数的默认值是一个不可变(Imuttable)数值,那么在函数体内如果修改了该参数,那么参数就会重新指向另一个新的不可变值。而如果参数默认值是和本文最开始的举例一样,是一个可变对象(Muttable),那么情况就比较糟糕了。所有函数体内对于该参数的修改,实际上都是对compile阶段就已经确定的那个对象的修改。

14. 单下划线(_)

1. 在解释器中:在这种情况下,“_”代表交互式解释器会话中上一条执行的语句的结果。这种用法首先被标准CPython解释器采用,然后其他类型的解释器也先后采用。

代码语言:javascript
复制
>>> _ Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
NameError: name '_' is not defined 
>>> 42
>>> _ 
42
>>> 'alright!' if _ else ':('
'alright!'
>>> _ 
'alright!'

2. 作为一个名称:这与上面一点稍微有些联系,此时“ ”作为临时性的名称使用。这样,当其他人阅读你的代码时将会知道,你分配了一个特定的名称,但是并不会在后面再次用到该名称。例如,下面的例子中,你可能对循环计数中的实际值并不感兴趣,此时就可以使用“ ”

代码语言:javascript
复制
n = 42
for _ in range(n): 
do_something()

3. 国际化:也许你也曾看到”_“会被作为一个函数来使用。这种情况下,它通常用于实现国际化和本地化字符串之间翻译查找的函数名称,这似乎源自并遵循相应的C约定。例如,在Django文档“转换”章节中,你将能看到如下代码:

代码语言:javascript
复制
from django.utils.translation import ugettext as _ 
from django.http import HttpResponse 
def my_view(request): 
output = _("Welcome to my site.") 
return HttpResponse(output)

可以发现,场景二和场景三中的使用方法可能会相互冲突,所以我们需要避免在使用“”作为国际化查找转换功能的代码块中同时使用“”作为临时名称。

15. 名称前的单下划线(如:_shahriar)

程序员使用名称前的单下划线,用于指定该名称属性为“私有”。这有点类似于惯例,为了使其他人(或你自己)使用这些代码时将会知道以“_”开头的名称只供内部使用。正如Python文档中所述:

以下划线 __ 为前缀的名称(如_pam)应该被视为API中非公开的部分(不管是函数、方法还是数据成员)。此时,应该将它们看作是一种实现细节,在修改它们时无需对外部通知。

正如上面所说,这确实类似一种惯例,因为它对解释器来说确实有一定的意义,如果你写了代码 : from <模块/包名> import * ,那么以 _ 开头的名称都不会被导入,除非模块或包中的 __all__ 列表显式地包含了它们。了解更多请查看 Importing * in Python

16. 名称前的双下划线(如:__shahriar)

名称(具体为一个方法名)前双下划线 _ 的用法并不是一种惯例,对解释器来说它有特定的意义。Python中的这种用法是为了避免与子类定义的名称冲突。Python文档指出,__spam 这种形式(至少两个前导下划线,最多一个后续下划线)的任何标识符将会被 正如所预料的,“_internal_use”并未改变,而“__method_name”却被变成了“_ClassName__method_name”。此时,如果你创建A的一个子类B,那么你将不能轻易地覆写A中的方法“__method_name”。spam 这种形式原文取代,在这里 classname 是去掉前导下划线的当前类名。例如下面的例子:

代码语言:javascript
复制
>>> class A(object): 
... def _internal_use(self): 
... pass
... def __method_name(self): 
... pass
... 
>>> dir(A()) 
['_A__method_name', ..., '_internal_use']

正如所预料的,“_internal_use”并未改变,而“__method_name”却被变成了“_ClassName__method_name”。此时,如果你创建A的一个子类B,那么你将不能轻易地覆写A中的方法“__method_name”。

17. 名称前后的双下划线(如:init)

这种用法表示Python中特殊的方法名。其实,这只是一种惯例,对Python系统来说,这将确保不会与用户自定义的名称冲突。通常,你将会覆写这些方法,并在里面实现你所需要的功能,以便Python调用它们。例如,当定义一个类时,你经常会覆写“init”方法。

虽然你也可以编写自己的特殊方法名,但不要这样做。

18. 隐藏特性 1,函数unpack

代码语言:javascript
复制
def foo(x, y):
print x, y

alist = [1, 2]
adict = {'x': 1, 'y': 2}

foo(*alist) # 1, 2

foo(**adict) # 1, 2

19. 隐藏特性 2, 链式比较操作符

代码语言:javascript
复制
>>> x = 3
>>> 1 < x < 5
True
>>> 4 > x >=3
True

20. 隐藏特性 3,函数的默认参数

代码语言:javascript
复制
>>> def foo(x=[]):
... x.append(1)
... print x
...
>>> foo()
[1]
>>> foo()
[1, 1]

更安全的做法是:

代码语言:javascript
复制
>>> def foo(x=None):
... if x is None:
... x = []
... x.append(1)
... print x
...
>>> foo()
[1]
>>> foo()
[1]
>>>

End.

作者:地球的外星人君

来源:知乎

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据分析1480 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档