programing

런타임에 인스턴스의 기본 클래스를 동적으로 변경하는 방법은 무엇입니까?

nasanasas 2020. 11. 12. 08:20
반응형

런타임에 인스턴스의 기본 클래스를 동적으로 변경하는 방법은 무엇입니까?


이 기사 에는 __bases__상속되는 클래스의 기존 클래스 컬렉션에 클래스를 추가하여 일부 Python 코드의 상속 계층 구조를 동적으로 변경하는 사용법을 보여주는 스 니펫이 있습니다 . 읽기 어렵습니다. 코드가 더 명확 할 것입니다.

class Friendly:
    def hello(self):
        print 'Hello'

class Person: pass

p = Person()
Person.__bases__ = (Friendly,)
p.hello()  # prints "Hello"

즉, 소스 수준에서 Person상속하지 않고 Friendly대신 __bases__Person 클래스 속성을 수정하여 런타임에이 상속 관계를 동적으로 추가 합니다. 그러나 객체에서 상속하여 새 스타일 클래스 를 변경 Friendly하고 변경 Person하면 다음 오류가 발생합니다.

TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object'

이것에 대한 약간의 인터넷 검색 은 런타임에 상속 계층을 변경하는 것과 관련하여 새로운 스타일과 이전 스타일 클래스 간의 일부 비 호환성나타내는 것으로 보입니다 . 특히 : "새 스타일 클래스 객체는 기본 속성에 대한 할당을 지원하지 않습니다 . " .

내 질문에, 아마도 __mro__속성 을 사용하여 Python 2.7+에서 새로운 스타일의 클래스를 사용하여 위의 Friendly / Person 예제를 작동시킬 수 있습니까?

면책 조항 : 이것이 모호한 코드라는 것을 완전히 알고 있습니다. 나는 실제 프로덕션에서 이와 같은 코드 트릭이 읽을 수없는 것에 접하는 경향이 있다는 것을 완전히 알고 있습니다. 이것은 순전히 생각 실험이며 펀지가 파이썬이 다중 상속과 관련된 문제를 어떻게 처리하는지에 대해 배울 수 있습니다.


다시 말하지만, 이것은 일반적으로해야 할 일이 아닙니다. 이것은 정보 제공 목적으로 만 사용됩니다.

파이썬이 인스턴스 객체에서 메소드를 찾는 곳 __mro__은 해당 객체를 정의하는 클래스 속성 ( M ethod R esolution O rder 속성)에 의해 결정됩니다 . 따라서 __mro__of를 수정할 수 있다면 Person원하는 동작을 얻을 수 있습니다. 다음과 같은 것 :

setattr(Person, '__mro__', (Person, Friendly, object))

문제는 __mro__읽기 전용 속성이므로 setattr이 작동하지 않는다는 것입니다. 아마도 당신이 파이썬 전문가라면 그 문제를 해결할 수있는 방법이 있을지 모르지만, 제가 생각할 수 없기 때문에 분명히 저는 전문가 지위에 미치지 못합니다.

가능한 해결 방법은 클래스를 재정의하는 것입니다.

def modify_Person_to_be_friendly():
    # so that we're modifying the global identifier 'Person'
    global Person

    # now just redefine the class using type(), specifying that the new
    # class should inherit from Friendly and have all attributes from
    # our old Person class
    Person = type('Person', (Friendly,), dict(Person.__dict__)) 

def main():
    modify_Person_to_be_friendly()
    p = Person()
    p.hello()  # works!

이것이하지 않는 것은 메서드 Person를 갖도록 이전에 생성 된 인스턴스를 수정 하는 hello()것입니다. 예를 들어 ( main()) :

def main():
    oldperson = Person()
    ModifyPersonToBeFriendly()
    p = Person()
    p.hello()  
    # works!  But:
    oldperson.hello()
    # does not

type호출 의 세부 사항이 명확하지 않은 경우 '파이썬의 메타 클래스는 무엇입니까?'에 대한 e-satis의 훌륭한 답변 을 읽으십시오. .


나도 이것으로 어려움을 겪고 있으며 귀하의 솔루션에 흥미가 있었지만 Python 3은 우리에게서 빼앗 았습니다.

AttributeError: attribute '__dict__' of 'type' objects is not writable

나는 실제로 데코 레이팅 된 클래스의 (단일) 수퍼 클래스를 대체하는 데코레이터가 필요합니다. 여기에 포함하기에는 너무 긴 설명이 필요할 것입니다. 다른 응용 프로그램에는 일부 코드의 약간 다른 변형이 필요했습니다.)

이 페이지 및 이와 유사한 다른 페이지에 대한 논의는 할당 문제가 __bases__정의 된 수퍼 클래스가없는 클래스 (즉, 유일한 수퍼 클래스가 객체 인 클래스)에서만 발생 한다는 힌트를 제공했습니다 . 나는 사소한 클래스의 서브 클래스로 대체해야하는 수퍼 클래스를 정의하여이 문제 (Python 2.7 및 3.2 모두)를 해결할 수있었습니다.

## T is used so that the other classes are not direct subclasses of object,
## since classes whose base is object don't allow assignment to their __bases__ attribute.

class T: pass

class A(T):
    def __init__(self):
        print('Creating instance of {}'.format(self.__class__.__name__))

## ordinary inheritance
class B(A): pass

## dynamically specified inheritance
class C(T): pass

A()                 # -> Creating instance of A
B()                 # -> Creating instance of B
C.__bases__ = (A,)
C()                 # -> Creating instance of C

## attempt at dynamically specified inheritance starting with a direct subclass
## of object doesn't work
class D: pass

D.__bases__ = (A,)
D()

## Result is:
##     TypeError: __bases__ assignment: 'A' deallocator differs from 'object'

결과에 대해 보증 할 수는 없지만이 코드는 py2.7.2에서 원하는 것을 수행합니다.

class Friendly(object):
    def hello(self):
        print 'Hello'

class Person(object): pass

# we can't change the original classes, so we replace them
class newFriendly: pass
newFriendly.__dict__ = dict(Friendly.__dict__)
Friendly = newFriendly
class newPerson: pass
newPerson.__dict__ = dict(Person.__dict__)
Person = newPerson

p = Person()
Person.__bases__ = (Friendly,)
p.hello()  # prints "Hello"

우리는 이것이 가능하다는 것을 알고 있습니다. 멋있는. 그러나 우리는 그것을 사용하지 않을 것입니다!


방망이 오른쪽, 클래스 계층 구조를 동적으로 망칠 때의 모든 경고가 유효합니다.

그러나 그렇게해야한다면, 분명히 "deallocator differs from 'object" issue when modifying the __bases__ attribute새로운 스타일 클래스에 대한 해킹이 있습니다.

클래스 객체를 정의 할 수 있습니다.

class Object(object): pass

내장 메타 클래스에서 클래스를 파생 type시킵니다. 이제 새 스타일 클래스가 __bases__문제없이를 수정할 수 있습니다 .

내 테스트에서 이것은 실제로 기존의 모든 (상속성을 변경하기 전에) 인스턴스와 그 파생 클래스 mro가 업데이트되는 것을 포함하여 변경의 영향을 느꼈습니다 .


이에 대한 해결책이 필요했습니다.

  • Python 2 (> = 2.7) 및 Python 3 (> = 3.2) 모두에서 작동합니다.
  • 종속성을 동적으로 가져온 후 클래스 기반을 변경할 수 있습니다.
  • 단위 테스트 코드에서 클래스 기반을 변경할 수 있습니다.
  • 사용자 정의 메타 클래스가있는 유형에서 작동합니다.
  • 여전히 unittest.mock.patch예상대로 작동 할 수 있습니다 .

내가 생각 해낸 것은 다음과 같습니다.

def ensure_class_bases_begin_with(namespace, class_name, base_class):
    """ Ensure the named class's bases start with the base class.

        :param namespace: The namespace containing the class name.
        :param class_name: The name of the class to alter.
        :param base_class: The type to be the first base class for the
            newly created type.
        :return: ``None``.

        Call this function after ensuring `base_class` is
        available, before using the class named by `class_name`.

        """
    existing_class = namespace[class_name]
    assert isinstance(existing_class, type)

    bases = list(existing_class.__bases__)
    if base_class is bases[0]:
        # Already bound to a type with the right bases.
        return
    bases.insert(0, base_class)

    new_class_namespace = existing_class.__dict__.copy()
    # Type creation will assign the correct ‘__dict__’ attribute.
    del new_class_namespace['__dict__']

    metaclass = existing_class.__metaclass__
    new_class = metaclass(class_name, tuple(bases), new_class_namespace)

    namespace[class_name] = new_class

응용 프로그램 내에서 다음과 같이 사용됩니다.

# foo.py

# Type `Bar` is not available at first, so can't inherit from it yet.
class Foo(object):
    __metaclass__ = type

    def __init__(self):
        self.frob = "spam"

    def __unicode__(self): return "Foo"

# … later …
import bar
ensure_class_bases_begin_with(
        namespace=globals(),
        class_name=str('Foo'),   # `str` type differs on Python 2 vs. 3.
        base_class=bar.Bar)

단위 테스트 코드 내에서 다음과 같이 사용하십시오.

# test_foo.py

""" Unit test for `foo` module. """

import unittest
import mock

import foo
import bar

ensure_class_bases_begin_with(
        namespace=foo.__dict__,
        class_name=str('Foo'),   # `str` type differs on Python 2 vs. 3.
        base_class=bar.Bar)


class Foo_TestCase(unittest.TestCase):
    """ Test cases for `Foo` class. """

    def setUp(self):
        patcher_unicode = mock.patch.object(
                foo.Foo, '__unicode__')
        patcher_unicode.start()
        self.addCleanup(patcher_unicode.stop)

        self.test_instance = foo.Foo()

        patcher_frob = mock.patch.object(
                self.test_instance, 'frob')
        patcher_frob.start()
        self.addCleanup(patcher_frob.stop)

    def test_instantiate(self):
        """ Should create an instance of `Foo`. """
        instance = foo.Foo()

위의 답변은 런타임에 기존 클래스를 변경해야하는 경우 유용합니다. 그러나 다른 클래스에서 상속하는 새 클래스를 만들려는 경우 훨씬 더 깨끗한 솔루션이 있습니다. https://stackoverflow.com/a/21060094/3533440 에서이 아이디어를 얻었 지만 아래 예제가 합법적 인 사용 사례를 더 잘 설명한다고 생각합니다.

def make_default(Map, default_default=None):
    """Returns a class which behaves identically to the given
    Map class, except it gives a default value for unknown keys."""
    class DefaultMap(Map):
        def __init__(self, default=default_default, **kwargs):
            self._default = default
            super().__init__(**kwargs)

        def __missing__(self, key):
            return self._default

    return DefaultMap

DefaultDict = make_default(dict, default_default='wug')

d = DefaultDict(a=1, b=2)
assert d['a'] is 1
assert d['b'] is 2
assert d['c'] is 'wug'

Correct me if I'm wrong, but this strategy seems very readable to me, and I would use it in production code. This is very similar to functors in OCaml.

참고URL : https://stackoverflow.com/questions/9539052/how-to-dynamically-change-base-class-of-instances-at-runtime

반응형