developer tip

Python : 런타임에 메서드 및 속성 변경

copycodes 2020. 10. 28. 08:11
반응형

Python : 런타임에 메서드 및 속성 변경


속성과 메서드를 추가하고 제거 할 수있는 Python 클래스를 만들고 싶습니다. 어떻게 할 수 있습니까?

오, 이유는 묻지 마세요.


속성과 메서드를 추가하고 제거 할 수있는 Python 클래스를 만들고 싶습니다.

import types

class SpecialClass(object):
    @classmethod
    def removeVariable(cls, name):
        return delattr(cls, name)

    @classmethod
    def addMethod(cls, func):
        return setattr(cls, func.__name__, types.MethodType(func, cls))

def hello(self, n):
    print n

instance = SpecialClass()
SpecialClass.addMethod(hello)

>>> SpecialClass.hello(5)
5

>>> instance.hello(6)
6

>>> SpecialClass.removeVariable("hello")

>>> instance.hello(7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'SpecialClass' object has no attribute 'hello'

>>> SpecialClass.hello(8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'SpecialClass' has no attribute 'hello'

이 예제는 클래스와 인스턴스에 메서드를 추가하는 것의 차이점을 보여줍니다.

>>> class Dog():
...     def __init__(self, name):
...             self.name = name
...
>>> skip = Dog('Skip')
>>> spot = Dog('Spot')
>>> def talk(self):
...     print 'Hi, my name is ' + self.name
...
>>> Dog.talk = talk # add method to class
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk()
Hi, my name is Spot
>>> del Dog.talk # remove method from class
>>> skip.talk() # won't work anymore
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'
>>> import types
>>> f = types.MethodType(talk, skip, Dog)
>>> skip.talk = f # add method to specific instance
>>> skip.talk()
Hi, my name is Skip
>>> spot.talk() # won't work, since we only modified skip
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Dog instance has no attribute 'talk'

types.MethodTypein 사용 대한 흥미로운 대안 :

>>> f = types.MethodType(talk, puppy, Dog)
>>> puppy.talk = f # add method to specific instance

함수가 설명 자라는 사실을 악용하는 것입니다 .

>>> puppy.talk = talk.__get__(puppy, Dog)

속성과 메서드를 추가하고 제거 할 수있는 Python 클래스를 만들고 싶습니다. 어떻게 할 수 있습니까?

모든 클래스에 속성과 메서드를 추가 및 제거 할 수 있으며 클래스의 모든 인스턴스에서 사용할 수 있습니다.

>>> def method1(self):
       pass

>>> def method1(self):
       print "method1"

>>> def method2(self):
       print "method2"

>>> class C():
       pass

>>> c = C()
>>> c.method()

Traceback (most recent call last):
  File "<pyshell#62>", line 1, in <module>
    c.method()
AttributeError: C instance has no attribute 'method'

>>> C.method = method1
>>> c.method()
    method1
>>> C.method = method2
>>> c.method()
    method2
>>> del C.method
>>> c.method()

Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    c.method()
AttributeError: C instance has no attribute 'method'
>>> C.attribute = "foo"
>>> c.attribute
    'foo'
>>> c.attribute = "bar"
>>> c.attribute
    'bar'

클래스에 직접 할당 할 수 있습니다 (원래 클래스 이름에 액세스하거나를 통해 __class__).

class a : pass
ob=a()
ob.__class__.blah=lambda self,k: (3, self,k)
ob.blah(5)
ob2=a()
ob2.blah(7)

인쇄됩니다

(3, <__main__.a instance at 0x7f18e3c345f0>, 5)
(3, <__main__.a instance at 0x7f18e3c344d0>, 7)

또 다른 대안은 클래스 도매를 교체해야하는 경우 클래스 속성 을 수정하는 것입니다.

>>> class A(object):
...     def foo(self):
...         print 'A'
... 
>>> class B(object):
...     def foo(self):
...         print 'Bar'
... 
>>> a = A()
>>> a.foo()
A
>>> a.__class__ = B
>>> a.foo()
Bar

간단히:

f1 = lambda:0                   #method for instances
f2 = lambda _:0                 #method for class
class C: pass                   #class

c1,c2 = C(),C()                 #instances

print dir(c1),dir(c2)

#add to the Instances
c1.func = f1
c1.any = 1.23

print dir(c1),dir(c2)
print c1.func(),c1.any

del c1.func,c1.any

#add to the Class
C.func = f2
C.any = 1.23

print dir(c1),dir(c2)
print c1.func(),c1.any
print c2.func(),c2.any

결과 :

['__doc__', '__module__'] ['__doc__', '__module__']
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__']
0 1.23
['__doc__', '__module__', 'any', 'func'] ['__doc__', '__module__', 'any', 'func']
0 1.23
0 1.23

클래스 자체를 반드시 수정해야합니까? 아니면 단순히 런타임 동안 특정 지점에서 object.method ()를 대체하는 것이 목표입니까?

기본 상속 개체에 대한 getattribute 및 Runtime Decorator를 사용하여 프레임 워크에서 특정 메서드 호출을 원숭이 패치로 실제로 수정하는 문제를 회피하기 때문에 묻습니다 .

getattribute 의 Base 객체에 의해 검색된 메서드는 적용 할 데코레이터 / 원숭이 패치에 대한 메서드 호출 키워드 인수를 구문 분석하는 Runtime_Decorator에 래핑됩니다.

이를 통해 object.method (monkey_patch = "mypatch"), object.method (decorator = "mydecorator") 및 object.method (decorators = my_decorator_list) 구문을 활용할 수 있습니다.

이것은 모든 개별 메서드 호출에 대해 작동합니다 (매직 메서드는 생략합니다). 실제로 클래스 / 인스턴스 속성을 수정하지 않고이를 수행하고, 임의의 외부 메서드를 패치에 활용할 수 있으며 Base에서 상속하는 하위 항목에서 투명하게 작동합니다 ( 물론 getattribute재정의하지 마십시오 ).

import trace

def monkey_patched(self, *args, **kwargs):
    print self, "Tried to call a method, but it was monkey patched instead"
    return "and now for something completely different"

class Base(object):

    def __init__(self):
        super(Base, self).__init__()

    def testmethod(self):
        print "%s test method" % self

    def __getattribute__(self, attribute):
        value = super(Base, self).__getattribute__(attribute)
        if "__" not in attribute and callable(value):
            value = Runtime_Decorator(value)
        return value

class Runtime_Decorator(object):

    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):

        if kwargs.has_key("monkey_patch"):
            module_name, patch_name = self._resolve_string(kwargs.pop("monkey_patch"))
            module = self._get_module(module_name)
            monkey_patch = getattr(module, patch_name)
            return monkey_patch(self.function.im_self, *args, **kwargs)

        if kwargs.has_key('decorator'):
            decorator_type = str(kwargs['decorator'])

            module_name, decorator_name = self._resolve_string(decorator_type)
            decorator = self._get_decorator(decorator_name, module_name)
            wrapped_function = decorator(self.function)
            del kwargs['decorator']
            return wrapped_function(*args, **kwargs)

        elif kwargs.has_key('decorators'):
            decorators = []

            for item in kwargs['decorators']:
                module_name, decorator_name = self._resolve_string(item)
                decorator = self._get_decorator(decorator_name, module_name)
                decorators.append(decorator)

            wrapped_function = self.function
            for item in reversed(decorators):
                wrapped_function = item(wrapped_function)
            del kwargs['decorators']
            return wrapped_function(*args, **kwargs)

        else:
            return self.function(*args, **kwargs)

    def _resolve_string(self, string):
        try: # attempt to split the string into a module and attribute
            module_name, decorator_name = string.split(".")
        except ValueError: # there was no ".", it's just a single attribute
            module_name = "__main__"
            decorator_name = string
        finally:
            return module_name, decorator_name

    def _get_module(self, module_name):
        try: # attempt to load the module if it exists already
            module = modules[module_name]
        except KeyError: # import it if it doesn't
            module = __import__(module_name)
        finally:
            return module

    def _get_decorator(self, decorator_name, module_name):
        module = self._get_module(module_name)
        try: # attempt to procure the decorator class
            decorator_wrap = getattr(module, decorator_name)
        except AttributeError: # decorator not found in module
            print("failed to locate decorators %s for function %s." %\
            (kwargs["decorator"], self.function))
        else:
            return decorator_wrap # instantiate the class with self.function

class Tracer(object):

    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        tracer = trace.Trace(trace=1)
        tracer.runfunc(self.function, *args, **kwargs)

b = Base()
b.testmethod(monkey_patch="monkey_patched")
b.testmethod(decorator="Tracer")
#b.testmethod(monkey_patch="external_module.my_patch")

The downside to this approach is getattribute hooks all access to attributes, so the checking of and potential wrapping of methods occurs even for attributes that are not methods + won't be utilizing the feature for the particular call in question. And using getattribute at all is inherently somewhat complicated.

The actual impact of this overhead in my experience/for my purposes has been negligible, and my machine runs a dual core Celeron. The previous implementation I used introspected methods upon object init and bound the Runtime_Decorator to methods then. Doing things that way eliminated the need to utilize getattribute and reduced the overhead mentioned previously... however, it also breaks pickle (maybe not dill) and is less dynamic then this approach.

The only use cases I have actually come across "in the wild" with this technique were with timing and tracing decorators. However, the possibilities it opens up are extremely wide ranging.

If you have a preexisting class that cannot be made to inherit from a different base (or utilize the technique it's own class definition or in it's base class'), then the whole thing simply does not apply to your issue at all unfortunately.

I don't think setting/removing non-callable attributes on a class at runtime is necessarily so challenging? unless you want classes that inherit from the modified class to automatically reflect the changes in themselves as well... That'd be a whole 'nother can o' worms by the sound of it though.

참고URL : https://stackoverflow.com/questions/962962/python-changing-methods-and-attributes-at-runtime

반응형