정보 은닉(Information Hiding) : Attribute의 값을 caller(객체 외부)가 바꾸는 것을 방지하기 위해 직접 호출을 막는다.
setter/getter 메소드를 통해 값을 변경/조회 하도록 한다.
- 데이터 보호가 주 목적이다.
Attribute 직접 호출 막기
보호하고 싶은 Attribute의 이름을 앞에 __(double underscore)붙이기
setter : Attribute의 값을 변경하는 메소드, set으로 시작
getter : Attribute의 값을 조회하는 메소드, get으로 시작
p = Person('홍길동', 20, '서울')
print(p.name, p.age, p.address)
p.print_info()
# 값 변경
p.age = 30
p.print_info()
p.age = '오십세'
p.print_info() # 나이의 값은 int로 받아야 하지만 동적타입 언어 특성상 문자열 '오십세'와 같은 값을 넣어도 작동한다.
p.add_age(2) # 그래서 add_age함수를 사용하여 나이에 2를 더하려고 해도 type이 맞지 않아 오류가 발생한다.
p.age = 3_000_000 # 나이에 비정상적인 숫자도 대입할 수 있기에 이를 막아줄 방법이 필요하다. -> 정보 은닉
p.add_age(5)
p.print_info()
# Person class에 정보 은닉 적용
# 1. attribute 변수들을 외부에서 호출 할 수 없도록 만들어 준다.
# - self.__변수명 = 초기값
# 2. attribute변수들을 조회(getter), 변경(setter)하는 메소드를 정의한다.
class Person:
def __init__(self, name, age, address):
self.name = name
self.__age = age # 직접 호출을 막는다.
self.address = address
self.email = None
# age 값을 조회하는 메소드
def get_age(self):
return self.__age # 같은 class에서는 __age(원래이름)로 호출 가능
# age 값을 변경하는 메소드
def set_age(self, age):
if 0 <= age <= 100: # 조건달기 -> 변경하려는 age의 값이 비정상적일 경우 변경하지 않도록 한다.
self.__age = age
else:
print(f"{age}는 나이에 넣을 수 없습니다. 0 ~ 100 사이의 정수를 넣어주세요.")
p = Person('홍길동', 20, '서울')
print(p.name)
print(p.address, p.email)
# print(p.age) 직접 호출 막힘
print(p.get_age())
p.set_age(5000) # set_age의 조건에 충족하지 못함
p.__dict__ # age의 값이 변경되지 않는다.
print(p._Person__age) # 외부에서는 이 이름으로 접근 가능하다. -> 굳이 호출할 수 있는 방법은 있다.
p.name = '홍길동2'
p.__age = 30 # 원래의 age는 바뀌지 않고 다른 변수가 추가되었다.
p._Person__age = 40 # 원래의 age가 바뀐다 -> 완벽히 변경을 제한하지는 못한다.
p.__dict__
property 함수 : 정보은닉되었던 __Attribute들을 다시 변수를 사용하는 방식으로 호출할 수 있게 한다.
목적 : 정보은닉된 instance 변수의 값을 사용하려고 할때 조건을 달기 위해서
구문 : property(get메소드, set메소드) (조건 : getter/setter 메소드가 있어야함)
class Person2:
def __init__(self, name, age, address):
self.__name = name
self.__age = age # 직접 호출을 막는다.
self.address = address
self.email = None
# age 값을 조회하는 메소드
def get_age(self):
return self.__age # 같은 class에서는 __age(원래이름)로 호출 가능
# age 값을 변경하는 메소드
def set_age(self, age):
if 0 <= age <= 100:
self.__age = age
else:
print(f"{age}는 나이에 넣을 수 없습니다. 0 ~ 100 사이의 정수를 넣어주세요.")
def get_name(self):
return self.__name
def set_name(self, name):
# 이름은 두글자 이상일 경우만 변경가능
if len(name) >= 2:
self.__name = name
else:
print("이름은 두 글자 이상만 가능합니다.")
name = property(get_name, set_name) # 변수 = property 선언
age = property(get_age, set_age)
p3 = Person2('유재석', 40, '인천')
p3.address = '새주소'
p3.email = '새 이메일 주소'
p3.name = '새이름'
p3.age = 20
print(p3.name, p3.age, p3.address, p3.email)
p3.name = '강' # 조건 충족 X
p3.age = 5000
p2 = Person2('유재석', 40, '인천')
# name, age 사용 -> 메소드
p2.set_name('류재석') # set_name(property함수)를 통해 이름 변경, 조건에 맞지 않다면 변경되지 않는다.
print(p2.get_age())
# address, email 사용 -> 변수를 호출
print(p2.address)
p2.email = 'afiepg@af.com'
p2.__dict__
데코레이터(decorator) 이용해 property 지정 : setter/getter 구현 + property()를 이용해 변수 등록 하는 것을 더 간단하게 구현하는 방식
getter메소드: @property 데코레이터를 선언
setter메소드: @getter메소드이름.setter 데코레이터를 선언.
! 반드시 getter 메소드를 먼저 정의한다.
setter메소드 이름은 getter와 동일해야 한다.
class Person3:
def __init__(self, name, age, address):
self.__name = name
self.__age = age # 직접 호출을 막는다.
self.address = address
self.email = None
# getter 메소드에 @property, 메소드이름은 변수처럼 지정.
@property
def age(self):
return self.__age # 같은 class에서는 __age(원래이름)로 호출 가능
# setter 메소드에 @getter이름.setter, 메소드 이름은 getter와 동일하게 지정. getter 메소드가 먼저 입력되어야 함.
@age.setter
def age(self, age):
if 0 <= age <= 100:
self.__age = age
else:
print(f"{age}는 나이에 넣을 수 없습니다. 0 ~ 100 사이의 정수를 넣어주세요.")
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
# 이름은 두글자 이상일 경우만 변경가능
if len(name) >= 2:
self.__name = name
else:
print("이름은 두 글자 이상만 가능합니다.")
p4 = Person3('박명수', 10, '부산')
print(p4.name, p4.age, p4.address, p4.email)
p4.__dict__
p4.name = '이순신' # 변경하는 방식을 똑같게 만들었지만 name과 age 변경에 대한 조건을 거친다.
p4.age = 50
p4.address = '서울'
p4.email = 'iajfi@e.com'
p4.__dict__
상속(Inheritance) : 기존 클래스를 확장하여 새로운 클래스를 구현한다.
ex) 동물 : 먹을 수 있다. 사람 : 말할 수 있다. 고양이 : 높이 점프할 수 있다. -> 사람/고양이는 동물을 상속한다.
분류의 개념으로 보았을 때 동물 : 상위 분류, 사람/고양이 : 하위 분류가 된다.
기반(Base) 클래스, 상위(Super) 클래스, 부모(Parent) 클래스
- 물려주는 클래스, 상속하는 클래스에 비해 더 추상적인 클래스가 된다.
- 상속하는 클래스의 데이터 타입이 된다.
파생(Derived) 클래스, 하위(Sub) 클래스, 자식(Child) 클래스
- 상속하는 클래스, 상속을 해준 클래스 보다 좀더 구체적인 클래스가 된다.
* is a 관계 ex) 사람(자식)은 동물(부모)이다(O), 동물(부모)은 사람(자식)이다.(X)
상위 클래스와 하위 클래스는 계층관계를 이룬다.
- 상위 클래스는 하위 클래스 객체의 타입이 된다.
class Person:
def go(self):
print('간다.')
def eat(self):
print('먹는다.')
# Person을 상속해서 Student를 정의
# class 클래스이름(상속할 클래스이름(, 상속할 클래스이름2, ....))
class Student(Person):
def study(self):
print('학생은 공부한다.')
# Person을 상속해서 Teacher를 정의
class Teacher(Person):
def teach(self):
print('수업을 가르친다.')
# 객체 생성
s = Student()
s.study() # Student 클래스의 기능
s.eat() # 부모 클래스의 기능을 사용
s.go() # 부모 클래스의 기능을 사용
t = Teacher()
t.teach() # Teacher 클래스의 기능
t.eat() # 부모 클래스의 기능을 사용
t.go() # 부모 클래스의 기능을 사용
다중 상속과 단일 상속
다중상속 : 여러 클래스로부터 상속할 수 있다
단일상속 : 하나의 클래스로 부터만 상속할 수 있다.
파이썬은 다중상속을 지원한다.
MRO (Method Resolution Order) : 다중상속시 메소드 호출할 때 그 메소드를 찾는 순서.
순서 : 자기자신, 상위클래스(하위에서 상위로 올라간다)
다중상속의 경우 먼저 선언한 클래스 부터 찾는다. (왼쪽->오른쪽)
MRO 순서 조회 : Class이름.mro()
Student.mro() # 다중 상속시 메소드를 찾는 순서
class E: # object클래스를 상속하고 있다.
pass
class F:
pass
class G:
pass
class C(E, F):
pass
class D(G):
pass
class A(C, D):
pass
A.mro() # object 클래스는 최상위 계층
Method Overriding(메소드 재정의) : 상위 클래스의 메소드의 구현부를 하위 클래스에서 다시 구현하는 것을 말한다.
상위 클래스는 모든 하위 클래스들에 적용할 수 있는 추상적인 구현밖에는 못한다.
이 경우 하위 클래스에서 그 내용을 자신에 맞게 좀 더 구체적으로 재구현할 수 있게 해주는 것을 Method Overriding이라고 한다.
방법은 하위 클래스에서 overriding할 메소드의 선언문은 그대로 사용하고 그 구현부는 재구현하면 된다.
상속하고 있는 하위 클래스에서 메소드를 재정의(Overriding)하여 구현부를 구체화(메소드 이름은 같게 사용)
메소드 이름을 다르게 사용하면 호출할 메소드의 종류가 많아서 복잡해진다. -> 같은 이름으로 오버라이딩 할 것.
메소드 재정의 -> 부모 클래스에서 정의된 메소드와 똑같은 이름으로 재정의할 시 하위 클래스에서 정의된 메소드가 작동한다.
class Person2:
def go(self):
self.eat()
print('간다.')
# print('스쿨버스를 타고 간다.') -> Student방식
def eat(self):
print('먹는다.', Person2) # Student와 Teacher에 공통적
# print('급식을 먹는다.') -> Student방식
class Student2(Person2):
# go()메소드를 Student 클래스에 맞게 좀 더 구체화된 내용으로 재정의(Overriding)한다.
# 상위클래스에 정의된 메소드와 동일한 선언(이름)으로 메소드를 구현.
def go(self):
print("스쿨버스를 타고 등교한다.")
def eat(self):
print("학교 식당에 간다.")
print("급식을 받는다.")
print("먹는다.") # 자식클래스에 정의한 eat()을 실행시킨다.
super().eat() # super() : 부모클래스를 가리킨다. -> 부모클래스에서 정의한 eat() 실행
def study(self):
print("학생이 공부한다. 2")
class Teacher2(Person2):
def teach(self):
print("교사가 가르친다. 2")
t = Teacher2()
t.go()
t.eat()
s = Student2()
s.go() # Student2클래스에서 오버라이딩한 go()함수 실행(우선순위를 가진다.)
print('-' * 20)
s.eat() # Student2클래스에서 오버라이딩한 eat()함수 실행(우선순위를 가진다.)
Student2.mro()
# 상속과 Attribute
class Person3:
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 name: # None이 아닐 경우
self.__name = name
else:
print("이름을 변경 못함")
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if 8 <= age <= 19: # 나이 변경 조건
self.__age = age
else:
print('나이를 변경 못함')
@property
def address(self):
return self.__address
@address.setter
def address(self, address):
if address: # None이 아닐 경우
self.__address = address
else:
print('주소 변경 못함')
# 나이를 더하는 메소드
def add_age(self, age):
self.age = self.age + age
# @age.setter : age = @property : age + 파라미터 age
# Person객체의 속성값들을 하나의 문자열로 묶어서 반환.
def get_info(self): # getter 메소드 호출.
return f"이름 : {self.name}, 나이 : {self.age}, 주소 : {self.address}"
# Student의 속성 : name, age, address -> 공통속성, grade(성적. 학생의 속성)
class Student3(Person3):
def __init__(self, name, age, address, grade): # 자식 클래스도 공통속성과 클래스만의 속성 모두 받아야 한다.
# name, age, address -> 부모클래스인 Person3의 속성
super().__init__(name, age, address) # super() -> 부모클래스의 initializer 호출 -> 부모클래스에게 공통속성인 name, age, address를 넘긴다.
self.__grade = grade
@property
def grade(self):
return self.__grade
@grade.setter
def grade(self, grade):
if grade > 0:
self.__grade = grade
else:
print('grade 변경 못함')
def get_info(self): # method overriding -> Person3에 정의된 get_info()에 grade까지 return 되도록 재정의
i = super().get_info() # 이름, 나이, 주소(공통속성)들은 Person3(부모클래스)에 정의된 메소드 호출
return f"{i}, 성적 : {self.grade}" # getter : grade 호출
s = Student3('김학생', 17, '서울', 3)
s.name, s.age, s.address, s.grade # getter 호출
s.add_age(-3)
s.age
info = s.get_info() # s : Student 객체
print(info)
s.age = 120 # 나이 변경 조건에 맞지 않음
print(s.get_info())
s.grade = 12 # grade 변경
s.grade
s.name = None # 변경 조건에 충족되지 못하는 경우
s.grade = -50
# Teacher의 속성 : name, age, address -> 공통속성, subject(과목. Teacher의 속성), job 직첵 -> 특별한 대입조건이 없다.
# 상속 속성 : name, age, address ,
# 메소드 : add_age(), get_info() 메소드
class Teacher3(Person3):
def __init__(self, name, age, address, subject, job = None): # job = 직책
# name, age, address -> 부모클래스의 __init__을 이용해서 초기화.
super().__init__(name, age, address)
self.__subject = subject
self.job = job # setter가 필요 없는 변수
# subject의 getter/setter 구현
@property
def subject(self):
return self.__subject
@subject.setter
def subject(self, subject):
if subject: # None이 아닐 경우
self.__subject = subject
else:
print('과목 변경 못함')
# Teacher 객체의 attribute들을 반환 -> get_info()를 method overriding
def get_info(self):
# 이름, 나이, 주소 -> Person3의 get_info()를 사용
i = super().get_info()
return f"{i}, 담당과목 : {self.subject}, 담당직책 : {self.job if self.job else '없음'}"
t = Teacher3('박선생', 30, '부산', '수학', '학생주임')
info = t.get_info()
print(info)
t.subject = None
t.job = None
info = t.get_info()
print(info)
객체 관련 유용한 내장 함수, 특수 변수
isinstance(객체, 클래스이름-datatype) : 결과값 -> True or False
- 조건을 달아줄 때 유용하게 사용한다.
p = Person3('이름', 30, '주소')
t = Teacher3('이선생', 40, '부산', '영어', '교감')
s = Student3('김학생', 14, '서울', 1)
# 변수 p의 타입이 무엇인지?
type(p) == Person3 # 클래스 이름이 타입
type(30) == int # 위와 같은 개념
print(isinstance(p, Person3)) # p가 Person3의 인스턴스(타입)인가? -> True
print(isinstance(p, Person), isinstance(p, str))
s1 = 30
print(type(s1) == int)
print(isinstance(s1, int))
print(isinstance(s1, float))
print(isinstance(s1, (int, float))) # s가 int 또는 float 타입인가? -> True
# isinstance 예시
def function(value):
if isinstance(value, (int, float)):
print(value ** 2)
else:
print('계산 못하는 타입', type(value))
function(10)
function(3.3)
function('aaa')
isinstance(t, Teacher3), isinstance(s, Student3)
# 상위클래스가 자식클래스 객체의 타입이 된다.
isinstance(t, Person3), isinstance(s, Person3)
def func(value):
# Student, Teacher 객체를 받아서 add_age() 이용해서 나이를 변경하고 attribute들을 출력
if isinstance(value, Person3): # value가 Person3의 클래스의 타입인가?
value.add_age(2)
i = value.get_info()
print(i)
func(s)
s.__dict__
s.__class__.__name__ # class 이름을 문자열로 반환'Data_Analysis_Track_33 > Python' 카테고리의 다른 글
| Python_05-4(특수 메소드, class 변수와 메소드, static 메소드) (0) | 2023.08.22 |
|---|---|
| Python_05-3(TO DO 정리) (0) | 2023.08.21 |
| Python_05(객체지향 프로그래밍) (0) | 2023.08.21 |
| Python_04-2(함수(First Class Citizen)) (0) | 2023.08.19 |
| Python_04(함수) (2) | 2023.08.18 |