강의영상

- (1/6) 오브젝트의 개념

- (2/6) 예제1

- (3/6) 예제2,3,4

- (4/6) 예제5

- (5/6) 예제 6,7

- (6/6) 숙제설명

클래스 공부 3단계

- 이 단계에서는 클래스오브젝트에 소속된 변수와 인스턴스오브젝트에 소속된 변수를 설명한다.

오브젝트의 개념

- 파이썬은 모든 것이 오브젝트로 이루어져 있다. <- 우선은 그냥 명언처럼 외우세요

- 오브젝트는 메모리주소에 저장되는 모든것을 의미한다.

a=1
id(a) # 메모리주소를 보는 명령어 
93968974949632
a='asdf' 
id(a)
139921516224624
a=[1,2,3]
id(a)
139921515960320

- 클래스와 인스턴스도 오브젝트다

class A:
    x=0 
    def f(self):
        print(self.x) 
id(A)
93969013080160
  • A는 오브젝트
a=A()
id(a)
139921516243696
  • a는 오브젝트
b=A()
id(b)
139921516243792
  • b는 오브젝트

- 앞으로는 A를 클래스오브젝트, a,b를 인스턴스오브젝트라고 부르자.

예제1: 클래스변수, 인스턴스변수

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
A.x, A.y
(0, 0)

- 시점1

a = A() 
b = A()
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 0], [0, 0], [0, 0])

- 시점2

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 1], [1, 1], [0, 1])

- 시점3

b.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 2], [1, 2], [1, 2])

- 시점4

b.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 3], [1, 3], [2, 3])

- 시점5

a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 4], [2, 4], [2, 4])

- 시점6

c=A()
[A.x,A.y], [a.x,a.y], [b.x,b.y], [c.x,c.y]
([0, 4], [2, 4], [2, 4], [0, 4])

- 시점7

c.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y], [c.x,c.y]
([0, 5], [2, 5], [2, 5], [1, 5])

- 신기한점: 각 인스턴스에서 인스턴스이름.f()를 실행한 횟수를 서로 공유하는 듯 하다. (A의 관리하는 것처럼 느껴진다)

- x와 y는 약간 느낌이 다르다. x는 지점소속, y는 본사소속의 느낌?

이 예제에서 x는 인스턴스오브젝트에 소속된 변수, y는 클래스오브젝트에 소속된 변수처럼 느껴짐

(약속) 앞으로 인스턴스오브젝트에 소속된 변수를 인스턴스변수라고 하고, 클래스오브젝트에 소속된 변수를 클래스변수라고 하자.

- 인스턴스변수와 클래스변수를 구분하는 방법? 인스턴스이름.__dict__를 쓰면 인스턴스변수만 출력된다

  • 따라서 a.+ tab을 눌러서 나오는 변수중 a.__dict__에 출력되지 않으면 클래스변수이다.
a.__dict__ 
{'x': 2}
b.__dict__ 
{'x': 2}
c.__dict__
{'x': 1}

c는 한 번 시행해서

- 이 예제에서 아래는 모두 클래스변수이다.

a.y, b.y, c.y 
(5, 5, 5)

예제2: 인스턴스에서 변수x 변경 (변경가능)

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
[A.x,A.y], [a.x,a.y]
([0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
[A.x,A.y], [a.x,a.y]
([0, 3], [3, 3])

- 시점2

a.x = 0 # f의 실행기록을 초기화하고 싶다
a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
[A.x,A.y], [a.x,a.y]
([0, 4], [1, 4])

예제3: 클래스에서 변수y 변경 (변경가능)

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
b=A()
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 0], [0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 1], [1, 1], [0, 1])

- 시점2

A.y = 100 
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 100], [1, 100], [0, 100])
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 101번 실행
[A.x,A.y], [a.x,a.y], [b.x,b.y]
([0, 101], [2, 101], [0, 101])

예제4: 클래스에서 변수x 변경 (변경가능)

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
[A.x, A.y], [a.x,a.y]
([0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
[A.x, A.y], [a.x,a.y]
([0, 1], [1, 1])

- 시점2

A.x = 100 # 이렇게 되면 앞으로 만들어진 인스턴스는 기본적으로 현재 인스턴스에서 100번 f를 실행하였다는 정보를 가지고 태어나게 된다. 
[A.x, A.y], [a.x,a.y]
([100, 1], [1, 1])

- 시점3

b=A()
[A.x, A.y], [a.x,a.y], [b.x,b.y] 
([100, 1], [1, 1], [100, 1])

- 시점4

b.f()
현재 인스턴스에서 f가 101번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행

- 시점5

a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
b.f()
현재 인스턴스에서 f가 102번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행

예제4의 변형

- 시점0

class B: 
    x=10 # 초기자본금 
    y=0 
    def f(self): # f()를 실행할때마다 돈을 쓴다. 
        self.x = self.x - 1 
        B.y = B.y + 1 
        print("현재 인스턴스에서 {}원 잔액남음".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 총 {}원 사용".format(self.y))
a=B()
b=B()
[B.x,B.y], [a.x,a.y], [b.x,b.y]
([10, 0], [10, 0], [10, 0])

- 시점1

a.f() # 돈을쓰는것
현재 인스턴스에서 9원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 1원 사용
a.f()
현재 인스턴스에서 8원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 2원 사용
b.f()
현재 인스턴스에서 9원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 3원 사용

- 시점2

[B.x,B.y], [a.x,a.y], [b.x,b.y]
([10, 3], [8, 3], [9, 3])
B.x = 999
[B.x,B.y], [a.x,a.y], [b.x,b.y]
([999, 3], [8, 3], [9, 3])

- 시점3

c = B()
c.f()
현재 인스턴스에서 998원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 4원 사용

- 시점4

a.f()
현재 인스턴스에서 7원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 5원 사용
b.f()
현재 인스턴스에서 8원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 6원 사용
c.f()
현재 인스턴스에서 997원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 7원 사용
c.f()
현재 인스턴스에서 996원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 8원 사용
c.f()
현재 인스턴스에서 995원 잔액남음
A클래스에서 만들어진 모든 인스턴스들에서 총 9원 사용

예제5: 인스턴스에서 클래스변수 변경 (변경가능??)

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행".format(self.y))
a=A()
b=A()
[A.x, A.y], [a.x,a.y], [b.x,b.y]
([0, 0], [0, 0], [0, 0])

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행
b.f()
현재 인스턴스에서 f가 1번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행
a.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행
[A.x, A.y], [a.x,a.y], [b.x,b.y]
([0, 3], [2, 3], [1, 3])

- 시점2

a.y ## 인스턴스a에 소속되어있지만 클래스변수
3
a.y = 999 ## 내가 하드코딩으로 a.y에 999를 입력 -> 이것이 A.y나 b.y에도 반영될까? (X) 
[A.x, A.y], [a.x,a.y], [b.x,b.y]
([0, 3], [2, 999], [1, 3])

- 시점3

b.f()
현재 인스턴스에서 f가 2번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행
a.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행
b.f()
현재 인스턴스에서 f가 3번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 6번 실행
b.f()
현재 인스턴스에서 f가 4번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 7번 실행
a.f()
현재 인스턴스에서 f가 4번 실행
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행

- 요약

  • 인스턴스에서 클래스변수의 값을 변경하면? -> 클래스변수의 값이 변경되는 것이 아니라 인스턴스변수가 새롭게 만들어져서 할당된다.
  • 이 예제에서 a.y는 이제 클래스변수에서 인스턴스변수로 재탄생되었다. 즉 999 오브젝트가 새롭게 만들어져서 a.y라는 이름을 얻은것임.
  • 기존의 A.y나 b.y에는 아무런 변화가 없다.

a.y = 999 은 새로운 인스턴변수 y를 할당하는 역할을 한다. 클래스변수의 값을 변경하는 것이 아니다. (왜냐하면 애초에 a.y는 없는 값이었고, A.y를 빌리고 있었던 것임)

a.__dict__
{'x': 4, 'y': 999}

b는 y의 값을 빌려 출력하고 있던 것이었다!

b.__dict__
{'x': 4}

예제5의 변형

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클래스레벨)".format(A.y))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))
a=A()
a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클래스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (인스턴스레벨)
b=A()
b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클래스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (인스턴스레벨)

- 시점1

a.y = 999 
a.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행 (클래스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행 (인스턴스레벨)
a.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행 (클래스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행 (인스턴스레벨)
a.f()
현재 인스턴스에서 f가 4번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행 (클래스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 999번 실행 (인스턴스레벨)

예제6: 인스턴스 생성시점에 대한 분석

- 의문: 아래의 코드에서 x는 클래변수라고 봐야할까? 인스턴스 변수라고 봐야할까? --> 클래스변수

class SoWhaTV: 
    x=0 ### 이 시점에서 x는 클래스변수인가? 아니면 인스턴변수인가? 
    def f(self):
        print(self.x)

- 시점0

class A: 
    x=0
    y=0 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클래스레벨)".format(A.y))
        #print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))
a=A()
b=A()
a.x,a.y,b.x,b.y 
(0, 0, 0, 0)
a.__dict__, b.__dict__
({}, {})
  • 지금 시점에서 a.x, a.y, b.x, b.y 는 모두 클래스변수임

- 시점1

a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클래스레벨)
a.__dict__, b.__dict__
({'x': 1}, {})
  • 이 순간 a.x가 클래스변수에서 인스턴스변수로 변경되었다. 왜? f가 실행되면서 self.x = self.x + 1 이 실행되었으므로!

- 시점2

b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클래스레벨)
a.__dict__, b.__dict__
({'x': 1}, {'x': 1})

예제7: 잘못된사용

- 아래처럼 코드를 바꾸면 어떻게 되는가?

class A: 
    def __init__(self): 
        self.x=0 # 인스턴스변수로 나중에 쓸꺼니까 명시함 
        A.y=0 # 클래스변수로 나중에 쓸꺼니까 명시함 
    def f(self):
        self.x = self.x + 1 
        A.y = A.y + 1 
        print("현재 인스턴스에서 f가 {}번 실행 (인스턴스레벨)".format(self.x))
        print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (클래스레벨)".format(A.y))
        #print("A클래스에서 만들어진 모든 인스턴스들에서 f가 총 {}번 실행 (인스턴스레벨)".format(self.y))

- 사용해보자.

a=A()
b=A()
a.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클래스레벨)
b.f()
현재 인스턴스에서 f가 1번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 2번 실행 (클래스레벨)
b.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 3번 실행 (클래스레벨)
b.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 4번 실행 (클래스레벨)
a.f()
현재 인스턴스에서 f가 2번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 5번 실행 (클래스레벨)

- 잘 되는것 같다?

- 조금만 생각해보면 엉터리라는 것을 알 수 있다. 아래를 관찰해보자.

c=A() # 이 시점에서 __init__()이 실행된다! 
a.f()
현재 인스턴스에서 f가 3번 실행 (인스턴스레벨)
A클래스에서 만들어진 모든 인스턴스들에서 f가 총 1번 실행 (클래스레벨)
  • 클래스레벨의 변수가 왜 초기화가 되었지?

- 오류의 이유? c=A()가 실행되는 시점에 __init__()이 실행되면서 A.y=0이 실행된다. 따라서 강제초기화가 진행되었음

숙제

아래의 조건에 맞는 클래스를 생성하라.

(1) ['가위','바위']와 같은 리스트를 입력으로 받아 인스턴스를 생성한다.

(2) 위의 리스트에서 하나의 값을 뽑는 메소드 f를 가지고 있다.

(3) f의 실행횟수를 기록하는 기능을 가진다. (각 인스턴스에서 실행한 횟수, 클래스에서 생성된 모든 인스턴스에서 실행한 횟수 모두 기록)

사용예시

a = Klass(['가위','바위'])
a.f() # 가위가 1/2 바위가 1/2의 확률로 출력 
b = Klass(['가위','바위','보'])
b.f() # 가위, 바위, 보가 1/3의 확률로 출력
import numpy as np
class Klass:
    def __init__(self,x):
        self.x = x
        Klass.y = 0
        
    def f(self):
        Klass.y = Klass.y + 1
        print(np.random.choice(self.x))
        print(Klass.y)
a = Klass(['가위','바위'])
b = Klass(['가위','바위','보'])
a.f() # 가위가 1/2 바위가 1/2의 확률로 출력 
가위
1
b.f() # 가위, 바위, 보가 1/3의 확률로 출력
보
2
a.f() # 가위가 1/2 바위가 1/2의 확률로 출력 
가위
3
b.f() # 가위, 바위, 보가 1/3의 확률로 출력
보
4