상세 컨텐츠

본문 제목

230114 객체지향 & 절차지향 / 오버라이딩 & 오버로딩 / python 깊은 복사 & 얕은 복사

카테고리 없음

by hunss 2023. 1. 15. 02:21

본문

절차지향

순차적인 처리가 중요시 되며, 프로그램 전체가 유기적으로 연결되도록 만드는 프로그래밍 기법임. C언어

이는 컴퓨터의 작업 처리 방식과 유사하기 때문에 객체지향 언어보다 처리속도가 빠르다.

--> 옛날에는 하드웨어와 소프트웨어의 개발 속도 차이가 크지 않았는데, 하드웨어가 빠르게 발전하면서 컴퓨팅 환경은 급속도로 증가했지만, 소프트웨어 개발 시간이 따라가지 못하게 되고, 이런 상황에서 소프트웨어의 개발시간을 단축하되 하드웨어에 기본적인 사양을 잡아먹어도 더 이상 큰 단점이 아니기 됐음.

그래서 모듈화, 캡슐화해서 개념적으로 접근하는 형태를 갖는 객체지향 프로그래밍이 탄생했다.

 

절차지향의 장점

- 컴퓨터의 처리 구조와 유사해 실행 속도가 빠르다.

 

단점

- 유지보수가 어려움

- 실행 순서가 정해져 있어서 코드의 순서가 바뀌면 결과가 바뀔 수 있음

- 디버깅이 어렵다.

 

객체지향 

객체지향 프로그래밍에서는 데이터와 절차를 하나의 덩어리로 묶어서 생각함.

 

객체지향의 특징

1. 캡슐화

  • 관련된 데이터와 코드가 하나의 묶음으로 정리된 것으로, 관련된 코드와 데이터 묶여있고 오류가 없어서 사용이 편리함. 데이터를 감추고 외부 세계와의 상호작용은 메소드를 통하는 방법인데, 라이브러리를 만들어 업그레이드하면 쉽게 바꿀 수 있다.
    • 메소드 = 객체지향 언어에서는 메세지를 보내 메소드를 수행시킴으로써 통신을 수행한다.

2. 상속

  • 이미 작성된 클래스를 이어 받아서 새로운 클래스를 생성하는 기법. 기존 코드를 재활용하는 것을 의미
    • 상속하면 좋은점?
      • 중복 개발을 피할 수 있다.
      • 부모Class의 기능을 변경하면 그 즉시 자식Class 도 적용시킬 수 있음. 전체적인 개발에 필요한 시간과 비용을 절감.
  • 데코레이터(Decorator) vs 상속
    • 함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용함. 
    • 좀 편하게 말하면 상속받지 않고 다른 함수를 가져다가 붙여서 쓴다.
      • 반복을 줄이고 메소드나 함수의 책임을 확장함.
    • Decorator의 목적은 메서드 별로 앞/뒤에 내용을 추가하는 것. 
  • 상속은 부모Class의 내용을 받아오는게 목적
    • 상속의 목적은 내용을 받아오는 것.

2-1. super().__init__() 함수의 뜻

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

    def get_name(self):
        print(f'제 이름은 {self.name}입니다.')
    
    def get_age(self):
        print(f'제 나이는 {self.age}세 입니다.')
class Student:
    def __init__(self, name, age, GPA):
        self.name = name
        self.age = age
        self.GPA = GPA

    def get_name(self):
        print(f'제 이름은 {self.name}입니다.')
    
    def get_age(self):
        print(f'제 나이는 {self.age}세 입니다.')

    def get_GPA(self):
        print(f'제 학점은 {self.GPA}입니다.')

이렇게 하게 되면 메소드 중복이 많이 발생하게 된다.

그래서 상속을 활용하는데

class Student(Person):
    def __init__(self, name, age, GPA):
        super().__init__(name, age)
        self.GPA = GPA

    def get_GPA(self):
        print(f'제 학점은 {self.GPA}입니다.')

초기화 단계에서 부모Class의 __init__메소드를 호출하는 원리를 진행하고,

부모Class에 전달할 input을 super().__init__() 내에 적어넣으면 됨.

상속받아서 같은 이름의 메소드를 다른 기능을 하도록 만들 수도 있다.

class Student(Person):
    def __init__(self, name, age, GPA):
        super().__init__(name, age)
        self.GPA = GPA

    def get_name(self):
        print(f'저는 대학생 {self.name}입니다.')

2-2. super() 는 무슨 역할을 하는가.

부모Class의 함수를 호출하기 위함임.

class Student(Person):
	def __init__(self, name, age, GPA):
		super().__init__(name, age)
		self.GPA = GPA
        
	def get_GPA(self):
		super().get_name()
		print(f'제 학점은 {self.GPA}입니다.')

3. 다향성

  • 하나의 이름으로 많은 상황에 대처하는 기법임. 개념적으로 동일한 작업을 하는 함수들에 똑같은 이름을 부여할 수 있으므로, 코드가 더 간단해지는 효과가 있음.

위 3가지 특성으로 인한 객체지향의 장점

1. 코드를 재사용하기 쉽다.

2. 업그레이드가 쉽다.

3. 디버깅이 쉽다.

 

단점

1. 하나의 기능만 필요하더라도 모듈 전체를 가져와야 하기 때문에 절차지향 프로그래밍 보다 프로그램 사이즈가 커진다.

2. 데이터에 대한 접근도 상대적으로 절차지향 보다 느리다.

3. 메소드를 통해서만 접근이 가능하기 때문에 절차지향처럼 특정 함수에 접근할 수 없고, 식으로만 접근이 가능하기 때문에 속도적인 측면에서 불이익이 있음.

 

절차지향은 데이터를 중심으로 함수를 구현한다. 객체지향은 기능을 중심으로 메서드를 구현한다.

 


오버라이딩 & 오버로딩

 

오버라이딩을 짧게 설명하면 "클래스 상속 시 부모 Class에서 정의한 메소드를 자식 Class에서 변경하는 것"을 뜻함.

부모 class의 메소드의 이름과 기본적인 기능은 그대로 사용하지만, 특정 기능을 바꾸고 싶을 때 사용함.

  • 확장 가능, 다형성
  • 가독성 증가, 오류가능성 감소, 메소드 이름 절약

 

오버로딩을 설명하면 "동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 할 수 있게 함" 을 뜻한다.

 --> 동일한 클래스 내에서, 매개변수의 개수 또는 자료형이 다른 동명의 메소드를 정의하는 것.

저런 느낌?

근데 오버로딩 같은 경우는 python에서는 정식으로는 지원하지 않는다. 그래서 실행시키면 오류 뜰거임.

이런식으로 하면 파이썬에서도 사용 가능하긴 함. 자료형에 따른 분기처리.

 

아니면 MultipleDispatch 패키지를 통해 메소드 오버로딩도 가능하다.


python 얕은 복사 & 깊은 복사

어제 공부한 내용중에 있던 내용이었는데, 단어를 처음 들어봐서 다시 정리해본다.

python의 자료형에는 불변(immutable)과 가변(mutable)이 있다.

어제 list와 tuple을 비교했을 때, list는 가변 tuple은 불변이라고 했다.

 

arr1 = [1,2,3] 

arr2 = arr1 이라고 하게되면 "=" 를 통해 얕은 복사를 하게 된다.

이러면 arr2라는 새로운 변수가 생성됐다기 보다는 참조만 복사된 것이다.

arr1 -> [1,2,3] <- arr2  둘 다 같은 메모리 주소를 가르키고 있는 것.

그래서 arr1.append(4)를 하면 arr1 -> [1,2,3,4] <- arr2 가 되는 거임. 

 

근데 불변타입 같은 경우는 다르다.

얕은 복사를 하던 깊은 복사를 하던 상관이 없음. 불변 자료형인 int로 보자면

int1 = 1

int2 = int1 이라고 했을 때, int1 -> 1 <- int2  이러다가, int1=4 로 바꾸게 되면

int1 -> 4 / int2 -> 1  새로운 메모리를 할당해서 4라는 값을 생성하고 int1이 참조하게 되는 것.

int1과 int2가 다른 곳을 가리키고 있는 것이다.

 

얕은 복사를 하는 방법도 여러가지가 있긴 함

"="를 예시로 들었지만, [:] / 객체.copy / copy.copy 가 있다.

 

좀 더 추가적으로 정확하게 하자면

"[:] 슬라이싱을 이용한 얕은 복사" 는 "="를 이용한 얕은 복사와 조오금 다르다.

arr1 = [ 1,2,[11,22] ] 

arr2 = arr1[:]

 

이러면 arr1과 arr2의 주소값이 다르다.

주소값이 달라? 깊은 복사?  아니다.

왜냐면 리스트 안의 리스트 [11,22] 의 주소값이 같다.

arr1[3].append(33) 하게 되면 arr2 = [1,2,[11,22,33] ] 이 되는 것을 확인 할 수 있다.

 

Copy를 이용한 복사도 [:] 을 이용한 복사와 같음.

 

 

깊은 복사는  리스트 내부의 리스트, 딕셔너리 내부 리스트 등 내부에 있는 객체 모두 새롭게 만들어 주는 작업.

copy.deepcopy 방법이 있다.

 

arr1 = [1, 2, [11,22,33] , 3]

arr2 = copy.deepcopy(arr1)

하게 되면 

arr1 과 arr2의 주소값이 다르고, 내부에 있는 [11,22,33] 리스트도 주소값이 다르다.

그리고 arr1.append(4)를 해도 arr2에는 영향이 없다. 아에 독립적인 존재인 것.