IT

[코딩온] 신재생에너지 IoT 개발자 입문_ 파이썬11

혈중당부족 2025. 12. 15. 17:09

 

13 class

 

같이 수업 받는 수강생분이 클래스도 분량이 많다고 들었는데... 얼마나 걸릴 지 알 수 없지만 하나로 넣기 위해 노력해보고자 한다.

class 들어가기 전에 기초OOP를 먼저 배웠다. 그래야 조금 더 이해할 수 있다며.

 

객체 지향 프로그래밍

OOP, Object-Oriented Programming

: C++ 이 대표적인 객체 지향 프로그래밍인데, 객체를 기반으로 프로그램을 설계하는 패러다임 같은 거다. 

방법론의 어떤 하나 같은 거라서, OOP라고 하면  상호작용하는 객체들의 집합이라 보는 거다. 함수 지향이면 값이 없는 함수들을 연속으로 나온다 같이...  

 

구체적으로 궁금하면 이 쪽 링크로... Plato and Object Oriented Programming | Richard Farrar

 

Plato and Object Oriented Programming | Richard Farrar

Some of the fundamental concepts behind today's Object Orientated Programming methods have direct analogies with Plato's metaphysical constructs almost 2,500 years ago. Was Plato the Great Great Grandfather of modern computing?

www.richardfarrar.com

 

 

여기서 말하는 객체는 데이터와 데이터를 조작하는 기능(메서드)을 하나로 묶은 독립적인 실행 단위 다. 자동차의 부품같이 여러개 모아서 상호작용을 해 자동차를 움직이게 하는 형식으로. 부품과 유사하다. 

속성과 행동을 함께 묶은 객체 여러개를 모델링해 구현한다 느낌이다.

 

여기의 예시도, Car 안에 여러 함수(행동)이 들어있고 변수(속성)을 기입해 Car는 이렇다고 구현한 거다.

그러면 OOP의 핵심 개념이 무엇인가.

 

1 캡슐화 (Encapsulation)

객체 내부 데이터를 외부에서 접근하지 못하도록 보호.

데이터 보호를 통해 잘못된 값이 저장되는 걸 방지하고, 코드가 바뀌어도 외부에서 영향 받지 않고 사용한다.(모듈)

 

2 상속 (Inheritance)

기존 클래스를 확장하여 새로운 기능을 추가

코드 재사용성을 높이고 유지보수를 쉽게 만든다. 기존 클래스를 기반으로 새로운 기능을 추가하는 거다.

 

3 다형성 (Polymorphism)

같은 함수를 서로 다른 방식으로 실행.

새로운 클래스 추가할 때 기존 코드 변경할 필요가 없고 같은 인터페이스를 사용해 다양한 기능을 쉽게 다룬다.

 

4 추상화 (Abstraction)

필요한 정보만 보여주고 불필요한 내부 구현을 숨긴다.

핵심 기능만 제공해 코드 관리가 쉬워지고, 공통적인 동작을 정의. 세부 구현은 각 클래스에서 따로 처리 가능하다. 

 

 

여기까지 OOP의 기초를 적었으니 본격적으로 클래스에 들어가자.

 

class 

클래스는 데이터 ( 속성 “ ) 와 기능 ( 메서드 ) 을 하나로 묶는 구조

 

새로운 형식을 정의하기에 객체를 만드는 틀이 된다.

객체는 클래스(틀)에서 만들어지는 것으로 '인스턴스'라고 부르기도 함. 

클래스는 객체 만드는 붕어빵 틀이고, 객체는 붕어빵이라 틀 통해 여러개 붕어빵이 나오기도 한다.(하나의 클래스에 여러개 객체) 그리고 그 붕어빵들 중 하나 먹어도 나머지가 온전하듯이 객체끼리 상호작용하지 않는다. 그럼 객체는, 속성을 가지는 모든 것인데 그 속성은 여기서 클래스, 또는 객체에 소속된 변수(필드)와 함수(메서드)를 가리킨다. 

 

클래스 기분 문법
클래스의 정의
class ClassName: # PascalCase 형태로 작성
    #생성자(comstructor): 인스턴스(객체)가 생성될 때 호출
    #인스턴스 변수를 초기화, 기본 상태 설정
    #하나의 클래스 에서 하나만 정의 가능
    def __init__ (self, name):
        # 인스턴스 변수
        # self : 인스턴스 자기 자신을 가리킴
        self.name = name #매개변수
        self.age = 0 #임의로 넣은 값

    #(인스턴스) 메서드
    def method_name(self): #메서드는 주로 스네이크 케이스 양식 씀
        print(f"이 인스턴스 이름은 {self.name} 입니다.")

 

강의 중 일부 설

물론 호출 시 파이썬은 자동으로 할당하므로. self에 직접 값을 넣지 않아도 된다. 이 self는 매개변수 없는 메소드를 정의할 때도 필수로 들어가는데 이는 '식별자'이기 때문이다. 

객체를 인스턴스라고 부르는 건, 클래스의 객체를 관계 중점으로 표현할 때 사용함. 

 

# 클래스 정의
class Person:
    # 메소드 정의
    def say_hello(self):
        print('Hi!! Nice to meet you.')

p = Person() # 객체 p
p.say_hello() # 메소드 호출

>> Hi!! Nice to meet you.로 출력

#인스턴스 생성
my_instance = ClassName("I1") #name 변수 받기로 했으니, 문자열 입력
print(my_instance.name)
my_instance.method_name()
코드 실행 출력 값

 

생성자 (__ self : init __) : 객체 생성될 때 자동 호출되는 초기화 메서드

self : 생성되는 개체 자신을 가리킴

인스턴스 변수: 객체마다 독립적으로 유지되는 변수 (self. 변수명)

메서드: 클래스 내부에 정의된 함수. 첫 번째 인지로 self 사용

 

 

 

생성자 __ init __() 함수

위에도 썼지만 클래스로부터 객체(인스턴스)를 생성할 때 자동으로 호출되는 초기화 함수

필요한 속성 초기화 및 기본 상태 설정에 쓴다.

하나의 클래스에, 하나의 __ init __() 만 정의 가능하다.

실습
책 클래스 만들기
인스턴스 변수는 title, author, total_pages, current_pages
메서드는 read_page (현재 페이지를 읽음, 총 페이지 수 넘기지 않게 처리) / progress(얼마나 읽었는지 %로 소수점 1까지 출력)
 
 
class BookClass:
    def __init__(self,title,author,total_pages,current_pages):
        self.title = title
        self.author = author
        self.current_pages = current_pages
        self.total_pages = total_pages

    def read_page(self,page):
       self.current_pages += page #입력한 값(page)을 더해서 할당
       if self.current_pages > self.total_pages:
           self.current_pages = self.total_pages
           #초과할 시 total과 동일하게 해 넘기지 않도록 한다.
   
    def progress(self): #함수 생성
        percent = (self.current_pages / self.total_pages) * 100
        #읽은 페이지 나누기 총 페이지에 100 곱한다.        
        print(f"현재 읽은 분량: {percent: .1f}%")
        #소수점 한자리 값까지만 보여준다. (.1f)

book1 = BookClass("넥서스","유발 할라리", 500, 1)
book1.read_page(10) #10 추가하겠다.
book1.progress()

BookClass에 타이틀, 작가, 총 페이지(500), 시작 페이지(1)를 넣었다. 그리고 

read_page 함수에 변수 10을 넣었고, page(10)은  current_pages에 할당되었다.

조건문으로 현재 페이지 수가 총 페이지 수보다 커지면 똑같다로 정의해둔다.

그리고 progress 함수는 백분율 계산을 하고 출력하는 걸로 코드 작성. 여태 넣은 데이터를 바탕으로 계산해 코드 실행 시키면 2.2%만 출력된다.

 

Rectangle 클래스 구현
인스턴스 변수 width, heigh
메서드는 area() 사각형의 넓이 반환, 사용자가 직접 입력해 계산 되도록 한다.
 
class Rectangle_Mathod:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

Rectangle = Rectangle_Mathod(int(input("가로 값: ")),int(input("세로 값: ")))
print(f"사각형의 넓이는 {Rectangle.area()}")
 

 

class에 변수 지정할 때 입력된 값을 변수로 저장한다.

그리고 print에서  Rectangle(변수).area () 를 실행해 그 값을 반환한다.

 

클래스 변수 : 클래스가 가지고 있는 변수
모든 인스턴스가 공유할 수 있다.

 

클래스로부터 생성된 인스턴스(객체)에 속하는 변수

각각의 객체에 속해 서로 공유되지 않고 영향받지 않음

 

클래스 자체에 속하는 변수

그 클래스로부터 생성된 모든 객체가 공유

객체를 생성하지 않아도 사용 가능

 

객체의 전체 수를 계하기 위해서면 클래스 변수를 만들어서 모든 객체가 공유해야 함. 객체가 생성될 때마다 변경해야 하므로.

예시 코드

class Person:
    count = 0 # 클래스 변수
   
    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.count = Person.count + 1 # 객체가 생성될 때 마다 증가

    def say_hello(self):
        print('Hi!! Nice to meet you.')

    def about_me(self):
        print('I am ' + self.name + ' (' + str(self.age) + ')')


print('******* 총 ' + str(Person.count) + '명 *******')
suzy = Person('Suzy', 20)
suzy.say_hello()
suzy.about_me()
print('******* 총 ' + str(Person.count) + '명 *******')

minho = Person('Minho', 23)
minho.say_hello()
minho.about_me()
print('******* 총 ' + str(Person.count) + '명 *******')

yeji = Person('Yeji', 21)
yeji.say_hello()
yeji.about_me()
print('******* 총 ' + str(Person.count) + '명 *******')

 

여기서 클래스 변수는 클래스 내부와 외부에서 접근할 때 [클래스명.변수명] 으로 쓴다.

그러니 여기서 클래스 변수를 확인하려면 Person.count 라고 작성한다.

 

 

class Dog:
    #클래스 변수
    kind = "강아지"

    def __init__(self, species, name, age):
        self.species = species
        self.name = name
        self.age = age

dog1 = Dog("포메라니안", "리치", 12)
dog2 = Dog("비숑", "비수", 6)
print("인1", dog1.kind)
print("인2", dog2.kind)
print("클래스", Dog.kind)

 

 

 

클래스 메서드
클래스 자체를 대상으로 동작하는 메서드
클래스 데이터를 조작하는데 사용

예제
 
class Book_1:
    book_count = 0 #책_카운트 변수는 0으로 시작한다.

    def __init__(self, title, author):
        Book_1.book_count += 1
        self.title = title
        self.author = author
        #제목, 작가 입력될 때마다 book_count가 1씩 올라간단 뜻이다.

    # 클래스 메서드
    @classmethod #데코레이터 (설명해준다 맥락)
    def get_count(cls):
        print(f"현재 {cls.book_count}권의 책을 가지고 있다.")

book1 = Book_1("a1","abc")
book2 = Book_1("a2","def")
print(Book_1.book_count)
Book_1.get_count()

 

Book_1에 (인자 2개) 변수가 2개 추가되었기에 count값이 올라갔고, 현재 class를 호출하니 count가 2인 걸 알려준다. 

 

 

정적 메서드
클래스나 인스턴스의 데이터를 조작하지 않는 클래스 메서드
클래스나 인스턴스의 상태를 의존하지 않는 일반 함수(self나 cls를 사용 안 한다.)
개념적으로는 클래스와 연관이 있으나, 클래스나 인스턴스의 데이터를 조작하지 않음.

class OperationTool:

    @staticmethod #데코레이터
    def add (a, b):
        return a + b
   
print(OperationTool.add(10, 20)) #곧바로 30이란 결과값이 나온다.

 
 
기록하면서 배운 것들을 요약하면 이렇다.

 

 
 

 


실습 클래스
User 클래스구현
 
인스턴스 변수 : username, points (초기값은 0)
클래스 변수: total_users
메서드
add_point(amount): 포인트 추가
get_level(): 포인트 기준으로 레벨 반환(설정) 0~99: Bronze, 100~499: Silver, 500 이상 : Gold
클래스 메서드: get_total_users () > 총 유저 수 출력
 
 
class User:
    total_users = 0

    def __init__(self,username,points):
        self.username = username
        self.points = points
        User.total_users += 1
        #이름, 점수 입력될 때마다 total 유저 수 1 증가

    #모범 답안
       #def __init__(self,username,points):
       #self.username = username
       #self.points = 0
       #User.total_users += 1
         #이름 입력 될 때마다 유저 수 증가로 설정.

    def add_points(self, amount):
        self.points += amount
        #amount는 self.points 값이 더해지는 값과 같다.

    def get_level(self):
        if 0 <= self.points < 100:
            return "Bronze"
        elif 100 <= self.points < 500:
            return "Silver"
        else:
            return "Gold"

    @classmethod
    def get_total_users(cls):
        print(f"총 유저 수는 {cls.total_users} 입니다.")

       
User1 = User("a", 200)
User2 = User("b", 500)
print(User1.get_level())
print(User2.get_level())
User.get_total_users()

#User1.add.points(100)
#def add_points(self, amount) 메서드 쓰기 위해서 입력하는 숫자값 맥락.

 

 

 

 

접근 제어와 정보 은닉
데이터 무결성을 보호하기 위함
코드 안정성을 향상 시키기 위함

파이썬에서 일반적으로 정의한 인스턴스 변수는 기본적으로 모두 public

 

protected

_ 변수명 으로 표현

명시적으로 표현하기 위해 사용

외부에서 직접 접근이 가능하나 캡슐화를 깨트리는 행위므로 권장하지 않는다.

 

private

__ 변수명 으로 표현 (underscore 두 번)

네임 맹글링 (name mangling )

내부적으로 _{ 클래스명 }__ 변수명으로 이름이 변경되어 접근을 어렵게 한다.

_{ 클래스명 }__ 변수명으로 접근할 수 있지만 권장되지 않는다. 

 
getter: 객체 내부 속성 값을 읽을 수 있도록 외부에서 제공하는 메서드
setter: 객체 내부 속성 값을 변경할 수 있도록 외부에서 제공하는 메서드
 
외부에서 직접 변수에 접근하지 못하도록 하고 , 메서드를 통해 접근할 때 사용
 
class Person:
    def __init__(self, age):
        self._age = age  

    @property
    def age(self):
        """Getter"""
        return self._age

    @age.setter
    def age(self, value):
        """Setter"""
        if value < 0:
            raise ValueError("나이는 0 이상이어야 합니다.")
        self._age = value

p = Person(20)

print(p.age)     # Getter 호출 → 20

p.age = 30       # Setter 호출
print(p.age)     # 30

 

p.age를 일반 변수처럼 입력하면 Setter가 호출해서 사용한다. 내부에서는 함수가 호출됨.

변수처럼 쓰이지만 함수와 같이 제어가 가능하다.


class Person2:
    def __init__(self, name, age):
        #public
        self.name = name
        #private : self 언더바(__) 두개 변수 앞에 붙여서 정의
        self.__age = age

    #getter
    def get_age(self):
        return self.__age
   
    #setter
    def set_age(self, value):
        if value > 120 or value < 0:
            print("유효하지 않습니다.")
        else:
            self.__age = value

p1 = Person2("lee", 20)
print(p1.name)
#print(p1.__age)
#AttributeError: 'Person2' object has no attribute '__age' 뜸.
print(p1.get_age())
p1.set_age(-10)
 
 
@property 데코레이터
메서드를 속성처럼 보이게 만들어주는 데코레이터
보통 데코레이터를 사용해 구현한다.

class Ex:
    def __init__(self):
        self.__value = 0
   
    #getter
    @property
    def value(self):
        return self.__value #위의 getter양식과 동일
   
    #setter
    @value.setter
    def value(self, val):
        if val < 0:
            print("유효하지 않은 값입니다.")
        else:
            self.__value = val

ex1 = Ex()
print(ex1.value) #ex1.value() 안해도 0으로 출력
ex1.value = 100
print(ex1.value) #setter 작동함.
ex1.value = -100 #유효하지 않은 값입니다.
print(ex1.value)

 

실습
UserAccount 클래스 비밀번호 보호

class UserAccount:
    def __init__(self, username, password):
        self.username = username
        self.__password = password

    def change_password(self, old_pw, new_pw):
        if self.__password != old_pw:
            return "비밀번호 불일치"
        else:
            print("비밀번호 변경되었습니다.")
            return new_pw

    #setter
    def set_check(self, value):
        if value != self.__password:
            return False
        else:
            return True


user1 = UserAccount("asd", 123456)

print(user1.set_check(1234567)) #false
user1는 클래스 정보에 저장되었고, 들어가있던 변수 123456과 방금 입력한 1234567를 비교함.
 
print(user1.set_check(123456)) #ture
 
print(user1.change_password(123, 123789)) #비밀번호 불일치
print(user1.change_password(123456, 1123)) #비밀번호 변경되었습니다. (변경 번호)
print(user1.set_check(1111)) #false

 

추후에 받은 모범 답안
class UserAccount:
    def __init__(self, username, password):
        self.username = username
        self.__password = password

    def change_password(self, old_pw, new_pw):
        if old_pw == self.__password:
            self.__password = new_pw
            print("비밀번호 변경되었습니다.")
        else:
            print("기존 비밀번호와 일치하지 않습니다.")

    #setter
    def check_password(self, password):
        return self.__password == password
   
user1 = UserAccount("asd", 123456)

print(user1.username)
#print(user1.__password)
user1.check_password("123456")

 

 


Student 클래스: 성적검증(@property 사용)

class Student:
    def __init__(self,score):
        self.__score = score # private 선언

    #getter
    @property
    def get_score(self):
        return self.__score
   
    #setter
    @get_score.setter
    def set_ver(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            raise ValueError("유효하지 않습니다.")

s1 = Student(80)
print(f"이 학생의 점수는 {s1.set_ver}점 입니다.")
s1.set_ver = -120
print(s1.set_ver) # ValueError 작동

 

 

 

상속
부모 클래스의 속성과 메서드를 물려받아 새로운 자녀 클래스를 만드는 것
코드 재사용성 높이고 공통 기능은 부모 클래스에서 정의, 자식 클래스에서 확장 또는 수정한다. 

상속 기본 문법
부모 클래스

class Animal:
    def __init__(self,name):
        self.name = name

    def bark(self):
        print("동물 소리를 냅니다.")

class Dag(Animal):
    pass

# super() 사용

class Animal:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def bark(self):
        print("동물 소리를 냅니다.")

class Dog(Animal):
    def __init__(self, name, age, species):
        super().__init__(name, age)
        #__init__ 누르자마자 바로 부모 클래스 메서드 갖고 왔다;
        # super는 부모 가리킴
        self.species = species

    #오버라이딩
    def bark(self):
        super().bark() #부모 클래스의 메서드도 적용
        print("멍멍")



dog = Dog("구름이",5, "포메라니안")
dog.bark()
print(dog.name)
print(dog.species) #부모 클래스에 없는 속성값을 추가해도 적용 된다.

 

 

super() 함수 : 부모 생성자 호출

자식 클래스에서 부모 클래스의 요소를 명시적 클래스명 없이 호출할 때 사용.

super().메서드명(인자) 구조: super().__init__() 로 많이 사용

상속 구조가 변경되어도 자동 추적이 된다. 

 

메서드 오버라이딩 (Overriding)

부모 클래스의 메서드를 자식 클래스에서 동일한 이름으로 다시 정의.

기존 기능을 새로운 방식으로 변경하거나 특화된 동작 실행.

=> 자식 클래스에서 부모클래스에 사용한 메서드를 덮어씌워 실행한다. 

 

 

 실습
class Shape:
    def __init__(self,sides,bass):
        self.sides = sides
        self.bass = bass

    def printlnfo(self):
        return print (f"변의 개수: {self.sides} , 밑변의 길이: {self.bass}")
   
    def area(self):
        print("넓이 계산이 정의되지 않았습니다.")

class Rectangle(Shape):
    def __init__(self, sides, bass, heigh):
        super().__init__(sides, bass)
        self.heigh = heigh

    def area(self):
        area = self.heigh * self.bass
        print (f"{self.sides}사각형의 넓이: {area}")
   
class Triangle(Shape):
    def __init__(self, sides, bass, heigh):
        super().__init__(sides, bass)
        self.heigh = heigh

    def area(self):
        area = int((self.bass * self.heigh) / 2) #area 값은 이거다의 계산식으로 수정
        print(f"{self.sides}의 넓이 {area}")
   
rect = Rectangle(4,10,5)
rect.printlnfo()
rect.area()

tri = Triangle(3,8,10)
tri.printlnfo()
tri.area()

 

 

 

 

추상 클래스 (파이썬에만 있는 개념은 아니다)
(Abstract Class)
클래스의 구조를 정의하는 클래스

 

직접 인스턴스를 만들 수 없고 반드시 자식 클래스에서 구현 완성 해야한다. 

공통적인 구조는 정의하되, 구체적인 동작을 상속받은 클래스에서 하도록 강제하기 위함.

 

 

from abc import ABC, abstractmethod #abc에서 ABC, abstractmethod 가져온다.
구현하기 위해 모듈을 갖고와야 하고, ABC를 반드시 상속. 직접 인스턴스화 불가능.
 

class Animal(ABC):
    #추상 메서드
    @abstractmethod #자식 클래스에서 꼭 구현해라
    def bark(self):
        pass
    #메서드는 여러개 만들어도 됨.

class Dog(Animal):
    def bark(self): #부모 메서드에 있는 걸 갖고와 온전히 구현해둬야 한다.
        print("멍멍")

#a = Animal() #TypeError: Can't instantiate abstract class Animal 에러남.
a = Dog()
a.bark() #멍멍

 

실습
Payment 구현

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def pay(self,amount):
        pass

class CardPayment(Payment):
    def pay(self, amount):
        self.amount = amount
        print(f"카드로 {self.amount}원을 결제합니다")

class CashPayment(Payment):
    def pay(self, amount):
        self.amount = amount
        print(f"현금으로 {self.amount}원 결제합니다.")

amo = CardPayment()
amo.pay(15000)
amo1 = CashPayment()
amo1.pay(20000)

 

#이후 받은 모범 답안
 
from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def pay(self,amount):
        pass

class CardPayment(Payment):
    def __init__(self):
        #pass 가능, 알아서 동작해줌.
        super().__init__()

    def pay(self, amount):
        self.amount = amount
        print(f"카드로 {self.amount}원을 결제합니다")

class CashPayment(Payment):
    def pay(self, amount):
        self.amount = amount
        print(f"현금으로 {self.amount}원 결제합니다.")

 

 

 

처음 인터넷 강의 들었을 때는 클래스 정의, 용법 정도로 익혔는데 실제 수업 듣고 실습하면서 여러 메서드, 데코레이터 등 확장되는 영역이 많으니 3일간 조금씩 매달리며 쓰게 됐다. 사실 갈 길이 멀지만 내가 이렇게 써두지 않으면 또 펼쳐볼 게 뻔하니까. 이렇게 클래스 부분 기록 끝!