이 글의 작성자는 C/C++ 프로그래밍을 하던 사람이다. 이 글은 Python을 복습하며 작성하는 글이니, 부족한 부분이 있으면 얼마든지 피드백을 주시기 바란다.

 

대학 생활에는 끝이 있다

처음 대학교에 들어오게 되면 4년이란 시간은 참 길어 보인다. 하지만 시간은 흘러가기 마련이고, 학생은 한 학년씩 올라가기 마련이다. 그러다 보면 어느새 마지막 학기를 다니게 되고, 결국엔 졸업하게 된다.

그러면 우리가 만들었던 student 클래스로도 학년이 올라가는 것을 표현할 수 있을까? 물론 가능하다. 다음과 같이 코드를 작성하면 그만이기 때문이다.

s = student('mike', 23,'서울',2,'컴퓨터')
s.grade = s.grade + 1

단순하게 학년을 표현하는 속성의 원본 값으로부터 1을 증가시키면 끝이다. 이 동작 이후로 introduce 메서드를 호출한다면 2학년에서 3학년이 된 것을 확인할 수 있을 것이다. 그리고 여기서 또다시 1을 증가시키면 4학년이 될 것이다.

하지만 여기서 생각해야 할 점이 하나 있다. 학년은 끝없이 올라가지 않는다. 자신이 배우는 전공에 따라 학년의 최대 한도는 정해져 있을 것이다. 물론 졸업을 유예한다고 미루는 경우도 있긴 하겠지만, 결국엔 끝은 있다. 재학연한이라는 것이 있기 때문이다.

따라서 학년이라는 속성을 다룰 때, 무언가의 제한이 필요할 것 같다.

 

Getter / Setter

그래서 보통 클래스를 설계할 때, 객체의 값을 가져오거나/변경하는 메서드를 제공하라는 이야기를 한다. 이러한 메서드들의 이름을 보통은 게터(getter), 세터(setter)라고 한다. 속성을 가져오고(get), 속성을 세팅한다(set)라는 의미로 사용한다.

사실 게터/세터 메서드를 만드는 방법은 아주 간단하다. 메서드를 만들고, 해당 메서드에서 속성의 값을 리턴/수정하는게 끝이다.

그러면 게터/세터 메서드를 직접 만들어보자.
슬슬 클래스의 코드가 길어지므로, 이번에 추가한 코드를 제외하고는 생략하도록 한다.

class student(person):
...
    def getGrade(self):
        return self.grade

    def setGrade(self, _grade):
        self.grade = _grade

위와 같이 메서드를 작성하면 기초적인 게터/세터가 만들어진다.

하지만 지금 우리가 하고자 하는 일은 여기서 끝이 아니다. 학년의 속성 값을 바꿀 때, 바꾸고자 하는 학년 값이 유효한 값인지 확인해야 한다. 따라서 세터 메서드를 조금 수정해야 한다.

세터 메서드로 들어온 값이 1보다 작으면 1로, 6보다 크면 6이 되도록 해 보자.

class student(person):
...
    def setGrade(self, _grade):
        if _grade < 1:
            _grade = 1
        elif _grade > 6 :
            _grade = 6
        self.grade = _grade

이제 게터와 세터가 완성되었다. 다음 코드를 실행하면 게터/세터가 정상적으로 동작하는 것을 확인할 수 있을 것이다.

s = student('mike', 23,'서울',2,'컴퓨터')
s.setGrade(s.getGrade()+22)
print(s.getGrade())

위의 코드를 실행하면 6을 볼 수 있을 것이다.

 

프로퍼티(property)

이제 게터와 세터를 만들었으니 모든 것이 잘 돌아갈 거라는 생각을 할 수 있겠지만, 아직 문제는 남아있다. 여전히 grade 속성에 직접 접근하여 수정할 수 있다는 점이다. 이 코드를 작성한 지 얼마 되지 않은 지금이라면 ‘게터와 세터를 사용해야지!’ 라는 생각이 머릿 속에 남아 있겠지만, 사람은 망각의 동물이다. 시간이 지나서 이 코드를 다시 쓰다 보면, 자연스럽게 게터/세터를 쓰지 않고 속성에 접근하는 자신의 모습을 발견할 지도 모른다. 그리고 끝없이 올라가는 학년 속성값을 발견하고 나서야 ‘아차, 그랬었지!’ 라는 생각을 하게 될 것이다.

그러면 직접적으로 grade 속성에 접근하지 않고 수정할 수 있게 하려면 어떻게 해야 할까? 그래서 파이썬에서 제공하는 것이 프로퍼티(property)다.

프로퍼티가 어떻게 생긴 녀석일까? 다음 코드를 통해 확인해보자.

class student(person):
    def __init__(self, _name, _age, _university, _grade, _major):
        self.hidden_grade = _grade
...
    def getGrade(self):
        return self.hidden_grade

    def setGrade(self, _grade):
        if _grade < 1:
            _grade = 1
        elif _grade > 6 :
            _grade = 6
        self.hidden_grade = _grade

    grade = property(getGrade, setGrade)

위의 코드는 뭔가 포함된 것이 많아 보인다. 무엇이 바뀌었을까?

  • grade의 속성 이름이 hidden_grade가 되었다

속성 이름이 grade에서 hidden_grade가 되었다. 자연스럽게 grade 속성을 사용하던 모든 메서드들이 hidden_grade 속성을 사용하게 되었다.

  • 클래스 속성에 grade가 property를 받아들인다

원래 grade는 객체 속성이었으나, 클래스 속성으로 변경되었다. 그리고 눈에 띄는 것이 하나 있다. property라는 것이 생겼다. 그리고 property를 호출할 때, grade 속성의 게터/세터인 getGradesetGrade를 인자로 전달하는 것을 볼 수 있다. 그리고 호출된 결과가 grade가 되는 것을 확인할 수 있다.

이렇게 하면 어떻게 되느냐? student 객체를 담고 있는 변수 s가 있다고 치면, 이제 다음 코드들은 다음 동작을 수행하게 된다.

  • s.grade를 사용하면, getGrade를 호출하게 된다.
  • s.grade = value를 사용하면, setGrade를 호출하게 된다. 그리고 그 메서드의 인자는 value가 된다.

정말로 그렇게 되는 걸까? 한 번 게터/세터 메서드에 자기 자신이 호출되면 호출되었다는 텍스트를 출력하도록 수정해보고, 테스트를 해 보자.

class student(person):
...
    def getGrade(self):
        print('게터 호출!')
        return self.hidden_grade

    def setGrade(self, _grade):
        print('세터 호출!')
        if _grade < 1:
            _grade = 1
        elif _grade > 6 :
            _grade = 6
        self.hidden_grade = _grade

    grade = property(getGrade, setGrade)

s = student('mike', 23,'서울',2,'컴퓨터')
s.grade = s.grade + 22
print(s.grade)

그러면 다음과 같은 결과를 확인할 수 있을 것이다.

게터 호출!
세터 호출!
게터 호출!
6

s.grade의 값은 2번 가져오므로 게터 호출!은 두 번 호출되고, s.grade의 값에 대입이 되는 건 한 번이므로, 세터 호출!은 한 번 호출된다. 그리고 학년 속성의 값도 정상적으로 6이 되는 것을 확인할 수 있다.

이제 오랜 시간이 지나 게터/세터를 써야 한다는 사실을 까먹어도, 변수에 값을 가져오거나 더하듯이 자연스럽게 게터/세터를 사용할 수 있게 되었다! 이런 식으로 사람이 저지를 수 있는 실수를 줄일 수 있다면 최대한 줄일 수 있는 방향으로 코드를 작성하는 편이 좋다.

 

여기서 끝낼 수도 있지만, property를 사용하는 방법은 또 있다. 무려 데코레이터를 사용할 수도 있다! 그러면 데코레이터를 사용해서 property를 사용하려면 어떻게 해야 할까? 다음과 같이 작성하면 된다.

class student(person):
...
    @property
    def grade(self):
        return self.hidden_grade

    @grade.setter
    def grade(self, _grade):
        if _grade < 1:
            _grade = 1
        elif _grade > 6 :
            _grade = 6
        self.hidden_grade = _grade

데코레이터를 사용해서 프로퍼티를 정의하려면, 게터를 가장 먼저 선언해야 한다. 그리고 세터를 선언할 때, 속성의 이름.setter를 데코레이터로 하여 속성의 이름으로 메서드를 다시 정의해야 한다.

왜 이렇게 해야 하는가? 동작을 차근차근 살펴보자면, 먼저 이 사실을 알아야 한다. property는 클래스다! 만약 여러분이 IDE를 사용하고 있다면, property를 선언한 곳을 따라가보라. 그러면 builtin.py에서 class property를 확인할 수 있을 것이다!

데코레이터에 대해 이야기 할 때, @를 붙여서 적용한 코드는 이 코드와 똑같다고 이야기 한 적이 있다.

@outer
def plus(a,b):
    return a+b

#위의 코드와 밑의 코드는 완전히 동일한 코드이다.
def plus(a,b):
    return a+b
plus = outer(plus)

그렇다면 가장 처음에 선언되고 @property가 붙은 grade 메서드는 이렇게 표현할 수 있을 것이다.

def grade(self):
    return self.hidden_grade
grade = property(grade)

그렇다면 grade는 무엇이 되는가? grade를 인자로 받는 property 객체가 생성되는 것이다. 그렇다. 사실 grade는 property 객체였던 것이다!

그리고 property 클래스는 setter라는 메서드를 가지고 있다. 그래서 다음과 같은 코드가 나온 것이다.

@grade.setter
def grade(self, _grade):
...

=>

def grade(self, _grade):
...
grade = grade.setter(grade)

이 쯤 되니 문법에 맞지 않는 표현이 되어 버렸지만, 왼쪽에 있는 grade와 오른쪽에 있는 grade는 다른 거라고 이해하고 넘어가자. 오른쪽에 있는 grade는 게터 메서드로 인해 생성된 property 객체라고 이해하고 넘어가면 되겠다.

@grade.setter 데코레이터로 또 다시 property 객체가 튀어나왔고, 이 property 객체는 자기 자신이 단순히 값만 받아오는가, 아니면 새롭게 값이 대입되는가에 따라 전달받은 게터/세터를 호출하게 된다.

이 쯤 되니 데코레이터를 쓰는 곳이 참 많다는 생각이 들 것이다.

 

private 맹글링

property를 사용하여 grade를 자연스럽게 게터/세터 메서드를 이용하여 값을 변경할 수 있게 되었지만, 또 하나의 문제점이 남아있다. IDE를 사용한다면 student 메서드에 들어 있는 속성들을 모두 확인할 수 있고, 결국에는 hidden_grade라는 속성을 찾을 수 있다는 점이다. 이는 student 클래스를 사용하는 사람이 직접 hidden_grade속성에 접근하여 사용할 수 있다는 뜻이고, 우리가 의도한 바 대로 grade 속성이 1에서 6까지의 값 만을 가지지 못하게 될 수도 있다는 것을 의미한다.

비록 파이썬에는 C++이나 Java처럼 클래스의 속성이 private 접근자를 가지게 할 수는 없지만, private과 비슷한 효과를 낼 수 있게 하는 방법이 있다. 바로 private 맹글링이다.

어떻게 하느냐? 숨기고자 하는 속성에 언더바(_) 두 개를 붙이면 된다.

self.__grade = _grade

hideen_grade를 모두 __grade로 바꿔본 다음, 다음과 같이 __grade 속성을 직접 가져와 보자. 그러면 오류가 발생하는 것을 확인할 수 있을 것이다.

print(s.__grade)
AttributeError: 'student' object has no attribute '__grade'

student 클래스 안에서는 __grade를 잘만 썼으면서, 밖에서는 __grade를 쓰려고 하니 없는 속성이라는 오류가 발생하게 된다. 이렇게만 보면 마치 진짜로 grade 속성이 private 접근자를 가진 것 같이 보인다.

하지만 맹글링(mangling)이라는 것의 의미를 알게 된다면, 금새 어떻게 된 일인지 알 수 있게 된다. mangle이라는 단어는 훼손하다라는 의미를 지니고 있다. 즉, 원래 가지고 있던 이름을 훼손하여, 다른 이름으로 바꿔치기 하고 있다는 것이다.

IDE 상으로는 이 속성 이름이 보이지 않겠지만, 다음과 같이 입력한다면 직접적으로 속성 값에 접근할 수 있을 것이다. __grade 앞에 _클래스이름을 붙여주면 된다.

print(s._student__grade)

그러면 현재 __grade 속성값이 출력될 것이고, 직접적으로 접근하여 수정할 수도 있을 것이다. 하지만 이런 식으로 private 맹글링을 달아둔다면, 이러한 의도를 전달할 수 있게 된다.

“이 값에 직접적으로 접근하지 마세요. 직접 접근해서 발생하는 부작용은 본인이 감당해야 합니다!”

이와 관련된 객체지향 프로그래밍의 개념을 알아보고 싶다면, 캡슐화(encapsulation)과 데이터 은닉(data hiding)에 대해 검색해보자. 나중에 다른 언어를 배울 때 개념을 이해하는 데 도움이 될 것이다.

 

Categories:

Updated:

Comments