@1007477689
2020-06-18T16:37:09.000000Z
字数 4911
阅读 486
Python
英文原文:https://martinheinz.dev/blog/1
有很多介绍 Python 中各种很酷的功能(如变量拆包、偏函数、枚举可迭代对象)的文章,但说到 Python 时,还有很多东西可以谈论,这里我将尝试展示我所知道和使用的一些特性,我还没有在其他地方看到有人提到过它们。我们开始吧。
对用户输入进行清理的问题几乎适用于您编写的所有程序。通常情况下,将字符转换为小写或大写就足够了,有时您可以使用 Regex
来完成这项工作,但对于复杂的情况来说,可能有更好的方法:
user_input = "This\nstring has\tsome whitespaces...\r\n"
character_map = {ord('\n') : ' ',
ord('\t') : ' ',
ord('\r') : None }
user_input.translate(character_map)
>>> This string has some whitespaces...
在本例中,您可以看到空白字符 \n
和 \t
已被单个空格替换,而 \r
已被完全删除。这是一个简单的例子,但是我们可以更进一步,使用 unicodedata
包和它的 combining()
函数来生成并进行映射,从而生成更大的重新映射表,我们可以使用它来删除字符串中的所有重音。
如果您尝试对一个迭代器进行切片,您会得到一个 TypeError
,这说明生成器对象是不可下标访问的,但有一个简单的解决方案可以解决这个问题:
import itertools
s = itertools.islice(range(50), 10, 20)
>>> <itertools.islice object at 0x7f70fab88138>
for val in s:
...
使用 itertools.islice
我们可以创建一个 islice
对象,它是一个会生成所需项的迭代器。需要注意的是,这将消耗 slice
开始之前的所有生成器项,以及 islice
对象中的所有项。
有时您必须处理那些以您不想要的可变数量的行(如注释)开始的文件。itertools
再次为这个问题提供了简单的解决方案:
import itertools
string_from_file = """
// Author: ...
// License: ...
//
// Date: ...
Actual content...
"""
import itertools
for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")):
print(line)
这代码段只生成初始注释部分之后的行。如果我们只想在可迭代对象的开头丢弃一些项目(本例中是一些行),并且不知道有多少个项目,那么这种方法是很有用的。
在使用以下这样的函数时,创建“只接受关键字参数”的函数来提供(强制)更多的清晰性是很有帮助的:
def test(*, a, b):
pass
test("value for a", "value for b")
>>> TypeError: test() takes 0 positional arguments...
test(a = "value", b = "value 2")
# It Works...
正如您所看到的,这可以通过在关键字参数之前放置单个参数来轻松解决。如果我们把位置参数放在参数之前,位置参数显然也会存在。
例如,我们都知道如何使用 with
语句来打开文件或获取锁,但是我们可以实现自己的 with
语句吗? 当然,我们可以使用 __enter__
和 __exit__
方法来实现上下文管理协议:
这是在 Python 中实现上下文管理最常见的方法,但是还有更简单的实现方法:
上面的代码片段使用 contextmanager 管理器装饰器实现了内容管理协议。在进入 with
块时,tag
函数(在yield之前)的第一部分会被执行,然后该with块被执行,最后,tag函数的其余部分会被执行。
使用slots节省内存
如果您曾经编写过一个创建某个类的大量实例的程序,您可能会注意到您的程序会突然需要大量内存。这是因为Python使用字典来表示类实例的属性,这使得它的速度很快,但是内存效率不高,这通常并不是一个问题。然而,如果它成为您的程序的一个问题时,您可以尝试使用slots:
这里的情况是,当我们定义了slots属性时,Python会使用小的固定大小的数组而不是字典来定义属性,这大大减少了每个实例所需的内存。使用slots也有一些缺点——我们不能声明任何新的属性,并且我们只能使用在slots上这些属性。而且,带有slots的类不能使用多重继承。
限制CPU和内存的使用
如果您不想优化您的程序内存或CPU使用,您只想把它限制在某个固定大小的内存上,那么Python也有一个这样的库来做到这一点:
这里我们可以看到设置最大CPU运行时间和最大内存使用限制的两个选项。对于CPU限制,我们首先获取特定资源(RLIMIT_CPU)的软限制和硬限制,然后使用参数指定的秒数和前面检索到的硬限制来设置它。最后,我们注册信号,如果CPU时间超过限制,该信号会导致系统退出。对于内存,我们再次检索软限制和硬限制,并使用带有大小参数的setrlimit和检索的硬限制来设置它。
一些语言有非常明显的导出成员(变量、方法、接口)的控制机制,例如Golang,其中只有以大写字母开头的成员会被导出。另一方面,在Python中,所有东西都可以被导出,除非我们使用all:
根据上面的代码片段,我们知道只有bar函数会被导出。同样,我们可以让all为空,这样,当我们从这个模块导入的时候,任何东西都不会被导出,并且会导致AttributeError。
考虑到目前已经有相当多的比较操作符 — __lt__
、__le__
、__gt__
或 __ge___
,因此,为一个类实现所有的比较操作符是相当烦人的。但如果有更简单的方法可以实现呢?functools.total_ordering就派上用场了:
那么,这到底是怎么工作的呢?total_ordering装饰器用于简化实现类实例排序的过程。我们只需要定义lt和eq,它们是剩余操作的映射所需的最小值,装饰器就会为我们填充空白。
并不是所有这些特性在日常的 Python 编程中都是必需的和有用的,但是它们中的一些可能会不时地派上用场,而且它们还可能会简化那些在其他情况下会非常冗长和难以实现的任务。我也想说明的是,所有这些特性是 Python 标准库的一部分,而其中的一些在我看来就像标准库中所具有的相当不标准的东西,所以当您想使用 Python 实现某些东西的时候,您首先应该去标准库中寻找它,如果您不能找到它,那么您可能还是不够努力(如果真的没有,那它肯定在一些第三方库中)。
Python 使用习惯是指那些经常被使用的语法、语义和结构,这样写更加符合 Python 风格,看起来更像一个地道的 Pythoner。
本系列目的,分类整理 Python 使用习惯。
直接使用 x
和 not x
判断 x
是否为 None
或 "空":
x = [1, 3, 5]
if x:
print('x is not empty ')
if not x:
print('x is empty')
下面写法不够 Pythoner:
if x and len(x) > 0:
print('x is not empty ')
if x is None or len(x) == 0:
print('x is empty')
直接使用 enumerate
枚举容器,第二个参数表示索引的起始值
x = [1, 3, 5]
for i, e in enumerate(x, 10):
# 枚举
print(i, e)
>>>
下面写法不够 Pythoner:
i = 0
while i < len(x):
print(i + 10, x[i])
i += 1
判断字符串是否包含某个子串,使用 in
明显更加可读:
x = 'zen_of_python'
if 'zen' in x:
print('zen is in')
>>>
find
的返回值 要与 -1
判断,不太符合习惯:
if x.find('zen') != -1:
print('zen is in')
使用 zip
打包后结合 for
使用输出一对,更加符合习惯:
keys = ['a', 'b', 'c']
values = [1, 3, 5]
for k, v in zip(keys, values):
print(k, v)
>>>
下面不符合 Python 习惯:
d = {}
i = 0
for k in keys:
print(k, values[i])
i += 1
打印被分为多行的字符串,使用一对 '''
更加符合 Python 习惯:
print('''"Oh no!" He exclaimed.
"It's the blemange!"''')
下面写法就太不 Python 风格:
print('"Oh no!" He exclaimed.\n' +
'It\'s the blemange!"')
直接解包赋值,更加符合 Python 风格:
a, b = 1, 3
a, b = b, a
# 交换 a, b 的值
不要再用临时变量 tmp
,这不符合 Python 习惯:
tmp = a
a = b
b = tmp
串联字符串,更习惯使用 join
:
chars = ['P', 'y', 't', 'h', 'o', 'n']
name = ''.join(chars)
print(name)
>>> Python
下面不符合 Python 习惯:
name = ''
for c in chars:
name += c
print(name)
列表生成式构建高效,符合 Python 习惯:
data = [1, 2, 3, 5, 8]
result = [i * 2 for i in data if i & 1]
# 奇数则乘以2
print(result)
>>> [2, 6, 10]
下面写法不够 Pythoner:
results = []
for e in data:
if e & 1:
results.append(e*2)
print(results)
除了列表生成式,还有字典生成式:
keys = ['a', 'b', 'c']
values = [1, 3, 5]
d = {k: v for k, v in zip(keys, values)}
print(d)
下面写法不太 Pythoner:
d = {}
for k, v in zip(keys, values):
d[k] = v
print(d)
曾几何时,看这别人代码这么写,我们也就跟着这么用吧,其实还没有完全弄清楚这行到底干啥。
def mymain():
print('Doing something in module', __name__)
if __name__ == '__main__':
print('Executed from command line')
mymain()
加入上面脚本命名为 MyModule
,不管在 vscode 还是 pycharm 直接启动,则直接打印出:
Executed from command line
Doing something in module main
这并不奇怪,和我们预想一样,因为有无这句 main ,都会打印出这些。
但是当我们 import MyModule 时,如果没有这句,直接就打印出:
In [2]: import MyModule
Executed from command line
Doing something in module MyModule
只是导入就直接执行 mymain 函数,这不符合我们预期。
如果有主句,导入后符合预期:
In [6]: import MyModule
In [7]: MyModule.mymain()
Doing something in module MyModule
以上就是 10 个 Python 习惯用法,第一期。希望能帮助到你,欢迎在看哦。