Я разрабатываю среду тестирования документации - в основном модульные тесты на PDFs. Тесты являются (украшенными) методами экземпляров классов, определенных платформой, и их располагают и инстанцируют во времени выполнения, и методы вызываются для выполнения тестов.
Моя цель состоит в том, чтобы сократить сумму изворотливого синтаксиса Python, которым должны быть обеспокоены люди, которые запишут тесты, как эти люди могут или не могут быть Python программистами или даже очень программистами вообще. Таким образом, я хотел бы, чтобы они смогли записать "нечто определения ()": вместо "нечто определения (сам)": для методов, но все еще смочь использовать "сам" для участников доступа.
В обычной программе я считал бы это ужасной идеей, но в domain-specific-languagey виде программы как этот, это кажется стоящим попытки.
Я успешно устранил сам из сигнатуры метода при помощи декоратора (на самом деле, так как я использую декоратора уже для тестовых сценариев, я просто прокрутил бы ее в тот), но "сам" ни к чему затем не относится в методе тестового сценария.
Я рассмотрел использование глобального для сам и даже придумал реализацию, которая более или менее работает, но я загрязнил бы самое маленькое возможное пространство имен, который является, почему я предпочел бы вводить переменную непосредственно в локальное пространство имен метода тестового сценария. Какие-либо мысли?
небольшое обновление для решения aaronasterling (у меня недостаточно репутации, чтобы его прокомментировать):
def wrap(f):
@functools.wraps(f)
def wrapper(self,*arg,**kw):
f.func_globals['self'] = self
return f(*arg,**kw)
return wrapper
но оба эти решения будут работать непредсказуемо, если функция 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')
Мой принятый ответ на этот вопрос был довольно глупым, но я только начинал. Вот способ лучше. Это мало проверено, но это хорошо для демонстрации того, как правильно делать то, что делать неправильно. На 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')
Уловка состоит в том, чтобы добавить «себя» в f.func_globals
. Это работает в python2.6. Мне действительно нужно установить другие версии, чтобы протестировать подобные вещи. Простите за стену кода, но я рассматриваю два случая: выполнение этого с помощью метакласса и выполнение с помощью декоратора. Для вашего варианта использования я думаю, что метакласс лучше, поскольку весь смысл этого упражнения - защитить пользователей от синтаксиса.
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()