ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 클래스 기초
    Python 2022. 1. 11. 19:22

    자바와 비교해보는 파이썬 클래스 공부

     

    목차

    클래스 선언

    생성자 __init__메서드(인스턴스 초기화)

    인스턴스 생성 __new__메서드

    인스턴스 메서드

    소멸자 __del__메서드

    클래스 변수

    static 메서드 vs class 메서드

    메서드 오버로딩


    클래스 선언

     

    클래스 상속부분을 공부하다 여러 포스팅에서 클래스 선언부의 문법 형태가 여러가지 인것을 확인하였다.

    구글링해봤다.

    https://jh-bk.tistory.com/24

     

    [Python] 클래스가 object를 상속받는 경우

    남의 파이썬 코드를 보다 보면, 종종 다음과 같은 형태의 클래스 선언과 마주하게 된다. class MyClass(object): # blah blah... 일단은 해당 라인만 보면 내가 선언한 클래스가 object 클래스를 상속 받아서

    jh-bk.tistory.com

     

     

    파이썬3 에서는 모든 클래스는 object를 상속받는 new-style class이다. 

    아래의 코드와 같이 클래스 선언 형태가 모두 달라도 object를 상속받는 클래스가 된다.

    class MyClass():
        pass
    
    
    class MyClass:
        pass
    
    
    class MyClass(object):
        pass

    생성자

     

    우리는 생성자를 통해 인스턴스의 초기화 과정을 수행하였다.

    자바의 경우 생성자는 메서드와는 비슷하지만 메서드와는 다른 생성자가 따로 존재하였다. 

     

    파이썬 생성자의 경우 __init__메서드가 인스턴스 초기화 과정을 수행하게 된다.

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
    
    
    my = MyClass() # 인스턴스 생성

     

     __init__ 생성자 메서드의 self 파라메터에 대하여 알아보자.

     

    자바의 경우 클래스 내부에서 인스턴스 자신을 나타내는 this 키워드를 통해 클래스 내부에서 인스턴스와 관련된 변수,메서드를 사용할 수 있었다.

     

    생성자에 해당하는 __init__메서드 역시 인스턴스 메서드인데, 중요한 것은 인스턴스 메서드를 정의할 때는 첫 파라메터에 self를 선언해야 한다는 것이다. 인스턴스 메서드에 정의된 self 파라메터를 인자로 넘겨받아 이 인자를 통해 인스턴스와 관련된 작업을 진행한다. 

     

    그렇다면 이 self 파라메터에 인자를 어떻게 넘겨줘야할까?

    1. 인스턴스 생성 or 2. 변수.인스턴스 메서드() 시에는 자동으로 self 인자가 넘어가기 때문에 넘겨주지 않아도 된다.

    (위의 my = MyClass() 코드시 자동으로 __init__메서드에 self인자가 자동으로 넘어가면서 인스턴스 초기화 작업이 진행되었다)

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
            print('__init__ 메서드의 self 인자로 객체 주소값 확인: ', id(self))
    
        def test(self):
            print('test 메서드의 self 인자로 객체 주소값 확인: ', id(self))
    
    
    my = MyClass()
    print('변수를 통해 객체 주소값 확인: ', id(my))
    my.test()

    인스턴스 자신이 넘어가기 때문에 변수가 가리키는 객체의 주소값이 동일한 것을 확인 할 수 있다.

     

     

    파이썬은 인스턴스 메서드 선언시 첫 파라메터는 인스턴스 자신을 나타내는 파라메터이다. 그렇기 때문에 self 대신 다른 이름의 파라메터 이름을 사용할 수 있게 되어있다.

    파라메터 이름을 self 대신 se로 바꾼 코드이다. 결과는 이전과 같다.

    class MyClass:
    
        def __init__(se):
            print('생성자 호출')
            print('__init__ 메서드의 self 인자로 객체 주소값 확인: ', id(se))
    
        def test(se):
            print('test 메서드의 self 인자로 객체 주소값 확인: ', id(se))
    
    
    my = MyClass()
    print('변수를 통해 객체 주소값 확인: ', id(my))
    my.test()

     

    구글링을 통해 다음과 같이 변경된다는 것을 알았다.

    객체.인스턴스메서드(self를제외한 파라메터들....)
    = 클래스명.인스턴스메서드(객체, self를 제외한 파라메터들...)

     

    전자의 경우는 자동으로 self인자가 넘어가는 bound 방식이고

    후자의 경우는 직접 self인자에 인스턴스를 넘겨주는 unbound 방식이다.

    https://victorydntmd.tistory.com/240

     

    [Python 기본] 클래스 - 멤버, 생성자/소멸자, 연산자 오버로딩

    클래스의 메서드를 호출하는 방식은 2가지가 있습니다. 1) 클래스 name space로 접근하는 방법 2) 객체 name space로 접근하는 방법 class MyClass: def foo(self, x): print(x) m = MyClass() MyClass.foo(m, 10)..

    victorydntmd.tistory.com

     

    bound 방식을 unbound 방식으로 바꾼 예제

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
    
        def test(self):
            print('테스트 메서드 호출')
    
    
    my = MyClass()
    MyClass.test(my)  # my.test()와 같음

     

    __init__()메서드를 정의하지 않으면 조상 클래스인 object의 생성자에 해당하는 __init__() 메서드가 호출된다.

    class MyClass:
    	pass:

    현재 아무것도 상속받은 것이 없으므로 최상위 클래스 object의 __init__()메서드가 호출된다.

    클래스가 상속 관계에 있을 때 생성자 메서드가 어떻게 호출되는지가 매우 중요하다.

    이에 대해서는 다음 포스팅에서 다룬다. 

     

     

    당연한 것이지만 파이썬 생성자 __init__메서드는 리턴하는 것이 없어야 한다.

    class MyClass:
        def __init__(self):
            print('생성자 호출')
            return 3
    
    
    my = MyClass()

     

    self 인자를 설명하다 보니 인스턴스 메서드를 설명해버렸다.

    이제 self 인자를 사용하여 인스턴스에 관련된 인스턴스 변수를 초기화해보자.


    인스턴스 변수

     

    자바의 경우 인스턴스 변수, 클래스 변수를 선언하는 부분이 따로 클래스의 처음 부분에 존재했다.

    (자바 관습으로 이부분에서 정의했다)

     

    파이썬은 동적언어로 선언과 초기화를 분리하는 문법이 없다.

    그렇기 때문에 생성자인 __init__ 메서드 내부에서 넘겨받은 self 인자를 통해, 인스턴스 변수를 초기화한다. 

    중요한 것은 인스턴스 변수를 초기화할 때 self를 붙여야 한다는 것이다.

    (self 대신 다른 이름을 사용하였으면 그 이름을 붙여야 한다)

    그렇지 않으면 메서드의 로컬 변수로 인식된다.

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
            self.test = '인스턴스 변수'
            self.test2 = '인스턴스 변수2'
    
    
    my = MyClass()
    print(my.test)
    print(my.test2)

     

    여담으로 파이썬의 인스턴스 변수는 생성자 메서드인 __init__메서드 이외에도 다른 메서드를 호출하여 초기화하는 것이 가능하다. 런타임에 동적으로 self. 변수명= 값만 있으면 새로운 변수가 할당되는 점이 자바와 다르다. 

    이렇구나라는 정도로만 보고 항상 인스턴스 변수 관련작업은 항상 생성자인 __init__ 메서드에서 초기화하자.

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
    
        def fake_init(self):
            self.test = '인스턴스 변수'
            self.test2 = '인스턴스 변수2'
    
    
    my = MyClass()
    my.fake_init()
    print(my.test)
    print(my.test2)

    그러나 생성자를 모방한 가짜 초기화 메서드인 fake_init()메서드가 호출되지 않으면 인스턴스 변수가 초기화되지 않아 사용한 경우 에러가 난다.

    my = MyClass()
    # my.fake_init()
    print(my.test)
    print(my.test2)

    내부적인 동작은 좀더 공부를 해봐야 할 것 같다.


    인스턴스 메모리 할당 __new__()메서드

     

    자바의 new 연산자를 통해 인스턴스를 생성하고 인스턴스가 메모리에 만들어진 상태에서 생성자가 호출되었다.

    파이썬의 __new__()메서드에서 인스턴스를 메모리에 할당하는 작업이 이루어진다. 그렇기 때문에 __new__메서드가 먼저 호출되고 생성자인__init__()메서드가 호출된다.

     

    기본적으로 __new__()메서드는 건드리지 않아도 된다. __new__()메서드를 오버라이딩 할 경우 메서드가 객체를 리턴하도록 해야 한다.

    ( __new__()메서드를 오버라이딩 한 후 객체를 리턴하지 않으면 인스턴스가 할당되지 않고, 이후 호출될 생성자도 호출되지 않는다.)

     

    https://weeklyit.code.blog/2019/12/24/2019-12%EC%9B%94-3%EC%A3%BC-python%EC%9D%98-__init__%EA%B3%BC-__new__/

     

    2019.12월 3주 – Python의 __init__()과 __new__()

    Python의 __init__()과 __new__()

    weeklyit.code.blog


    소멸자 __del__()메서드

     

    객체가 메모리에서 사라지게 되는 경우 __del__()메서드가 호출된다.

    del(변수) 내장함수와 __del__()메서드가 관계가 있다. 

     

    메서드를 설명하기 전에 del() 내장함수를 알아보자. 

    del(변수) 내장함수는 변수가 가리키는 참조를 없애는 것이다.

    (자바의 가비지 컬렉션과 변수의 null관점에서 매우 흡사한 느낌을 받았다.)

    a = 1
    print(id(a))
    del (a)
    print(a)

    a는 1객체를 참조하고 있다. del 내장함수를 통해 a가 참조하고 것을 없앴다. 이제 a 변수는 사용할 수 없게 된다.

    1값 객체는 참조되고 있는 것이 a밖에 없는 상태였으므로 del()을 통해 참조를 없애 버리면, 1객체는 가비지 컬렉션의 대상이되어 메모리에서 사라진다.

     

    a = 1
    test_list = [a, 2, 3]
    del (a)
    print(test_list)

    a가 참조하고 있는 것을 없애 a변수는 사용하지 못한다.

    그러나 int 1객체는 리스트에서 참조되고 있기 때문에 가비지 컬렉션의 대상이 아니다. 

    리스트를 출력해보면 1,2,3이다.

    위의 내용을 기초로 소멸자 메서드가 호출되는 것을 확인해보자.

     

     

    함수의 로컬 변수로 함수 종료시 가비지 컬렉션의 대상이 된 예제

    class MyClass:
    
        def __init__(self):
            print('인스턴스 초기화 작업 진행')
    
        def __del__(self):
            print('인스턴스 소멸시 작업 진행')
    
    
    def test_function():
        print('함수 호출')
        my = MyClass()
        print('함수 종료')
        return
    
    
    test_function()

    함수가 종료되고 가비지 컬렉션이 일어나므로 __del__메서드가 호출된다.

    함수가 종료되고 가비지 컬렉션이 일어나므로 함수 종료 출력 이후에 프린트된다.

     

     

    del 내장함수를 사용한 예제

    class MyClass:
    
        def __init__(self):
            print('생성자 호출')
    
        def __del__(self):
            print('소멸자 호출')
    
    
    print('프로그램 시작')
    my = MyClass()
    del (my)
    print('프로그램 종료')

     

    my변수는 사용할 수 없지만 , my2 변수가 동일한 객체를 참조하고 있으므로 가비지 컬렉션의 대상이 아니라 소멸자 메서드가 호출되지 않는다. 

    class MyClass:
    
        def __del__(self):
            print('소멸자 호출')
    
    
    my = MyClass()
    my2 = my
    del (my)


    클래스 멤버

     

    클래스 변수는 기존 자바처럼 클래스 선언 바로 아래에 선언하며 self 없이 변수이름으로 선언과 동시에 초기화 한다.

    클래스 변수 호출시 인스턴스 변수가 아님을 혼동 방지하기 위해 클래스 변수를 호출할 때는 클래스명.클래스변수이름으로 하는 것은 자바와 같다. 

    class MyClass:
        t = '클래스 변수'
    
    
    print(MyClass.t)
    MyClass.t = '클래스 변수 값 변경'
    print(MyClass.t)


    class method vs static method

     

    자바에서는 인스턴스 메서드와 클래스 메서드가 존재하지만, 파이썬의 비 인스턴스 메서드는 두가지로 나뉜다.

    class 메서드, static 메서드 모두 인스턴스 없이 사용할 수 있는 메서드이다.

     

    차이점은 메서드 안에서 클래스 속성의 사용 유무이다.

    class method는 메서드 내부에서 클래스 속성을 사용하는 메서드이다.

    static method는 메서드 내부에서 클래스와 아무런 연관이 없는 작업을 하는 메서드이다.

     

     

    1. static method

     

    아래의 코드는 계산기 클래스의 static 메서드이다. 클래스 속성과 관련된 작업이 없어 static 메서드이다.

    class Calculator:
    
        def add(num1, num2):
            return num1 + num2
    
        def subtract(num1, num2):
            return num1 - num2
    
    
    print(Calculator.add(3, 1))
    print(Calculator.subtract(3, 1))

    @staticmethod 데코레이터를 통해 IDE의 도움을 받을 수 있다.

    @staticmethod가 자바의 어노테이션과 비슷해서 파이썬의 어노테이션인줄 알았는데 파이썬의 어노테이션은 따로 있고, 데코레이터(장식자)라고 한다.

    class Calculator:
    
        @staticmethod
        def add(num1, num2):
            return num1 + num2
    
        @staticmethod
        def subtract(num1, num2):
            return num1 - num2
    
    
    print(Calculator.add(3, 1))
    print(Calculator.subtract(3, 1))

    static method는 클래스의 속성을 사용하지 않는 메서드이다. 속성을 사용하지 않는다 해도 클래스의 여러 다른 메서드들의 로직에 관련되어 있기 때문에 클래스의 내부에 static method를 포함시켜 놓게 되는 것이다.

     

     

    2. class method

     

    인스턴스 메서드의 첫 파라메터로 인스턴스 자신을 나타내는 self를 선언하여야 했다.

    클래스 메서드 또한 첫 파라메터로 클래스 자신을 나타내는 cls를 선언해야 한다.

    이 cls인자를 통해 클래스의 속성 (class메서드,static메서드, 클래스 변수) 에 접근할 수 있다.

    (리플렉션과 비슷한 느낌인 것 같다)

     

    인스턴스.인스턴스메서드 처럼 self인자가 자동으로 넘어갔다.

    클래스명.클래스메서드 또한 cls인자가 자동으로 넘어갈 줄 알았는데 넘어가지 않았다.

    class MyClass:
        v = 0
    
        def class_method(cls):
            print(cls.v)
    
    
    MyClass.class_method()

    에러가 난다. 

     

    왜인지는 모르겠지만 @classmethod 데코레이터를 붙여주지 않으면 호출시 첫 인자에 클래스 인자가 자동으로 넘어가지 않았는데 인스턴스 메서드로 취급되는 것 같다.

     

    명시적으로 넘겨줬다.

    class MyClass:
        v = 0
    
        def class_method(cls):
            print(cls.v)
    
    
    MyClass.class_method(MyClass)

     

    @classmethod 데코레이터를 사용하면 cls인자가 자동으로 넘어간다. 가능하면 함수 선언시에는 데코레이터를 항상 사용해야겠다.

    class MyClass:
        v = 0
    
        @classmethod
        def class_method(cls):
            print(cls.v)
    
    
    MyClass.class_method()


    메서드 오버로딩

     

    파이썬은 오버로딩을 정식으로 지원하지 않는다. 구글링 해보니 오버로딩을 사용하는 법이 있긴한데 나중에 공부해야겠다.

     

    파이썬 코드작성 부분에서는 동일한 이름의 메서드를 여러개 선언해도 아무런 문제가 없다....

    class MyClass:
    
        def __init__(self):
            print('첫번째 생성자')
    
        def __init__(self):
            print('두번째 생성자')
    
    
    my = MyClass()

    동일한 이름의 메서드 선언시 가장 마지막에 선언했던 __init__메서드가 사용이 되는 것을 확인할 수 있다.

     

    바로 아래의 코드는 오버로딩 해놓은 것이다.

    자바처럼 오버로딩하여 꾸며놓을 수 있지만 실상 오버로딩이 아니다. 

    마지막에 선언된 것만 존재하는 것으로 취급이된다.

    class MyClass:
    
        def __init__(self):
            print('첫번째 생성자')
    
        def __init__(self, arg1):
            print('두번째 생성자')
    
        def __init__(self, arg1, arg2):
            print('세번째 생성자')
    
    
    my = MyClass()

     

    댓글

Designed by Tistory.