코딩하는 타코야끼

[Python] 6강_객체지향 프로그 본문

[T.I.L] : Today I Learned/Python

[Python] 6강_객체지향 프로그

가스오부시 2023. 4. 5. 20:17
728x90
반응형

1. 객체지향 프로그래밍 (Object Oriented Programming)

  • 프로그램을 구성하는 변수와 함수를 서로 연관성있는 것 끼리 묶어서 모듈화하는 개발하는 언어들을 객체지향프로그래밍 언어라고 한다.

2. Instance(객체)

📍 정의

  • 연관성 있는 값들과 그 값들을 처리하는 함수(메소드)들을 묶어서 가지고 있는 것(값).
  • 객체의 구성요소
    • 속성(Attribute)
      • 객체의 데이터/상태로 객체를 구성하는 값들.
    • 메소드(method)
      • 객체가 제공하는 기능으로 주로 Attribute들을 처리한다.

📍Class(클래스) 정의

  • class란: 객체의 설계도
    • 동일한 형태의 객체들이 가져야 하는 Attribute와 Method들을 정의 한 것
      • 클래스를 정의할 때 어떤 속성과 메소드를 가지는지 먼저 설계해야 한다.
    • 클래스로 부터 객체(instance)를 생성한 뒤 사용한다.

  • 클래스 이름의 관례: 파스칼 표기법-각 단어의 첫글자는 대문자 나머진 소문자로 정의한다.
    • ex) Person, Student, HighSchoolStudent

📍 클래스로부터 객체(Instance) 생성

  • 클래스는 데이터 타입 instance는 값이다.

# 클래스 구현
class Person:
    # 메소드들 구현
    pass

📍 Attribute(속성)

  • attribute는 객체의 데이터, 객체가 가지는 값, 객체의 상태

🌓  객체에 속성을 추가, 조회

  • 객체의 속성 추가(값 변경)
    1. Initializer(생성자)를 통한 추가
    2. 객체.속성명 = 값 (추가/변경)
    3. 메소드를 통한 추가/변경
    • 1(Initializer)은 초기화할 때. 2, 3은 속성값을 변경할 때 적용.
  • 속성 값 조회
    • 객체.속성명
  • 객체.__dict__
    • 객체가 가지고 있는 Attribute들을 dictionary로 반환한다.
    # 객체 생성
    p = Person()
    # 객체가 가진attribute 들을 조회
    print(p.__dict__)
    >>>
    {}
    
    # attribute 추가 (2번 방식)
    p.name = '홍길동' # p.name -> p객체"의" name 변수
    p.age = 30
    p.address = "서울"
    print(p.__dict__)
    >>>
    {'name': '홍길동', 'age': 30, 'address': '서울'}
    

🌓  생성자(Initializer)

  • 객체를 생성할 때 호출되는 특수메소드로 attribute들 초기화에 하는 코드를 구현한다.
    • Inializer를 이용해 초기화하는 Attribute들이 그 클래스의 객체들이 가져야 하는 공통 Attribute가 된다.

구문)

class Person2:
    # self는 이 메소드의 소유객체이다 / "__init__"인스텐스 변수를 초기화하기위해 만들어진다.
    def __init__(self, name, age, address=None):
        """                                        
        Person 객체의 name, age, address 속성을 초기화.
        """
        # 속성에 값 대입 - self.변수명 = 값
        self.name = name
        self.age = age
        self.address = address

🌓  self parameter

  • 메소드는 반드시 한개 이상의 parameter를 선언해야 하고 그 첫번째 parameter를 말한다.
  • 메소드 호출시 그 메소드를 소유한 instance가 self parameter에 할당된다.
  • Initializer의 self
    • 현재 만들어 지고 있는 객체를 받는다.
  • 메소드의 self
    • 메소드를 소유한 객체를 받는다.
  • Caller에서 생성자/메소드에 전달된 argument들을 받을 parameter는 두번째 변수부터 선언한다.
p = Person2('강호동', 30, '광주')
print(p.name, p.age, p.address)
>>>
강호동 30 광주

🌓  Instance 메소드(method)

  • 객체가 제공하는 기능
  • 객체의 attribute 값을 처리하는 기능을 구현한다.

구문)

🌓  self (첫번째 매개변수)

  • 메소드를 소유한 객체를 받는 변수
  • 호출할 때 전달하는 argument를 받는 매개변수는 두번째 부터 선언한다.

  • 메소드 호출
    • 객체.메소드이름([argument, ...])
class Person3:
    
    def __init__(self, name, age, address=None):
        self.name = name
        self.age = age
        self.address = address
    
    def print_info(self): # argument가 없는 메소드
        # attribute 들을 출력하는 메소드
        print(f'이름: {self.name}, 나이: {self.age}, 주소 {self.address}')
        
    def add_age(self, age):
        # 나이에 전달받은 age를 더하는 메소드
        self.age += age
         # self.age = self.age + age
p2 = Person3("유관순", 20, "서울")
p2.print_info()
>>>
이름: 유관순, 나이: 20, 주소 서울
p2.add_age(2)
p2.print_info()
>>>
이름: 유관순, 나이: 22, 주소 서울
# p1의 이름 조회
print(p2.name)
# p1의 이름 변경
p2.name = ("리순신")
p2.print_info()
>>>
유관순
이름: 리순신, 나이: 22, 주소 서울

📍 정보 은닉 (Information Hiding)

  • Attribute의 값을 caller(객체 외부)가 마음대로 바꾸지 못하게 하기 위해 직접 호출을 막고 setter/getter 메소드를 통해 값을 변경/조회 하도록 한다.
    • 데이터 보호가 주목적이다.
    • 변경 메소드에 Attribube 변경 조건을 넣어 업무 규칙에 맞는 값들만 변경되도록 처리한다.
    setter
    • Attribute의 값을 변경하는 메소드. 관례상 set 으로 시작
    getter
    • Attribute의 값을 조회하는 메소드. 관례상 get 으로 시작

🌓  Attribute 직접 호출 막기

  • Attribute의 이름을 __(double underscore)로 시작한다. (__로 끝나면 안된다.)
  • 같은 클래스에서는 선언한 이름으로 사용가능하지만 외부에서는 그 이름으로 호출할 수 없게 된다.
class Person4:
    # name: 두글자 이상만 대입할 수 있다.
    # age : 10이상만 대입 할 수 있다.
    # address : 특별한 규칙이 없다.
    # nage, age 는 아무값이나 대입할 수 없도록 처리 -> 정보은닉
    def __init__(self, name, age, address):
        
        self.__name = None
        self.set_name(name) # 같은 클래스의 instance 메소드를 호출 -> self.메소드()

        self.__age = None
        self.set_age(age)
        self.address = address
        
    # 이름변경
    def set_name(self, name):
        if len(name) >= 2:
            self.__name = name
            
    # 이름조회
    def get_name(self):
        return self.__name
    
    # 나이변경
    def set_age(self, age):
        if age >= 10:
            self.__age = age
    
    #나이조회
    def get_age(self):
        return self.__age
    
    def print_info(self):
        print(f'이름: {self.__name}, 나이: {self.__age}, 주소: {self.address}')

🌓  getter / setter

# 정보 은닉으로 값 변경 불가능.
p2 = Person4('김', 5, "인천")
p2.print_info()
>>>
이름: None, 나이: None, 주소: 인천
# setter로 값 변경 가능.
p2.set_name('홍길동')
p2.set_age(30)
p2.address = '부산'
p2.print_info()
>>>
이름: 홍길동, 나이: 30, 주소: 부산

🌓  property함수를 사용

  • 은닉된 instance 변수의 값을 사용할 때 getter/setter대신 변수를 사용하는 방식으로 호출할 수 있도록 한다.
  • 구현
    1. getter/setter 메소드를 만든다.
    2. 변수 = property(getter, setter) 를 등록한다.
    3. 호출
      • 값조회: 변수를 사용 => getter가 호출 된다.
      • 값변경: 변수 = 변경할 값 => setter가 호출 된다.
class Person5:
    # name: 두글자 이상만 대입할 수 있다.
    # age : 10이상만 대입 할 수 있다.
    # address : 특별한 규칙이 없다.
    # nage, age 는 아무값이나 대입할 수 없도록 처리 -> 정보은닉
    def __init__(self, name, age, address):
        
        self.__name = None
        self.set_name(name) # 같은 클래스의 instance 메소드를 호출 -> self.메소드()
        
        self.__age = None
        self.set_age(age)
        self.address = address
        
    # 이름변경
    def set_name(self, name):
        if len(name) >= 2:
            self.__name = name
            
    # 이름조회
    def get_name(self):
        return self.__name
    
    # 나이변경
    def set_age(self, age):
        if age >= 10:
            self.__age = age
    
    #나이조회
    def get_age(self):
        return self.__age
    
    def print_info(self):
        print(f'이름: {self.__name}, 나이: {self.__age}, 주소: {self.address}')
        
        
        
        ### property() 함수를 이용해서 getter/setter를 변수에 등록.변수를 이용해 getter /setter 를 호출할 수 있게 해준다.
    name = property(get_name, set_name) # property(getter, setter)
    age = property(get_age, set_age)
p.name = '이순신'   # set_name()
p.age = 20         # set_age()
p.address = "부산" # address
print(p.name)      # get_name()
print(p.age)       # get_age()
print(p.address)   # address
>>>
이순신
20
부산

🌓  데코레이터(decorator)를 이용해 property 지정.

  • setter/getter 구현 + property()를 이용해 변수 등록 하는 것을 더 간단하게 구현하는 방식
  • setter/getter 메소드이름을 변수처럼 지정. (보통은 같은 이름으로 지정)
  • getter메소드: @property 데코레이터를 선언
  • setter메소드: @getter메소드이름.setter 데코레이터를 선언.
    • 반드시 getter 메소드를 먼저 정의한다.
    • setter메소드 이름은 getter와 동일해야 한다.
  • getter/setter의 이름을 Attribute 변수처럼 사용한다.
  • 주의: getter/setter 메소드를 직접 호출 할 수 없다. 변수형식로만 호출가능하다.
class Person6:
    def __init__(self, name, age, address=None):
        self.name = name  # setter name 메소드 호출
        self.age = age    # setter age 메소드 호출
        self.address = address
        
    @property    
    def name(self):
        print('getter name')
        return self.__name
    
    @name.setter
    def name(self,name):
        print('setter name', name)
        if len(name) >= 2:
            self.__name = name
            
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, age):
        if age >= 10:
            self.__age = age
            
    def print_info(self):
        print(f'이름: {self.name}, 나이: {self.age}, 주소: {self.address}')

p = Person6('홍길동', 30, '서울')
p.print_info()
>>>
setter name 홍길동
getter name
이름: 홍길동, 나이: 30, 주소: 서울

📍 상속 (Inheritance)

  • 기존 클래스를 확장하여 새로운 클래스를 구현한다.
    • 생성된 객체(instance)가 기존 클래스에 정의된 Attribute나 method를 사용할 수있고 그 외의 추가적인 member들을 가질 수 있는 클래스를 구현하는 방법.
  • 상위 클래스와 하위클래스는 계층관계를 이룬다.

🌓  기반(Base) 클래스, 상위(Super) 클래스, 부모(Parent) 클래스

  • 물려 주는 클래스.
  • 상속하는 클래스에 비해 더 추상적인 클래스가 된다.
  • 상속하는 클래스의 데이터 타입이 된다.

🌓  파생(Derived) 클래스, 하위(Sub) 클래스, 자식(Child) 클래스

  • 상속하는 클래스.
  • 상속을 해준 클래스 보다 좀더 구체적인 클래스가 된다.

🌓  다중상속과 단일 상속

  • 다중상속
    • 여러 클래스로부터 상속할 수 있다
  • 단일상속
    • 하나의 클래스로 부터만 상속할 수 있다.
  • 파이썬은 다중상속을 지원한다.
  • MRO (Method Resolution Order)
    • 다중상속시 메소드 호출할 때 그 메소드를 찾는 순서.
    1. 자기자신
    2. 상위클래스(하위에서 상위로 올라간다)
      • 다중상속의 경우 먼저 선언한 클래스 부터 찾는다. (왼쪽->오른쪽)
  • MRO 순서 조회
    • Class이름.mro()
class Person:
    
    def go(self):
        print('학교에 간다.')
        
    def eat(self):
        print("점심을 먹는다.")

# Person을 상속받아서 Student/Teacher 클래스를 구현
class Student(Person): # 클래스르이름(상속할 클래스 이름.[, 상속할 클래스 이름,.....])
    
    def study(self):
        print("학생이 공부한다.")

class Teacher(Person):
    
    def teach(self):
        print("수업을 가르친다.")

class UniversityStudent(Student):
    
    def drink(self):
        print("술을 마신다.")

🌓  Method Overriding (메소드 재정의)

  • 기반 클래스의 메소드의 구현부를 파생클래스에서 다시 구현하는 것을 말한다.기반 클래스는 모든 파생클래스들에 적용할 수 있는 추상적인 구현밖에는 못한다.이 경우 파생클래스에서 그 내용을 자신에 맞게 좀더 구체적으로 재구현할 수 있게 해주는 것을 Method Overriding이라고 한다.방법은 파생클래스에서 overriding할 메소드의 선언문은 그래로 사용하고 그 구현부는 재구현하면 된다.

🌓  super() 내장함수

  • 파생 클래스에서 기반 클래스의 instance를 반환(return) 해주는 함수
  • 구문

  • 기반 클래스의 Instance 메소드를 호출할 때 – super().메소드()
    • 특히 method overriding을 한 클래스에서 기반 클래스의 overriding한 메소드를 호출 할 경우 반드시 super().메소드() 형식으로 호출해야 한다.
  • 같은 클래스의 Instance 메소드를 호출할 때 – self.메소드()
## 부모 클래스
class Person:
    
    def __init__(self, name, age, address = None):
        self.name = name
        self.age = age
        self.address = address
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self,name):
        if len(name) >= 2:
            self.__name = name
            
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, age):
        if age >= 10:
            self.__age = age
            
     #나이값을 더하는 메소드       
    def add_age(self, age):
        self.age += age
        
    # Person의 Attribute를(name, age, address)를 하나의 문자열로 만들어서 반환
    
    def get_info(self):
        return f'이름: {self.name}, 나이: {self.age}, 주소: {self.address}'
## 자식클래스

# Person 속성: name, age, address, 
# Student 속성: grade(성적)
class Student(Person):
    
    def __init__(self, name, age, address, grade):
        #name, age, address는 부모객체에 초기화 -> Person클래스의 __init__()을 호출.
        super().__init__(name, age, address) # 부모클래스의 __init__()메소드를 호출. - "super()"
        self.grade = grade
 #_______________________________________________________________________________ #      
    #get_info()메소드를 overriding -> grade 정보까지 추가
    
    def get_info(self):
        # name, age, address는 Person의 get_info()를 이용해서 조회
        i = super().get_info()
        return f'{i},  성적: {self.grade}'

🌓  객체 관련 유용한 내장 함수, 특수 변수

  • isinstance(객체, 클래스이름-datatype) : bool
    • 객체가 두번째 매개변수로 지정한 클래스의 타입이면 True, 아니면 False 반환
    • 여러개의 타입여부를 확인할 경우 class이름(type)들을 리스트로 묶어 준다.
    • 기반클래스는 파생클래스객체의 타입이 되므로 객체와 그 객체의 기반클래스 비교시 True가 나온다.
  • 객체.__dict__
    • 객체가 가지고 있는 Attribute 변수들과 대입된 값을 dictionary에 넣어 반환
  • 객체.__class__
  • 객체의 타입을 반환
isinstance(p, Person)
isinstance('abc', Person)
isinstance(s, Student)
#객체(값)의 타입 -> 생성한 클래스, 생성한 클래스의 상위클래스
# s(Student객체)의 타입 : Sturdent, Person
>>>
True
-------------------------
isinstance(10,int)
print(type(10))
type(10) ==int, isinstance(10,int)
>>>
<class 'int'>
(True, True)

📍 특수 메소드

🌓  특수 메소드란

  • 특정한 상황에서 사용될 때 자동으로 호출되도록 파이썬 실행환경에 정의된 약속된 메소드들이다. 객체에 특정 기능들을 추가할 때 사용한다.
    • 정의한 메소드와 그것을 호출하는 함수가 다르다.
      • ex) __init__() => 객체 생성할 때 호출 된다.
  • 메소드 명이 더블 언더스코어로 시작하고 끝난다.
    • ex) __init__(), __str__()
  • 매직 메소드(Magic Method), 던더(DUNDER) 메소드라고도 한다.
  • 특수메소드 종류

🌓  주요 특수메소드

  • __init__(self [, …])
    • Initializer
    • 객체 생성시 호출 된다.
    • 객체 생성시 Attribute의 값들을 초기화하는 것을 구현한다.
    • self 변수로 받은 instance에 Attribute를 설정한다.
  • __call__(self, […])
    • 객체를 함수처럼 호출 할 면 실행되는 메소드
    • Argument를 받을 Parameter 변수는 self 변수 다음에 필요한대로 선언한다.
    • 처리결과를 반환할 경우 return value 구문을 넣는다. (필수는 아니다.)
  • __repr__(self)
    • Instance(객체) 자체를 표현할 수 있는 문자열을 반환한다.
      • 보통 객체 생성하는 구문을 문자열로 반환한다.
      • 반환된 문자열을 eval() 에 넣으면 동일한 attribute값들을 가진 객체를 생성할 수 있도록 정의한다.
    • 내장함수 repr(객체) 호출할 때 이 메소드가 호출 된다.
    • 대화형 IDE(REPL) 에서 객체를 참조하는 변수 출력할 때도 호출된다.
    • eval(문자열)실행 가능한 구문의 문자열을 받아서 실행한다.
  • __str__(self)
    • Instance(객체)의 Attribute들을 묶어서 문자열로 반환한다.
    • 내장 함수 str(객체) 호출할 때 이 메소드가 호출 된다.
      • str() 호출할 때 객체에 __str__()의 정의 안되 있으면 __repr__() 을 호출한다. __repr__()도 없으면 상위클래스에 정의된 __str__()을 호출한다.
      • print() 함수는 값을 문자열로 변환해서 출력한다. 이때 그 값을 str() 에 넣어 문자열로 변환한다.
class Plus:
    
    def __init__(self, num1, num2):
        # 덧셈할때 사용할 피연산자 두개를 속성으로 저장.
        self.num1 = num1
        self.num2 = num2
    
    def __call__(self, n):
        #객체의 메인 기능(동작)을 구현하는 메소드 -> 객체를 함수처럼 사용할 수 있도록 한다.
    
        return (self.num1 +self.num2)**n
 #   def calc(self):
#      return self.num1 + self.num2
class Person: ###부모 클래스
    
    def __init__(self, name, age, address = None):
        self.name = name
        self.age = age
        self.address = address
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self,name):
        if len(name) >= 2:
            self.__name = name
            
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, age):
        if age >= 10:
            self.__age = age
            
     #나이값을 더하는 메소드       
    def add_age(self, age):
        self.age += age
        
    #특수메소드 정의
    def __repr__(self):
        return f"Person('{self.name}', {self.age}, '{self.address}')" # 객체에 무엇이 들어있는지
    
    def __str__(self):
        return f'이름: {self.name}, 나이: {self.age}, 주소: {self.address}' #객체의 상세내용
    
    # 연산자 재정의 특수메소드
    # 덧셈
    def __add__(self, other):
  #      other 가 숫자이면 self.age에 더한다.
  #      other 가 다른 Person객체이면 그 객체의 age에 self의 age를 더한다.
  #      p + 30 (self:p, other:30)
        
        if isinstance(other, (int,float)):
            return self.age + other
        elif isinstance(other, Person):
            return self.age + other.age
        
    def __gt__(self, other):
        
        #self 객체와 other 객체의 나이를 비교
        if isinstance(other, Person):
            return self.age > other.age
        
        else:
            raise TypeError(f'Person과 {type(other)}는 비교할 수 없습니다') #Exception을 발생시킨다.

📍 연산자 재정의(Operator overriding) 관련 특수 메소드

  • 연산자의 피연산자로 객체를 사용하면 호출되는 메소드들
  • 다항연산자일 경우 가장 왼쪽의 객체에 정의된 메소드가 호출된다.
    • a + b 일경우 a의 __add__() 가 호출된다.

🌓  비교 연산자

  • __eq__(self, other) : self == other
    • == 로 객체의 내용을 비교할 때 정의 한다.
  • __lt__(self, other) : self < other, gt(self, other): self > other
    • min()이나 max()에서 인수로 사용할 경우 정의해야 한다.
  • __le__(self, other): self <= other
  • __ge__(self, other): self >= other
  • __ne__(self, other): self != other

🌓  산술 연산자

  • __add__(self, other): self + other
  • __sub__(self, other): self - other
  • __mul__(self, other): self * other
  • __truediv__(self, other): self / other
  • __floordiv__(self, other): self // other
  • __mod__(self, other): self % other

3. class변수, class 메소드

📍 class변수

  • (Intance가 아닌) 클래스 자체의 데이터
  • Attribute가 객체별로 생성된다면, class변수는 클래스당 하나가 생성된다.
  • 구현
    • class 블럭에 변수 선언.

📍 class 메소드

  • 클래스 변수를 처리하는 메소드
  • 구현
    • @classmethod 데코레이터를 붙인다.
    • 첫번째 매개변수로 클래스를 받는 변수를 선언한다. 이 변수를 이용해 클래스 변수나 다른 클래스 메소드를 호출 한다.

4. static 메소드

📍 정의

  • 클래스의 메소드로 클래스 변수와 상관없는 단순기능을 정의한다.
    • Caller 에서 받은 argument만 가지고 일하는 메소드를 구현한다.
  • 구현
    • @staticmethod 데코레이터를 붙인다.
    • Parameter에 대한 규칙은 없이 필요한 변수들만 선언한다.

📍 class 메소드/변수, static 메소드 호출

  • 클래스이름.변수
  • 클래스이름.메소드()
class Calculator:
    PI = 3.14 #클래스 변수
    
    @classmethod
    def circle_area(clazz, radius):
        return radius * radius * clazz.PI #클래스 변수 호출.
    
    @staticmethod # 함수로 사용하기엔 거의 사용 안함
    def square_area(width, height):
        return width * height
****
# 클래스 변수 호출: 클래스이름.변수명
print(Calculator.PI)
>>>
3.14
반응형

'[T.I.L] : Today I Learned > Python' 카테고리의 다른 글

[Python] 8강_예외처리  (0) 2023.04.09
[Python] 7강_패키지 모듈 import  (0) 2023.04.05
[Python] 5강_함수  (0) 2023.04.04
[Python] 4강_제어문, 컴프리헨션  (0) 2023.04.04
[Python] 3강_자료구조  (0) 2023.04.04