一些python中的小坑
最近在重度使用 python
,
记录一下一些进阶使用时的问题.
0x0 小心输入参数为可能为生成器
给一个例子如下,我们的 node
需要接受一个 tuple 参数
class Node():
def __init__(self, op: str, args: tuple) -> None:
self.op = op
self.args = args
def __repr__(self) -> str:
if self.args:
return '{' + f" {self.op}({','.join(str(arg) for arg in self.args)}) " + '}'
else:
return f' {self.op}() '
但是我们可能在构造的时候没有注意到实际传入的变量类型是什么,当我们调用
node
第一次与第二次时,可以观察到出现了不同的结果:
node = Node('a', (Node('a' + str(i), ()) for i in range(10)))
node
# { a( a0() , a1() , a2() , a3() , a4() , a5() , a6() , a7() , a8() , a9() ) }
node
# { a() }
这其实就是 python3
里面的一个 feature
那就是列表推导默认得到生成器, 结果我一不小心踩进去找了半天
bug
. 所以我们在构造节点的时候需要这样: node = Node('a', tuple(Node('a' + str(i), ()) for i in range(10)))
0x1 __new__
的时候默认参数的问题
比如我们这样定义一个类,但是用默认参数初始化(无参数初始化)就会报错,
比如得在 overwrite
的 __new__
那边加上默认参数才可以,我属实是没明白.
class Base(): |
0x02
__future__.annotations
与type hint
反射冲突
通常情况下__future__.annotations
是用来解决当前type hint
的类型还没有被声明就被使用的情况,但是如果像我这样拿type hint
来做自省,那么就会遇到问题,也就是自省得到的结果从一个type
被转变为了一个str
,就做不了更多的事情了.
# from __future__ import annotations |
输出 # 开启__future__.annotations
{'c': 'color.r'}
# 关闭__future__.annotations
{'c': <color.r: 1>}
0x03
python
与cpp
交互时需要注意对象的生命周期
我们底层在cpp
中实现了一个解释器,这个解释器的
pc
指向了一个指令bytes
,但是如果在python
中以这种形式调用就会出错:
interp = interpreter()
interp.load(open('xx','rb').read())
interp.run()python
的
debug
时候,直接报错 core dump
, 如果开启
debug
的时候,
解释器pc
读到的指令全部都是0x00
.
实际上问题就是python
会自动把我们读出来的指令流回收,因为他认为退出了load
的函数域就没有人在使用了,但是实际上我们的指针还指向那块内存.
同时我估计 python debug
的时候分配的内存区域和正常执行时不一样,所以debug
状态指针不会报越界错误.
目前的解决方案就是在pybind11
外面再用继承的方式重新实现一些类方法,手动保存好程序数据.
0x04
namedtuple
默认参数为list
时
代码如下,
如果是默认参数为list
的NamedTuple
,在新创建对象时那两个
list
还是指向同一个 list
, 就会导致意外的问题.
其实主要是NamedTuple
是通过metaclass
构造的.我们没法重写new
方法,这个太蛋疼了.
from typing import NamedTuple |
0x05 product 的实现
fi = [i for i in range(2)] |
0x06 split combine 实现
sequences = [tuple(pool) for pool in [fi, fj, fk]] |