那些用起来很爽,但用不好可能会被人打的Python骚操作

这是作者(Loco)在PyCon China 2020 上海场闪电演讲内容的文字版本。

快速将两个分别存放有key和value的列表合并成一个字典

>>> a = ["key1", "key2", "key3"]
>>> b = ["value1", "value2", "value3"]
>>> dict(zip(a, b))
{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

这个操作的用途对于爬虫工程师而言挺常见的,比如说:

你需要采集一些键值对类型的信息,然后你可能会碰到一些平台的接口返回这些信息时是直接返回的一个只有value的列表,并且key都是写死在代码或请求参数里的,这时候你就可以通过这个操作来将它们快速合并成一个字典,这样就能比较方便地取值了,取值部分的可读性也会好很多。

还有一种情况就是,你可能会碰到一些网站上的这种键值对信息在HTML中key和value的元素是平级关系,并且key和value也没有能够区分的标识,这些情况我们只能通过把整个键值对部分的元素都提取出来转换成列表,然后再通过按列表下标间隔切片的方式分别取出key和value的列表,然后再将它们合并成一个字典。比如这样:

>>> result = ["key1", "value1", "key2", "value2", "key3", "value3"]
>>> result[0::2]
['key1', 'key2', 'key3']
>>> result[1::2]
['value1', 'value2', 'value3']
>>> dict(zip(result[0::2], result[1::2]))
{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

需要注意的是,这个操作在碰到key和value数量不一致的时候,会自动忽略掉多的那部分列表尾部的值,你需要确保key和value是对得上的才行。

简单来说就是,如果你的key列表比value列表多一个值,那么最终出来的字典就会缺失掉key列表中的最后那一个多出来的值。

快速按头、尾、中间部分切割元素,并将它们分别赋值给三个变量

>>> a = "123456789"  # 也可以是list之类的
>>> a1, *a2, a3 = a
>>> a1
'1'
>>> a2
['2', '3', '4', '5', '6', '7', '8']
>>> a3
'9' 

这个操作的用途,对于爬虫工程师或者是一些会接触到私有协议的后端工程师应该都挺常见的,比如说:

你可能会碰到一些基于TCP或UDP搞的私有协议,然后它们可能会定义一个由内容类型头、通信内容、校验码之类的东西组成的结构,每次通信时都会给你返回这么一个东西,然后你需要将它们切开并赋值给不同的变量,这时候你就可以通过这个操作来快速实现这个效果了。

当然,这个操作也不能乱用,你最好是能够确保它的内容不会变化,像私有协议这种情况,你可以判断一下协议版本号之类的,以确保它的内容一定是这个结构。

快速解压内含嵌套列表的列表,并同时将嵌套列表内的值也赋值给不同的变量

>>> result = [1, 2, [3, 4], 5]
>>> [a, b, [c, d], e] = result
>>> a
1
>>> b
2
>>> c
3
>>> d
4
>>> e
5

这个操作也是偶尔会在处理一些私有协议时用到,你可以通过这个操作快速地将列表中的值赋值给不同的变量再进行处理,非常方便。

如果你只需要嵌套列表中的某个开头的值,其余的不需要怎么办呢?可以像这样:

>>> result = [1, 2, [3, 4, 5]]
>>> [a, b, [c, *_]] = result
>>> a
1
>>> b
2
>>> c
3

总之就是和前面的操作一样,使用星号来处理后面的多个值,并将它们赋值给下划线这种临时变量抛弃。

注:下划线变量的用途和含义可以自行通过搜索引擎搜索一下,网上有很多文章提到,这里就不再赘述了。

遍历嵌套且长短不一的列表时,按头、尾切割,并将它们分别赋值给两个变量

>>> result = [["items", "item1", "item2", "item3"], ["status", 1]]
>>> for key, *values in result:
...     print(key)
...     print(values)
...
>>> items
['item1', 'item2', 'item3']
>>> status
[1]


这个操作偶尔会在处理一些私有协议或者奇葩平台的接口时用到,就是对方给你返回的内容可能是这么一个嵌套列表,子列表的第一个值是key、后面的部分是value,然后有些奇葩点的平台可能连子列表的顺序都不相同。

这种时候如果用这个操作去取key和value的话,就会方便很多,你不需要管它的value到底有多少个,也不需要按下标0去取嵌套列表中的key,你只需要直接这么for一下然后处理key和value就完事了。

>>> {key:values for key, *values in result}
{'items': ['item1', 'item2', 'item3'], 'status': [1]}

甚至你还可以直接把这个代码写成一行,直接将它们转成字典再做后续的处理。

快速解压一个字典,并将它里面的key和value们分别赋值给不同的变量

>>> a = {"key1": "value1", "key2": "value2", "key3": "value3"}
>>> (key1, value1), (key2, value2), (key3, value3) = a.items()
>>> key1
'key1'
>>> value1
'value1'
>>> key2
'key2'
>>> value2
'value2'

这个操作的用途也是对于爬虫工程师和后端工程师而言会比较常见,比如说:

你需要提取一个接口返回的带有状态码、状态信息和data的内容,并且你需要判断一下状态码是不是代表请求成功的那个,这时候如果是一个一个通过key去取值、赋值的话就会很麻烦,但如果你用这个操作的话,就可以快速解决。

可能光是这么说不太直观,看一段样例代码吧:

>>> result = {"code": 200, "data": {"balabala": 111}, "msg": None}
>>> (_, code), (_, data), (_, msg)  = result.items()
>>> code
200
>>> data
{'balabala': 111}
>>> msg
>>>

当然这个操作也不能乱用,在使用这个操作时,你需要确保字典中key的顺序严格一致,否则就可能会出现提取到错误内容的情况,所以提取之前为了防止顺序错乱,可以先按key做个排序,以确保顺序是严格一致的。

然后字典中的内容是否会有变化也是需要考虑的,如果你写的代码需要非常严谨,那就还是老老实实地按key一个一个取吧,毕竟如果这里面多了一个key在中间位置,取到的东西就会完全不同。

动态构建出Python中的各种运算符

这个操作是基于Python标准库之一的operator库做的,它可以实现各种比较运算、逻辑运算、数学运算、序列运算。由于这个库里的东西比较多,所以我们这里看个图就好,不多赘述了,大家可以后面自己看看官方文档。

然后用途的话,举个例子,比如你是一个爬虫工程师,你想弄一个纯图形化界面采集数据的工具,然后这时候你可能会碰到某些平台(网站和APP的统称)的内容较为复杂,有些字符串需要按下标取出特定部分的内容,这时候你就可以通过operator.getitem(str, slice(start, end))来取,这里的startend就直接在工具的界面上填写或选择出来就好了。

>>> import operator
>>> a = "666111666"
>>> operator.getitem(a, slice(3, 6))
'111'

动态创建函数

有时候你可能会碰到类似这样的特殊情况:你有一些不同的值需要通过处理方式相同的函数进行处理,但由于条件限制你还不能将这个函数归纳为一个并通过传参的方式进行处理,只能是写出多个不同名称的函数来分别处理。

或者你可能就是单纯有个需求需要动态创建一个临时函数来使用。

这时候如果使用这个操作的话,就可以很轻松地解决这个问题,只需要像这样就可以动态地创建出一个函数了:

>>> from types import FunctionType
>>>
>>> func = FunctionType(compile(
...     ("def func():\n"
...      "    print(1)\n"
...      "    return 2"),
...     "<string>",
...     "exec"
... ).co_consts[0], globals())
>>> print(func())
1
2

注:这里面用括号包起来的字符串会自动进行拼接,属于Python中多行字符串的写法之一,优点是不会因为像三个引号那样会因为缩进导致字符串内容受影响。如果想了解更多可以自行查看Python官方文档,里面的字符串部分有讲这个小技巧。

我们还可以对里面那个函数代码字符串进行动态生成(比如用format),以实现对函数名和内容的修改,甚至我们还可以通过使用像Jinja这种模板渲染库来实现更方便的函数代码生成。

像我前段时间写那篇发票整理工具的文章时,就碰到了前面说的那个特殊情况,由于Django Admin的那个action函数的参数是固定的,并且如果需要传参给action函数的话就需要通过中间页来实现,而我既不想弄中间页、又有好几个不同的参数需要分别处理,于是就直接采用动态创建函数的方式来解决了。

动态导入

有时候你可能会有一些扩展代码之类的需要在运行时动态地被导入,这时候你就可以用上这个操作了,比如说我们需要导入operator这个库:

>>> import importlib
>>>
>>> module = importlib.import_module("operator")


然后这个module变量就是被导入后的模块名称了,我们可以直接和正常导入时一样使用,比如我们要调用它的add函数:

>>> module.add(1, 1)  # 加法运算符
2

动态调用

有时候你在动态导入之后,还会需要进行动态调用,这时候你就可以这样:

>>> import importlib
>>>
>>> module = importlib.import_module("operator")
>>> func = getattr(module, "add")
>>> func(1, 1) 
2 

当然,在使用动态创建、动态导入、动态调用这种比较Hack的操作的时候,一定要注意安全问题,就是如果你在使用时有部分参数是需要用户输入的的话,就一定要对输入内容进行检查,以免被利用来直接执行危险代码。

比如说你提供了一个动态创建函数的功能,如果没有检查内容的话,可能有些比较坏的人直接就通过os库之类的来调用命令行把你的机子给黑了,非常危险。

猜你喜欢

转载自blog.csdn.net/lantian_123/article/details/110914091