[day-9] 클래스

Joohyung Park·2024년 1월 14일
0

[모두연] 오름캠프

목록 보기
9/95
post-thumbnail

내용 요약

  • 현재 폴더를 알아보는 while문 실습
  • break과 continue
  • while문을 사용한 스무고개
  • 반복문에서 else => break없이 정상 종료되면 실행
  • while문에서 else는 조건이 False면 바로 실행됨
  • 클래스 = 붕어빵 틀 / 인스턴스 = 붕어빵

클래스 부분은 아직 익숙하지 않기에 좀 더 자세히 적어보기로 하겠다.


인스턴스 만들어보기

데이터(멤버, 애트리뷰트) => 클래스 내 변수로 선언
최대 속도
최대 탑승객
기어

기능(메서드) => 클래스 내 함수로 선언
출발
정지

class Car: # 차에 설계 도면 또는 차 공장, 클래스
    max_speed = 300 # 멤버 또는 애트리뷰트
    max_people = 5
    car_gear = ['P', 'D', 'R', 'N']

    def start(self): # 메서드
        print('차가 출발합니다!')

    def stop(self):
        print('차가 멈췄습니다!')

# 공장에서 생산된 자동차 modelx, y, s, 인스턴스
modelx = Car() # 인스턴스 = 클래스()
modely = Car()
models = Car()

print(models.car_gear[1])
models.start()
models.stop()

# Car.max_speed # modelx에 속도를 보고 싶으면 modelx.max_speed를 찍어야 합니다.
# 초기 설계는 300으로 찍혀 있지만, 고객이 아래와 같이 자동차 튜닝을 했을 수도 있으니까요.

models.max_speed = 500	# models객체 고유의 max_speed 설정(인스턴스 변수 설정)

print(models.max_speed)
print(modely.max_speed)
# 출력
D
차가 출발합니다!
차가 멈췄습니다!
500
300

위의코드를 보면 modely는 고유의 변수를 선언하지 않았기에 클래스 변수인 max_speed=300의 값을 출력하고, modelx는 고유의 인스턴스 변수를 설정했기에 500이 출력된다.

자료형의 정체

class Car:
    max_speed = 300
    max_people = 5
    car_gear = ['P', 'D', 'R', 'N']

    def __init__(self, name): # 클래스로 인스턴스를 만들 때 호출!
        self.name = name

    def start(self):
        print('차가 출발합니다!')

    def stop(self):
        print('차가 멈췄습니다!')

modelx = Car('Tesla Model X') # 여기서 Car 클래스에 넣었던 'Tesla Model X' 
															# 이 __init__ 메직메서드에서 처리!
modelx.name
# 출력
Tesla Model X

위의 코드에서 modelx 객체를 만들면서 init의 name에 Tesla Model X가 들어가게 되고 modelx 객체 고유의 이름을 출력하면 당연히 이 name 값이 출력이 되는 모습이다.

init 부분만 따로 떼어 실행해보기

class Car:
    def __init__(self, name, CEO): # 클래스에서 __init__은 붕어빵 틀 중에서 가장 기본 틀!
        self.name = name           # self는 인수로 전달받지도 전달하지도 않음 -> Car 클래스 그 자체
        self.CEO = CEO             # 위에 꼭 self를 붙여야 self를 사용해서 self.name처럼 사용할 수 있음

modelx = Car('Tesla Model X', '일론 머스크') # __init__과 self로 구현된 값은 클래스 할당시 입력자료를 넣을 수 있다.
print(modelx.name)                        # 입력을 받을때는 파라미터를 설정해야한다.(name, CEO처럼)
print(modelx.CEO)                         # 그러면 modelx처럼 name과 CEO의 데이터를 불러올수 있다.
# 출력
Tesla Model X
일론 머스크

init부분은 객체를 만들면 제일 처음에 무조건 실행되는 메서드라고 보면 된다. 메서드를 선언할 때, self는 거의 고정으로 넣는다고 보면 편하다.

클래스 변수

# 클래스 변수는 클래스 바로 하위에 자리하고 있다. 이러한 클래스 변수는 아래와 같이
# 클래스 이름, 인스턴스 이름을 통해 접근 가능하다.

class Car:
    # 클래스 변수의 위치
    max_speed = 300
    max_people = 5
    car_gear = ['P', 'D', 'R', 'N']

modelx = Car()
print(Car.max_speed)
print(modelx.max_speed)
# 출력
300
300

첫 번째 print()문 처럼 찍어도 결과는 나오지만 이렇게 하는 것을 권장하지는 않는다. 여기서 Car클래스의 3개의 변수는 클래스 변수라고 하며, 객체가 고유의 값(인스턴스 변수)를 선언하지 않는 이상은 저 값을 출력한다.

  • 이러한 클래스 변수는 해당 클래스를 통해 만들어진 모든 인스턴스 객체들이 공유하는 변수
  • 인스턴스 객체들 각자가 관리하고 있는 변수를 인스턴스 변수라고 한다.
class Car:
    max_speed = 300    # 클래스 변수
    max_people = 5     # 클래스 변수
    car_gear = ['P', 'D', 'R', 'N']    # 클래스 변수

modelx = Car()
modely = Car()

modely.max_speed = 500

print(Car.max_speed)
print(modelx.max_speed)
print(modely.max_speed)
# 출력
300
300
500

클래스 변수는 모두가 공유하므로 modely.max_speed = 500라고 정의를 하면 modelx의 max_speed도 500으로 바뀌어야 할 것 같지만 modelx의 max_speed는 300이다. 왜 그럴까?
이는 modely의 인스턴스 변수의 값을 조정하는 것과 같기 때문인데 뒤에서 다시 살펴보자.
위의 결과를 GPT에게 물어보니 좀 더 잘 이해할 수 있었다.


값이 다른 이유?

위 코드에서 modelx.max_speedmodely.max_speed의 값이 다른 이유는 파이썬의 클래스 변수와 인스턴스 변수의 동작 방식 때문입니다.

클래스 변수는 클래스 내부에서 선언되지만, 모든 인스턴스들이 공유하는 변수입니다. 즉, 클래스 레벨에서 한 번만 생성되고 모든 인스턴스들이 접근할 수 있습니다. 여기서 max_speedCar 클래스의 클래스 변수입니다.

인스턴스 변수는 각각의 인스턴스 내에서 선언되며, 각 인스턴스는 자신만의 값을 가집니다. 이 변수들은 인스턴스 레벨에서 생성되며, 다른 인스턴스들이 접근할 수 없습니다.

modelx = Car()modely = Car() 코드로 Car 클래스의 두 인스턴스 modelxmodely를 생성했습니다. modelx.max_speed를 출력하면, modelx 인스턴스는 max_speed 인스턴스 변수를 가지고 있지 않기 때문에 Car 클래스의 클래스 변수인 max_speed를 참조하게 됩니다. 따라서 300이 출력됩니다.

반면에 modely.max_speed = 500 코드는 modely 인스턴스에 max_speed 인스턴스 변수를 생성하고, 이 변수에 500을 할당합니다. 그래서 modely.max_speed를 출력하면, modely는 자신의 인스턴스 변수 max_speed를 참조하여 500이 출력됩니다.


변수의 값이 공유가 되고 있는지 한번 더 확인해보자

class Car:
    kinds = []
    speed = 300

    def add_kinds(self, name):
        self.kinds.append(name)

    def change_speed(self, speed):
        self.speed = speed

modelx = Car()    # 인스턴스 객체 생성
modely = Car()    # 인스턴스 객체 생성

modelx.add_kinds('x')    # modelx 인스턴스 고유의 kinds 값이 없으므로 Car클래스의 kinds참조
modely.add_kinds('y')    # 위와 마찬가지로 modely에 고유의 kinds값이 없으므로 참조
                         # 이 상태에서 Car클래스의 클래스 객체 kinds = ['x', 'y']가 됨
Car.speed = 100    # Car클래스의 클래스 객체 speed를 100으로 변경함

# modelx.change_speed(500)    # 이 코드를 실행하면, x 고유의 스피드 값이 생기고
# modely.change_speed(250)    # x 고유의 스피드값을 500으로 설정함.
                              # y도 마찬가지
                              # 따라서 밑의 print문에서 스피드 값이 500, 250이라 예상됨

print(f'modelx.kinds: {modelx.kinds}')    # 클래스 변수인 kinds를 참조 -> ['x', 'y']
print(f'modely.kinds: {modely.kinds}')    # 클래스 변수인 kinds를 참조 -> ['x', 'y']
print(f'modelx.speed: {modelx.speed}')    # 클래스 변수인 speed를 참조 -> 100
print(f'modely.speed: {modely.speed}')    # 클래스 변수인 speed를 참조 -> 100
# 출력
modelx.kinds: ['x', 'y']
modely.kinds: ['x', 'y']
modelx.speed: 100
modely.speed: 100

위의 코드에서 kinds는 따로 인스턴스 변수로 선언이 되지 않았기 때문에 모든 객체가 공유하는 클래스 변수를 참조한다.

다음 코드를 실행하면 어떻게 될까?

class Car:
    kinds = []
    speed = 300

    def add_kinds(self, name):
        self.kinds.append(name)

    def change_speed(self, speed):
        self.speed = speed

modelx = Car()
modely = Car()

# Car.speed = 100

modelx.change_speed(500)    # 마찬가지로 x고유의 speed 변수가 500으로 설정됨
modely.change_speed(250)    # 마찬가지로 y고유의 speed 변수가 250으로 설정됨

print(f'modelx.speed: {modelx.speed}')    # 500
print(f'modely.speed: {modely.speed}')    # 250

위의 코드에서 클래스 변수인 speed는 그대로 300인 상태이다. 각 객체의 speed값만 변한 상태

조금 더 알아보도록 하자

speed = 300 # 전역변수 speed 300

def change_speed(value):
    speed = value # 지역변수 speed

change_speed(100)
speed # 출력: 300

위의 코드에서 인스턴스 안에 speed라는 전역변수와 이름은 똑같지만 지역변수인 speed가 정의되어 있다. 따라서 speed를 출력하면 전역변수인 300이 출력된다. 왜냐하면,, 지역변수는 그 함수 내에서만 효과가 있기 때문이다. 하나 더 보도록 하자.

kinds = [] # 전역변수 speed 300

def add_kinds(value):
    kinds.append(value) # add_kinds라는 지역에 해당 변수가 없으므로 
												# 전역변수 kinds에 append

add_kinds(100)
kinds    # kinds = [100]

조금 더 보도록 하자

class Car:
    default_speed = 100  # 클래스 변수 고정값

    def __init__(self, color):
        self.color = color  # 인스턴스 변수 -> 커스터마이즈!
class Car:
    kinds = []  # 클래스 변수 -> append()값을 추가 -> 재할당(x)
    speed = 300 # 클래스 변수 -> 인스턴스 변수!

    def add_kinds(self, name):
        self.kinds.append(name)  # 클래스 변수를 변경(기존값이 추가되는 형태)
																 # kinds라는 배열이 지역변수로 설정이 안되있기에
																 # 클래스의 kinds 배열을 참조한다!

    def change_speed(self, speed):
        self.speed = speed  # 각 객체 고유의 인스턴스 변수 생성 및 변경

# 각 인스턴스는 자신만의 speed 값을 가질수 있게 되는 형태로 원본은 변하지 않음

위 코드에서 kinds가 공유되지 않도록 구현한다면?

class Car:
    def __init__(self):
        self.kinds = []  # 인스턴스 변수

    def add_kinds(self, name):
        self.kinds.append(name)  # 인스턴스 변수를 변경

갑자기 init 메서드가 나와서 당황스러울텐데 이해를 해보자면 다음과 같다. 인스턴스 변수(인스턴스 객체 고유의 변수)는 self가 위치한 어디서나 선언이 가능하지만 보통 init 메서드 안에서 한다고 한다.

이는 생성자(constructor)라고도 불리며 언더바가 2개 있는 매직 메서드, 던더 함수라고 불린다.

이러한 init 메서드는 인스턴스 객체를 생성할 때 자동으로 실행된다. 이는 인스턴스 내부에서만 사용이 가능하다. 코드 예시를 보도록 하자.

class Car:
    max_speed = 300

    def __init__(self, name): # self는 자신만의 영역
        self.name = name

    def start(self, speed): # self는 자신만의 영역
        self.speed = speed
        return f'{self.name}차가 {self.speed}의 속력으로 움직이고 있습니다.'

modelx = Car('Tesla Model X')    # Tesla Model X라는 이름의 modelx 객체 생성
print(modelx.name)               # modelx 객체의 고유 name 출력
print(modelx.start(100))         # modelx 객체만의 고유 speed에 100 대입
# 출력
Tesla Model X
Tesla Model X차가 100의 속력으로 움직이고 있습니다.

self가 있는 곳에서 선언된 변수들은 인스턴스 영역으로 간주함. 즉, 고유의 값이다. 또한, 이러한 인스턴스 값은 아래와 같이 추가, 변경 가능하다.

class Car:
    max_speed = 300

    def __init__(self, name): # self는 자신만의 영역
        self.name = name

    def start(self, speed): # self는 자신만의 영역
        self.speed = speed
        return f'{self.name}차가 {self.speed}의 속력으로 움직이고 있습니다.'

modelx = Car('Tesla Model X')
modelx.name = 'ModelY'    # modelx 객체의 고유 이름
modelx.welcome = 'hello world'    # modelx 객체의 welcome 변수
print(modelx.name, modelx.welcome) # 출력: ModelY hello world

위의 modelx 객체에 welcome이라는 인스턴스 변수(고유의 변수)를 선언했고 이를 출력하면 당연히 고유의 이름과 함께 출력됨을 볼 수 있다.


다른 자료형의 인스턴스 변수 변경

# hello라는 함수도 class라는 것을 알 수 있다.
def hello():
    print('hello world')

print(type(hello)) # 출력: <class 'function'>

hello() 함수에 인스턴스 변수를 넣어보자

# 일반적으론 권장되지 않음!!
hello.hi = 'hi, world'    # hello()라는 함수 객체에 hi라는 속성을 추가
hello.hi # 출력: hi, world

그렇다면.. 클래스를 왜 쓸까?

- 기본 자료형보다 속도에서 이점(리스트에서 탐색과 트리구조에서 탐색을 생각해보자)
- 한번 선언한 클래스를 재사용 가능

피드백

클래스를 잘 안다뤄봐서 그런지 좀 어렵게 느껴졌다. 강의를 듣기만 할때는 잘 이해가 안가던 것을 16시 이후에 복습하며 정리하니 조금씩 이해가 가기 시작했다. 확실히 뭔가 적고 실천하는 것이 귀찮을 순 있어도 이해하기 쉬운 길이라는 것을 다시금 깨닫는다. 아 그리고.. 김진환 강사님이 오늘까지 수업하신다고 하는데 아쉽다. 강사님 덕에 카훗 퀴즈라는 것도 풀어보고 나름 즐겁게 공부한 것 같기 때문이다. 강사님 고생하셨습니다.

오늘. 드디어 소규모 프로젝트의 주제가 정해졌다. 음식점(맛집)을 추천해주는 프로그램인데 기본적으로 지도에 표시해 줄 예정이고 GPS는 가능하다면 반영하고 아니면 사용자가 대략적인 주소를 입력하면 그 근처 맛집을 보여주도록 할 예정이다. 맛집의 정보로는 상호명, 주소, 평점 등의 요소가 있겠는데.. 이는 1/12일까지 각자 우선순위(가중치)를 생각해서 말해보기로 하였다. 이게 정해지면 1/15일까지 맛집 데이터를 뭐 유튜브 2명 / 인터넷 서핑 3명 해갖고 csv형식을 찾아올 예정이다. 후에 각자 모은 데이터를 합쳐 EDA를 진행할 것이며, 잘 흘러가면 최종 프로젝트도 가능할 것이다. 기대가 되는 부분이다.

profile
익숙해지기 위해 기록합니다

0개의 댓글