인스턴스 수준에서 메서드 재정의


87

Python에서 인스턴스 수준에서 클래스 메서드를 재정의하는 방법이 있습니까? 예를 들면 :

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!

답변:


11

표시된대로이 작업을 수행하지 마십시오. 인스턴스를 클래스와 다른 것으로 monkeypatch하면 코드를 읽을 수 없게됩니다.

monkeypatched 코드를 디버깅 할 수 없습니다.

bobyprint type(boby)에서 버그를 발견하면 (a) 개이지만 (b) 일부 모호한 이유로 제대로 짖지 않는다는 것을 알 수 있습니다. 이것은 악몽입니다. 하지마.

대신 이것을하십시오.

class Dog:
    def bark(self):
        print "WOOF"

class BobyDog( Dog ):
    def bark( self ):
        print "WoOoOoF!!"

otherDog= Dog()
otherDog.bark() # WOOF

boby = BobyDog()
boby.bark() # WoOoOoF!!

9
@arivero : "표시된대로이 작업을 수행하지 마십시오"라는 내용이 완벽하게 명확 해 졌다고 생각했습니다. 질문 한 질문에 대한 답이 아니라 왜 나쁜 생각인지에 대한 조언을 제공하고 있음을 더 명확히하기 위해 어떤 다른 또는 다른 단어를보고 싶습니까?
S.Lott

45
나는 조언이나 OP에 동의하지 않습니다. 그러나 나는 사람들이 물어볼 이유가 있다고 생각합니다. 또는 OP가 그렇지 않더라도 미래의 다른 방문자는 할 수 있습니다. 그래서, IMHO, 대답과 견책이 단지 견책보다 낫습니다.
arivero

13
@arivero : 제 질문에 대답하지 않았습니다.
S.Lott

9
@ S.Lott 나는 당신이 실제 대답에 "표시된"을 연결해야한다고 생각한다. 나는 이것이 받아 들여진 대답이되는 것에 정말로 문제가 없지만, 어떤 상황에서 그리고 나의 훑어보기에서 원숭이 패치를해야하는 이유가있다. 나는 다른 대답이 아니라 당신이 보여주고있는 것을 의미하기 위해 "보여진대로"를 취했습니다.
Daniel Chatfield 2013 년

4
서브 클래 싱은 메소드에 원숭이 패치를 적용하는 것보다 훨씬 강력한 계약입니다. 가능한 경우 강력한 계약은 예기치 않은 행동을 방지합니다. 그러나 어떤 경우에는 더 느슨한 계약이 바람직합니다. 이러한 상황에서는 원숭이 패치 대신 콜백을 사용하는 것을 선호합니다. 실용적인 조언이긴하지만 귀하의 대답은이 질문에 대해 매우 열악하며 코딩 스타일이 일치하지 않습니다.
Aaron3468

166

예, 가능합니다 :

class Dog:
    def bark(self):
        print "Woof"

def new_bark(self):
    print "Woof Woof"

foo = Dog()

funcType = type(Dog.bark)

# "Woof"
foo.bark()

# replace bark with new_bark for this object only
foo.bark = funcType(new_bark, foo, Dog)

foo.bark()
# "Woof Woof"

1
funcType (new_bark, foo, Dog)을 수행 할 때 foo .__ dict__에 이름 == bark 및 인스턴스 메소드 Dog.new_bark를 추가하는 작은 주석입니다. 맞습니까? 따라서 다시 호출하면 먼저 인스턴스 사전을
James

11
이것이 무엇을하는지, 특히 무엇을 funcType하고 왜 필요한지 설명 하십시오.
Aleksandr Dubinsky

3
대신에 funcType = types.MethodType(가져온 후 types) 사용하여 조금 더 간단하고 명시 적으로 만들 수 있다고 생각합니다 funcType = type(Dog.bark).
Elias Zamaria

13
나는 이것이 파이썬 3에서 작동하지 않는다고 생각한다. "TypeError : function () argument 1 must be code, not function"오류가 발생합니다. Python 3에 대한 제안 사항이 있습니까?
Sait

2
__get__함수를 호출 하여 인스턴스에 바인딩 할 수도 있습니다 .
Mad Physicist

37

모듈 에서 MethodType 을 사용해야 types합니다. 의 목적은 MethodType인스턴스 수준 메서드를 덮어 쓰는 것입니다 ( self덮어 쓴 메서드에서 사용할 수 있음).

아래 예를 참조하십시오.

import types

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print "WoOoOoF!!"

boby.bark = types.MethodType(_bark, boby)

boby.bark() # WoOoOoF!!

31

@codelogic의 탁월한 대답을 설명하기 위해 더 명시적인 접근 방식을 제안합니다. 이는 .인스턴스 속성으로 액세스 할 때 연산자가 클래스 메서드를 바인딩하기 위해 철저히 수행 하는 동일한 기술입니다. 단, 메서드는 실제로 클래스 외부에서 정의 된 함수가됩니다.

@codelogic의 코드로 작업 할 때 유일한 차이점은 메서드가 바인딩되는 방식입니다. 함수와 메서드가 Python에서 데이터가 아닌 설명 자라는 사실을 사용하고 메서드 를 호출합니다 __get__. 특히 원본과 대체 모두 동일한 서명을 가지고 있습니다. 즉, 대체를 전체 클래스 메서드로 작성하여 .NET을 통해 모든 인스턴스 속성에 액세스 할 수 있습니다 self.

클래스 Dog :
    def bark (self) :
        "Woof"인쇄

def new_bark (self) :
    "Woof Woof"인쇄

foo = Dog ()

# "멍청이"
foo.bark ()

#이 개체에 대해서만 bark를 new_bark로 바꿉니다.
foo.bark = new_bark .__ get __ (foo, Dog)

foo.bark ()
# "Woof Woof"

바인딩 된 메서드를 인스턴스 속성에 할당하여 메서드 재정의에 대한 거의 완전한 시뮬레이션을 만들었습니다. 누락 된 편리한 기능 중 하나 super는 클래스 정의에 있지 않기 때문에 인수가없는 버전에 대한 액세스 입니다. 또 다른 한 가지는 __name__바인딩 된 메서드 의 속성이 클래스 정의 에서처럼 재정의하는 함수의 이름을 사용하지 않지만 수동으로 설정할 수 있다는 것입니다. 세 번째 차이점은 수동으로 바인딩 된 메서드가 함수 인 일반 속성 참조라는 것입니다. .운영자는 아무것도하지 않습니다하지만 참조를 가져옵니다. 반면에 인스턴스에서 일반 메서드를 호출 할 때 바인딩 프로세스는 매번 새로운 바인딩 된 메서드를 만듭니다.

그런데 이것이 작동하는 유일한 이유는 인스턴스 속성이 비 데이터 설명자를 재정의하기 때문 입니다. 데이터 디스크립터에는 __set__메서드가 있지만 (다행히도) 메서드에는 없습니다. 클래스의 데이터 설명자는 실제로 모든 인스턴스 속성보다 우선합니다. 이것이 속성에 할당 할 수있는 이유입니다. 할당을 __set__시도 할 때 해당 메서드가 호출됩니다. 저는 개인적으로 이것을 한 단계 더 나아가 인스턴스의 기본 속성의 실제 값을 숨기고 싶습니다 __dict__. 속성이 그림자를 가리기 때문에 정상적인 방법으로는 액세스 할 수 없습니다.

이것은 매직 (이중 밑줄) 메서드 에는 무의미하다는 것을 명심해야 합니다 . 물론 매직 메서드는 이런 방식으로 재정의 될 수 있지만이를 사용하는 작업은 유형 만 확인합니다. 예를 들어 __contains__인스턴스에서 특별한 것을 설정할 수 있지만 호출 x in instance하면이를 무시하고 type(instance).__contains__(instance, x)대신 사용 합니다. 이것은 Python 데이터 모델에 지정된 모든 매직 메서드에 적용됩니다 .


1
이것은 받아 들여진 대답이어야합니다. 깨끗하고 Python 3에서 작동합니다.
BlenderBender

@BlenderBender. 지원해 주셔서 감사합니다
Mad Physicist

이 답변과 @Harshal Dhumai의 답변 위의 답변의 차이점은 무엇입니까?
1313e

1
@ 1313. 기능적으로는 큰 차이가 없어야합니다. 위의 답변이 더 넓은 범위의 콜 러블을 입력으로 받아 들일 수 있다고 생각하지만 문서를 읽고 놀지 않고는 확실하지 않습니다.
Mad Physicist

이에 대한 Python 2 문서 : docs.python.org/2/howto/descriptor.html#functions-and-methods . 따라서 이론적으로 @Harshal Dhumai의 답변과 동일합니다.
Rockallite

26
class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

# METHOD OVERRIDE
def new_bark():
    print "WoOoOoF!!"
boby.bark = new_bark

boby.bark() # WoOoOoF!!

boby필요한 경우 함수 내 에서 변수 를 사용할 수 있습니다 . 이 하나의 인스턴스 객체에 대해서만 메서드를 재정의하기 때문에이 방법이 더 간단하고를 사용하는 것과 정확히 동일한 효과가 self있습니다.


1
원래 서명을 사용하는 IMHO는 특히 함수가 인스턴스 근처가 아닌 코드의 다른 위치에 정의 된 경우 가독성을 높입니다. 재정의 메서드도 함수로 독립적으로 사용되는 경우는 예외입니다. 물론,이 간단한 예제에서는 중요하지 않습니다.
codelogic

1
왜 이것이 허용되지 않는 대답인지 이해하지 못합니다. 호출 patching되고 이것이 올바른 방법입니다 (예 : boby = Dog()boby.bark = new_bark). 컨트롤에 대한 단위 테스트 에 매우 유용 합니다. 자세한 설명은 tryolabs.com/blog/2013/07/05/run-time-method-patching-python (예제)을 참조하십시오. 아니요 링크 된 사이트 또는 작성자와 관련이 없습니다.
Geoff

5
new_bark 메서드는 self (인스턴스)에 대한 액세스 권한이 없으므로 사용자가 new_bark의 인스턴스 속성에 액세스 할 수있는 방법이 없습니다. 대신 유형 모듈에서 MethodType을 사용해야합니다 (아래 답변 참조).
Harshal Dhumal

3

functools.partial여기 에 언급 된 사람이 없기 때문에 :

from functools import partial

class Dog:
    name = "aaa"
    def bark(self):
        print("WOOF")

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print("WoOoOoF!!")

boby.bark = partial(_bark, boby)
boby.bark() # WoOoOoF!!

1

함수는 Python에서 첫 번째 클래스 객체이므로 클래스 객체를 초기화하는 동안 전달하거나 주어진 클래스 인스턴스에 대해 언제든지 재정의 할 수 있습니다.

class Dog:
    def __init__(self,  barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        print "woof"

d=Dog()
print "calling original bark"
d.bark()

def barknew():
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()

def barknew1():
    print "nowoof"

d1.bark=barknew1
print "calling another new"
d1.bark()

결과는

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof

-4

S. Lott의 상속 아이디어를 좋아하고 'type (a)'에 동의했지만 함수에도 접근 가능한 속성이 있기 때문에 다음과 같이 관리 할 수 ​​있다고 생각합니다.

class Dog:
    def __init__(self, barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        """original bark"""
        print "woof"

d=Dog()
print "calling original bark"
d.bark()
print "that was %s\n" % d.bark.__doc__

def barknew():
    """a new type of bark"""
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

def barknew1():
    """another type of new bark"""
    print "nowoof"

d1.bark=barknew1
print "another new"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

출력은 다음과 같습니다.

calling original bark
woof
that was original bark

calling the new bark
wooOOOoof
that was a new type of bark

another new
nowoof
that was another type of new bark

2
그것이 "관리"되어야한다면, 나에게는 뭔가 잘못된 것입니다. 특히 이미 작업을 수행하는 일류 언어 기능이있을 때.
S.Lott

-4

친애하는 이것은 객체를 사용하여 동일한 함수를 두 번 호출하는 것을 재정의하는 것이 아닙니다. 기본적으로 재정의는 둘 이상의 클래스와 관련이 있습니다. 동일한 서명 방법이 다른 클래스에 존재할 때 호출하는 함수는 이것을 호출하는 객체를 결정합니다. 둘 이상의 클래스가 동일한 함수를 작성하고 파이썬에서 오버로딩이 허용되지 않는다는 것을 공유하기 위해 하나 이상의 클래스를 만들 때 파이썬에서 재정의가 가능합니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.