2012-04-12 2 views
9

클래스 A, BC이 있다고 해봅시다.MRO 오류없이 기본 클래스로 mixin을 어떻게 동적으로 추가합니까?

클래스 AB은 모두 C 클래스의 믹스 인 클래스입니다.

class A(object): 
    pass 
class B(object): 
    pass 
class C(object, A, B): 
    pass 

클래스 C를 인스턴스화 할 때 나는 그것이 작동하도록 클래스 C에서 object을 제거해야 작동하지 않습니다. (그렇지 않으면 MRO 문제가 생길 것입니다.)

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution
order (MRO) for bases B, object, A

그러나 제 경우는 좀 더 복잡합니다. 내 경우에 클래스 C서버입니다. 여기서 AB은 시작할 때로드되는 플러그인입니다. 이들은 자신의 폴더에 있습니다.

또한 Cfactory이라는 클래스가 있습니다. Cfactory에서 나는 그래서 다음은 가능성이, 내가 플러그인에 대한 검색 __new__ 방법에서 완전한 기능 오브젝트 C를 만들 __import__를 사용하여로드 한 다음 C.__bases__ += (loadedClassTypeGoesHere,)

에 할당하는 __new__ 방법이 있습니다 (가) 매우 추상적

class A(object): 
    def __init__(self): pass 
    def printA(self): print "A" 
class B(object): 
    def __init__(self): pass 
    def printB(self): print "B" 
class C(object): 
    def __init__(self): pass 
class Cfactory(object): 
    def __new__(cls): 
     C.__bases__ += (A,) 
     C.__bases__ += (B,) 
     return C() 

이 다시 작동하지 않습니다, 다시 MRO 오류를 줄 것이다 만든 :

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, A

이것에 대한 쉬운 수정 AB에서 object baseclass를 제거하는 것입니다. (가능해야하는 유닛 테스트는 현명한)

또 다른 쉽게 수정 C에서 object을 제거하고 그러나이 이러한 플러그인은 독립 실행 될 때 피해야 그 이전 스타일의 객체를 만들 것입니다하지만,이 또한 그것에게 만들 것 예전 스타일의 클래스와 C.__bases__을 사용할 수 없으므로 추가 객체를베이스에 추가 할 수 없습니다. C

어떻게하면 좋을까요? 어떻게하면 좋을까요? 지금은 플러그인에 대한 구식 클래스로 살 수 있습니다. 하지만 나는 그들을 사용하지 않습니다.

+2

파이썬 3에서는 구식 클래스가 존재하지 않으므로 결국 해결책이 필요하다. –

+0

정말로 사실입니다. –

답변

10

이런 식으로 생각해보십시오 - 믹스 인이 object의 동작 중 일부를 재정의하기를 원하므로 메서드 확인 순서로 object보다 앞에 있어야합니다. 정말 할 수

class C(A, B, object): 
    pass 

인해 this bug에, 당신이 직접 제대로 __bases__에 할당 할 수있는 객체를 상속하지 C이 필요하고 공장 :

그래서 당신은 기지의 순서를 변경해야

class FakeBase(object): 
    pass 

class C(FakeBase): 
    pass 

def c_factory(): 
    for base in (A, B): 
     if base not in C.__bases__: 
      C.__bases__ = (base,) + C.__bases__ 
    return C() 
+1

그래, 그 벌레를 실제로주는 첫 번째 예를 시도해 보았다. (예전의 편집 코드에서도 발견되었다.) 몇 가지 일을하고 그것이 효과가 있는지 살펴 봅시다. –

+0

@DaanTimmer 그래, 버그가 적절한 기지 질서 문제를 혼란스럽게하는 것은 불행한 일이다. – agf

3

세부 사항을 모르므로 여기서 벗어날 수는 있지만 설계를 수행하는 데 잘못된 메커니즘을 사용하는 것처럼 보입니다.

먼저 Cfactory이 클래스이고 왜 __new__ 메서드가 다른 인스턴스를 반환하는 이유는 무엇입니까? 꽤 자연스럽게 기능을 구현하는 기괴한 방법 인 것 같습니다. 설명했듯이 (그리고 단순화 된 예제를 보여 주었기 때문에) 클래스처럼 전혀 동작하지 않습니다. 기능을 공유하는 인스턴스가 여러 개 존재하지 않습니다 (실제로 자연스럽게 인스턴스를 구성 할 수없는 것처럼 보입니다).

솔직히 말해서 C은 나에게 수업과별로 비슷하지 않습니다. 하나 이상의 인스턴스를 생성 할 수없는 것처럼 보입니다. 그렇지 않으면 끊임없이 증가하는 기본 목록으로 끝날 것입니다. 따라서 C은 기본적으로 클래스가 아닌 모듈이며 여분의 상용구 만 있습니다. Java "이 필요하기 때문에 대중적이라는 것을 알고 있지만"응용 프로그램 또는 일부 외부 시스템을 나타내는 단일 인스턴스 클래스 "패턴을 피하려고합니다. 그러나 클래스 상속 메커니즘은 플러그인 시스템과 같이 클래스가 아닌 클래스에 유용 할 수 있습니다.

나는 발견하고 그것이 좋은 상태에서 항상있어 수 있도록 모듈에 의해 호출 부하 플러그인, C을 정의하는 C에 classmethod으로 이런 짓을 한 것입니다. 또는 메타 클래스를 사용하여 발견 한 모든 플러그인을 클래스 기반에 자동으로 추가 할 수 있습니다. 클래스를 구성하는 메커니즘을과 혼합하면 클래스 인스턴스를 생성하는 메커니즘이 잘못되었습니다. 이는 유연한 디 커플 드 디자인의 반대입니다.

이 시간 C에로드 할 수 없습니다 플러그인이 생성되면, 나는 C 인스턴스가 생성되기 전에, 플러그인을 검색 할 수 있습니다 때 수동 시점에서 구성자 classmethod를 호출와 함께 갈 것입니다.

사실, 클래스를 만든 즉시 일관된 상태로 만들 수 없다면 기존 클래스의베이스를 수정하는 것보다 동적 클래스 생성을 사용하는 것이 좋습니다. 그런 다음 시스템은 한 번 구성된 클래스에 잠기지 않고 한 번 인스턴스화됩니다. 적어도 다른 플러그인 세트가로드 된 여러 인스턴스를 가질 가능성은 열려 있습니다. 이런 식으로 뭔가 :

def Cfactory(*args, **kwargs): 
    plugins = find_plugins() 
    bases = (C,) + plugins 
    cls = type('C_with_plugins', bases, {}) 
    return cls(*args, **kwargs) 

그런 식으로, 당신은 당신에게 올바르게 구성 인스턴스를 제공하여 C 인스턴스를 만들려면 단일 통화를 가지고 있지만, 그것은 C의 다른 가상 인스턴스 수도 이상한 부작용이 없습니다 이미 존재하고 그 행동은 이전에 실행되었는지 여부에 달려 있지 않습니다. 나는 두 속성 중 어느 것도 필요로하지 않는다는 것을 알고 있습니다 만, 단순화 된 예제보다 간신히 코드가 더 많으며, 왜 클래스의 개념 모델을 깨뜨리지 않아도됩니까?

+0

Cfactory이 클래스이고 함수가 아니라는 것에 대한 조언은 agf가 제공했습니다. 그리고 나는 그것을 내 자신의 코드로 변경했습니다. C에는 플러그인이없는 경우에도 항상 사용할 수있는 몇 가지 일반적인 메소드가 있습니다. 좋은 대답이기 때문에 대답을 +1 하겠지만 agf의 대답은 현재의 문제에 대한 해결책을 제공했습니다. 그래도 고마워. –

+1

Cfactory 함수를 제외하고이 대답이 맘에 든다. 하나의 클래스를 만들고 인스턴스는 발견 된 플러그인에 따라 서로 다른 기본 클래스를 가질 수있다. 각 인스턴스의 클래스 이름이 더 이상 의미가 없습니다. 오히려, Cfactory는베이스로 포함 된 플러그인의 조합에 고유 한 클래스 이름을 작성해야하지만 플러그인이 발견 된 순서에 관계없이 동일해야합니다. – Schollii

3

간단한 해결 방법이 있습니다. PluginBase과 같은 멋진 이름으로 도우미 클래스를 만드십시오. 그리고 그 대신 객체의 상속을 사용하십시오.

이렇게하면 코드가 더 읽기 쉽고 (imho) 상황이 버그가됩니다.

class PluginBase(object): pass 
class ServerBase(object): pass 

class pluginA(PluginBase): "Now it is clearly a plugin class" 
class pluginB(PluginBase): "Another plugin" 

class Server1(ServerBase, pluginA, pluginB): "This works" 
class Server2(ServerBase): pass 
Server2.__base__ += (PluginA,) # This also works 

참고로 : 아마 당신이 공장이 필요하지 않습니다; C++에서는 필요하지만, 파이썬에서는 거의 쓰이지 않는다.

+0

OS X에서 파이썬 2.7.6을 사용하고 있으며 마지막 라인이 작동하지 않습니다. "TypeError : + = : 'type'과 'tuple'에 대해 지원되지 않는 피연산자 유형을 얻습니다 (대문자 P의 오타를 수정 한 후). – mikeazo

관련 문제