如何在不显式接受Python方法的情况下将self引入Python方法
我正在开发一个文档测试框架——基本上是PDF的单元测试。测试是由框架定义的类实例的(修饰的)方法,这些方法在运行时被定位和实例化,并调用这些方法来执行测试 我的目标是减少编写测试的人员需要关注的奇怪Python语法的数量,因为这些人可能是Python程序员,也可能不是Python程序员,甚至是非常多的程序员。因此,我希望他们能够为方法编写“def foo():”而不是“def foo(self):”,但仍然能够使用“self”访问成员 在一个普通的程序中,我会认为这是一个可怕的想法,但在一个特定于领域的语言类程序中,像这样的一个程序,似乎值得一试。 我已经通过使用decorator成功地从方法签名中消除了self(实际上,因为我已经在测试用例中使用了decorator,所以我只想将其应用到其中),但是“self”并不引用测试用例方法中的任何内容如何在不显式接受Python方法的情况下将self引入Python方法,python,variables,local,self,Python,Variables,Local,Self,我正在开发一个文档测试框架——基本上是PDF的单元测试。测试是由框架定义的类实例的(修饰的)方法,这些方法在运行时被定位和实例化,并调用这些方法来执行测试 我的目标是减少编写测试的人员需要关注的奇怪Python语法的数量,因为这些人可能是Python程序员,也可能不是Python程序员,甚至是非常多的程序员。因此,我希望他们能够为方法编写“def foo():”而不是“def foo(self):”,但仍然能够使用“self”访问成员 在一个普通的程序中,我会认为这是一个可怕的想法,但在一个特定
我曾考虑过使用全局for self,甚至提出了一个或多或少都能工作的实现,但我宁愿污染尽可能小的名称空间,这就是为什么我更愿意将变量直接注入测试用例方法的本地名称空间。有什么想法吗?这可能是一个使用案例-你给他们一小套乐高积木来构建函数,复杂的框架材料通过
@testcase
或类似的方式导入
编辑:您没有发布任何代码,因此这将是粗略的,但他们不需要编写方法。它们可以编写没有“self”的普通函数,您可以使用decorator,如我链接的文章中的示例所示:
class myDecorator(object):
def __init__(self, f):
print "inside myDecorator.__init__()"
f() # Prove that function definition has completed
def __call__(self):
print "inside myDecorator.__call__()"
@myDecorator
def aFunction():
print "inside aFunction()"
我对这个问题的回答相当愚蠢,但我才刚刚开始。这里有一个更好的方法。这只是很少测试,但它有助于演示正确的方法来做这件不合适的事情。它在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')
导入操作码
导入类型
def说明(代码):
“”“迭代生成整数[op,arg]对的代码字符串
如果操作码不带参数,只需在第二部分中不带参数
"""
代码=地图(作战需求文件,代码)
i、 L=0,len(代码)
扩展参数=0
而我>24)和255),
人权专员(op),
chr(oparg&255),
chr((oparg>>8)&255)]
其他:
raise VALUERROR(“无效的oparg:{0}太大”。格式(oparg))
def添加_self(f):
“”“将self添加到方法”
通过在co_varnames前面加上名称“self”来创建新函数,以及
递增co_argcount和co_nlocals。增加所有其他局部变量的索引
以1作为补偿。同时从co_名称中删除“self”并减少索引
在它之后出现的所有名称都被1替换。最后,替换所有出现的名称
`LOAD_GLOBAL i,j`用'LOAD_FAST 0,0'引用旧的'self'。
本质上,只需创建一个完全相同但又多了一个的代码对象
论点
"""
code_obj=f.func_代码
尝试:
self\u index=code\u obj.co\u names.index('self'))
除值错误外:
raise NOTEImplementedError(“自我不是全局的”)
#参数只是第一个co_argcount co_varnames
varnames=('self',)+code_obj.co_varnames
名称=元组(如果名称为'self',则为代码中的名称命名)
代码=[]
对于安装说明(代码为obj.co代码):
op=inst[0]
如果opcode.haslocal中存在op:
#该指数现在大了一倍,因为我们将“自我”添加到了
#元组
指令[1]+=1
操作码中的elif op.hasname:
arg=inst[1]
如果arg==自索引:
#这是指旧的全球“自我”
如果op==opcode.opmap['LOAD_GLOBAL']:
inst[0]=opcode.opmap['LOAD\u FAST']
指令[1]=0
其他:
#如果“self”用作属性,则为实全局模块
#名称、模块属性或查看有趣的、退出。
引发未实施错误(“异常使用自我”)
elif参数>自索引:
#这将重写索引以说明旧的全局“自我”
#被移走的。
仪器[1]-=1
代码+=写入指令(inst)
代码=“”。加入(代码)
#在解释器提示符处键入help(types.CodeType)以获取此代码
new_code_obj=types.CodeType(code_obj.co_argcount+1,
代码\u obj.co\u nlocals+1,
代码\u obj.co\u堆栈大小,
代码_obj.co_标志,
代码,
代码_obj.co_consts,
姓名,
varnames,
'',
代码_obj.co _名称,
代码_obj.co_firstlineno,
代码_obj.co _lnotab,
代码_obj.co_freevars,
代码_obj.co_cellvars)
#帮助(类型.函数类型)
返回类型.FunctionTyp
import new, functools
class TestMeta(type):
def __new__(meta, classname, bases, classdict):
for item in classdict:
if hasattr(classdict[item], '__call__'):
classdict[item] = wrap(classdict[item])
return type.__new__(meta, classname, bases, classdict)
def wrap(f):
@functools.wraps(f)
def wrapper(self):
f.func_globals['self'] = self
return f()
return wrapper
def testdec(f):
@functools.wraps(f)
def wrapper():
return f()
return wrapper
class Test(object):
__metaclass__ = TestMeta
message = 'You can do anything in python'
def test():
print self.message
@testdec
def test2():
print self.message + ' but the wrapper funcion can\'t take a self argument either or you get a TypeError'
class Test2(object):
message = 'It also works as a decorator but (to me at least) feels better as a metaclass'
@wrap
def test():
print self.message
t = Test()
t2 = Test2()
t.test()
t.test2()
t2.test()
def wrap(f):
@functools.wraps(f)
def wrapper(self,*arg,**kw):
f.func_globals['self'] = self
return f(*arg,**kw)
return wrapper
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')
# 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
# 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