2022/01/02/SUN
- 오브젝트
- 클래스 오브젝트
- 인스턴스 오브젝트
- 클래스 (=클래스 오브젝트)
- 인스턴스 (=인스턴스 오브젝트)
class Testclass1:
x=0
y=0
def my_print(self):
self.x += 1
Testclass1.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
f=Testclass1
a=Testclass1()
b=f()
a.my_print()
b.my_print()
b.my_print()
a.my_print()
a.my_print()
- 신기한점: 각 인스턴스에서 instance.my_print()를 실행한 횟수를 서로 공유하는 듯 하다.
- 코드를 시점별로 분석해보자.
- 분석을 위해서 커널을 재시작한다.
class Testclass1:
x=0
y=0
def my_print(self):
self.x += 1
Testclass1.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
dir(Testclass1)
# dir(b)
# 이 둘은 아직 존재 X
– 이 시점에는 Testclass1만이 존재한다. Testclass1를 바로 클래스 오브젝트라고 부름.
Testclass1.x
Testclass1.y
– 현재시점에서는 클래스 오브젝트의 수 1개, 인스턴스 오브젝트의 수 0개, 따라서 총 오브젝트 수는 1개임.
f=Testclass1
f.x
f.y
Testclass1.x
Testclass1.y
– 이 시점에서 클래스 오브젝트는 2개가 있는 것 처럼 보인다.
- 그렇다면 이 2개의 클래스 오브젝트는 컴퓨터의 어딘가에 저장이 되어 있을 것이다.
- 구체적으로는 메모리에 저장되어있을것.
- 2개의 클래스오브젝트는 서로 다른 메모리 공간에 저장되어 있을것이다.
- 진짜인가? 확인해보자. id()는 오브젝트(클래스 오브젝트, 인스턴스 오브젝트)가 저장된 메모리 주소를 확인하는 명령어이다.
id(f)
– f라는 오브젝트는 93967322676384 메모리에 저장되어 있다.
id(Testclass1)
- 어? 그런데 Testclass1의 오브젝트 역시 93967322676384 메모리에 저장되어 있다.
- 추론: 사실 93967322676384라는 메모리공간에 저장된 어떠한 것은 동일한데, 그것을 어떤사람은 Testclass1 이라고 부르고 어떤사람은 f라고 부른다.
- 이는 마치 별명이랑 비슷하다. 나라는 오브젝트를 어떤사람은 최규빈이라고 부르고, 어떤사람은 팬더라고 부른다. 부르는 이름이 2개라고 해서 나라는 오브젝트가 2개가 있는것은 아니다.
- 결국 이 시점에서 클래스 오브젝트의 수는 여전히 1개라고 볼 수 있다. (인스턴스 오브젝트의 수는 0개)
a=Testclass1() # 인스턴스 object 만듦
b=f() # 인스턴스 object 만듦
id(Testclass1),id(f),id(a),id(b)
– 이 순간에는 클래스 오브젝트 1개, 인스턴스 오브젝트 2개 존재한다. 즉 총 3개의 오브젝트가 존재한다.
- 메모리주소 93967322676384 에 존재하는 오브젝트는 클래스 오브젝트이며 Testclass1 또는 f 라고 불린다.
- 메모리주소 139694857660688 에 존재하는 오브젝트는 인스턴스 오브젝트이며 a라고 불린다.
- 메모리주소 139694848860656 에 존재하는 오브젝트는 인스턴스 오브젝트이며 b라고 불린다.
Testclass1.x, Testclass1.y
f.x,f.y
a.x,a.y
b.x,b.y
a.my_print()
(f.x,f.y),(a.x,a.y),(b.x,b.y)
- 특징
- a.my_print()를 실행하면 a.x 의 값이 1이 증가한다.
- a.my_print()를 실행하면 f.y, a.y, b.y 의 값이 동시에 1이 증가한다. (공유가 되는 느낌)
b.my_print()
(f.x,f.y),(a.x,a.y),(b.x,b.y)
b.my_print()
(f.x,f.y),(a.x,a.y),(b.x,b.y)
a.my_print()
(f.x,f.y),(a.x,a.y),(b.x,b.y)
a.my_print()
(f.x,f.y),(a.x,a.y),(b.x,b.y)
- 아래처럼 코드를 바꿔도 잘 동작할것 같다.
class Testclass2:
def __init__(self): # 클래스가 생성되는 시점에서는 실행되지 않고, 인스턴스가 생성되는 시점에서 실행될 것
self.x=0
self.y=0
def my_print(self):
self.x += 1
Testclass2.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
c=Testclass2()
# 인스턴스 object 생성
– 왜 에러가 나는가?
dir(Testclass2)
dir(Testclass1)
- 관찰1: Testclass2에서는 Testclass1과는 다르게 x,y가 없다.
dir(c)
– 관찰2: 그런데 c라는 인스턴스 오브젝트에서는 x,y가 있다.
- 추론: __init__함수는 클래스 오브젝트가 만들어지는 시점에서는 실행되지 않고, 인스텐스 오브젝트가 만들어지는 시점에 실행된다.
- 결국 __init__ 함수의 역할은 클래스 오브젝트에서 인스턴스 오브젝트를 만든후에 초기화를 위해서 실행하는 어떠한 일련의 명령들을 묶어놓은 것에 불과하다.
– 즉 위의 코드는 굳이 따지면 아래를 실행한 것과 동일하다.
class Testclass2:
# def __init__(self):
# self.x=0
# self.y=0
def my_print(self):
self.x += 1
Testclass2.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
c=Testclass2()
c.x=0
c.y=0
- 이 상황에서
c.my_print()
를 실행하면
c.x += 1
Testclass2.y +=1
print("현재 인스턴스에서 %s 회 출력" % c.x)
print("전체 인스턴스에서 총 %s 회 출력" % c.y)
이 실행되는데, 이때 Testclass2.y 이 정의되어 있지 않으므로
Testclass2.y +=1
에서 에러가 난다.
- 그렇다면 아래와 같이 수정하면 어떨까?
class Testclass3:
def __init__(self):
self.x=0
Testclass3.y=0
def my_print(self):
self.x += 1
Testclass3.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
# def __init__(self): # 클래스가 생성되는 시점에서는 실행되지 않고, 인스턴스가 생성되는 시점에서 실행될 것
# self.x=0
# self.y=0
# def my_print(self):
# self.x += 1
# Testclass2.y +=1
# print("현재 인스턴스에서 %s 회 출력" % self.x)
# print("전체 인스턴스에서 총 %s 회 출력" % self.y)
a=Testclass3()
b=Testclass3()
a.my_print()
b.my_print()
a.my_print()
a.my_print()
b.my_print()
b.my_print()
– Testclass1과 동일한 기능이 수행되는것 같다.
- 그런데 조금만 생각해보면 엉터리라는 것을 알 수 있다. 아래의 코드를 관찰하여보자.
class Testclass3:
def __init__(self):
self.x=0
Testclass3.y=0
def my_print(self):
self.x += 1
Testclass3.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
a=Testclass3()
a.my_print()
a.my_print()
b=Testclass3() # 초기화
b.my_print()
- Testclass3는 인스턴스를 생성할때마다 y=0이 설정된다. 그래서
b=Testclass3()
이 시점에서 의도하지 않게 '전체 인스턴스에서 출력된 횟수'를 의미하는 y가 초기화되었다.
- 코드는 엉터리이지만, Testclass3은 의외로 분석할만한 가치가 있다. 특히 위의 실행결과를 시점별로 Testclass1과 비교해보면 재미있다.
- Testclass1
### Testclass1
## 시점1: 클래스 오브젝트 생성
class Testclass1:
x=0
y=0
def my_print(self):
self.x += 1
Testclass1.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
## 시점2: 인스턴스 오브젝트 a를 생성
a=Testclass1()
## 시점3: a에서 메소드 실행
a.my_print()
## 시점4: a에서 메소드를 한번 더 실행
a.my_print()
## 시점5: 인스턴스 오브젝트 b를 생성
b=Testclass1()
## 시점6: b에서 메소드를 실행
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 2 회 출력
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 3 회 출력
| 시점1 | 시점2 | 시점3 | 시점4 | 시점5 | 시점6 | |
|---|---|---|---|---|---|---|
| Testclass1.x | 0 | 0 | 0 | 0 | 0 | 0 |
| Testclass1.y | 0 | 0 | 1 | 2 | 2 | 3 |
| a.x | 값없음 | 0 | 1 | 2 | 2 | 2 |
| a.y | 값없음 | 0 | 1 | 2 | 2 | 3 |
| b.x | 값없음 | 값없음 | 값없음 | 값없음 | 0 | 1 |
| b.y | 값없음 | 값없음 | 값없음 | 값없음 | 2 | 3 |
– Testclass3
#### Testclass3
## 시점1: 클래스 오브젝트 생성
class Testclass3:
def __init__(self):
self.x=0
Testclass3.y=0
def my_print(self):
self.x += 1
Testclass3.y +=1
print("현재 인스턴스에서 %s 회 출력" % self.x)
print("전체 인스턴스에서 총 %s 회 출력" % self.y)
## 시점2: 인스턴스 오브젝트 a를 생성
a=Testclass3()
## 시점3: a에서 메소드 실행
a.my_print()
## 시점4: a에서 메소드를 한번 더 실행
a.my_print()
## 시점5: 인스턴스 오브젝트 b를 생성
b=Testclass3()
## 시점6: b에서 메소드를 실행
b.my_print()
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
현재 인스턴스에서 2 회 출력
전체 인스턴스에서 총 2 회 출력
현재 인스턴스에서 1 회 출력
전체 인스턴스에서 총 1 회 출력
| 시점1 | 시점2 | 시점3 | 시점4 | 시점5 | 시점6 | |
|---|---|---|---|---|---|---|
| Testclass3.x | 값없음 | 값없음 | 값없음 | 값없음 | 값없음 | 값없음 |
| Testclass3.y | 값없음 | 0 | 1 | 2 | 0 | 1 |
| a.x | 값없음 | 0 | 1 | 2 | 2 | 2 |
| a.y | 값없음 | 0 | 1 | 2 | 0 | 1 |
| b.x | 값없음 | 값없음 | 값없음 | 값없음 | 0 | 1 |
| b.y | 값없음 | 값없음 | 값없음 | 값없음 | 0 | 1 |
– Testclass3.y가 업데이트 되면 a.y, b.y도 자동으로 업데이트 된다.
class Testclass1:
x=0
Testclass1.x
a=Testclass1()
# 인스턴스
a.x
– Testclass1.x를 수정하면 a.x가 강제로 수정된다.
Testclass1.x=100
a.x
- a.x를 수정한다고 하여 Testclass1.x가 강제로 수정되는 것은 아님
a.x=200
Testclass1.x
a.x
- 이건 왜이러지?
Testclass1.x=300
a.x
- 아래의 상황과 비슷하다.
x=39
def nextyear():
y=x+1
print(x,y)
nextyear()
x
# x=39
# def nextyear():
# y=x+1
# print(x,y)
# x=0
# nextyear()
- [code2]와 [code1]의 차이점은 x=0이라는 코드가 추가로 포함되었는지 유무다.
- code1에서는 x는 global variable, code2에서는 x가 local variable 이라서 생기는 문제점이다.
x=39
def nextyear():
x=0
y=x+1
print(x,y)
nextyear()
x
– 다시 우리의 예제로 돌아오자.
### 시점1
class Testclass1:
x=0
### 시점2
a=Testclass1()
### 시점3
Testclass1.x=100
### 시점4
a.x=200 `이 순간 a.x의 속성이 instance로 변함`
### 시점5
Testclass1.x=300
| 시점1 | 시점2 | 시점3 | 시점4 | 시점5 | |
|---|---|---|---|---|---|
| Testclass1.x | 0 | 0 | 100 | 100 | 300 |
| a.x | 값없음 | 0 | 100 | 200 | 200 |
| a.x의 속성 | - | class | class | instance | instance |
– a.x가 클래스로부터 물려받은 속성인지 (그래서 클래스와 연결되어있는지) 아니면 instance가 독자적으로 가지고 있는 속성인지 어떻게 알 수 있을까?
class Testclass1:
x=0
print('시점1',Testclass1.x)
### 시점2
a=Testclass1()
print('시점2',Testclass1.x,a.x,a.__dict__)
### 시점3
Testclass1.x=100
print('시점3',Testclass1.x,a.x,a.__dict__)
### 시점4
a.x=200
print('시점4',Testclass1.x,a.x,a.__dict__)
# 이젠 독자성을 지님
### 시점5
Testclass1.x=300
print('시점5',Testclass1.x,a.x,a.__dict__)
참고 : _dict__ 용도? 클래스 객체의 속성 정보를 확인하기 위해 사용. 객체가 가진 여러가지 속성들을 딕셔너리 형태로 편하게 확인할 수 있다.
x=11 ## 전역변수 ... A
def f():
x=22 ## 함수 f안에 설정된 지역변수
print(x) ## 전역에 x=11 있지만 함수안에 x=22가 있으므로 x=22를 사용. --> 22출력됨
def g():
print(x) ## 함수 g안에 x를 찾아봤는데 없음 --> 전역에서 x를 찾음 --> x=11 --> 11출력함.
class Testclass2:
x=33 ## 클래스 변수 ... B
def m1(self):
x=44 ## 메소드 변수 ... C
def m2(self):
self.x=44 ## 인스턴스 변수 ... D
- 결과를 관찰하고 해석해보자.
print(x)
f()
x
g()
x,Testclass2.x
a=Testclass2()
(x,Testclass2.x,a.x),a.__dict__
a.__dict__의 결과로 보아 인스턴스내의 변수는 클래스 오브젝트내의 변수를 빌려쓰고 있다. ).
Testclass2.x=200
(x,Testclass2.x,a.x),a.__dict__
a.m1()
(x,Testclass2.x,a.x),a.__dict__
m1내에서 선언된 x=44라는 선언은 아무것도 변화시킬수 없음.
a.m2()
(x,Testclass2.x,a.x),a.__dict__ # 독자성을 갖는 것
m2에 있는 self.x는 결국 a.x라는 의미이고, 이 선언은 클래스오브젝트 내의 변수와 독립적으로 인스턴스오브젝트 내에서 통용되는 변수를 선언하는 것임. 이 선언의 결과는 a.__dict__의 출력결과에서도 확인가능.
Testclass2.x=300
(x,Testclass2.x,a.x),a.__dict__
전역변수(A), 클래스 변수(B), 메소드 변수(C), 인스턴스 변수(D)
A>B>D>C
- 아래의 코드를 관찰하자.
1+1
- 생각해보니까 1은 int class 에서 생성된 인스턴스이다.
- 코드를 관찰하니 instance와 instance를 +라는 연산이 연결하는 형태임.
class Student:
def __init__(self,age=20.0,semester=1):
self.age=age
self.semester=semester
def __add__(self,val):
# val==0: 휴학
# val==1: 등록
if val==0:
self.age=self.age +0.5
elif val==1:
self.age=self.age+0.5
self.semester=self.semester+1
return self
def __repr__(self):
return '나이: %s \n학기: %s' % (self.age,self.semester)
guebin=Student()
guebin.age
guebin.semester
guebin
guebin+1
guebin+0
guebin+0+0+0+0+1+0+1
- 연산자 오버로드 핵심아이디어
- 클래스가 일반적인 파이썬 연산을 재정의하는 것, 즉 가로채는 것이라고 생각해도 됨
- 여기에서 연산은 단순히 덧셈, 뺄셈을 의미하는게 아니라,
print(),+,[0](인덱싱) 와 같은 파이썬 내장문법을 모두 포괄하는 개념이라 이해하는 것이 옳다.
class Student2(Student):
def __getitem__(self,index):
return [self.age,self.semester][index]
hynn=Student2()
hynn+1+1+0+0
hynn[0]
hynn[1]
hynn[:]
- 넘파이의 경우 아래와 같이 도움말이 잘 작성되어 있다.
import numpy as np
a=np.array([1,2,3])
# a?
- 하지만 우리는?
hynn?
- 우리도 도움말을 작성하고 싶다.
class Student2(Student):
'''
Student2는 Student의 개선
# Student 클래스의 기능
1. 출력기능 (__repr__)
2. 연산기능 (__add__): 학기와 나이를 카운트
Examples
--------
>>> hynn=Student2()
>>> hynn+1
나이: 20.5
학기: 2
# Student2에서 추가된 기능
1. 인덱싱
'''
def __getitem__(self,index):
return [self.age,self.semester][index]
hynn=Student2()
hynn?
hynn=Student2(21,1)
hynn
hynn?
– 사실 이름이 self가 아니어도 된다.
-클래스의 첫번째 인자는 굳이 self가 아니라 임의로 a라고 명명해도 됨
class MooYaHo:
'''
201821994
'''
def __init__(a):
a.text='mooyaho'
moo1=MooYaHo()
moo1?
moo1=MooYaHo()
moo1.text
– 그런데 self를 많이 쓴다.