Development Tip

Python에서 인스턴스 재 분류

yourdevel 2020. 12. 31. 23:02
반응형

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__널리 사용되는 관용구는 아니지만 대부분의 답변 (작성 당시 6 개 중 4 개)은이를 유효한 접근 방식으로 간주합니다. 한 답변 (ojrac에 의해)은 그것이 "첫눈에 꽤 이상하다"고 말하는데, 나는 동의합니다 (질문을 한 이유였습니다). 단 하나의 답변 (제이슨 베이커 (Jason Baker), 두 개의 긍정적 인 의견 및 투표 포함)만이이 작업을 적극적으로 낙담 시켰지만 일반적인 기술보다는 예제 사용 사례를 기반으로했습니다.

  • 긍정적이든 아니든 어떤 대답도이 방법에서 실제 기술적 문제를 찾지 못합니다. 작은 예외는 사실 일 가능성이있는 구식 클래스와 C 확장을 조심하도록 언급하는 jls입니다. 나는 새로운 스타일의 클래스를 인식하는 C 확장이이 방법으로 파이썬 자체만큼 괜찮아 야한다고 생각한다 (후자가 사실이라고 가정). 동의하지 않는다면 대답을 계속하라.

이것이 얼마나 비단뱀 적인지에 대한 질문에 대해 몇 가지 긍정적 인 답변이 있었지만 실제 이유는 제시되지 않았습니다. Zen ( import this)을 보면 이 경우에 가장 중요한 규칙은 "명시적인 것이 암시적인 것보다 낫다."라고 생각합니다. 그러나 그 규칙이 이런 식으로 재 분류를 옹호하는지 반대하는지 확실하지 않습니다.

  • {has,get,set}attr마법을 사용하는 대신 객체를 명시 적으로 변경하므로 사용 이 더 명시 적으로 보입니다.

  • 사용하는 __class__ = newclass것은 우리가 "이제 클래스 'newclass'의 객체입니다. 다른 동작을 기대합니다."라고 명시 적으로 말했기 때문에 사용 이 더 명확 해 보입니다. 속성을 조용히 변경하는 대신 객체 사용자는 이전 클래스의 일반 객체를 다루고 있다고 믿게됩니다.

요약 : 기술적 인 관점에서이 방법은 괜찮아 보입니다. 비단뱀성에 대한 질문은 "예"에 대한 편견으로 아직 답이 없습니다.

Mercurial 플러그인 예제가 상당히 강력하기 때문에 Martin Geisler의 대답을 받아 들였습니다 (또한 아직 나에게 묻지 않은 질문에도 대답했기 때문입니다). 그러나 비단뱀 성 질문에 대한 논쟁이 있다면 여전히 듣고 싶습니다. 지금까지 감사합니다.

추신 실제 사용 사례는 런타임에 추가 기능을 확장해야하는 UI 데이터 제어 개체입니다 . 그러나 질문은 매우 일반적입니다.


이와 같은 인스턴스 재 분류는 확장 (플러그인)이 로컬 저장소를 나타내는 객체를 변경하고자 할 때 Mercurial (분산 개정 제어 시스템)에서 수행됩니다. 객체가 호출 repo되고 처음에는 localrepo인스턴스입니다. 그것은 차례로 각 확장에 전달하고, 필요한 경우, 확장의 하위 클래스 인 새 클래스를 정의합니다 repo.__class__변경 의 클래스를 repo이 새로운 서브 클래스에!

코드에서 다음 과 같이 보입니다 .

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, 다음 확장 변경해야합니다 repo.__class__의 서브 클래스로 foo_repo와의뿐만 아니라 서브 클래스 localrepo. 마지막으로 함수는 코드에서했던 것처럼 instanceø의 클래스를 변경합니다.

I hope this code can show a legitimate use of this language feature. I think it's the only place where I've seen it used in the wild.


I'm not sure that the use of inheritance is best in this case (at least with regards to "reclassing"). It seems like you're on the right track, but it sounds like composition or aggregation would be best for this. Here's an example of what I'm thinking of (in untested, pseudo-esque code):

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

You may have to read more about sets and arbitrary argument lists if you aren't familiar with them to get this example.


This is fine. I've used this idiom plenty of times. One thing to keep in mind though is that this idea doesn't play well with old-style classes and various C extensions. Normally this wouldn't be an issue, but since you are using an external library you'll just have to make sure you're not dealing with any old-style classes or C extensions.


"The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change it's class." - Head First Design Pattern. Something very similar write Gamma et.al. in their Design Patterns book. (I have it at my other place, so no quote). I think that's the whole point of this design pattern. But if I can change the class of an object at runtime, most of the time i don't need the pattern (there are cases when State Pattern does more than simulate a class change).

Also, changing class at runtime doesn't always work:

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!

Apart from that, I consider changing class at runtime Pythonic and use it from time to time.


Heheh, fun example.

"Reclassing" is pretty weird, at first glance. What about the 'copy constructor' approach? You can do this with the Reflection-like hasattr, getattr and setattr. This code will copy everything from one object to another, unless it already exists. If you don't want to copy methods, you can exclude them; see the commented 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

All this reflection isn't pretty, but sometimes you need an ugly reflection machine to make cool stuff happen. ;)


This technique seems reasonably Pythonic to me. Composition would also be a good choice, but assigning to __class__ is perfectly valid (see here for a recipe that uses it in a slightly different way).


In ojrac's answer, the break breaks out of the for-loop and doesn't test any more attributes. I think it makes more sense to just use the if-statement to decide what to do with each attribute one at a time, and continue through the for-loop over all attributes. Otherwise, I like ojrac's answer, as I too see assigning to __class__ as weird. (I'm a beginner with Python and as far as I remember this is my first post to StackOverFlow. Thanks for all the great information!!)

So I tried to implement that. I noticed that dir() doesn't list all the attributes. http://jedidjah.ch/code/2013/9/8/wrong_dir_function/ So I added 'class', 'doc', 'module' and 'init' to the list of things to add if they're not there already, (although they're probably all already there), and wondered whether there were more things dir misses. I also noticed that I was (potentially) assigning to 'class' after having said that was weird.


I will say this is perfectly fine, if it works for you.

ReferenceURL : https://stackoverflow.com/questions/990758/reclassing-an-instance-in-python

반응형