根据大众的需求(好吧,一个人的好奇心……),以下是在调用函数之前实际更改函数中字符串的方法。
在实践中你不应该这样做。有一些使用代码对象的用例,但这确实不是其中之一。另外,如果你做的事情不那么琐碎,你应该使用像bytecode 或byteplay 这样的库,而不是手动进行。此外,不言而喻,并非所有 Python 实现都使用 CPython 风格的代码对象。但无论如何,这里是:
import types
def string():
print ("This cat was scared.")
def main():
# A function object is a wrapper around a code object, with
# a bit of extra stuff like default values and closure cells.
# See inspect module docs for more details.
co = string.__code__
# A code object is a wrapper around a string of bytecode, with a
# whole bunch of extra stuff, including a list of constants used
# by that bytecode. Again see inspect module docs. Anyway, inside
# the bytecode for string (which you can read by typing
# dis.dis(string) in your REPL), there's going to be an
# instruction like LOAD_CONST 1 to load the string literal onto
# the stack to pass to the print function, and that works by just
# reading co.co_consts[1]. So, that's what we want to change.
consts = tuple(c.replace("cat", "dog") if isinstance(c, str) else c
for c in co.co_consts)
# Unfortunately, code objects are immutable, so we have to create
# a new one, copying over everything except for co_consts, which
# we'll replace. And the initializer has a zillion parameters.
# Try help(types.CodeType) at the REPL to see the whole list.
co = types.CodeType(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
consts, co.co_names, co.co_varnames, co.co_filename,
co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
string.__code__ = co
string()
main()
如果这对你来说还不够 hacky:我提到代码对象是不可变的。当然,字符串也是如此。但在幕后足够深,它们只是指向一些 C 数据的指针,对吧?同样,仅当我们使用 CPython 时,但如果我们……
首先,从 GitHub 上获取我的 superhackyinternals 项目。 (它是故意不可安装的,因为您真的不应该使用它,除非您尝试本地构建的解释器等。)然后:
import ctypes
import internals
def string():
print ("This cat was scared.")
def main():
for c in string.__code__.co_consts:
if isinstance(c, str):
idx = c.find('cat')
if idx != -1:
# Too much to explain here; see superhackyinternals
# and of course the C API docs and C source.
p = internals.PyUnicodeObject.from_address(id(c))
assert p.compact and p.ascii
length = p.length
addr = id(c) + internals.PyUnicodeObject.utf8_length.offset
buf = (ctypes.c_int8 * 3).from_address(addr + idx)
buf[:3] = b'dog'
string()
main()