관리 메뉴

HAMA 블로그

파이썬의 함정 - 2 (@classmethod 와 @staticmethod 의 차이) 본문

Python

파이썬의 함정 - 2 (@classmethod 와 @staticmethod 의 차이)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 9. 13. 11:33

 

파이썬의 함정 - 2  

@classmethod 와  @staticmethod 의 차이

자바나  C++  베이스에서 파이썬으로 옮겨 왔을때 가장 실수하기 쉬운 부분에 대해서 살펴보겠습니다.
특히나 클래스 메소드와 정적 메소드는 가장 이질적인
 부분 중 하나인데요
전문가를 위한 파이썬을 지은 파이썬구루인 루시아누 하말류는 이렇게 말합니다.

@classmethod 는 쓰임새가 많은 게 확실하지만, @staticmethod 는 사용해야하는 이유를 잘 모르겠다.
클래스와 함께 작동하지 않는 함수를 정의하려면, 단지 함수를 모듈에 정의하면 된다. 아마 함수가 클래스를 건드리지는 않지만, 그 클래스와 밀접히 연관되어 있어서 클래스 코드 가까운 곳에 두고 싶을 수는 있을 것이다. 그런 경우에는 클래스의 바로 앞이나 뒤에서 함수를 정의하면 된다. 

루시아누 하말류의 이 말에 대해 주변에서 동의하지 않는 사람중 레오나르도 로챌은 '파이썬에서 정적,클래스,추상메서드를 사용하는 최종 가이드'를 읽어보라고 권했지만 하말류는 그래도 역시 납득하지 못했다는 썰이 있습니다.

이제 이것들이 무엇인지 살펴보고 각자 판단해 보시길 바랍니다. (저는 하말류에 동감합니다)

@staticmethod

먼저 @staticmethod 를 살펴보시죠.  (우리가 알고 있는 그 정적메소드라고 생각하면 됩니다) 

class Test :
    num = 0

    @staticmethod
    def add (x, y) :
        return x + y


print Test.add(1,1)

이건 우리가 평소 알고 있던 정적 메소드와 비슷합니다.
클래스에서 직접 접근하며, 객체별로 달라지는 것이 아니라 함께 공유하는 것이죠.
주로 유틸리티성 클래스 만들때 사용하기도 합니다.

여기까지는 매우 마음이 편한데.. 다음을 보세요.

class Test :
    num = 0

    @staticmethod
    def add (x, y) :
        return x + y


t = Test()
print t.add(1,1)

파이썬은 이렇게 객체를 통해서 정적함수를 호출하는데 아무 이상이 없습니다. (@classmethod 도 마찬가지) 

C++ 이나 자바에서는 에러죠. 즉 객체를 위한 메소드와 정적메소드의 스코프가 완전히 다릅니다만.. 
위에 제 실수 입니다. C# 은 에러 , Java , C++은 warning 정도지, 언어스펙으로 안되지는 않군요. ;;
아마 고슬링도 잘못된 것 을 알 겁니다. 다만 하위호환성 때문에 고치질 못하고 있는 것 일뿐..
허용하면 정적메소드,일반메소드 리팩토링이 원할하다 등등 각종 이유도 있긴 한데  개인적으로 꽝입니다.
그냥 막는게 최선이라 봅니다.

파이썬은 그렇지 않습니다.  막나갑니다. @@  


class TestTestTestTest :
num = 10

@staticmethod
def add (x, y) :
return x + y + self.num # TestTestTestTest.num 이면 가능


t = TestTestTestTest()
print t.add(1,1)

마지막으로 정적함수이기때문에 당연히 self 를 통한 접근은 불가능합니다. 저 코드는 에러입니다.!

@classmethod

이제 @classmethod 를 살펴보시죠.  이것도 staticmethod와 거의 동일합니다만.. 한가지 차이가 있습니다.


class Test :
    num = 10

    @classmethod
    def add (x, y) :
        return x + y


print Test.add(1,1)

이렇게 하면 에러입니다. -.-;;;  눈을 크게 뜨고 봐도 모르겠지요? 

 

class Test :
    num = 10

    @classmethod
    def add (cls, x, y) :
        return x + y


print Test.add(1,1)

파이썬은 ~~이렇게 add 함수의 첫번째 인자로 cls 를 줘야합니다. (cls 말고 다른 말로 넣어도 됩니다) 
클래스 메소드는 클래스 자체를 첫번째 인자로 넣어줘야합니다. 즉 클래스 자체를 객체처럼 봄. 

(파이썬은 객체함수를 나타낼때 무조건 def (self, x, y) 이렇게 self 를 첫번째 인자로 넣어야하는데요.
즉 자바나 C++ 에서 this 를 명시 안해도 되나, 파이썬은 강제합니다.)

그럼 이런게 왜 필요한걸까요? 필요할때가 있습니다.

(@classmethod 로 하면 cls 로 접근하는 편리성은 있습니다. 클래스명이 길다면 @staticmethod 경우 클래스를 다 적어줘야 하지만요. 뭐 이건 그냥 매우 부차적인 겁니다) 

다음 예를 보시죠

# coding: utf-8
class Date :

word = 'date : '

def __init__(self, date):
self.date = self.word + date

@staticmethod
def now():
return Date("today")


def show(self):
print self.date


a = Date("2016, 9, 13")
a.show()
b = Date.now()
b.show()

@staticmethod 를 이용해서 Factory 메소드 2개를 만들었습니다.

아래처럼 지정일/오늘로 초기화한 객체를 돌려주고 있네요.

date : 2016, 9, 13
date : today

근데 이렇게 상속받아서 하면 어떻게 될까요?

class KoreanDate(Date):
word = '날짜 : '


a = KoreanDate.now()
a.show() 결과) daate : today

date : today 이렇게 나옵니다.  now() 를 통해 KoreanDate 객체가 생성되는게 아니라 Date 객체가 생성되네요.

이때 @classmethod 의 힘이 나옵니다.

# coding: utf-8
class Date :

word = 'date : '

def __init__(self, date):
self.date = self.word + date

@classmethod
def now(cls):
return cls("today")

def show(self):
print self.date



class KoreanDate(Date):
word = '날짜 : '


a = KoreanDate.now()
a.show() 결과) 날짜 : today

위처럼 now 를 @classmethod 로 바꾸어서 cls 를 전달해서 그것으로 객체를 생성하면 
Date 객체 대신해서 KoreaDate 객체를 돌려 받게 됩니다. 

날짜 : today   # 이제 제대로 나오네요.

자 이제 이해 했으니 처음에 루시아누 하말류가 왜 "staticmethod 를 사용해야하는지 모르겠다" 를 각자 생각해보세요. 클래스라는 네임스페이스로 묶고 싶은 정적 메소드는 모두 @classmethod  를 이용해서 사용하면 될 거 같습니다만 상속에서 사용되어 혼동을 초래할 여지가 없거나 조금이라도 더 간략하게 표현하고 싶을 경우 @staticmethod 를 사용하는게 더 편해 보이구요.

자바 같은 경우는 클래스와 분리 될 수 가 없지만 파이썬 경우는 애초에 모듈에 유틸리티 함수들 만 따로 모으는게 자연스러운 일이니 어떻게 보면 굳이 필요 없어 보이기도 합니다. 따라서 논쟁의 여지가 없어 보이네요. (다중상속이 필요하냐? 필요 없냐? 혹은 체크예외가 필요하냐 필요없냐 같은 차원높은 논의가 발생할 여지가 없는 단순한 사항) 

부록 : java 에서 static method on instance

class Base
{
    static void foo()
    {
        System.out.println("Base.foo()");
    }
}

class Derived extends Base
{
    static void foo()
    {
        System.out.println("Derived.foo()");
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Base b = new Derived();
        b.foo(); // Prints "Base.foo()"
        b = null;
        b.foo(); // Still prints "Base.foo()"
    }
}

http://stackoverflow.com/questions/610458/why-isnt-calling-a-static-method-by-way-of-an-instance-an-error-for-the-java-co

Comments