복붙노트

[PYTHON] 명시 적으로 수락하지 않고 자신을 Python 메소드로 가져 오는 방법

PYTHON

명시 적으로 수락하지 않고 자신을 Python 메소드로 가져 오는 방법

필자는 문서 테스트 프레임 워크를 개발 중입니다. 기본적으로 PDF에 대한 단위 테스트입니다. 테스트는 프레임 워크에 의해 정의 된 클래스 인스턴스의 (데코 레이팅 된) 메서드이며 런타임에 위치를 지정하고 인스턴스화하며 메서드를 호출하여 테스트를 실행합니다.

필자의 목표는 테스트를 작성할 사람들이 염려해야 할 기발한 파이썬 구문의 양을 줄이는 것입니다.이 사람들은 Python 프로그래머 일 수도 있고 아닐 수도 있고 프로그래머 일 수도 있습니다. 그래서 메소드에 def foo (self) : 대신 "def foo () :"를 쓸 수있게하고 싶지만 멤버에게 액세스하려면 "self"를 사용할 수 있어야합니다.

평범한 프로그램에서 나는 이것을 끔찍한 생각이라고 생각 하겠지만,이 프로그램과 같은 도메인 특정 언어의 프로그램에서는 시도해 볼 만하다.

필자는 데코레이터를 사용하여 메서드 시그니처에서 자체를 성공적으로 제거했습니다. 실제로 (이미 테스트 케이스에 데코레이터를 사용하고 있기 때문에이를 롤백 할 것입니다.) 그러나 "self"는 아무 것도 참조하지 않습니다. 테스트 케이스 방법.

나는 스스로를 위해 전역을 사용하는 것을 고려해 왔고, 어느 정도 구현 된 구현을 생각해 낼 수도있다. 그러나 가능한 한 가장 작은 네임 스페이스를 오염시키고 싶다. 그래서 변수를 테스트 케이스 메소드의 로컬에 직접 주입하는 것을 선호한다. 네임 스페이스. 이견있는 사람?

해결법

  1. ==============================

    1.다음은 호출 가능 유형의 특수 속성을 수정하지 않고 작업을 수행하는 것으로 보이는 한 줄의 메소드 장식 자입니다. * 읽기 전용으로 표시 :

    다음은 호출 가능 유형의 특수 속성을 수정하지 않고 작업을 수행하는 것으로 보이는 한 줄의 메소드 장식 자입니다. * 읽기 전용으로 표시 :

    # 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
    

    이를 방지하기 위해 예방 조치를 취하지 않으면 eval () 함수의 부작용은 전달 된 사전에 자동으로 __builtins__ 키 아래의 __builtin__ 모듈에 대한 참조와 같은 몇 가지 항목을 추가하는 것일 수 있습니다 .

    @ 켄달 : 당신이 컨테이너 클래스에있는 방법과 함께 이것을 사용하는 방법에 대한 귀하의 의견 (그러나 순간에 추가 변수의 주입을 무시) - 당신이하고있는 것과 같은 다음과 같은 것입니까? 프레임 워크와 사용자가 작성한 내용이 어떻게 분리되는지 이해하는 것은 어렵습니다. 나에게 흥미로운 디자인 패턴처럼 들리 네.

    # 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
    
  2. ==============================

    2.aaronasterling의 솔루션에 대한 약간의 업그레이드 (나는 그것에 대한 평판이 부족합니다.) :

    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')
    
  3. ==============================

    3.이 질문에 대한 나의 대답은 꽤 바보 같았으나 나는 이제 막 시작했다. 여기에 훨씬 좋은 방법이 있습니다. 이것은 거의 테스트되지 않았지만 부적절한 일을하는 적절한 방법을 시연하는 것이 좋습니다. 그것은 확실히 2.6.5에서 작동합니다. 다른 버전을 테스트하지는 않았지만 어떤 opcode도 하드 코드되어 있지 않으므로 대부분의 다른 2.x 코드만큼 이식성이 있어야합니다.

    이 질문에 대한 나의 대답은 꽤 바보 같았으나 나는 이제 막 시작했다. 여기에 훨씬 좋은 방법이 있습니다. 이것은 거의 테스트되지 않았지만 부적절한 일을하는 적절한 방법을 시연하는 것이 좋습니다. 그것은 확실히 2.6.5에서 작동합니다. 다른 버전을 테스트하지는 않았지만 어떤 opcode도 하드 코드되어 있지 않으므로 대부분의 다른 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')
    
  4. ==============================

    4.트릭은 f.func_globals에 'self'를 추가하는 것입니다. 이것은 Python 2.6에서 작동합니다. 필자는 이와 같은 테스트를 위해 다른 버전을 설치해야합니다. 코드 벽은 유감스럽게 생각하지만 두 가지 경우를 다룹니다. 즉, 메타 클래스로 수행하고 데코레이터로 처리하는 경우입니다. 귀하의 유스 케이스에 대해서는,이 연습의 요점은 사용자를 구문으로부터 보호하는 것이기 때문에 메타 클래스가 더 좋다고 생각합니다.

    트릭은 f.func_globals에 'self'를 추가하는 것입니다. 이것은 Python 2.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()
    
  5. ==============================

    5.이것은 데코레이터의 유스 케이스 일 수 있습니다 - 당신은 그들에게 함수를 빌드하기위한 작은 세트의 레고 브릭을 주었고, 복잡한 프레임웍은 @testcase 나 somesuch를 통해 파이프됩니다.

    이것은 데코레이터의 유스 케이스 일 수 있습니다 - 당신은 그들에게 함수를 빌드하기위한 작은 세트의 레고 브릭을 주었고, 복잡한 프레임웍은 @testcase 나 somesuch를 통해 파이프됩니다.

    편집 : 당신은 코드를 게시하지 않았으므로 이것은 스케치가 될 것입니다. 그러나 메소드를 작성할 필요가 없습니다. 그들은 "self"없이 일반적인 함수를 작성할 수 있으며, 내가 링크 된 기사의 다음 예제와 같은 데코레이터를 사용할 수 있습니다.

    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()"
    
  6. from https://stackoverflow.com/questions/3453976/how-to-get-self-into-a-python-method-without-explicitly-accepting-it by cc-by-sa and MIT license