객체 지향 프로그래밍

객체

"Python에서 모든 것은 객체(Object)이다. 그리고 대부분 객체는 속성(attributes)과 메서드(methods)를 갖는다." 라는 말이 있다.

파이썬에서 bool, 정수, 실수, 문자열, 배열, 딕셔너리, 함수, 모듈, 프로그램 등 모든 것은 객체이다. 참조 Everything is an Object

변수

변수 a에 3 할당하기

a = 3

보통 변수를 선언할 때 변수에 값을 할당한다고 하는데 이 말의 의미는 값에 별명을 지어주는 것이다.

animal = 'cat'

위 코드는 cat이 담긴 문자열 타입의 객체를 생성하고 animal이라는 변수(객체)에 할당, 문자열 객체를 참조하게 한다.

animal.upper()

'cat'.upper()

위 두 코드의 출력값은 같다. animal이 가리키는 문자열과 cat의 값이 같기 때문에

id 확인하기

id란 identity(고유값)를 의미한다. id를 확인하기 위해서는 id()함수를 사용한다.
id()는 파이썬 내장함수로 프로그램이 실행되는 동안 메모리에 저장된 주소(=객체의 고유값(identity))를 반환한다. (파이썬 공식 문서: id() 함수)

num = 4

print(id(num), id(4))

각각의 id 값을 확인해보면 같은 값이 나오는 것을 알 수 있다.

얕은 복사, 깊은 복사

list = [1,2,3]
var = list
var

list에 append() 메서드를 이용해 4를 추가하고 list와 var 확인

list.append(4)
print(list)

print(var)

var에도 list와 동일한 연산이 적용되었다. 그 이유는 id 값을 확인해보면 알 수 있다.

print(id(var), id(list))

두 id의 값이 동일한 것을 확인할 수 있다.

var에 list를 할당하면 var와 list는 같은 [1,2,3]이라는 데이터를 참조하게 된다. 따라서 var와 list의 id값이 같다. (쉽게 말해 이름은 하나인데 별명은 서너개)

이와 같이 원본 데이터는 그대로 두고, 참조하는 데이터의 id만 복사하는 것을 얕은 복사라고 한다.

우리가 일반적으로 생각한 복사는 내용은 동일하지만 id는 다른 [1,2,3] 데이터를 생성하는 것이다. 이런 복사를 깊은 복사라고 한다

참조 파이썬 공식 문서 : copy() 모듈

  • 얕은 복사 : copy()
  • 깊은 복사 : deepcopy()

기본적으로 얕은 복사는 원본 객체의 주소(id)를 복사하고, 깊은 복사는 원본 객체의 값을 복사한다.

객체 지향 프로그래밍이란

앞에서 객체에 대한 개념과 객체를 참조하는 변수에 대해 설명했다. 이렇게 객체를 활용한 프로그래밍을 객체 지향 프로그래밍(OOP: Object Oriented Programming)이라고 한다.

객체지향 프로그래밍은 컴퓨터 프로그래밍 패러다임 중 하나로 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, 객체들의 모임으로 파악하는 것이다.

객체 지향 프로그래밍은 프로그램을 유연하고 변경이 쉽게 만들어 대규모 소프트웨어 개발에 많이 사용되고, 소프트웨어 개발과 보수를 간편하게 하는 편리함이 있다

Class

객체를 설계하기 위해서는 class를 사용한다.

클래스 선언

클래스는 기본적으로 그림과 같은 형식을 가지고 있다

class Car:
    pass
    
class Person:
    pass

클래스 사용 - 객체 인스턴스화

클래스를 사용하기 위해서는 클래스를 객체로 만들어주는데, 이를 인스턴스화 또는 인스턴스 생성이라고 한다.

인스턴스화는 클래스에 괄호를 붙이고 변수에 할당하면 된다.

porsche = Car()
Jack = Person()

클래스는 특정 개념을 표현하기만 할 뿐 사용하려면 인스턴스를 생성해야 한다.
porsche, Jack에 각각 Car, Person 클래스의 인스턴스를 할당했다.

porsche, Jack은 객체이면서 인스턴스이다. 객체와 인스턴스는 같다고 보면 되지만 사용할 때 관계 위주로 설명할 때 사용한다.

  • porsche은 객체이다. Jack은 객체이다.
  • porsche는 Car의 인스턴스이다. Jack은 Person의 인스턴스이다
    이런 식으로 표현한다.

표기법

함수 호출과, 클래스 인스턴스는 문법이 비슷하다. 이를 해결하기 위해 파이썬에서는 클래스명과 함수명을 다르게 표기하는 방법을 사용한다.

PEP(Python Enhancement Proposals)는 파이썬 개선사항 공식문서이다.
그 중 PEP8은 파이썬 코딩 스타일(변수명, 코드 표기법 등)을 개정한 문서이다.

클래스명 표기법 : 카멜 케이스

  • 카멜 케이스 : 각 단어의 앞 글자를 대문자로 쓴다.
  • ex) Car, Mycar, Python

함수명 표기법 : 스네이크 케이스

  • 스네이크 케이스 : 단어는 소문자로 쓰고 각 단어의 연결은 언더바 _를 사용한다.
  • ex) my_car, my_list, jack_daniel

클래스 속성, 메서드

이번엔 클래스의 속성과 메서드를 이해해보자

  • 클래스의 속성은 상태(state)를 표현한다. 속성은 변수로 나타낸다.
  • 클래스의 메서드는 동작(behavior)을 표현한다. 메서드는 def 키워드로 나타낸다.

Person 클래스를 만들어 속성과 메서드를 이해해 보자

속성

sex = 'man'
age = '28'

동작

def walk():
	pass
    
def run():
	pass

을 추가한다.

class Person:
    sex = 'man'
    age = '28'
    
    def walk(self):
        print("I'm walking in the rain")

    def run(self, run_fast, current_speed=8):
        self.run_fast = run_fast
        self.current_speed = current_speed + run_fast
        print("run_fast", self.run_fast, "running at", self.current_speed)

아래에서 속성과 메서드를 사용하는 법과 함께 각 인자에 대해서 알아보자

클래스 속성, 메서드 사용

속성

클래스의 속성에 접근하기 위해서는 인스턴스에 .을 쓰고 속성 이름을 쓰면 된다.

Jack = Person() 	# 인스턴스 생성 밑 변수에 할당

print(Jack.age)

#Jack.grade		# 없는 속성값에 접근하면 에러 발생

메서드

메서드를 속성과 비슷하게 호출한다. 인스턴스에 .을 쓰고 메서드 이름을 쓴다.
메서드는 class 내부에 정의된 함수로 사용법은 일반적인 함수와 같다

메서드를 정의할 때는 self가 필요하지만 호출할 때는 self가 필요없다.

Jack.walk()

Jack.run(5)

self

Jack.walk() 코드는 인터프리터 내부에서 Person.walk(Jack)로 동작한다.
self는 클래스를 인스턴스화 한 인스턴스 객체를 가르킨다.
메서드를 호출할 때, 인자를 넣지 않지만 파이썬 내부적에서는 인자 한개를 사용하고 있고, 그 인자는 파이썬 클래스에 의해 선언된 객체 자기 자신(self)이다.

Jack.walk()

Person.walk(Jack)

위의 두 코드는 똑같이 동작하지만 Person.walk(Jack)와 같이 사용하지는 않는다. 파이썬 인터프리터가 처리해 주는 일을 굳이 어렵게 구현할 필요가 없다.

그림으로 확인해 보자

클래스 메서드를 정의할 때 self를 사용하지 않으면 에러가 발생한다

class Test:
	def test1(self):
		print("run1")
        
	def test2():
		print("run2")
        
t = Test()

t.test1()

t.test2()	# 에러 발생

메서드 안의 self.

인스턴스 속성으로 사용하고 싶은 변수는 self.을 써야한다. 이는 self 인자를 통해 선언된 객체의 값이라는 의미이다.

객체 안에서 self를 사용하면 인스턴스 객체의 고유한 속성을 나타낼 수 있다. 클래스가 아닌 self, 인스턴스화된 객체 자신의 속성이라는 뜻이다.

클래스 메서드 내부에서 self. 접두사 없이 일반 변수와 같이 선언된 변수는 메서드 내부에서만 사용되어 self.를 사용해 참조할 수 없다.

class Test2:
	def test1(self, a):
		self.a = float(a) * 10
		print(self.a)
        
	def test2(self, b):
		b = float(b) + 10
        print(self.b)
        
t = Test2()

t.test1(1)

t.test2(1)	# error 발생

self는 처음에 많이 어려운 개념이다

  • self는 자기 자신이다.
  • 클래스에 의해 생성된 객체(인스턴스)를 가리킨다.
  • 클래스의 메서드는 인자로 해당 인스턴스(self)를 받아야 한다.
  • 메서드를 호출할 때 self 인자를 전달하지 않는다. self의 값은 인터프리터가 제공한다.
  • 인스턴스 변수를 정의할 때는 접두사 self.을 붙여준다

클래스 생성자

생성자 __init__

클래스로 만든 인스턴스 속성값을 초기화하는 법을 알아보자
속성을 생성할 때는 그냥 변수에 지정하는 방법도 있지만 일반적으로는 __init__ 메서드 안 self.속성에 값을 할당한다

아까는 속성을 아래와 같이 지정했지만 이번엔 생성자(__init__)을 사용해 생성해보자

sex = 'man'
age = '28'
# 위 코드를 아래로 바꿔 주었다
def __init__(self, sex, age):
	self.sex = sex
    self.age = age
  • __init__ 메서드는 인스턴스를 만들 때 호출되는 특별한 메서드이다.
  • __init__ 메서드 안에 정의된 속성 sex와 age는 클래스를 인스턴스화 할 때 값을 설정할 수 있다.
  • init은 initialize라는 이름 그대로 인스턴스(객체)를 초기화 한다.
  • __init__처럼 앞뒤로 언더바(_)가 두 개씩 붙은 메서드를 매직 메서드 또는 스페셜 메서드라고 한다라고 한다.

전체코드

class Person2:
    def __init__(self, sex, age):
        self.sex = sex
        self.age = age
        
    def walk(self):
        print("I'm walking in the rain")

    def run(self, run_fast, current_speed=8):
        self.run_fast = run_fast
        self.current_speed = current_speed + run_fast
        print("run_fast", self.run_fast, "running at", self.current_speed)
Jack = Person()
Jessica = Person2('woman', '24')

Jack.age
Jessica.age

Jack.sex
Jessica.sex

일반 함수와 문법은 비슷하다. 아래처럼 키워드 인자를 지정할 수도 있다.

class Person:
    def __init__(self, sex='woman', age='24'):
        self.sex = sex
        self.age = age

클래스 변수와 인스턴스 변수

클래스에서 변수를 선언하는 방법은 2가지로, 하나는 보통 변수와 동일하게 변수명을 쓰고 값을 할당하는 방법, 다른 하나는 __init__ 메서드 안에 self.와 함께 설정하는 방법

예시

class Person:
    hometown = "USA"
    
    def __init__(self, sex, age='24'):
        self.sex = sex
        self.age = age

클래스 변수

  • 위 코드에서 hometown과 같이 클래스 내부에서 선언된 변수를 클래스 변수라고 한다.
  • 클래스에 의해 생성된 모든 객체에서 같은 값을 조회가 가능하다 (서로 다른 객체같에 값을 공유)
  • hometown 속성은 Person 클래스의 인스턴스들끼리 모두 공유한다

인스턴스 변수

  • __init__ 메서드 내부에서 self.를 사용해 선언한 변수를 인스턴스 변수라고 한다.
  • 객체가 인스턴스화될 때마다 새로운 값이 할당되어 다른 객체와는 공유하지 않는다.
  • sexage 속성은 Person 클래스 인스턴스들끼리 공유하지 않음

예시로 Person 클래스로 사람들을 분류 할때 고향이 모두 korea로 같다면 hometown = korea로 클래스 변수를 지정하고 그 외적인 개별 속성을 __init__메서드로 속성을 정의한다.

클래스 상속

기존의 클래스를 남겨둔 상태에서 기능이 추가된 새로운 클래스를 만들고 싶다면 클래스 상속을 사용한다.

class Person:
    hometown = "USA"

    def __init__(self, sex, age):
        self.sex = sex
        self.age = age

    def walk(self):
        print("I'm walking in the rain")

    def run(self, run_fast, current_speed=8):
        self.run_fast = run_fast
        self.current_speed = current_speed + run_fast
        print("run_fast", self.run_fast, "running at", self.current_speed)

위의 Person 클래스의 기능은 유지한 채 jobs 속성만 추가된 새로운 클래스 Worker를 선언한다.

상속은 소괄호 안에 상속받을 클래스의 이름을 적어준다

class Worker(Person):
    pass

Daniel = worker()
Daniel.walk()
Daniel.run()

안에 아무것도 없지만 Person 클래스의 기능을 그대로 사용할 수 있다.
이제 jobs 속성을 추가해보자

class Worker(Person):
	jobs = "programmer"
    
Daniel = Worker()
Daniel.run()

클래스 상속

  • 기존 클래스를 베이스(base), 슈퍼(super), 부모(parent) 클래스라고 한다
  • 상속받는 클래스를 파생(derived), 서브(sub), 자식(child) 클래스라 한다.

상속 사용하기

클래스를 잘 상속하기 위해 필요한 3가지 사용법

  • 메서드 추가하기(add)
  • 메서드 재정의하기(override)
  • 부모 메서드 호출하기(super())

메서드 추가하기

자식 클래스에 새로운 메서드를 추가할 수 있다.
새로운 메서드 work()를 추가해보자

class Worker(Person):
	def work(self):
		print("I'm working so hard")

메서드 오버라이드

기존 클래스에 있던 drive() 메서드에 "I'm driving and can fly"라는 문구가 출력되는 메서드를 정의한다고 했을 때, 기존에 있던 메서드를 변경하는 것을 메서드 오버라이드(재정의, override)라고 한다.

class Worker(Person):
	def work(self):
		print("I'm working so hard")

	def walk(self):
		print("When I finished my work, I walk home")

부모 메서드 호출하기 super()

부모 메서드 호출은 super()라는 함수를 이용한다. super()는 파이썬 내장함수이다. 자식 클래스에서 부모 클래스의 메서드를 호출하고 싶을 때 사용한다. 참조 Built-in Functions (super())

super() 함수 사용하기

def (부모클래스의)메서드이름():
	super().메서드이름()

상속을 받으면 부모 클래스의 메서드를 그대로 이어받아 사용할 수 있고 기능을 추가하고 싶다면 메서드 오버라이드 기능을 사용하면 된다. 왜 굳이 메서드를 호출해야하는지 알아보자

Person 클래스를 상속받은 Worker 클래스의 생성자(초기화 함수) __init__() 메서드에 sex, age 속성은 동일하게 유지한 채 jobs 속성을 추가한 Worker클래스를 만든다.

class Worker(Person):
    def __init__(self, sex, age, jobs):
        super().__init__(sex, age)
        self.jobs = jobs

	def work(self):
		print("I'm working so hard")

	def walk(self):
		print("When I finished my work, I walk home")

    def run(self, run_fast, current_speed=8):
        self.run_fast = run_fast
        self.current_speed = current_speed + run_fast
        print("run_fast", self.run_fast, "running at", self.current_speed)

왜 아래 코드처럼 오버라이드 하지 않고 super()를 사용했을까?

class Worker(Person):
	def __init__(self, sex, age, jobs):
		self.sex = sex
		self.age = age
		self.jobs = jobs

메서드 오버라이드를 사용한 이후 부모 클래스의 메서드를 바꿔야 한다면 상속받은 클래스를 전부 일일이 바꿔야 하는 불편함이 있다. 하지만 super()를 사용하면 부모 클래스만 변경한다면 전부 바뀌게 된다.

부모클래스의 변경사항이 그대로 자식 클래스에 반영된다

class Person:
    hometown = "USA"

    def __init__(self, sex, age):
        self.sex = sex
        self.age = '내 나이는 ' + age +'살 입니다.'

class Worker(Person):
    def __init__(self, sex, age, jobs):
        super().__init__(sex, age)
        self.jobs = jobs

Daniel = Worker('man','28', 'Programmer')
print(Daniel.age)
profile
하루에 집중하자

0개의 댓글