지금까지 코드 추상화와 관련하여 함수와 모듈 두 가지 프로그래밍 기본 요소를 다뤘다. 여기서는 코드 추상화와 관련된 세번 째 프로그래밍 기본 요소인 클래스를 소개한다.
안내: python-textbook.readthedocs.io의 Classes 내용을 요약 및 수정한 내용입니다.
클래스(class)는 서로 관련된 데이터와
해당 데이터를 다루는 함수들을 하나로 묶어 추상화하는 방법이다.
클래스는 기본적으로 문자열, 정수 또는 리스트와 같은 자료형의 일종이다.
"python"
, 17
, [1, 2, 3]
을 각각
문자열, 정수, 리스트 자료형의 값이라 부르듯이
특정 클래스의 값에 해당하는 대상을 정의할 수 있다.
그런 대상을 해당 클래스의 인스턴스(instance)라 부른다.
사실 파이썬에서 다루는 모든 대상은 특정 클래스의 인스턴스이다.
예를 들어, "python"
, 17
, [1, 2, 3]
각각은
str
, int
, list
클래스의 인스턴스들이다.
이와 같이, 특정 클래스의 인스턴스를 일반적으로 객체(object)라고 부른다.
심지어 클래스 자체도 type
클래스의 인스턴스이다.
특정 객체의 클래스, 즉, 자료형을 확인하려면 type()
함수를 활용한다.
주의: 일부 다른 언어에서는 상황이 다르다. 예를 들어, 자바 언어의 경우 정수, 부동소수점 등은 클래스와 아무 상관 없다.
type(str)
type(int)
type(list)
사용할 객체를 디자인할 때 어떤 속성의 데이터들을 사용할 것인지, 그리고 그 값들을 어떻게 다룰 것인지 결정해야 한다. 속성을 저장하는 변수를 속성 변수 또는 그냥 속성, 속성을 다루는 함수를 메서드라고 부른다.
예를 들어 문자열 "python"
은 어떤 형식으로든 python이라는 단어를 속성으로 갖고 있어야 하며,
split
, strip
, find
등 문자열 메서드에 의해 이용될 수 있다.
반면에 [1, 2, 3]
은 어떤 형식으로든 1, 2, 3을 속성으로 갖고 있어야 하며,
append
, pop
, sort
등 리스트 메서드에 의해 이용될 수 있다.
다음은 개인정보를 저장하는 간단한 사용자 정의 클래스를 소개한다.
import datetime # date 클래스 사용 목적
class Person:
def __init__(self, name, surname, birthdate, address, telephone, email):
self.name = name
self.surname = surname
self.birthdate = birthdate
self.address = address
self.telephone = telephone
self.email = email
def age(self): # 나이 계산 함수
today = datetime.date.today()
age = today.year - self.birthdate.year
if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day):
age -= 1
return age
class
지정자와 클래스 이름 및 콜론으로 클래스 정의를 시작한다.
클래스의 본문은 함수의 경우처럼 들여 쓴다.
클래스 이름에 소괄호를 사용하여 상속할 부모 클래스들을 함수의 인자들처럼 나열하는
방식으로 명시할 수도 있다.
class 클래스이름(부모클래스1, ..., 부모클래스n):
클래스본문
Person
클래스는 부모 클래스가 없으며, 부모 클래스가 없으면 괄호를 생략할 수 있다.
부모 클래스와 상속 개념은 이후에 자세히 다룬다.
Person
클래스 내부에는 __init__
와 age
두 함수가 정의되어 있다.
이 중에 __init__
함수는 특별한 메서드이며,
이와같이 양끝이 밑줄 두 개로 감싸인 메서드를 매직 메서드(magic method)라 부른다.
반면에 age
함수는 사용자 정의 메서드(user-defined method)이다.
모든 클래스는 __init__
메서드 이외에 다수의 매직 메서드를 기본적으로 포함한다.
하지만 클래스를 선언할 때 명시되지 않으면 기본으로 정의된 기능을 수행하며,
이에 대해서는
코드 추상화: 클래스와 객체 2부에서
다룰 것이다.
마치 함수를 호출하듯이 클래스 객체를 호출하면 해당 클래스의 새 인스턴스가 생성된다.
예를 들어, 아래와 같이 Jane Doe라는 사람의 개인 정보를 담은 객체를 생성하여
jDoe
변수에 할당할수 있다.
jDoe = Person(
"Jane",
"Doe",
datetime.date(1992, 3, 12), # 년, 월, 일
"No. 12 Short Street, Greenville",
"555 456 0987",
"jane.doe@example.com"
)
클래스이름(인자1, ..., 인자n)
형식을 이용하여 클래스를 호출하면
해당 클래스의 인스턴스가 생성된 후 바로
해당 클래스의 __init__
함수가 지정된 인자들과 함께 호출된다.
클래스의 객체를 선언할 때 사용되는 인자는 따라서 __init__
함수 선언에서 사용된
매개변수에 해당하는 인자이어야 한다.
__init__
함수는 생성된 객체의 속성을 초기 설정하는 일을 수행한다.
이런 의미에서 초기 설정 메서드 또는 초기 설정자 라고 부를 수 있다.
주의:
__init__
함수를 생성자(constructor)라고도 부르지만 기술적으로 정확하지 않은 표현이다.
예를 들어, jDoe
객체가 생성되자 마자
__init__
함수가 아래와 같이 호출된다.
__init__(
"Jane",
"Doe",
datetime.date(1992, 3, 12), # 년, 월, 일
"No. 12 Short Street, Greenville",
"555 456 0987",
"jane.doe@example.com"
)
즉, Jane Doe의 개인정보를 jDoe
객체내부에 해당 속성에 저장한다.
self
매개변수¶self
의 역할¶__init__
와 age
메서드 모두 첫째 매개변수로 self
를 사용한다.
하지만 self
에 해당하는 인자를 직접 요구하지는 않는다.
예를 들어, jDoe
객체를 생성할 때 자동으로 호출되는
__init__
메서드는 앞서 보았듯이 self
를 제외한 인자들을 이용하여 호출된다.
이유는, __init__
함수가 호출될 때 이미 객체가 생성되어 있으며,
그 객체가 자동으로 첫째 인자로 사용되기 때문이다.
age
메서드는 따라서 호출 될 때 아무런 인자도 사용하지 않는다.
self
에 대한 인자로 역시 이미 생성된 객체가 자동으로 입력되기 때문이다.
예를 들어, __init__
함수가 실제로 호출되는 과정은 다음과 같다.
__init__(
jDoe
"Jane",
"Doe",
datetime.date(1992, 3, 12), # 년, 월, 일
"No. 12 Short Street, Greenville",
"555 456 0987",
"jane.doe@example.com"
)
__init__
함수의 매개변수 이름과 함수 본문에서 선언되는 속성 변수 이름이
동일할 필요는 없지만 역시 관례적으로 그렇게 한다.
다만, 속성 변수는 항상 다음과 같이 self
와 점(.
) 연산자로
구분되는 형식으로 사용되어야 한다.
self.속성 변수
이 방식은 __init__
메서드에서 뿐만 아니라 모든 메서드 내부에서 선언 또는 사용되는
속성 변수에 대해서 적용된다.
주의:
다른 많은 언어에서는 self
와 같은 매개변수를 사용하지 않는다.
따라서 해당 객체를 확인하거나 이용하려면 특별한 지정자를 활용해야 한다.
매개변수 이름을 self
가 아닌 다른 변수를 사용해도 되지만,
관례적으로 self
를 사용한다.
birthdate
매개변수에 의해 전달되는 값은 datetime
모듈에서 정의된
date
클래스의 인스턴스이다.
즉, 모든 객체는 변수 할당, 함수 호출, 리턴값 등에 사용될 수 있는
제1종 객체이다.
생성된 객체는 해당 객체의 속성과 메서드를 통해 활용된다. 속성을 확인하고 메서드를 호출하는 방식은 다음과 같다.
객체이름.속성 변수
또는
객체이름.메서드(인자1, ..., 인자k)
예를 들어 jDoe
의 이름, 이메일 주소에 해당하는 속성을 확인하려면 다음과 같이 실행한다.
print(jDoe.name)
print(jDoe.email)
반면에 나이를 확인하려면 age
메서드를 아래와 같이 호출한다.
print(jDoe.age())
지금까지의 설명을 PythonTutor: Person 클래스 1에서 코드를 실행하면서 확인할 수 있다.
다음 변수들의 역할과 활동영역(scope)을 설명하라.
Person
jDoe
surname
self
age
(함수이름)age
(age
함수 내부에서 선언된 변수)self.email
jDoe.email
self.age()
jDoe.age()
Person
: 클래스 이름. 전역변수.jDoe
: Person
클래스의 인스턴스 이름. 전역변수.surname
: __init__
함수의 매개변수.
__init__
함수 본체에서만 사용되는 지역변수.self
: 모든 메서드의 첫째 매개변수.
메서드가 호출될 경우 해당 객체로 대체됨. 지역변수.age
(함수이름): Person
클래스의 메서드 이름.
Person
클래스 내부에서만 사용되는 지역변수.age
(age
함수 내부에서 선언된 변수)
age
메서드 내부에서만 사용되는 지역변수.self.email
: 엄밀한 의미의 변수 아님.
self
가 가리키는 객체의 내부에서 선언된 속성 변수 email
을 가리키는 이름 역할 수행.jDoe.email
: 이하 동일self.age()
: 이하 동일jDoe.age()
: 이하 동일클래스의 속성은 인스턴스 속성과 클래스 속성 두 종류로 나뉜다. 먼저 인스턴스 속성을 설명하고 이후에 클래스 속성을 다룬다.
self.name
등 메서드 내부에서 선언되는 변수를 인스턴스 속성 변수 또는 인스턴스 속성이라 부른다.
예를 들어, 아래 age
메서드를 아래와 같이 수정하면 age
메서드가
호출될 때 _age
라는 인스턴스 속성이 선언된다.
import datetime
class Person:
def __init__(self, name, surname, birthdate, address, telephone, email):
self.name = name
self.surname = surname
self.birthdate = birthdate
self.address = address
self.telephone = telephone
self.email = email
def age(self):
if hasattr(self, "_age"): # _age 속성의 존재 여부 확인
return self._age
today = datetime.date.today()
age = today.year - self.birthdate.year
if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day):
age -= 1
self._age = age
return age
jDoe = Person(
"Jane",
"Doe",
datetime.date(1992, 3, 12), # 년, 월, 일
"No. 12 Short Street, Greenville",
"555 456 0987",
"jane.doe@example.com"
)
dScotty = Person(
"Dana",
"Scotty",
datetime.date(1970, 5, 22), # 년, 월, 일
"No. 2 Long Street, Bluecity",
"444 654 0135",
"dana.scotty@example.com"
)
이미 생성된 객체에서 독립적으로 새로운 속성과 새로운 메서드를 추가할 수 있다.
주의: C++ 등 일부 언어에서는 클래스를 정의할 때, 미리 객체 속성 목록을 지정해야 하며, 나중에 객체에 새 속성을 추가하지 못할 수 있다.
예를 들어, Person
클래스에는 애완동물 관련 속성을 저장하는 pets
인스턴스 변수가 없다.
따라서 jDoe
역시 애완동물 속성을 갖지 못한다.
jDoe.pets
하지만 jDoe
스스로 애완동물 속성을 추가할 수 있다.
jDoe.pets = ['고양이', '고양이', '강아지']
jDoe.pets
하지만 애완동물 속성을 jDoe만 갖는다.
예를 들어, Person
클래스의 다른 인스턴스는 여전히 애완동물 속성을 갖지 않는다.
dScotty.pets
밑줄로 시작하는 속성 또는 메서드의 이름은 일반적으로 클래스 외부로 알려지면 안되는 것들을 가리킨다.
또한 _age
속성의 경우, 먼저 age
메서드가 최소 한 번 실행되어야 선언되기 때문에
임의로 사용하다보면 오류가 발생할 수 있다.
jDoe._age
age
함수를 먼저 호출해야 한다.
jDoe.age()
이제 _age
에 저장된 속성을 확인할 수 있다.
jDoe._age
따라서 jDoe
의 나이를 확인하기 위해서는 age
메서드를 호출하도록 하는 게 좋다.
즉, _age
인스턴스 변수는 외부에 노출하지 않고, 대신에 age
메서드를 사용하도록 권장해야 한다.
객체에서 사용되는 속성들은 초기 설정 과정에서 모두 선언하는 것이 좋다.
그렇지 않으면 앞서 pets
와 _age
속성의 경우에서 보았듯이 오류가 발생할 확률이 높아진다.
객체가 생성되면 바로 __init__
메서드가 실행되기 때문에,
생성되는 객체와 관련된 모든 속성을
__init__
메서드 실행과정에서
초기 설정되도록 하는 것이 좋다.
예를 들어, 차라리 pets
와 _age
의 속성값을 아래와 같이 일단 비워두더라도
__init__
메서드 본문에서 선언하는 것이 좋다.
import datetime
class Person:
def __init__(self, name, surname, birthdate, address, telephone, email):
self.name = name
self.surname = surname
self.birthdate = birthdate
self.address = address
self.telephone = telephone
self.email = email
self.pets = None # 비워두기
self._age = None # 비워두기
def age(self):
if getattr(self, "_age") != None: # _age 속성 업데이이트 여부 확인
return self._age
today = datetime.date.today()
age = today.year - self.birthdate.year
if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day):
age -= 1
self._age = age
return age
jDoe = Person(
"Jane",
"Doe",
datetime.date(1992, 3, 12), # 년, 월, 일
"No. 12 Short Street, Greenville",
"555 456 0987",
"jane.doe@example.com"
)
dScotty = Person(
"Dana",
"Scotty",
datetime.date(1970, 5, 22), # 년, 월, 일
"No. 2 Long Street, Bluecity",
"444 654 0135",
"dana.scotty@example.com"
)
이제 jDoe
에서만 왜완동물을 추가해도 dScotty
에서 애완동물을 확인할 때 오류가
발생하지 않는다.
jDoe.pets = ['고양이', '고양이', '강아지']
dScotty.pets # None 값이라 출력되지 않는다.
_age
역시 age
메서드를 실행하지 않아도 오류를 발생시키지 않는다.
물론 아래와 같은 식으로 _age
속성을 확인하는 것은 피해야 한다.
dScotty._age
getattr
, setattr
, hasattr
¶파이썬은 인스턴스의 속성을 확인하거나 지정하는 세 개의 내장 함수를 지원한다.
getattr
함수¶특정 객체의 특정 속성값을 확인해줄 때 사용하는 함수이다.
예를 들어, jDoe
의 pets
속성값을 다음과 같이 확인한다.
getattr(jDoe, "pets")
물론 아래와 같이 하는 게 보다 편하다.
jDoe.pets
하지만 특정 속성이 존재하지 않을 경우를 대비해야 할 때 getattr
이 유용하다.
예를 들어, 취미 속성인 hobbies
가 선언되어 있지 않을 경우
아래와 같이 확인할 수 있다.
getattr(jDoe, "hobbies", "해당사항 없음")
또한 아래와 같이 여러 개의 속성에 대해 반복문 등을 작성할 때는 반드시 getattr
을 사용해야 한다.
for attr in ["pets", "_age"]:
print(getattr(jDoe, attr))
심지어 다음과 같이 활용할 수 있다.
for attr in ["pets", "_age", "hobbies"]:
print(getattr(jDoe, attr, "해당사항 없음."))
하지만 아래와 같이 작성하는 것은 불가능하다.
이유는 변수를 객체이름.변수
형식으로 사용할 수 없기 때문이다.
for attr in ["pets", "_age"]:
print(jDoe.attr)
setattr
함수¶setattr
함수는 객체의 속성을 지정할 때 사용한다.
예를 들어, dScotty
의 애완동물 속성을 지정하려면 다음과 같이 한다.
dScotty.pets
setattr(dScotty, 'pets', ['고양이', '강아지'])
dScotty.pets
물론 아래와 같이 직접 지정할 수 있다.
dScotty.pets = ['흰고양이', '강아지', '검은고양이']
dScotty.pets
새로운 속성을 지정할 수도 있다.
예를 들어, 원래 없었던 취미 속성 hobbies
를 아래와 같이 추가할 수도 있다.
setattr(dScotty, 'hobbies', ['테니스', '배드민턴'])
dScotty.hobbies
setattr
또한 for
반복문과 함께 사용될 수 있다.
mydict = {'a': 10, 'b': 20, 'c':30}
for attr in ['a', 'b', 'c']:
setattr(dScotty, attr, mydict[attr])
dScotty
에 추가된 세 개의 속성 a
, b
, c
의 값을 다음과 같이 확인할 수 있다.
주의: f-문자열과 r-문자열의 혼합사용에 주의하라.
for attr in ['a', 'b', 'c']:
print(fr"'{attr}' : {getattr(dScotty, attr, mydict[attr])}")
hasattr
함수¶age
메서드의 정의에서 보았듯이 hasattr
함수는 특정 객체가 특정 속성을 가지고 있는지 여부를 판단한다.
예를 들어, dScotty
객체는 a
속성을 갖지만 d
속성은 갖지 않는다.
hasattr(dScotty, 'a')
hasattr(dScotty, 'd')
Person
클래스를 다음 조건을 만족하도록 수정하라.
age
메서드를 호출했을 때, 마지막으로 나이 계산한 후부터 하루 이상 지났을 경우 새롭게 나이를 계산한다.import datetime
class Person:
def __init__(self, name, surname, birthdate, address, telephone, email):
self.name = name
self.surname = surname
self.birthdate = birthdate
self.address = address
self.telephone = telephone
self.email = email
self._age = None
self._age_last_recalculated = None
self._recalculate_age() # 나이 확인
def _recalculate_age(self): # 나이 계산 함수
today = datetime.date.today()
age = today.year - self.birthdate.year
if today < datetime.date(today.year, self.birthdate.month, self.birthdate.day):
age -= 1
self._age = age
self._age_last_recalculated = today # 마지막 나이 확인 날짜 기억하기
def age(self):
if (datetime.date.today() > self._age_last_recalculated):
self._recalculate_age()
return self._age
함수를 정의한다고해서 바로 함수가 실행되는 것은 아니듯이, 클래스 또한 선언된 후 아무 것도 실행하지 않는다. 다만 클래스가 선언되었다는 것을 파이썬이 알게되는 것 뿐이다. 따라서 클래스 내부에서 속성이나 메서드를 선언할 때 다른 속성이나 메서드를 활용할 수 있다.
예를 들어, __init__
메서드 정의에 self._recalculate
메서드를 활용해도
아무런 문제가 없다.
왜냐하면, Person
클래스의 선언이 완료되었을 때는 이미
_recalculate
메서드가 정의되어 있을 것이기 때문이다.
물론 그렇지 않다면 실행중에 오류가 발생할 것이다.
그리고 __init__
메서드가 호출될 때는 이미 Person
클래스의 인스턴스가
이미 생성되어 있어야 한다. 따라서
이 객체가 self._recalculate
의 self
에 자동으로 삽입되어
초기 설정이 문제 없이 진행된다.
PythonTutor: Person 클래스 2에서 위 설명을 실행하면서 확인할 수 있다.
__init__
메서드에 의해 Person
클래스의 인스턴스의 속성을 초기 설정할 때 선언되는
속성 변수는 생성된 인스턴스 고유의 속성을 다룬다.
반면에 클래스 속성은 기본적으로 특정 클래스와 밀접하게 관련된 상수의 역할을 수행하는 값을 정의하는 데에 사용되며,
해당 클래스의 모든 인스턴스에서 공유된다.
클래스 속성은 많은 면에서 인스턴스 속성과 유사한 기능을 갖지만
주의해야 할 사항이 있다.
클래스 속성은 메서드 밖에서 선언되며,
self
를 사용하지 않고 일반적인 변수 선언 방식을 사용한다.
들여쓰기는 메서드와 동일한 수준으로 사용된다.
예를 들어, Person
클래스에 호칭(title)으로 사용될 항목들을 TITLES
라는 클래스 변수에
선언하려면 다음과 같이 한다.
class Person:
TITLES = ('Dr', 'Mr', 'Mrs', 'Ms')
def __init__(self, title, name, surname):
if title not in self.TITLES: # 메서드 내부에서 클래스 변수 사용
raise ValueError("%s is not a valid title." % title)
self.title = title
self.name = name
self.surname = surname
메서드 내부에서 클래스 속성을 사용하려면 인스턴스 속성의 경우처럼
self
와 함께 사용된다.
여기서 self
는 기본적으로 클래스 자신, 즉 위의 경우에는 Person
클래스를 가리킨다.
하지만 해당 클래스의 모든 인스턴스도 클래스 속성을 공유한다.
따라서 경우에 따라서는 self.TITLES
의 self
가 해당 클래스의 인스턴스가 될 수도 있다.
예를 들어, 다음 jDoe
객체 역시 TITLES
속성을 공유한다.
jDoe = Person("Dr", "Jane", "Doe")
jDoe.TITLES
그런데 클래스 속성은 인스턴스 없이도 확인이 가능하다. 클래스 속성이라 불리는 이유가 바로 여기에 있다.
Person.TITLES
클래스는 인스턴스 속성에 액세스 할 수 없다. 이유는 인스턴스 속성은 인스턴스를 생성할 때만 의미를 갖기 때문이다.
Person.surname
클래스와 객체 모두 클래스 속성을 업데이트 할 수 있다. 하지만 클래스 속성을 업데이트 하면 경우에 따라 특정 인스턴스에 혼란을 야기할 수 있다. 따라서 클래스 속성은 변하지 않는 상수처럼 취급해야 한다.
아래 코드를 PythonTutor: 클래스 속성 업데이트에서 실행하면서 클래스 변수를 업데이트할 때 발생할 수 있는 문제를 확인해볼 수 있다.
class Person:
TITLES = ('Dr', 'Mr', 'Mrs', 'Ms') # 튜플 사용
def __init__(self, title, name, surname):
if title not in self.TITLES: # 메서드 내부에서 클래스 변수 사용
raise ValueError("%s is not a valid title." % title)
self.title = title
self.name = name
self.surname = surname
jDoe = Person('Dr', "Jane", "Doe")
print(f"Person:\t {Person.TITLES}")
print(f"jDoe:\t {jDoe.TITLES}\n")
Person.TITLES = ('Prof', 'Dr', 'Mr', 'Mrs', 'Ms') # 모든 인스턴스에 영향끼침
print(f"Person:\t {Person.TITLES}")
print(f"jDoe:\t {jDoe.TITLES}\n")
jDoe.TITLES = ('Dr', 'Mr', 'Ms') # 이 경우 jDoe 만의 TITLES 속성 생성됨.
print(f"Person:\t {Person.TITLES}") # 따라서 클래스의 TITLES와 별개 속성을 가짐
print(f"jDoe:\t {jDoe.TITLES}\n")
Person.TITLES = ('Dr', 'Mr', 'Mrs', 'Ms') # 이제 클래스 속성을 업데이트 하더라도
print(f"Person:\t {Person.TITLES}") # jDoe의 TITLES 속성에 영향 없음.
print(f"jDoe:\t {jDoe.TITLES}")
클래스 속성을 사용하여 기본 속성 값을 제공 할 수도 있습니다.
class Person:
married = False # 결혼 여부 확인. 기본값은 미혼
def mark_as_married(self): # 기혼자 처리
self.married = True
클래스 속성을 업데이트할 때 주의해야할 사항에서 설명하였듯이 클래스 속성 변수와 동일한 이름의 속성 변수를 인스턴스에서 새로 설정하면 클래스 속성이 인스턴스 속성으로 전환된다. 그리고 동일한 이름의 속성 변수가 존재할 때 객체는 인스턴스 속성을 우선적으로 사용한다.
위 코드에서 married
클래스 변수는 결혼여부를 저장하여 기본값으로 False
,
즉, 미혼으로 지정해 놓았다.
그런데 아래와 같이 인스턴스를 생성할 때마다
각각 독립적으로 결혼여부를 지정할 수 있다.
주의: __init__
메서드가 선언되어 있지 않다.
즉, 인스턴스를 생성할 때 초기 설정을 진행하지 않는다.
jDoe = Person()
jDoe.mark_as_married()
print(jDoe.married)
클래스 변수로서는 속성값이 변하지 않았다.
Person.married
따라서 새로운 인스턴스를 생성하도 기본값은 미혼이다.
dScotty = Person()
print(dScotty.married)
클래스 속성값이 리스트, 사전 처럼 수정 가능한 자료형(mutable data type)의 경우 인스턴스 속성 변경이 클래스 속성 변경에 영향을 끼칠 수 있다.
class Person:
pets = []
def add_pet(self, pet):
self.pets.append(pet)
jane = Person()
bob = Person()
jane.add_pet("cat") # jane의 고양이 애완동물 추가
# pets의 값이 리스트임. 기존 리스트를 수정함.
print(f"jane:\t{jane.pets}")
print(f"bob:\t{bob.pets}") # 그런데 bob까지 영향 받음.
위 코드를 PythonTutor: 클래스 속성 주의점 2에서 실행하며 문제점을 확인할 수 있다.
클래스 속성을 사용하는 대신에 __init__
메서드를 이용하여
인스턴스를 생성할 때마다 기본값으로 초기 설정하는 방식을 사용해야 한다.
이유는 pets
와 같은 속성은 속성값을 공유하는 목적이 아니라
모든 인스턴스에서 사용될 속성 변수를 미리 지정하는 역할을 수행하기 때문이다.
class Person:
def __init__(self):
self.pets = []
def add_pet(self, pet):
self.pets.append(pet)
jane = Person()
bob = Person()
jane.add_pet("cat")
print(f"jane:\t{jane.pets}")
print(f"bob:\t{bob.pets}") # 서로 영향 주지 않음.
클래스 변수를 메서드 내에서 사용하려면 self
와 함께 사용해야 한다.
하지만 메서드 매개변수의 인자로 사용할 때는 self
가 필요없다.
주의: 메서드 내부에서만 self
가 클래스 자신, 또는 생성된 인스턴스
둘 중 하나의 의미를 갖는다.
self
를 사용하는 진짜 이유가 여기에 있다.
예를 들어, 아래 정의에서 클래스 변수인 TITLES
가
__init__
메서드의 allowed_titles
라는 옵션변수의 기본값으로
사용되었다.
class Person:
TITLES = ('Dr', 'Mr', 'Mrs', 'Ms')
def __init__(self, title, name, surname, allowed_titles=TITLES):
if title not in allowed_titles:
raise ValueError("%s is not a valid title." % title)
self.title = title
self.name = name
self.surname = surname
아래 정의에서 사용된 세 개의 속성 name
, surname
, profession
의 차이점을 설명하라.
또한 서로 다른 객체에서 해당 속성들이 가질 수 있는 값들을 설명하라.
class Smith:
surname = "Smith"
profession = "smith"
def __init__(self, name, profession=None):
self.name = name
if profession is not None:
self.profession = profession
name
:surname
:profession
:PythonTutor: 대장장이에서 코드를 실행하며 위 내용을 확인할 수 있다.