Python 기초

접근자와 설정자

private이 앞에 붙은 인스턴스 변수와 메소드는 클래스 내부에서만 접근될 수 있습니다. 클래스 외부에서는 접근이 불가능합니다.

하지만 외부에서 이들 변수 값이 필요한 경우에는 어떻게 하면 좋을까요?

이 경우에는 어떤 특수한 메소드가 있어 이들 메소드가 데이터 값을 읽어서 외부로 전달해주면 좋을 것입니다.

 

인스턴스 변수와 관련된 두 가지의 종류의 메소드가 있습니다.

하나는 인스턴스 변수값을 반환하는 접근자(getters)이고 또 하나는 인스턴스 변수값을 설정하는 설정자(setters)입니다.

이러한 메소드는 대개 get이나 set이 메소드 이름 앞에 붙여집니다. 예를 들면 getAge()는 접근자이고 setAge()는 설정자입니다.

인스턴스 변수는 접근자와 설정자 메소드만을 통하여 접근하여야 합니다.

 

아래 코드는 접근자와 설정자를 적용한 Student 클래스입니다.

class Student:
    def __init__(self, name=None, age=0):
        self.__name = name
        self.__age = age

    def getAge(self):
        return self.__age

    def getName(self):
        return self.__name

    def setAge(self, age):
        self.__age = age

    def setName(self, name):
        self.__name = name

obj = Student("Hong", 20)
print(obj.getName())

 

<실행 결과>

Hong

 

클래스 Student의 인스턴스 변수 name과 age는 모두 private으로 정의되었습니다. 이들 인스턴스 변수와 연결된 설정자와 접근자도 정의하였습니다.

에를 들면 인스턴스 변수 name에 대한 접근자는 getName()이고 설정자는 setName()입니다.

인스턴스 변수가 private으로 정의되어 있더라도 외부에서는 접근자나 설정자 메소드를 이용하면 불편없이 인스턴스 변수의 값을 변경하거나 읽을 수 있습니다.

 

접근자와 설정자의 사용 이유

앞의 예제를 처음 본 사람들은 의아하게 생각할 것입니다.

obj.__age = 20 과 같이 객체의 인스턴스 변수에 바로 접근하는 것이 시간을 절약할텐데 무엇 때문에 귀찮게 메소드를 통하여 변수에 간접적으로 접근하라 강요하는 것일까요?

여기에는 아주 중요한 이유가 있습니다. 접근자와 설정자를 사용하여서 인스턴스 변수를 간접적으로 접근하는 것은 다음과 같은 이점을 가져다줍니다.

  • 접근자와 설정자를 사용해야만 나중에 클래스를 업그레이드할 때 편합니다.
  • 접근자에서 매개 변수를 통하여 잘못된 값이 넘어오는 경우, 이를 사전에 차단할 수 있습니다.
  • 필요할 때마다 인스턴스 변수값을 계산하여 반환할 수 있습니다.
  • 접근자만을 제공하면 자동적으로 읽기만 가능한 인스턴스 변수를 만들 수 있습니다.

 

첫 번째 이유가 가장 중요합니다. 예를 들어서 앞의 Student 클래스를 봅시다.

만약 Studnet 클래스의 멤버를 외부에서 마음대로 사용하고 있었다면 개발자가 Student 클래스의 구조를 변경하기가 아주 어려울 것입니다.

예를 들어 나이를 나타내는 인스턴스 변수인 __age를 외부에서 마음대로 사용하고 있었다면 나이 대신에 생년월일을 사용하도록 변경하는 것이 불가능할 것입니다.

하지만 만약 설정자나 접근자를 통하여 사용하고 있었다면 개발자는 안심하고 __age 변수를 __birthday 변수로 변경할 수 있습니다.

개발자는 접근자와 설정자만 변경해주면 됩니다.

 

설정자는 변수의 값을 변경하려는 외부의 시도를 주의 깊게 검사할 수 있습니다. 예를 들어서 시간의 값을 25시로 변경하는 시도는 거부되어야 합니다.

또한 인간의 나이를 음수로 변경하려는 것도 거부되어야 합니다. 예를 들어서 학생을 나타내는 Student 클래스의 인스턴스 변수 __age를 변경한다고 가정합시다.

만약 메소드를 통하지 않고 인스턴스 변수를 직접 조작하게 한다면 다음과 같은 잘못된 값이 변수에 들어갈 수 있습니다.

obj.__age = -10    # 학생의 나이가 -10?

 

따라서 다음과 같이 설정자를 사용하는 편이 여러모로 안전합니다.

def setAge(self, age):
    if age < 0:
        self.__age = 0
    else:
        self.__age = age

 

도전 과제

1. 원을 클래스로 표시해봅시다. 원은 반지름(radius)을 가지고 있습니다. 원의 넓이와 둘레를 계산하는 메소드도 정의해봅시다. 설정자와 접근자 메소드도 작성합니다.

 

<실행 결과>

원의 반지름 =  10
원의 넓이 =  314.1592653589793
원의 둘레 =  62.83185307179586

 


2. 우리는 은행 계좌에 돈을 저금할 수 있고 인출할 수도 있습니다. 은행 계좌를 클래스로 모델링하여 봅시다.

은행 계좌는 현재 잔액(balance)만을 인스턴스 변수로 가집니다. 생성자와 인출 메소드 (withdraw()와 저축 메소드 deposit() 만을 가정합시다.

 

<실행 결과>

통장에서 100 원이 출금되었습니다.
통장에 10 원이 입금되었습니다.

 


3. 고양이를 클래스로 정의합니다. 고양이는 이름(name)과 나이(age)를 속성으로 가집니다.

이름이 Bongmin이고 나이가 20살인 고양이 객체를 생성합니다. 또 이름이 Seulgi이고 나이가 24살인 고양이 객체를 생성합니다.

고양이의 이름과 나이를 출력합니다. 접근자와 생성자를 사용해봅시다.

 

<실행 결과>

이름은 Bongmin 이고 나이는 20 입니다.
이름은 Seulgi 이고 나이는 24 입니다.

 


4. 상자를 나타내는 Box 클래스를 작성하여 봅시다. Box 클래스는 가로 길이, 세로 길이, 높이를 나타내는 인스턴스 변수를 가집니다.

Box 클래스를 정의하고 Box 객체를 하나 생성합니다. 상자의 가로, 세로, 높이를 100, 100, 100 으로 설정하여 봅시다.

상자에 대한 정보와 상자의 부피를 계산하여 출력하여 봅시다. 이번에는 접근자와 설정자를 모두 만들어서 사용합니다.

 

HINT
  • 가로 길이, 세로 길이, 높이를 나타내는 인스턴스 변수를 다음과 같은 이름의 변수로 구현합니다.
    • self.width : 상자의 가로 길이
    • self.length : 상자의 세로 길이
    • self.height : 상자의 높이
  • 객체를 생성하려면 생성자를 호출합니다. 생성된 객체의 인스턴스 변수에 접근하려면 멤버 연산자 .을 사용합니다.

 

<실행 결과>

(100, 100, 100)
상자의 부피는 1000000

 


5. 자동차를 나타내는 클래스를 정의하여 봅시다. 예를 들어, 자동차의 객체의 경우, 속성은 색상, 현재 속도, 현재 기어 등입니다.

자동차의 동작은 기아 변속하기, 가속하기, 감속하기 등을 들 수 있습니다. 이 중에서 아래와 같은 속성과 동작만을 추려서 구현해봅시다.

 

  • 멤버변수
    • color - "white"
    • speed - 100
    • gear - 6
  • 메서드
    • setColor()
    • setSpeed()
    • setGear()

 

HINT

추가적으로 __str__() 메소드를 Car 클래스에 추가하여 봅시다.  __str__()에서는 인스턴스 변수들의 값을 하나의 문자열로 만들어서 반환합니다.

클래스가 __str__() 을 가지고 있으면 다음과 같은 문장으로 객체의 현재 상태를 화면에 출력할 수 있습니다.

 

<실행 결과>

(white, 100, 6)