2016-11-04 2 views
7

나는 상응하는 의미의 세 가지 메소드를 가진 추상 클래스를 가지고있다 - 그들은 비싼 변환 함수를 사용하여 서로의 관점에서 정의 될 수있다. 나는 메소드 중 하나를 오버라이드하고 자동으로 다른 두 개를 얻는 데 필요한 파생 클래스를 작성할 수 있기를 원합니다. 예세 가지 방법을 순환 방식으로 정의하는 방법은 무엇입니까?

class FooBarBaz(object): 
    def foo(self, x): 
     return foo_from_bar(self.bar(x)) 
     # OR return foo_from_baz(self.baz(x)) 

    def bar(self, x): 
     return bar_from_foo(self.foo(x)) 
     # OR return bar_from_baz(self.baz(x)) 

    def baz(self, x): 
     return baz_from_bar(self.bar(x)) 
     # OR return baz_from_foo(self.foo(x)) 

class Derived1(FooBarBaz): 
    def bar(self, x): 
     return 5 
     # at this point foo = foo_from_bar(5) and 
     # baz = baz_from_bar(5), which is what I wanted 

class Derived2(FooBarBaz): 
    def foo(self, x): 
     return 6 
     # at this point bar = bar_from_foo(6) and 
     # baz = baz_from_bar(bar_from_foo(6)), 
     # which is not ideal, but still works 

class Derived3(FooBarBaz): 
    def baz(self, x): 
     return 7 
     # at this point foo and bar remain defined 
     # in terms of each other, which is a PROBLEM 

나는 각 변환 클래스에 사용할 변환을 명시 적으로 알 수 있습니다. 부모 클래스가 자식을 수정하지 않고이를 스스로 알아낼 수있는 방법이 있는지 알고 싶습니다.

+1

아래의 답변에서 알 수 있듯이, 어떤 방법을 사용할지 결정하는 것은 전환 비용이 얼마나 큰지, 실제로 대안이 3 개 또는 20 개인지 여부에 따라 결정됩니다. 후자는 필요한 경우 여러 번 전환을 피하면서 좋은 운동을 얻을 것입니다 :-D. –

답변

3

남은 메서드를 자동으로 채우는 메타 클래스 작성과 같은 메타 프로그래밍 기법을 사용하거나, 내성 검사를 사용하여 type(self).mro()의 클래스를 차례로 보면 어떤 메서드가 재정의되었는지 확인할 수 있습니다. 그러나 이러한 옵션은 나를 위해 "너무 많은 마법"범주에 빠지기 때문에 좀 더 간단하게 처리 할 것입니다.

각 메소드를 두 개의 메소드로 분리하십시오. 하나는 일반적인 메소드이고 실제 구현은 하나입니다.

class FooBarBaz(object): 

    def foo_impl(self, x): 
     raise NotImplementedError 

    def foo(self, x): 
     try: 
      return self.foo_impl(x) 
     except NotImplementedError: 
      try: 
       return foo_from_bar(self.bar_impl(x)) 
      except NotImplementedError: 
       return foo_from_baz(self.baz_impl(x)) 

    # Similarly fo bar and baz 

class Dervied(FooBarBaz): 

    def bar_impl(self, x): 
     return 5 

일반적인 논리

도 장식에서 고려 될 수있다 :

def first_implemented(func): 
    @functools.wraps 
    def wrapper(*args, **kwargs): 
     for f in func(*args, **kwargs): 
      try: 
       return f() 
      except NotImplementedError: 
       pass 
     raise NotImplementedError 
    return wrapper 

class FooBarBaz(object): 

    def foo_impl(self, x): 
     raise NotImplementedError 

    @first_implemented 
    def foo(self, x): 
     yield lambda: self.foo_impl(x) 
     yield lambda: foo_from_bar(self.bar_impl(x)) 
     yield lambda: foo_from_baz(self.baz_impl(x)) 
+0

나는 약간 더 마술적인 해결책 (누가 마술을 좋아하지 않습니까?)에 나갔지 만, 당신의 아이디어는 영리합니다. –

+0

@ KarolisJuodelė Magic은 앞으로 코드를 확장하거나 리팩토링하려고 할 때 여러분 (또는 다른 누군가)을 물려는 경향이 있습니다. 내 말을 기억해. –

+0

내가 언급 한 데코레이터를 구현하는 방법이 확장되었습니다. –

2

깨끗한 원으로 전환을 정의하여 한 가지 방법을 다시 정의하면 해당 원이 깨질 수 있습니다. 같은 문제에 코멘트와 함께 동시에 편집

class FooBarBaz(object): 
    def foo(self, x): 
     return foo_from_baz(self.baz(x)) 

    def bar(self, x): 
     return bar_from_foo(self.foo(x)) 

    def baz(self, x): 
     return baz_from_bar(self.bar(x)) 

이 제기되고 : : 즉 작동합니다,하지만 물론, 때로는 두 번 변환해야합니다. 이를 방지하기 위해 일종의 지연 평가를 구현할 수 있습니다. 즉, 함수가 먼저 자체를 평가하는 방법을 알고있는 객체를 생성하지만, 먼저 값을 요청하면 함수가 수행됩니다. 그렇게하면 비용이 많이 소요되는 경우 평가 전에 변환 체인을 단순화 할 수 있습니다.

+1

OP는 변환 함수가 비싸다고 말 했으므로이 솔루션은 하나가 충분할 경우 두 가지 변환 함수가 필요하기 때문에 최적이 아닙니다. –

1

실제로 Dr. V's answer을 복제하고 파생 클래스는 실제 구현을 재정의합니다. 내가 코드를 작성 소요되는 노력을 정당화하기 위해 내 대답을 게시하고있다 :

#!/usr/bin/env python 
def foo_from_bar(x): 
    return 'foo_from_bar(%s)' % x 

def bar_from_baz(x): 
    return 'bar_from_baz(%s)' % x 

def baz_from_foo(x): 
    return 'baz_from_foo(%s)' % x 

class FooBarBaz(object): 
    def foo(self, x): 
     return foo_from_bar(self.bar(x)) 

    def bar(self, x): 
     return bar_from_baz(self.baz(x)) 

    def baz(self, x): 
     return baz_from_foo(self.foo(x)) 

class Derived1(FooBarBaz): 
    def bar(self, x): 
     return 5 

class Derived2(FooBarBaz): 
    def foo(self, x): 
     return 6 

class Derived3(FooBarBaz): 
    def baz(self, x): 
     return 7 

d1 = Derived1() 
d2 = Derived2() 
d3 = Derived3() 

def check(expr): 
    print expr, '->', eval(expr) 

for i,d in enumerate([d1, d2, d3]): 
    print '--- d = Derived%d() ----' % (i+1) 
    check('d.foo(0)') 
    check('d.bar(0)') 
    check('d.baz(0)') 

출력 :

--- d = Derived1() ---- 
d.foo(0) -> foo_from_bar(5) 
d.bar(0) -> 5 
d.baz(0) -> baz_from_foo(foo_from_bar(5)) 
--- d = Derived2() ---- 
d.foo(0) -> 6 
d.bar(0) -> bar_from_baz(baz_from_foo(6)) 
d.baz(0) -> baz_from_foo(6) 
--- d = Derived3() ---- 
d.foo(0) -> foo_from_bar(bar_from_baz(7)) 
d.bar(0) -> bar_from_baz(7) 
d.baz(0) -> 7 
2

데코레이터 또한 트릭을 할 수 있습니다. 이 라인을 따라 뭔가 :

def define_missing(cls): 
    has_at_least_one = False 
    if hasattr(cls, 'foo'): 
     if not hasattr(cls, 'bar'): 
      cls.bar = lambda self, x: bar_from_foo(self.foo(x)) 
     if not hasattr(cls, 'baz'): 
      cls.baz = lambda self, x: baz_from_foo(self.foo(x)) 
     has_at_least_one = True 
    if hasattr(cls, 'bar'): 
     if not hasattr(cls, 'foo'): 
      cls.foo = lambda self, x: foo_from_bar(self.bar(x)) 
     if not hasattr(cls, 'baz'): 
      cls.baz = lambda self, x: baz_from_bar(self.bar(x)) 
     has_at_least_one = True 
    if hasattr(cls, 'baz'): 
     if not hasattr(cls, 'bar'): 
      cls.foo = lambda self, x: foo_from_baz(self.baz(x)) 
     if not hasattr(cls, 'baz'): 
      cls.bar = lambda self, x: bar_from_baz(self.baz(x)) 
     has_at_least_one = True 
    if not has_at_least_one: 
     raise TypeError("Class needs to implement at least one of the methods foo, bar, and baz.") 
    return cls 

다음과 같이 사용 :

@define_missing 
class Derived1(FooBarBaz): 
    def bar(self, x): 
     return 5 

이이 functools.total_ordering 장식에서 영감을.

+0

이것은 약간 일치하지 않으며 작성된대로 작동하지 않습니다. 메소드를 다른 메소드로 변환하는 함수로'foo_from_bar'를 사용하고 있습니다. 이것은 OP가'foo_from_bar'를 도입 한 방식도 아니고'total_ordering' 데코레이터가하는 일도 아닙니다. 이 접근 방식을 사용하기 위해서 당신의 과제는'cls.foo = lambda self, x : foo_from_bar (self.bar (x))'이어야합니다. –

+0

@Sven : True. 저의 의도는 그러한 구현이 어떻게 수행 될 수 있는지에 대한 힌트를 제공하는 것이 었 습니다만, 그러한 기능을 다시 정의해야한다고 언급 했어야합니다. 'foo_from_bar' 등을 사용하는 방법에 대한 답을 편집했습니다. 이제 OP가 어떻게 사용했는지와 일치해야합니다. – freidrichen

관련 문제