替换 Python 的解析器功能?
Replacing Python's parser functionality?
首先,我想说的是,我知道这是一个糟糕的想法,不应该这样做。我的目的主要是好奇和学习 Python 的内部结构,以及如何 'hack' 它们。
我想知道是否有可能改变我们使用 []
创建列表时发生的事情。有没有办法修改解析器的行为方式,例如,使 ["hello world"]
调用 print("hello world")
而不是创建包含一个元素的列表?
我试图找到关于此的任何文档或帖子,但没有找到。
下面是替换内置字典以使用自定义字典的示例 class:
from __future__ import annotations
from typing import List, Any
import builtins
class Dict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
def subset(self, keys: List[Any]) -> Dict:
return Dict({key: self[key] for key in keys})
builtins.dict = Dict
导入此模块时,它会将内置的 dict
替换为 Dict
class。然而,这仅在我们直接调用 dict()
时有效。如果我们尝试使用 {}
它将回退到基础 dict
内置实现:
import new_dict
a = dict({'a': 5, 'b': 8})
b = {'a': 5, 'b': 8}
print(type(a))
print(type(b))
产量:
<class 'py_extensions.new_dict.Dict'>
<class 'dict'>
[]
和 {}
被编译为特定的操作码,分别是 return 一个 list
或一个 dict
。另一方面,list()
和 dict()
编译为字节码,在全局变量中搜索 list
和 dict
,然后将它们作为函数调用:
import dis
dis.dis(lambda:[])
dis.dis(lambda:{})
dis.dis(lambda:list())
dis.dis(lambda:dict())
returns(为清楚起见,有一些额外的换行符):
3 0 BUILD_LIST 0
2 RETURN_VALUE
5 0 BUILD_MAP 0
2 RETURN_VALUE
7 0 LOAD_GLOBAL 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
9 0 LOAD_GLOBAL 0 (dict)
2 CALL_FUNCTION 0
4 RETURN_VALUE
因此,您可以通过覆盖全局 dict
来覆盖 dict()
return 的内容,但不能覆盖 {}
return 的内容.
这些操作码已记录 here. If the BUILD_MAP opcode runs, you get a dict
, no way around it. As an example, here is the implementation of BUILD_MAP in CPython, which calls the function _PyDict_FromItems。它不查看任何类型的 user-defined classes,它专门创建一个 C 结构来表示 python dict
.
至少在某些情况下可以在运行时操纵 python 字节码。如果您真的想要将{}
return设为自定义class,我想您可以编写一些代码来搜索BUILD_MAP
操作码并将其替换为适当的操作码。尽管这些操作码的大小不同,因此您可能还需要进行一些额外的更改。
ast
模块是 Python 的抽象语法树的接口,它是在解析 Python 代码后构建的。
通过修改 Python 代码的抽象语法树,可以用 dict
调用替换文字 dict
({}
)。
import ast
import new_dict
a = dict({"a": 5, "b": 8})
b = {"a": 5, "b": 8}
print(type(a))
print(type(b))
print(type({"a": 5, "b": 8}))
src = """
a = dict({"a": 5, "b": 8})
b = {"a": 5, "b": 8}
print(type(a))
print(type(b))
print(type({"a": 5, "b": 8}))
"""
class RewriteDict(ast.NodeTransformer):
def visit_Dict(self, node):
# don't replace `dict({"a": 1})`
if isinstance(node.parent, ast.Call) and node.parent.func.id == "dict":
return node
# replace `{"a": 1} with `dict({"a": 1})
new_node = ast.Call(
func=ast.Name(id="dict", ctx=ast.Load()),
args=[node],
keywords=[],
type_comment=None,
)
return ast.fix_missing_locations(new_node)
tree = ast.parse(src)
# set parent to every node
for node in ast.walk(tree):
for child in ast.iter_child_nodes(node):
child.parent = node
RewriteDict().visit(tree)
exec(compile(tree, "ast", "exec"))
输出;
<class 'new_dict.Dict'>
<class 'dict'>
<class 'dict'>
<class 'new_dict.Dict'>
<class 'new_dict.Dict'>
<class 'new_dict.Dict'>
首先,我想说的是,我知道这是一个糟糕的想法,不应该这样做。我的目的主要是好奇和学习 Python 的内部结构,以及如何 'hack' 它们。
我想知道是否有可能改变我们使用 []
创建列表时发生的事情。有没有办法修改解析器的行为方式,例如,使 ["hello world"]
调用 print("hello world")
而不是创建包含一个元素的列表?
我试图找到关于此的任何文档或帖子,但没有找到。
下面是替换内置字典以使用自定义字典的示例 class:
from __future__ import annotations
from typing import List, Any
import builtins
class Dict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
def subset(self, keys: List[Any]) -> Dict:
return Dict({key: self[key] for key in keys})
builtins.dict = Dict
导入此模块时,它会将内置的 dict
替换为 Dict
class。然而,这仅在我们直接调用 dict()
时有效。如果我们尝试使用 {}
它将回退到基础 dict
内置实现:
import new_dict
a = dict({'a': 5, 'b': 8})
b = {'a': 5, 'b': 8}
print(type(a))
print(type(b))
产量:
<class 'py_extensions.new_dict.Dict'>
<class 'dict'>
[]
和 {}
被编译为特定的操作码,分别是 return 一个 list
或一个 dict
。另一方面,list()
和 dict()
编译为字节码,在全局变量中搜索 list
和 dict
,然后将它们作为函数调用:
import dis
dis.dis(lambda:[])
dis.dis(lambda:{})
dis.dis(lambda:list())
dis.dis(lambda:dict())
returns(为清楚起见,有一些额外的换行符):
3 0 BUILD_LIST 0
2 RETURN_VALUE
5 0 BUILD_MAP 0
2 RETURN_VALUE
7 0 LOAD_GLOBAL 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
9 0 LOAD_GLOBAL 0 (dict)
2 CALL_FUNCTION 0
4 RETURN_VALUE
因此,您可以通过覆盖全局 dict
来覆盖 dict()
return 的内容,但不能覆盖 {}
return 的内容.
这些操作码已记录 here. If the BUILD_MAP opcode runs, you get a dict
, no way around it. As an example, here is the implementation of BUILD_MAP in CPython, which calls the function _PyDict_FromItems。它不查看任何类型的 user-defined classes,它专门创建一个 C 结构来表示 python dict
.
至少在某些情况下可以在运行时操纵 python 字节码。如果您真的想要将{}
return设为自定义class,我想您可以编写一些代码来搜索BUILD_MAP
操作码并将其替换为适当的操作码。尽管这些操作码的大小不同,因此您可能还需要进行一些额外的更改。
ast
模块是 Python 的抽象语法树的接口,它是在解析 Python 代码后构建的。
通过修改 Python 代码的抽象语法树,可以用 dict
调用替换文字 dict
({}
)。
import ast
import new_dict
a = dict({"a": 5, "b": 8})
b = {"a": 5, "b": 8}
print(type(a))
print(type(b))
print(type({"a": 5, "b": 8}))
src = """
a = dict({"a": 5, "b": 8})
b = {"a": 5, "b": 8}
print(type(a))
print(type(b))
print(type({"a": 5, "b": 8}))
"""
class RewriteDict(ast.NodeTransformer):
def visit_Dict(self, node):
# don't replace `dict({"a": 1})`
if isinstance(node.parent, ast.Call) and node.parent.func.id == "dict":
return node
# replace `{"a": 1} with `dict({"a": 1})
new_node = ast.Call(
func=ast.Name(id="dict", ctx=ast.Load()),
args=[node],
keywords=[],
type_comment=None,
)
return ast.fix_missing_locations(new_node)
tree = ast.parse(src)
# set parent to every node
for node in ast.walk(tree):
for child in ast.iter_child_nodes(node):
child.parent = node
RewriteDict().visit(tree)
exec(compile(tree, "ast", "exec"))
输出;
<class 'new_dict.Dict'>
<class 'dict'>
<class 'dict'>
<class 'new_dict.Dict'>
<class 'new_dict.Dict'>
<class 'new_dict.Dict'>