Mi respuesta aceptada a esta pregunta fue bastante tonto, pero yo estaba empezando a cabo. Aquí hay una manera mucho mejor. Esto solo se prueba escasamente, pero es bueno para una demostración de la forma correcta de hacer esto que no es adecuada. Funciona en 2.6.5 seguro. No he probado ninguna otra versión, pero los códigos de operación no están codificados, por lo que debería ser tan portátil como la mayoría de los otros códigos 2.x.
add_self
se puede aplicar como decorador, pero eso sería contrario al propósito (¿por qué no simplemente escriba 'self'?) Sería fácil adaptar la metaclase de mi otra respuesta para aplicar esta función.
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')
Simplemente enséñeles que 'def foo (self):' es parte de la plantilla que debe cumplirse en cada función. No te centres en el porqué, solo enfatiza que DEBE estar ahí, y probablemente estés bien. – derekerdmann
¡Probablemente tengas razón, pero todavía estoy interesado en ver lo que se le ocurre a la gente! – kindall
¿Por qué no simplemente hace que sus módulos de clases y sus métodos funcionen en el módulo? Menos repetitivo y cosas para hacer. 'py.test' hace de manera muy efectiva. –