Как добраться сам в метод Python, явно не принимая его

Я разрабатываю среду тестирования документации - в основном модульные тесты на PDFs. Тесты являются (украшенными) методами экземпляров классов, определенных платформой, и их располагают и инстанцируют во времени выполнения, и методы вызываются для выполнения тестов.

Моя цель состоит в том, чтобы сократить сумму изворотливого синтаксиса Python, которым должны быть обеспокоены люди, которые запишут тесты, как эти люди могут или не могут быть Python программистами или даже очень программистами вообще. Таким образом, я хотел бы, чтобы они смогли записать "нечто определения ()": вместо "нечто определения (сам)": для методов, но все еще смочь использовать "сам" для участников доступа.

В обычной программе я считал бы это ужасной идеей, но в domain-specific-languagey виде программы как этот, это кажется стоящим попытки.

Я успешно устранил сам из сигнатуры метода при помощи декоратора (на самом деле, так как я использую декоратора уже для тестовых сценариев, я просто прокрутил бы ее в тот), но "сам" ни к чему затем не относится в методе тестового сценария.

Я рассмотрел использование глобального для сам и даже придумал реализацию, которая более или менее работает, но я загрязнил бы самое маленькое возможное пространство имен, который является, почему я предпочел бы вводить переменную непосредственно в локальное пространство имен метода тестового сценария. Какие-либо мысли?

8
задан kindall 10 August 2010 в 23:01
поделиться

3 ответа

небольшое обновление для решения 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')
5
ответ дан 5 December 2019 в 08:50
поделиться

Мой принятый ответ на этот вопрос был довольно глупым, но я только начинал. Вот способ лучше. Это мало проверено, но это хорошо для демонстрации того, как правильно делать то, что делать неправильно. На 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')
5
ответ дан 5 December 2019 в 08:50
поделиться

Уловка состоит в том, чтобы добавить «себя» в 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()
3
ответ дан 5 December 2019 в 08:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: