-
Notifications
You must be signed in to change notification settings - Fork 1
2.09. 내용정리: 9일차
객체지향이란, 하나의 프로그래밍 패러다임으로써 객체(object)를 중심으로 사고하면서 프로그래밍을 하는 것을 의미한다.
-
객체(Object): 속성(특성 및 특징)과 행동(행위)을 가지는 주체를 의미한다.
- 객체의 속성(특성과 특징; attribute)은 객체가 가지는 데이터(변수)를 의미하며, 객체에 정의된 변수를 멤버 변수(member variable) 라고 한다.
- 일반적으로
Python,C++에서는 멤버 변수,JAVA에서는 필드(field; 혹은 멤버 필드) 라고 표현한다. - 객체의 속성을 의미하는 어트리뷰트(attribute) 라는 표현도 자주 쓰인다.
- 일반적으로
- 객체의 행동은 함수를 의미하며, 객체에 정의된 함수를 메서드(method) 라고 한다.
- 일반적으로
C++에서는 멤버함수(member function),Python,JAVA에서는 메서드 라고 한다.
- 일반적으로
- 객체의 속성(특성과 특징; attribute)은 객체가 가지는 데이터(변수)를 의미하며, 객체에 정의된 변수를 멤버 변수(member variable) 라고 한다.
-
클래스(Class): 객체를 프로그래밍 언어로 구현한 것을 의미한다.
- 클래스에 정의된 변수를 클래스 변수(class variable) 혹은 클래스 전역 변수라고 한다.
-
인스턴스(Instance): 정의된 클래스를 선언하여 메모리에 생성된 객체를 의미한다.
- 인스턴스화(Instantiate): 새로운 객체를 생성한다는 의미로써, 메모리에 새로운 객체를 생성시킨다는 것읠 의미한다.
- 인스턴스 변수(instance variable): 인스턴스화를 통해 생성된 객체가 가지고 있는 멤버 변수를 의미하며, 인스턴스화를 통해서만 사용이 가능하다.
- 생성자(Constructor): 객체가 생성될 때(인스턴스화) 호출되는 함수(메서드)이며, 주로 객체의 초기화 작업을 담당한다.
-
추상화(Abstraction): 어떤 종류의 대상들에 대해 그것이 가져야 할 핵심적인 특징들을 가지는 모델을 만드는 것이다.
- 즉, 객체가 가진 수많은 특징과 행동들 중에서 프로그래밍에 필요한 핵심적인 요소들만을 모델로 구현하는 것을 의미한다.
-
상속(Inheritance): 상위 객체(부모 객체라고도 함)로부터 하위 객체(자식 객체라고도 함)가 특성 및 특징과 행동을 물려받는 것을 의미한다.
- 오버라이딩(Overriding): 상위 객체가 가지고 있는 속성과 메서드를 하위 객체에서 재정의하여 사용하는 것을 의미한다.
-
캡슐화(Encapsulation): 객체가 가지고 있는 속성을 캡슐처럼 감싸 객체가 가진 데이터를 보호하는 것을 의미하며,
C++과JAVA와 같은 객체지향 프로그래밍 언어에서는 아래와 같은 키워드로 접근 단계를 나눈다.- private: 객체 내부에서만 접근 가능한 데이터
- protected: 다른 객체에서는 접근 불가능하지만, 상속을 받은 하위 객체는 접근 가능한 데이터
- public: 객체 외부에서도 접근 가능한 데이터
-
다형성(Polymorphism): 하나의 객체가 여러 가지 형태를 취해 상황에 따라 다른 방식으로 해석이 가능한 것을 의미한다.
-
오버로딩(Overloading): 같은 이름의 여러 메서드가 다른 매개 변수를 갖도록 하는 것을 의미한다.
- 다른 객체지향 프로그래밍 언어에서는 지원하는 기술이지만, 파이썬에서는 메서드 오버로딩은 불가능하다고 한다.
- 단, 파이썬에서는 연산자 오버로딩(Operator Overloading; 인스턴스 객체끼리 서로 연산을 할 수 있게끔 기존에 있는 연산자의 기능을 바꾸어 중복으로 정의하는 것)은 가능하다고 한다.
-
오버로딩(Overloading): 같은 이름의 여러 메서드가 다른 매개 변수를 갖도록 하는 것을 의미한다.
파이썬 공식 문서의 용어집의 객체(object) 항목 에서는 다음과 같이 설명하고 있다.
상태 (어트리뷰트나 값) 를 갖고 동작 (메서드) 이 정의된 모든 데이터. 또한, 모든 뉴스타일 클래스 의 최종적인 베이스 클래스이다.
이를 요약하자면 다음과 같다.
- 객체란, 속성과 행위를 갖는 주체이다.
- 파이썬에서의 모든 타입(type)은 객체이다.
- 파이썬에서 모든 타입의 상위(부모) 클래스는
object클래스이다.
파이썬 공식 문서의 용어집의 클래스(class) 항목 에서는 다음과 같이 설명하고 있다.
사용자 정의 객체들을 만들기 위한 주형이다. 클래스 정의는 보통 클래스의 인스턴스를 대상으로 연산하는 메서드 정의들을 포함한다.
이를 요약하자면 다음과 같다.
- 클래스란, 객체의 구조(속성과 행위)을 프로그래밍 언어로 정의한 것이다.
- 객체의 클래스는 초기화를 통해 제어한다.
- 클래스를 작성하기 위해서는
class키워드 사용하여 새로운 클래스를 작성한다. - 또한, 파이썬 명명 규칙 (PEP8) 에 의해 클래스명은 파스칼 표기법으로, 변수와 함수(메서드)는 스네이크 표기법으로 정의한다.
- 추상화란, 객체가 가진 수많은 특징과 행동들 중에서 프로그래밍에 필요한 핵심적인 요소들만을 모델로 구현하는 것을 의미한다.
- 모델을 정의하는 방식은 클래스로 정의한다.
- 클래스를 정의할 때는
class키워드를 사용한다. - PEP8에 의해 암묵적인 규칙으로 클래스 명칭은 파스칼 표기법으로 정의한다.
class MyClass:
pass- 생성자란, 객체가 생성될 때 호출되는 함수이다.
- 주로 객체의 초기화 작업을 담당한다.
- 파이썬에서 생성자는
def __init__(self, ...):와 같이 정의한다.
class MyClass:
def __init__(self):
print('생성자 호출')정의한 클래스를 사용하기 위해서는 객체를 생성(인스턴스화) 한 후에 사용한다.
class MyClass:
def __init__(self):
print('생성자 호출')
my_class = MyClass()
print(type(my_class))클래스 전역변수 란, 말 그대로 전역에서 사용한 변수로, 클래스 외부에서도 사용이 가능한 변수를 의미한다.
class MyClass:
a = 10클래스에 선언된 변수와 매서드는 접근 연산자 . 을 사용하여 데이터에 접근할 수 있다.
class MyClass:
a = 10
a = MyClass.a
print(a)멤버 변수 (혹은 인스턴스 변수) 란, 클래스 내부에서만 사용이 가능한 변수이며, 외부에서 접근하고자 하는 경우에는 클래스를 인스턴스 시킨 후 접근 연산자 . 를 통해 접근이 가능하다.
class MyClass:
def __init__(self):
# 생성자에 클래스 멤버 변수 'a' 정의
self.a = 10마찬가지로, 인스턴스화를 한 후에 접근 연산자 . 을 사용하여 데이터에 접근할 수 있다.
class MyClass:
def __init__(self):
# 생성자에 클래스 멤버 변수 'a' 정의
self.a = 10
# 객체생성 (인스턴스화)
my_class = MyClass()
a = my_class.a
print(a)- 메서드(method) 란, 클래스에 정의된 함수를 의미한다.
- 생성자 역시 메서드이다.
- 접근 연산자
.를 통해 메서드를 호출할 수 있다. - 메서드의 첫번째 매개변수는 항상
self를 받는다.- 파이썬의 암묵적인 명명 규칙에 의해
self라는 변수명으로 통일하여 사용한다. - 따라서
self말고 다른 매개변수 명을 지어도 상관은 없다.
- 파이썬의 암묵적인 명명 규칙에 의해
class MyClass:
def __init__(self):
self.a = 10
def my_method(self):
self.a = 20
my_class = MyClass()
print(my_class.a)
# 메소드를 호출할때 역시 '.' 연산자를 통해서 호출한다.
my_class.my_method()
print(my_class.a)-
self는 새롭게 생성된 객체, 즉 인스턴스화된 객체를 의미한다.
여러 참고서에서는 클래스를 붕어빵을 만드는 틀, 객체는 붕어빵으로 비유해서 설명하였다.
# 새로운 클래스 'MyClass' 정의
class MyClass:
# 클래스 변수 'a'
a = 1
def __init__(self):
# 인스턴스 변수 'a'
self.a = 10
# 클래스 변수 'a'를 의미한다.
print(MyClass.a)
# 새로운 객체 'my_class' 인스턴스화
my_class = MyClass()
# 새롭게 생성된 객체 'my_class'의 변수 'a'를 의미한다.
print(my_class.a)
# 새로운 객체 'my_class2' 인스턴스화
my_class2 = MyClass()
# 새롭게 생성된 객체 'my_class'의 변수 'a'의 값을 '20'으로 변경
my_class2.a = 20
# '10'이 출력된다.
print(my_class.a)
# '20'이 출력된다.
print(my_class2.a)인스턴스란, 클래스로 만든 객체를 의미하며, 클래스를 객체로 만드는 것을 인스턴스화 라고 한다.
- 예를 들어,
a = Cookie()이렇게 만든a는 객체이다. - 그리고 객체
a는Cookie의 인스턴스이다. - 즉, 인스턴스라는 말은 특정 객체(
a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할 때 사용한다. - 따라서
'a'는 인스턴스보다는'a'는 객체라는 표현이 어울리며,'a'는 'Cookie'의 객체보다는'a'는 'Cookie'의 인스턴스라고 표현한다.
- 상속이란, 상위(부모) 객체로부터 하위(자식) 객체가 속성과 행위를 물려받는 것을 의미한다.
- 문법은
class 클래스명(상위 클래스명):으로 정의한다.
먼저, 상위 클래스 A 를 정의한다.
class A:
a = 10
def test(self):
print('클래스 A')그 다음 위에서 정의한 클래스 A 를 상속받는 하위 클래스 B 를 정의한다.
class B(A):
pass그 다음 아래의 코드를 실행해본다.
b = B()
print(b.a)
b.test()분명 클래스 B 에는 멤버 변수 a 와 메서드 test 가 정의되어있지 않음에도 불구하고 멤버 변수 a 에 접근이 가능하며, 메서드 test 역시 잘 실행된다.
- 일반적으로 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경하려고 할 때 사용한다.
- "클래스에 기능을 추가하고 싶으면 기존 클래스를 수정하면 되는데 왜 굳이 상속을 받아서 처리해야 하지?" 라는 의문이 들 수도 있다.
- 하지만 기존 클래스가 라이브러리 형태로 제공되거나(다른 사람이 만든 클래스를 가져와서 사용하는 경우) 수정이 허용되지 않는 상황이라면 상속을 사용해야 한다.
오버라이딩이란, 상위 객체로부터 물려받은 속성과 메소드를 재정의하는 것을 의미한다.
아래는 클래스 A 로부터 상속받은 클래스 B 에서 멤버 변수 a 와 메서드 test 를 재정의(메서드 오버라이딩)하는 예시이다.
class A:
a = 10
def test(self):
print('클래스 A')
class B(A):
a = 20
def test(self):
print('클래스 B')
a = A()
b = B()
# '10'이 출력된다.
print('클래스 A의 멤버 변수 a:', a.a)
# '20'이 출력된다.
print('클래스 B의 멤버 변수 b:', b.a)
# '클래스 A'가 출력된다.
a.test()
# '클래스 B'가 출력된다.
b.test()- 상위 클래스를 다른 말로 슈퍼(Super) 클래스 라고도 부른다.
-
super함수는 하위 객체에서 상위 객체의 내용을 사용하고 싶은 경우 사용하는 함수이다.
아래는 super 함수를 사용하는 예시이다.
class A:
a = 10
def test(self):
print('클래스 A')
class B(A):
a = 20
def test(self):
super().test()
a = A()
b = B()
# '클래스 A'가 출력된다.
a.test()
# '클래스 A'가 출력된다.
b.test()-
super().__init__()라는 코드를 통해 상위 클래스의 속성과 메소드를 자동으로 불러와서 해당 클래스에서도 사용이 가능하도록 해준다. - 즉, 상위 클래스의 생성자를 호출하여 하위 클래스에서도 상위 클래스에 정의된 요소들을 초기화하는 방식으로 응용이 가능하다.
class Person:
def __init__(self, name):
self.name = name
def saying_my_name(self):
print(f'저는 {self.name}입니다.')
class Loser(Person):
def __init__(self, name, age):
# super 함수를 통해 상위 클래스의 생성자를 호출한다.
super().__init__(name)
self.age = age
def saying_my_age(self):
print(f'저는 올해 {self.age}살 입니다.')
me = Loser('흔한 찐따', 28)
me.saying_my_name()
me.saying_my_age()- 캡슐화란, 객체가 가지고 있는 속성을 캡슐처럼 감싸 객체가 가진 데이터를 보호하는 것을 의미한다.
- 클래스를 작성할 때는 내부적인 세부사항을 캡슐화하여 데이터를 보호하는 것이 일반적이다.
- 파이썬에서는 변수명 앞에
_기호를 붙여서 캡슐화를 시킨다.
- 프라이빗(private): 객체 내부에서만 접근 가능한 데이터
- 프로텍티드(protected): 다른 객체에서는 접근 불가능하지만, 상속을 받은 하위 객체는 접근 가능한 데이터
- 퍼블릭(public): 객체 외부에서도 접근 가능한 데이터
- 클래스의 주역할은 객체의 데이터와 내부 구현 세부사항을 캡슐화하는 것이다.
- 그러나, 외부에서 객체를 조작하는 데 사용할 퍼블릭 데이터도 클래스에 정의해야 한다.
- 구현 세부사항과 퍼블릭 데이터를 구분하는 것이 중요하다.
파이썬에서는 객체의 거의 모든 것이 오픈되어 있다.
- 객체의 내부를 쉽게 조사할 수 있다.
- 원하는 대로 바꿀 수 있다.
- 접근 제어에 대한 강력한 개념은 없다. (ex. 프라이빗 클래스 멤버).
이는 내부 사항을 구현할 때 격리하고자 하는 경우 문제가 된다.
- 파이썬은 의도된 사용법을 지시하는 프로그래밍 관례를 따른다.
- 이러한 관례는 명명(naming) 규칙에 기반을 둔다.
- 언어에서 규칙을 강요하기보다는 프로그래머가 자발적으로 준수하는 것이 일반적이다.
이름이 _ 로 시작하는 어트리뷰트는 프라이빗(private)으로 간주한다.
class Person:
def __init__(self, name):
self._name = '아무개'
앞에서 언급한 것과 같이, 이것은 프로그래밍 스타일일 뿐이며, 여전히 해당 변수에 접근하고 변경할 수 있다.
me = Person('흔한 찐따')
print(me._name)
me._name = '안 흔한 찐따'
print(me._name)-
_로 시작하는 이름은, 그것이 변수든 함수든 모듈명이든, 모두 내부 구현으로 간주하는 것이 일반적인 규칙이다. - 만약
_로 시작하는 이름을 직접 사용하고 있다면 뭔가 잘못 하고 있는 것이다. - 따라서
_로 시작하는 이름에 접근할 수 있는 고수준(High-level)의 기능을 찾아봐야 한다.
아래의 경우, 단순하게 self 매개 변수를 통한 인스턴스 변수를 정의하였다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age그리고 아래의 코드를 실행해보았다.
me = Person('흔한 찐따', 28)
print(me.name)
print(me.age)
me.name = '안 흔한 찐따'
print(me.name)
me.age = 27.5
print(me.age)
me.age = '뭘까요...?'
print(me.age)내가 원하는 것은 age 라는 인스턴스 변수는 무조건 int 타입이어야만 하는데, 그것이 되지 않고 있다.
- 접근자(accessor) 메서드를 도입하는 방법이 있다.
- 값을 불러오는 메서드를 겟터(Getter), 값을 변경하는 메서드를 셋터(Setter) 메서드라고 한다.
class Person:
def __init__(self, name, age):
self.set_name(name)
self.set_age(age)
# getter 메서드
def get_name(self):
return self._name
def get_age(self):
return self._age
# setter 메서드
def set_name(self, name):
# 이름은 문자열 타입으로 바꿔서 지정해준다.
self._name = str(name)
def set_age(self, age):
# 나이는 정수 타입으로 바꿔서 지정해준다.
self._age = int(age)
# 음의 정수는 사용이 불가능하도록 해준다.
if self._age < 0:
self._age = 0
me = Person('흔한 찐따', 28)
print(me.get_name())
print(me.get_age())
me.set_name('안 흔한 찐따')
print(me.get_name())
# 음의 정수인 경우, 0이 된다.
me.set_age(-10)
print(me.get_age())
# 아래를 실행하려고 하면 int 타입이 아니므로 에러가 발생하게 된다.
me.set_age('뭘까요...?')
print(me.get_age())그러나 기존의 코드들을 전부 변경해줘야 하는 문제가 발생한다.
- 프로퍼티란, getter와 setter를 가지는 멤버 변수를 의미한다.
-
@property를 붙여서 사용한다.- 이것을 데코레이터(decorator) 라고 하는데, 아직 잘 모르겠다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = str(name)
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = int(age)
if self._age < 0:
self._age = 0
me = Person('흔한 찐따', 28)
print(me.name)
print(me.age)
me.name = '안 흔한 찐따'
print(me.name)
# 음의 정수인 경우, 0이 된다.
me.age = -10
print(me.age)
# 아래를 실행하려고 하면 int 타입이 아니므로 에러가 발생하게 된다.
me.age = '뭘까요...?'
print(me.age)이제 평범한 프로퍼티에 대한 접근은 @property 와 @age.setter 하에서 getter와 setter 메서드를 통해 운용된다.