2009-06-13 4 views
53

나는 외부 라이브러리에 의해 제공되는 클래스가 있습니다. 이 클래스의 하위 클래스를 만들었습니다. 또한 원래 클래스의 인스턴스가 있습니다.Python에서 인스턴스 재구성

이제 인스턴스가 이미 가지고있는 속성을 변경하지 않고이 인스턴스를 내 하위 클래스의 인스턴스로 바꾸고 싶습니다. (내 서브 클래스가 재정의 한 것을 제외하고).

다음 해결책이 효과가있는 것 같습니다.

# This class comes from an external library. I don't (want) to control 
# it, and I want to be open to changes that get made to the class 
# by the library provider. 
class Programmer(object): 
    def __init__(self,name): 
     self._name = name 

    def greet(self): 
     print "Hi, my name is %s." % self._name 

    def hard_work(self): 
     print "The garbage collector will take care of everything." 

# This is my subclass. 
class C_Programmer(Programmer): 
    def __init__(self, *args, **kwargs): 
     super(C_Programmer,self).__init__(*args, **kwargs) 
     self.learn_C() 

    def learn_C(self): 
     self._knowledge = ["malloc","free","pointer arithmetic","curly braces"] 

    def hard_work(self): 
     print "I'll have to remember " + " and ".join(self._knowledge) + "." 

    # The questionable thing: Reclassing a programmer. 
    @classmethod 
    def teach_C(cls, programmer): 
     programmer.__class__ = cls # <-- do I really want to do this? 
     programmer.learn_C() 


joel = C_Programmer("Joel") 
joel.greet() 
joel.hard_work() 
#>Hi, my name is Joel. 
#>I'll have to remember malloc and free and pointer arithmetic and curly braces. 

jeff = Programmer("Jeff") 

# We (or someone else) makes changes to the instance. The reclassing shouldn't 
# overwrite these. 
jeff._name = "Jeff A" 

jeff.greet() 
jeff.hard_work() 
#>Hi, my name is Jeff A. 
#>The garbage collector will take care of everything. 

# Let magic happen. 
C_Programmer.teach_C(jeff) 

jeff.greet() 
jeff.hard_work() 
#>Hi, my name is Jeff A. 
#>I'll have to remember malloc and free and pointer arithmetic and curly braces. 

그러나, 나는 마법의 __class__를 재 할당하는 것은 바로 생각하지 않기 때문에 특히,이 솔루션은 내가 (트리플 부정에 대한 죄송합니다) 생각하지 않은주의 사항을 포함하지 않는 것을 확신 아니에요. 이 방법이 효과가 있다고하더라도, 나는이 일을하는 더 평범한 방법이 있어야한다고 생각합니다.

있습니까?


편집 : 답변 해 주셔서 감사합니다. 여기에 내가 그들로부터 무엇을 얻을 수 있습니다 :

  • __class__에 할당하여 인스턴스를 reclassing의 아이디어는 널리 사용되는 관용구는 아니지만, 대부분의 답변을 (4 ~ 6에서 글을 쓰는 시점은) 그것을 유효을 고려 접근. 하나의 답변 (ojrac에 의한)은 "처음에는 꽤 이상한"것이라고 말하면서 동의합니다. (질문하는 이유였습니다). 한 가지 대답 (Jason Baker, 두 가지 긍정적 인 의견이 & 표를 얻음)은이 기술을 사용하지 못하도록 적극적으로 권장하지 않았습니다.

  • 긍적 적이든 아니든이 방법 중 실제 기술적 문제는 없습니다. 예외는 거의 있을지도 모르는 예전 스타일의 클래스와 C 확장을주의해야한다는 jls입니다. 나는 새로운 스타일 클래스 인식 C 확장이 Python 자체 (이 코드는 후자라고 가정)가이 메소드와 잘 맞아야한다고 생각하지만, 동의하지 않으면 응답을 계속 가져온다.

얼마나 진절 통성이 있는지에 관해서는 몇 가지 긍정적 인 답변이 있지만 실제 이유는 없습니다. Zen (import this)을 보면,이 경우 가장 중요한 규칙은 "Explicit은 암묵적인 것보다 낫다"는 것입니다. 그렇긴하지만 그 규칙이 이러한 방식으로 재현되도록 반대하는지 여부에 대해서는 확실하지 않습니다. 우리가 명시 적으로 객체에 우리의 변경을 대신 마법을 사용으로 {has,get,set}attr를 사용

  • 은 더 명시 적으로 보인다.

  • __class__ = newclass을 사용하면 명시 적으로 "이제는 클래스 'newclass의 개체입니다.'는 특성을 자동 변경하는 대신 다른 동작을 기대하지만 개체의 사용자는 일반 개체 구 클래스의

요약 : 기술적 인 관점에서 보면이 방법은 괜찮은 것 같습니다. pythonicity 질문은 "긍정"에 편향되어 답이 없습니다.

Mercurial 플러그인 예제가 매우 강하기 때문에 마틴 가이 슬러의 답변을 수락했습니다 (또한 아직 나 자신에게 묻지 않은 질문에 답변했기 때문에).그러나, pythonicity 질문에 대한 논쟁이 있다면, 나는 여전히 그들을 듣고 싶습니다. 지금까지 모두에게 감사합니다.

P. 실제 사용 사례는 런타임에에 추가 기능 을 성장시켜야하는 UI 데이터 컨트롤 개체입니다. 그러나이 질문은 매우 일반적입니다.

+4

잠깐 ... 뭐라 구요? ... –

+0

좋은 예입니다. 어떤 경우에이 패턴이 유용하다고 설명 할 수 있습니까? 나는 그 아이디어가 마음에 들지만, 어떻게 유용하게 쓰는지 모르겠다. :) – NicDumZ

+0

@ NicDumZ : 질문 주셔서 감사합니다. 그것은 편집에서 응답합니다. – balpha

답변

17

확장 (플러그인)이 로컬 저장소를 나타내는 객체를 변경하고자 할 때 Mercurial (분산 개정 관리 시스템)에서 이러한 인스턴스를 재구성합니다. 이 개체는 repo이며 처음에는 localrepo 인스턴스입니다. 그것은 차례대로 각 확장에 전달되고, 필요할 경우 확장은 repo.__class__의 변경 클래스의 하위 클래스 인 새 클래스를 정의합니다.이 새로운 하위 클래스에 대한 클래스는 repo입니다. 코드에서

것 같습니다 like this :

def reposetup(ui, repo): 
    # ... 

    class bookmark_repo(repo.__class__): 
     def rollback(self): 
      if os.path.exists(self.join('undo.bookmarks')): 
       util.rename(self.join('undo.bookmarks'), self.join('bookmarks')) 
      return super(bookmark_repo, self).rollback() 

     # ... 

    repo.__class__ = bookmark_repo 

확장 (나는 북마크 확장의 코드를했다) reposetup라는 모듈 수준의 기능을 정의합니다. Mercurial은 확장을 초기화 할 때이를 호출하고 ui (사용자 인터페이스) 및 repo (저장소) 인수를 전달합니다.

이 함수는 클래스 repo의 서브 클래스를 정의합니다.이 아닌이면 확장자가 서로 확장 될 수 있어야하므로 localrepo의 하위 클래스로 충분합니다. 따라서 첫 번째 확장자가 repo.__class__에서 foo_repo으로 변경되면 다음 확장자는의 하위 클래스가 아닌 foo_repo의 하위 클래스로 repo.__class__을 변경해야합니다. 마침내 함수는 코드에서했던 것처럼 instanceø의 클래스를 변경합니다.

이 코드가이 언어 기능의 적법한 사용을 나타낼 수 있기를 바랍니다. 나는 그것이 야생에서 사용되는 것을 본 유일한 곳이라고 생각합니다.

1

재미있는 예.

"Reclassing"은 처음 보면 꽤 이상합니다. '복사 생성자'접근법은 어떻습니까? 리플렉션 (예 : hasattr, getattrsetattr)과 함께이 작업을 수행 할 수 있습니다. 이 코드는 이미 존재하지 않는 한 모든 객체를 다른 객체로 복사합니다. 메소드를 복사하지 않으려면 제외 할 수 있습니다. 덧글이있는 if을 참조하십시오.

class Foo(object): 
    def __init__(self): 
     self.cow = 2 
     self.moose = 6 

class Bar(object): 
    def __init__(self): 
     self.cat = 2 
     self.cow = 11 

    def from_foo(foo): 
     bar = Bar() 
     attributes = dir(foo) 
     for attr in attributes: 
      if (hasattr(bar, attr)): 
       break 
      value = getattr(foo, attr) 
      # if hasattr(value, '__call__'): 
      #  break # skip callables (i.e. functions) 
      setattr(bar, attr, value) 

     return bar 

이 모든 반사는 예쁘지 않지만 때로는 멋진 물건을 만들기 위해 못생긴 반사 기계가 필요합니다. ;)

-1

나는 이것이 당신에게 효과가 있다면 이것은 완벽하다고 말할 것입니다.

11

적어도이 경우에는 상속을 사용하는 것이 가장 좋습니다 (최소한 "재 분류"와 관련하여). 당신이 올바른 길을 가고있는 것 같지만 조성이나 응집이 가장 좋을 것 같습니다. 여기 (안된, 의사 - 억양 코드에서) 내가 생각하고 무엇의 예 :

from copy import copy 

# As long as none of these attributes are defined in the base class, 
# this should be safe 
class SkilledProgrammer(Programmer): 
    def __init__(self, *skillsets): 
     super(SkilledProgrammer, self).__init__() 
     self.skillsets = set(skillsets) 

def teach(programmer, other_programmer): 
    """If other_programmer has skillsets, append this programmer's 
     skillsets. Otherwise, create a new skillset that is a copy 
     of this programmer's""" 
    if hasattr(other_programmer, skillsets) and other_programmer.skillsets: 
     other_programmer.skillsets.union(programmer.skillsets) 
    else: 
     other_programmer.skillsets = copy(programmer.skillsets) 
def has_skill(programmer, skill): 
    for skillset in programmer.skillsets: 
     if skill in skillset.skills 
      return True 
    return False 
def has_skillset(programmer, skillset): 
    return skillset in programmer.skillsets 


class SkillSet(object): 
    def __init__(self, *skills): 
     self.skills = set(skills) 

C = SkillSet("malloc","free","pointer arithmetic","curly braces") 
SQL = SkillSet("SELECT", "INSERT", "DELETE", "UPDATE") 

Bob = SkilledProgrammer(C) 
Jill = Programmer() 

teach(Bob, Jill)   #teaches Jill C 
has_skill(Jill, "malloc") #should return True 
has_skillset(Jill, SQL) #should return False 

당신은이 예제를 얻을 그들에 익숙하지 않은 경우보다 약 setsarbitrary argument lists를 읽을 수 있습니다.

+2

+1 패턴입니다. 여기에서 가장 이해가됩니다. – Triptych

+2

+1 외부 API에서 제공하는 객체를 처리 할 때 항상 배치가 더 좋습니다. –

1

이 기술은 나에게 상당히 파이썬 적으로 보입니다. 작곡도 좋은 선택이지만, __class__에 할당하는 것은 완전히 유효합니다. 약간 다른 방식으로 그것을 사용하는 요리법은 here을 참조하십시오.

3

괜찮습니다. 나는이 관용구를 많이 사용했다. 명심해야 할 것은이 아이디어가 예전 스타일의 클래스와 다양한 C 확장으로 잘 돌아 가지 않는다는 것입니다. 일반적으로 이것은 문제가되지 않지만 외부 라이브러리를 사용하고 있으므로 이전 스타일의 클래스 나 C 확장을 다루지 않아야합니다.

3

"상태 패턴을 사용하면 개체의 내부 상태가 변경 될 때 개체의 동작을 변경할 수 있습니다. 개체의 클래스가 변경된 것처럼 보입니다." - 헤드 퍼스트 디자인 패턴. 비슷한 점이 감마 등. 그들의 디자인 패턴 책. (나는 다른 곳에서 그것을 가지고있다. 따옴표가 없다). 나는 그것이이 디자인 패턴의 요점이라고 생각합니다. 그러나 런타임에 객체의 클래스를 변경할 수 있다면 대부분 패턴을 필요로하지 않습니다 (State Pattern이 클래스 변경을 시뮬레이트하는 것 이상의 경우가 있습니다).

class A(object): 
    def __init__(self, val): 
     self.val = val 
    def get_val(self): 
     return self.val 

class B(A): 
    def __init__(self, val1, val2): 
     A.__init__(self, val1) 
     self.val2 = val2 
    def get_val(self): 
     return self.val + self.val2 


a = A(3) 
b = B(4, 6) 

print a.get_val() 
print b.get_val() 

a.__class__ = B 

print a.get_val() # oops! 

그 외에도에서, 내가 런타임 파이썬에서 클래스를 변경하면 수시로 사용 :

또한, 런타임에 클래스를 변경하면 항상하지 않습니다 작동합니다.

0

ojrac의 답변에서 breakfor -loop에서 벗어나 더 이상 특성을 테스트하지 않습니다. 한 번에 하나씩 각 속성을 처리 할 것인지를 결정하기 위해 if- 문구를 사용하는 것이 더 합리적이라고 생각하고 모든 속성에 대해 for -loop을 계속합니다. 그렇지 않으면, 나는 ojrac의 대답을 좋아합니다. 나는 또한 __class__을 이상한 것으로 보았습니다. (필자는 파이썬의 초보자이며 StackOverFlow에 대한 첫 번째 게시물이라는 것을 기억합니다. 모든 훌륭한 정보를 주셔서 감사합니다 !!)

그래서 구현하려고했습니다. 나는 dir()이 모든 속성을 나열하지 않는다는 것을 알아 차렸다. 그들은 이미 아마있어 비록 http://jedidjah.ch/code/2013/9/8/wrong_dir_function/ 그래서 내가 (이미 거기 있다면 추가, 사물의 목록에 '모듈'와 '초기화'을 '문서'을 '클래스을'추가 그곳에), 그리고 더 많은 것들을 놓쳤는 지 궁금해했다. 나는 또한 (잠재적으로) 'class'으로 할당되었다는 사실을 알게 된 후 이상하게 보였다.