들어가며,
python의 class상속에 대해 공부해야겠다는 생각이 들었다. (어떤 코드를 리뷰하면서 개념적 이해가 부족하다 느꼈다.)
이에 따라 class상속을 공부하다 보니, class를 보고, 사용하며 큰 이해 없이 사용했던 self의 의미, __init__의 의미에 대한 이해가 부족하다는 생각이 함께 들었다.
따라서 본 글은 class 상속에 대해 이해하기 위해
1. 클래스 이해
2. 생성자 이해
(다음 글, 3. 클래스 상속 이해)
순으로 진행될 예정이다.
매우 중요 : '나는 클래스, 생성자 이런건 알고있고 상속에 대한 얘기만 듣고싶다' 하시는 분은 다음 글로 넘어가주시기 바랍니다. (분량조절 실패로 클래스 상속은 다음 장으로 넘겨졌습니다...)
https://justhash.tistory.com/21
[python] 클래스 상속(inheritance)
앞장에 클래스 상속을 공부하다보니 필요한 클래스(class), 생서자(__init__) 추가로 (self)에 관하여 간략하게 정리를 진행해 보았다.이제 본격적으로 클래스 상속에 관해 알아보자.알아볼 내용:상속
justhash.tistory.com
python의 class란
class를 공부하다 보니 이해하지 못하는 내용이 너무 많았다. 따라서, 본 글의 목표인 '클래스 상속'을 이해하는데 필요한 내용만 정리하였다.
1. 클래스의 개념
: 어떠한 '형'을 의미하며 그 '형'은 자신만의 고유한 변수, 메소드 등을 가지고 있다. 사용자는 본인이 원하는 변수, 메소드 등 포함하는 '형' 즉 class를 만들어 사용할 수 있다.
보통(본인이 보고 사용했던) 사용되는 경우는 코드를 간편하게 만들기 위해, 동일한 메소드 변수를 가지는 여러개의 변수를 만들기 위해 사용한다. 혹은 단순히 하나의 변수에 여러 정보를 넣고 싶을 때 사용할 수도 있다.
예를 들어, 사람에 대한 정보를 기입하는 코드를 짠다고 가정하자.
사람이 가지고 있는 공통된 정보가 있다. 이름, 나이, 키 등등. 이 것들은 사람 모두가 가지고 있는 정보를 분류하는 명사(변수)이다. 그 명사(변수)안에는 정보(값)이 들어있다.
명사(변수) | 이름 | 나이 | 키 |
정보(값) | 철수 | 10 | 140 |
정보(값) | 영희 | 11 | 145 |
(굉장히 말장난 같은 표현이 나오는데 말장난 아닙니다.)
이럴 경우 하나의 사람하나(철수)가 여러개의 정보(이름:철수,나이:10,키:140)를 가지게 된다.
모든 사람은 공통된 변수(이름,나이,키)를 가지고 있고 그 변수에 본인만의 값(철수, 10, 140)를 가지는 것이다.
따라서, 우리는 여러 사람에 대한 정보를 나타내고 싶을 때 '사람'이라는 class를 만들어 해당 class에 이름, 나이, 키 라는 변수를 생성하는 것이다.
예를 들면 아래와 같다.
class Person:
def __init__(self, name, age, tall):
self.name = name
self.age = age
self.tall = tall
person_1 = Person('철수', 10, 140)
person_2 = Person('영희', 11, 145)
print(person_1.name)
print(person_2.name)
이 'Person'이란 클래스를 통해서 어떠한 사람이든 동일한 구조, 변수로 정보를 저장할 수 있게 된 것이다.
이때, 'Person'이라는 class로 정의된 'person_1'이라는 변수를 객체 부르고, 이는 곧 'Person'의 인스턴스 라 부른다고 한다. 이에 대한 깊은 이해는 하지 못했다. 개념적 이해가 필요할 때가 된다면 더 공부하는 것으로 하고 넘어간다.
여기서 본인이 의문이 든 것은. 그동안 별로 신경쓰지 않았던, '__init__'과 'self'이다. 항상 붙이던 별 생각 없이 사용하던 것들이다.
2. 클래스의 속성(규칙)
: 클래스가 가지는 몇 가지 특징, 규칙에 대한 설명을 다룰 것이다. 그 중 첫번째는 뒤에서 다루게 될 생성자를 이해하는데 필요한 내용이다. 클래스를 직접 사용해보거나 코드를 리뷰해본 경우 매우 흔하게 봤을 'self'에 관한 규칙이다.
여기서 생성자(__init__)에 대한 개념 또한 간단하게 엿볼 수 있다.
2-1. class내부에 정의된 메소드(함수)는 호출될 때 항상 첫 번째 인자로 본인 class의 인스턴스를 입력받는다. (class의 인스턴스 = 객체(==해당 클래스로 정의된 변수)
이게 무슨소리냐?
아래의 경우를 살펴보자.
class Computer:
def __init__(self):
self.gpu = 'cuda'
def gpu_name(self):
print("Computer에 정의된 gpu이름 : ",self.gpu)
def gpu_name2(what):
print("Computer에 정의된 gpu이름 : ",what.gpu)
클래스를 작성하거나, 작성된 코드에서는 항상 아래의 'gpu_name'함수 처럼 'self'가 인자로 들어가있는 것을 확인할 수 있었을 것이다. 이 self.은 자기 자신의 인스턴스에 접근할 수 있는 방법이다. 그런데 함수가 호출될 때 self를 통해 접근할 수 있는 방법은, 인자로 입력받는 self가 자기 자신의 인스턴스를 입력받기 때문에 접근할 수 있는 것이다.
따라서 'gpu_name'에서 self를 없애고 코드를 실행해볼 경우 'gpu_name'의 함수가 받을 수 있는 인자가 없는데 1개를 입력받았다는 에러가 발생할 것이다. 그 이유는 class에 정의된 메소드는 호출될 때 항상 자기 자신의 인스턴스를 숨겨진(?) 첫번째 인자로 입력받기 때문이다.
그러한 이유로 'gpu_name2'메소드에는 'self'대신 'what'으로 인자가 정의되어있는데, 이것이 self. 라는 어떤 특정한 네임스페이스로 변수가 정의된 것이 아닌. __init__메소드 (생성자)에서 변수를 정의할 때 역시 self로 본인의 인스턴스를 입력받아, 본인 인스턴스의 네임스페이스로 변수를 정의한 것이기 때문에, 'gpu_name2'에서 같은 인스턴스를 입력받은 것이다. 즉, 'gpu_name'메소드의 'self'와 'gpu_name2'메소드의 'what'모두 입력받은 인스턴스를 의미하는 것이다.
2-2. 'class 변수'와 'instance 변수'
필자는 공부하며 처음 들어본 개념이다. 정확히는 구분을 해본 적이 없었다.
아래 코드는 위키독스(파이썬으로 배우는 알고리즘 트레이딩)에서 참고한 코드이다.(https://wikidocs.net/1744)
class Account:
num_accounts = 0
def __init__(self, name):
self.name = name
Account.num_accounts+=1
def __del__(self):
Account.num_accounts-=1
kims_account = Account("kim")
kims_account.name
'kim'
kims_account.num_accounts
>>> 1
kims_account.num_accounts
>>> 1
lees_account = Account("lee")
lees_account.name
>>> 'lee'
lees_account.num_accounts
>>> 2
kims_account.num_accounts
>>> 2
del lees_account
kims_account.num_accounts
>>> 1
위의 'Account' 클래스에 정의된 변수는 2개를 확인할 수 있다.
num_accounts와 self.name이다.
여기서 self.은 __init__생성자에서 정의된다. 생성자 역시 메소드(함수)이기 때문에 인자로 자기 자신의 인스턴스를 입력받는다. 즉 self.name은 정확히는 입력받은 인스턴스(self)의 변수 라는 것이다.
하지만, num_accounts는 class자체에 정의된 변수이기 때문에 class변수이다. 이 두가지의 차이는 위 코드 실행 결과로 확인해볼 수 있다.
Account 클래스로 정의된 서로 다른 두개의 인스턴스가 있다. 각각이 생성될 때, 자기 자신의 인스턴스로 이름(self.name)을 가지게 된다. 따라서 각각의 인스턴스에 고유의 name값('lee', 'kim')을 가지게 된다.
하지만, 이름이 정의될때 함께 실행되는 'Account.num_accounts+=1'이 보인다. name을 할당하는 self.과 다르게 Account.이 붙어있다. 이것이 바로 class변수에 접근하는 방법이다.(네임스페이스가 인스턴스가 아닌 class를 사용한다.) python의 객체들이 가지고있는 class에 공통적으로 영향을 미치는 방법이다. 즉 위 코드 출력결과를 살펴보면 알 수 있듯, Account의 class변수 'num_accounts'는 kims_account가 정의된 이후 해당 인스턴스에서 별도의 조작이 없었지만 'lees_account'의 생성과, 삭제에 영향을 받아 값이 변하게 되었다.
즉, class변수란 해당 클래스를 가지는 모든 객체들에 영향이 미쳐지는 것이다.
이것이 인스턴스 변수와 클래스 변수의 차이이다.
분량조절 실패로 여기서 마무리하고 이후 클래스 상속에 대해 다음장에서 이어 설명한다.
혹시나 원하는 분이 있을까 제가 공부하며 끄적였던 코드 전문은 아래 '더보기'에 첨부 해 두었습니다.
"""
참조 링크
class의 생성자
https://wikidocs.net/1740
class의 self
https://wikidocs.net/1742
"""
class Person:
def __init__(self, name, age, tall):
self.name = name
self.age = age
self.tall = tall
person_1 = Person('철수', 10, 140)
person_2 = Person('영희', 11, 145)
print(person_1.name)
print(person_2.name)
"이때 __init__함수에 앞서, 인자로 사용되는 self에 대해 먼저 알아보자"
class Foo:
a = 1
def func1(selft):
# print(a)
print(selft.a)
selft.a+=1
print("function 1")
return 0
def func2(self):
# nonlocal a
# print(a)
print(self.a)
self.a+=1
print('function 2')
return 0
f = Foo()
f.func2()
print(f.a)
f.func1()
print(f.a)
"위의 경우 TypeError: Foo.func1() takes 0 positional arguments but 1 was given 라는 에러가 발생한다."
"1개의 인자를 받았다고? 이상하다. 난 준게 없다"
"해답은 이렇다. python에서 객체속의 method를 호출할 때에는 항상 자기 자신의 객체를 첫번째 인자로 받는다. 이말이 무슨말이냐"
"f라는 객체속의 method를 호출하는 것은 해당 method에 f라는 객체를 받으며 시작한다."
"이건 그냥 그렇구나 하고 받아들인다고 치자, 궁금한건 왜? 이다."
"만일 class안의 메소드 에서 해당 class가 가지고 있는 변수를 사용해야할 경우 어떻게 해결할까?"
"이러한 경우를 위해 self를 사용하는 것이다."
"print(a)는 에러가 발생하지만, print(self.a)는 정상 실행된다. func2라는 함수 내부에는 a라는 변수가 존재하지 않는다. a라는 변수는 Foo class의 인스턴스인 f가 가지고 있는 것이다."
"따라서, 메소드에서 본인을 인자로 받으면서 해당 객체가 가지고있는 변수들을 사용할 수 있게 된다."
"따라서 위와같이 func1의 인자를 다른 이름으로 설정해도 동일하게 작동한다. 하지만, 실제로 우린 이들을 통일한다. 헷갈리니까......"
"그럼 위처럼 바로 class에 a=1과 같이 변수를 정의하면 되지 왜 굳이 귀찮게 __init__(self)라고 생성자를 만드는 것일까?"
class Foo:
a = 1
b = 'b'
def func1(selft):
# print(a)
print(selft.a)
selft.a+=1
print("function 1")
return 0
def func2(self):
# nonlocal a
# print(a)
print(self.a)
self.a+=1
print('function 2')
return 0
"위와 같은 class의 인스턴스를 만들면 내부에 변수 a,b에 .을 통해 접근할 수 있다는 걸 알 것이다."
f=Foo()
print(f.a)
print(f.b)
"하지만 이때, a,b가 위 처럼 1,'b'로 고정된 값이 아닌 인스턴스를 생성할 때 입력하는 인자로 정의하기 위해서는 어떡할까?"
"method를 정의할 때 처럼 Foo뒤에 ()넣는 방법으로는 사용할 수 없다. class의 이름 뒤에 ()로 받을 수 있는 것은 인자가 아닌, 상속 클래스 이기 때문이다."
"따라서, __init__함수를 이용해 입력받는 것이다."
"아래와 같이 코드를 구성할 경우 class의 인스턴스를 생성할 때, 생성자가 받도록 정해진 수 만큼 인자를 입력받을 수 있다."
class Foo:
def __init__(self, a,b):
self.a = 1
self.b = 'b'
def func1(selft):
# print(a)
print(selft.a)
selft.a+=1
print("function 1")
return 0
def func2(self):
# nonlocal a
# print(a)
print(self.a)
self.a+=1
print('function 2')
return 0
"여기서 __init__이라는 생성자 함수 뒤에 self를 붙이는 것 또한 위에서 다룬 이유와 같은 이유이다."
"생성자 또한 결국 메소드이기 때문에 해당 객체를 인자로 받는다."
"즉, 생성자(__init__(self,a,b)에서 실행되는 내용 self.a=1, self.b='b'는 이전 코드에서 생성자 없이 a=1, b='b'로 생성한 것과 같은 역할을 한다.)"
"(실제로는 전자는 인스턴스변수, 후자는 클래스 변수 라고 다르긴 하지만 일단은 같은 개념으로 넘어간다. https://wikidocs.net/1744 참조.)"
"다만 위 코드에서는 이전 과 달리 class의 인스턴스를 생성하는 과정에서 인자를 받을 수 있다는 점이다."
class Foo:
def __init__(self, a,b):
self.a = a
self.b = b
exit()
"이하, 클래스 변수와 생성자 변수 차이"
class Account:
num_accounts = 0
def __init__(self, name):
self.name = name
Account.num_accounts+=1
def __del__(self):
Account.num_accounts-=1
kims_account = Account("kim")
kims_account.name
'kim'
kims_account.num_accounts
# >>> 1
kims_account.num_accounts
# >>> 1
lees_account = Account("lee")
lees_account.name
# >>> 'lee'
lees_account.num_accounts
# >>> 2
kims_account.num_accounts
# >>> 2
del lees_account
kims_account.num_accounts
# >>> 1
'Python' 카테고리의 다른 글
[python] 클래스 상속(inheritance) (0) | 2025.04.27 |
---|---|
우선순위 큐 heapq (0) | 2025.04.05 |