My accepted answer to this question was pretty dumb but I was just starting out. Here's a much better way. This is only scantily tested but it's good for a demonstration of the proper way to do this thing which is improper to do. It works on 2.6.5 for sure. I haven't tested any other versions but no opcodes are hardcoded into it so it should be about as portable as most other 2.x code.
add_self
can be applied as a decorator but that would defeat the purpose (why not just type 'self'?) It would be easy to adapt the metaclass from my other answer to apply this function instead.
import opcode
import types
def instructions(code):
"""Iterates over a code string yielding integer [op, arg] pairs
If the opcode does not take an argument, just put None in the second part
"""
code = map(ord, code)
i, L = 0, len(code)
extended_arg = 0
while i < L:
op = code[i]
i+= 1
if op < opcode.HAVE_ARGUMENT:
yield [op, None]
continue
oparg = code[i] + (code[i+1] << 8) + extended_arg
extended_arg = 0
i += 2
if op == opcode.EXTENDED_ARG:
extended_arg = oparg << 16
continue
yield [op, oparg]
def write_instruction(inst):
"""Takes an integer [op, arg] pair and returns a list of character bytecodes"""
op, oparg = inst
if oparg is None:
return [chr(op)]
elif oparg <= 65536L:
return [chr(op), chr(oparg & 255), chr((oparg >> 8) & 255)]
elif oparg <= 4294967296L:
# The argument is large enough to need 4 bytes and the EXTENDED_ARG opcode
return [chr(opcode.EXTENDED_ARG),
chr((oparg >> 16) & 255),
chr((oparg >> 24) & 255),
chr(op),
chr(oparg & 255),
chr((oparg >> 8) & 255)]
else:
raise ValueError("Invalid oparg: {0} is too large".format(oparg))
def add_self(f):
"""Add self to a method
Creates a new function by prepending the name 'self' to co_varnames, and
incrementing co_argcount and co_nlocals. Increase the index of all other locals
by 1 to compensate. Also removes 'self' from co_names and decrease the index of
all names that occur after it by 1. Finally, replace all occurrences of
`LOAD_GLOBAL i,j` that make reference to the old 'self' with 'LOAD_FAST 0,0'.
Essentially, just create a code object that is exactly the same but has one more
argument.
"""
code_obj = f.func_code
try:
self_index = code_obj.co_names.index('self')
except ValueError:
raise NotImplementedError("self is not a global")
# The arguments are just the first co_argcount co_varnames
varnames = ('self', ) + code_obj.co_varnames
names = tuple(name for name in code_obj.co_names if name != 'self')
code = []
for inst in instructions(code_obj.co_code):
op = inst[0]
if op in opcode.haslocal:
# The index is now one greater because we added 'self' at the head of
# the tuple
inst[1] += 1
elif op in opcode.hasname:
arg = inst[1]
if arg == self_index:
# This refers to the old global 'self'
if op == opcode.opmap['LOAD_GLOBAL']:
inst[0] = opcode.opmap['LOAD_FAST']
inst[1] = 0
else:
# If `self` is used as an attribute, real global, module
# name, module attribute, or gets looked at funny, bail out.
raise NotImplementedError("Abnormal use of self")
elif arg > self_index:
# This rewrites the index to account for the old global 'self'
# having been removed.
inst[1] -= 1
code += write_instruction(inst)
code = ''.join(code)
# type help(types.CodeType) at the interpreter prompt for this one
new_code_obj = types.CodeType(code_obj.co_argcount + 1,
code_obj.co_nlocals + 1,
code_obj.co_stacksize,
code_obj.co_flags,
code,
code_obj.co_consts,
names,
varnames,
'<OpcodeCity>',
code_obj.co_name,
code_obj.co_firstlineno,
code_obj.co_lnotab,
code_obj.co_freevars,
code_obj.co_cellvars)
# help(types.FunctionType)
return types.FunctionType(new_code_obj, f.func_globals)
class Test(object):
msg = 'Foo'
@add_self
def show(msg):
print self.msg + msg
t = Test()
t.show('Bar')
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…