kin*_*all 8 python variables local self
我正在开发一个文档测试框架 - 基本上是PDF的单元测试.测试是框架定义的类实例的(修饰)方法,它们在运行时定位和实例化,并调用方法来执行测试.
我的目标是减少编写测试的人需要关注的古怪Python语法的数量,因为这些人可能是也可能不是Python程序员,甚至根本不是程序员.所以我希望他们能够为方法编写"def foo():"而不是"def foo(self):",但仍然可以使用"self"来访问成员.
在一个普通的程序中,我会认为这是一个可怕的想法,但在像这样的特定于域的语言类程序中,似乎值得一试.
我已经通过使用装饰器成功地从方法签名中消除了自我(实际上,因为我已经为测试用例使用了装饰器,我只是将其滚动到那个),但是"self"不会引用任何内容.测试用例方法.
我已经考虑过使用global for self,甚至提出了一个或多或少有效的实现,但我宁愿污染最小的命名空间,这就是为什么我更愿意将变量直接注入test case方法的本地命名空间.有什么想法吗?
我对这个问题的接受答案非常愚蠢,但我刚刚开始.这是一个更好的方法.这只是经过严格测试,但它有助于演示正确的做这件事的方法.它肯定适用于2.6.5.我还没有测试任何其他版本,但没有操作码硬编码到它,所以它应该像大多数其他2.x代码一样可移植.
add_self 可以作为装饰器应用,但这会失败的目的(为什么不只是键入'self'?)从我的其他答案中调整元类很容易应用这个函数.
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')
Run Code Online (Sandbox Code Playgroud)
aaronasterling解决方案的升级很少(我没有足够的声誉来评论它):
def wrap(f):
@functools.wraps(f)
def wrapper(self,*arg,**kw):
f.func_globals['self'] = self
return f(*arg,**kw)
return wrapper
Run Code Online (Sandbox Code Playgroud)
但是如果为不同的实例递归调用f函数,这两个解决方案都将无法预测,所以你必须像这样克隆它:
import types
class wrap(object):
def __init__(self,func):
self.func = func
def __get__(self,obj,type):
new_globals = self.func.func_globals.copy()
new_globals['self'] = obj
return types.FunctionType(self.func.func_code,new_globals)
class C(object):
def __init__(self,word):
self.greeting = word
@wrap
def greet(name):
print(self.greeting+' , ' + name+ '!')
C('Hello').greet('kindall')
Run Code Online (Sandbox Code Playgroud)
这是一个单行方法装饰器,它似乎可以完成这项工作,而无需修改可调用类型*标记为只读的任何特殊属性:
# method decorator -- makes undeclared 'self' argument available to method
injectself = lambda f: lambda self: eval(f.func_code, dict(self=self))
class TestClass:
def __init__(self, thing):
self.attr = thing
@injectself
def method():
print 'in TestClass::method(): self.attr = %r' % self.attr
return 42
test = TestClass("attribute's value")
ret = test.method()
print 'return value:', ret
# output:
# in TestClass::method(): self.attr = "attribute's value"
# return value: 42
Run Code Online (Sandbox Code Playgroud)
请注意,除非您采取预防措施来防止它,否则该函数的副作用eval()可能是它自动添加一些条目(例如对__builtin__键下模块的引用__builtins__)dict传递给它。
@kendall:根据您关于如何将其与容器类中的方法一起使用的评论(但暂时忽略其他变量的注入)——以下内容是否类似于您正在做的事情?我很难理解框架和用户编写的内容之间是如何划分的。对我来说,这听起来是一个有趣的设计模式。
# method decorator -- makes undeclared 'self' argument available to method
injectself = lambda f: lambda self: eval(f.func_code, dict(self=self))
class methodclass:
def __call__():
print 'in methodclass::__call__(): self.attr = %r' % self.attr
return 42
class TestClass:
def __init__(self, thing):
self.attr = thing
method = injectself(methodclass.__call__)
test = TestClass("attribute's value")
ret = test.method()
print 'return value:', ret
# output
# in methodclass::__call__(): self.attr = "attribute's value"
# return value: 42
Run Code Online (Sandbox Code Playgroud)