14장. 객체 지향 프로그래밍-2

asda주asda·2022년 1월 31일
0

Python

목록 보기
18/31

인스턴스를 함수의 인자값으로 전달할 때

인스턴스가 함수의 인자값으로 전달될 때는 참조값이 전달된다.(Call by reference) 그렇기에 인스턴스가 함수 내에서 변경이 가능하다.

# 사각형을 클래스로 정의한다.
class Rectangle:
# 밑의 side= 0 만일 매개변수가 없다면 기본값으로 0을 하겠다. 라는 의미다.
	def __init__(self, side=0):
		self.side = side
	def getArea(self):
		return self.side*self.side

# 사각형 객체와 반복 횟수를 받아서 변을 증가시키면서 면적을 출력한다.
def printAreas (r, n):
	while n >= 1: 객체의 변수가 증가된다.
		print(r.side, "\t", r.getArea())
		r.side = r.side + 1
		n = n - 1
        
# printAreas()를 호출하여서 객체의 내용이 변경되는지를 확인한다.
myRect = Rectangle();
count = 5
printAreas (myRect, count)
print("사각형의 변=", myRect.side)
print("반복횟수", count)

출력 결과:
0 0
1 1
2 4
3 9
4 16
사각형의 변= 5
반복횟수 5

위의 코드는 정사각형의 넓이를 5번 출력한다. 이 때 인스턴스를 매개변수로 받는 함수가 있으며, 출력 결과를 통해 확인이 가능하드시 인스턴스의 값이 처음의 0이 아닌, 5로 바껴있음을 확인할 수 있다. 즉 함수에 인스턴스를 전달하면 객체의 참조값이 전달되고, 이에 myRect와 같은 인스턴스는 변경이 가능하므로 함수 안에서 인스턴스의 내용을 변경하면 원본 객체가 변경된다.

특수 메소드

파이썬에서는 연산자(+, -, *, / 등)에 관련된 특수 메소드(special method)가 있다. 이들 메소드는 우리가 객체에 대하여 +, -, *, / 와 같은 연산을 적용하면 자동으로 호출된다.

예를 들면 인스턴스 a, b가 존재하고 'a + b' 를 하게 되면 '주소 + 주소' 가 되는 형태이기에 연산 자체가 불가하다. 허나 +연산이 가능하게끔 __add()__를 클래스 안 메소드로 정의해주고 피연산자가 인스턴스라면 자동으로 __add()__를 호출해준다.

정확하게 설명하자면 모든 클래스의 최고 조상클래스는 object 클래스이다.
이 때 object 클래스는 __eq__와 같은 여러 메소드들을 가지는데 특수 메소드는 이를 수정한 것이다.

아래의 코드에 원을 나타내는 Circle 클래스에 대하여 __eq__() 메소드를 정의하였다.

class Circle:
    def __eq__(self, other):
		# other의 위치에 다른 인스턴스를 받는다.
        return self.radius == other.radius
        
c1 = Circle(10)
c2 = Circle(10)
if c1 == c2:
	print("원의 반지름은 동일합니다. ")

출력 결과:
원의 반지름은 동일합니다. 

특수 메소드와 관련된 연산자는 아래와 같다.

연산자메소드설명
x + y__add__(self, y)덧셈
x - y__sub__(self, y)뺄셈
x * y__mul__(self, y)곱셈
x / y__truediv__(self, y)실수나눗셈
x // y__floordiv__(self, y)정수나눗셈
x % y__mod__(self, y)나머지
divmod(x, y)__divmod__(self, y)실수나눗셈과 나머지
x ** y__pow__(self, y)지수
x << y__lshift__(self, y)왼쪽 비트 이동
x >> y__rshift__(self, y)오른쪽 비트 이동
x <= y__le__(self, y)less than or equal(작거나 같다)
x < y__lt__(self, y)less than (작다)
x >= y__ge__(self, y)greater than or equal(크거나 같다)
x > y__gt__(self, y)greater than (크다)
x == y__eq__(self, y)같다.
x != y__neq__(self, y)같지 않다.

특수 메서드의 예1: 벡터

2차원 공간에서 벡터(vector)는 (a, b)와 같이 2개의 실수로 표현될 수 있다. 벡터 간에는 덧셈이나 뺄셈이 정의된다.

class Vector2D :
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)
        # 새로운 인스턴스를 생성해서 반환한다.

    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)
        # 새로운 인스턴스를 생성해서 반환한다.

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __str__(self):
        return '(%g, %g)' % (self.x, self.y)

u = Vector2D(0, 1)
v = Vector2D(1, 0)
w = Vector2D(1, 1)
a = u + v                   # 여기서 __add__()가 호출된다.
			    # 이때 a는 인스턴스 벡터이다.
print(a)


출력 결과:
(1, 1)

특수 메서드의 예2: len()

연산 뿐만 아니라 len()과 같은 내장 함수도 특수 메서드로 프로그래머가 정의할 수 있다. 예를 들어 책을 나타내는 클래스 Book을 만들고 len() 함수를 다시 정의하여 책의 페이즈를 반환하는 프로그램이다.

class Book:
	def _init__(self, title, author, pages):
		self.title = title
		self.author = author
		self.pages = pages
        
	def __str__(self):
		return “제목 : %s, 저자 : %s, 페이지 : %s" % (self.title, self.author, self.pages)
        
	def len(self):
		return self.pages

book = Book("Data Structure", "Chun", 650)
# print 함수는 클래스의 __str__를 실행시킨다
print(book)
# len 함수를 클래스의 메소드로 재정의하였다.
print(book.len())

출력 결과:
제목 : Data Structure, 저자 : Chun, 페이지: 650
650

파이썬에서의 변수의 종류

변수의 종류

  • 지역 변수: 함수 안에서 선언되는 변수
  • 전역 변수: 함수 외부에서 선언되는 변수
  • 인스턴스 변수: 클래스 안에서 선언된 변수로 앞에 self가 붙는다.
class Bag:
	def __init__(self):
		self.data = []
	def add(self, x) :
		self.data.append(x)
	def add2(self, x) :
		self.add(x)
		self.add(x)

위의 Bag 클래스에서 data가 인스턴스 변수이다. 인스턴스 변수를 클래스 안에서 사용하려면 항상 앞에 self.를 붙여야 한다. self. 를 붙이지 않으면 파이썬은 지역 변수를 새로 생성할 것이다.
위에서 보면 알수 있드시 클래스 안에서 메소드의 호출 또한 self.를 붙여야 한다. 위에서는 클래스 안에서 정의된 add()메소드의 사용을 위해 self.를 붙였다.

None 참조값

C언어에서의 Null과 유사하게 파이썬에서 None이 있다. 변수가 현재 아무것도 가리키고 있지 않다면 None으로 설정하는 편이 좋다. None은 아무것도 참조하고 있지 않다는 것을 나타내는 특별한 값이다.
예를 들어 변수 tv가 아무런 값을 가지고 있지 않다면 아래와 같이 설정하는 것이 좋다.

tv = None

# 아래는 tv가 None의 값을 가지는지 확인하는 코드다.
if tv = None:
	print("현재 tv가 없습니다.")

# 아래와 같은 코드는 오류를 발생시킨다. tv는 없기에 객체의 멤버를 호출하는 것은 안된다.
tv.setChannel(5) 

상수 정의

PI = 3.14 와 같은 상수들은 흔히 클래스 변수로 정의된다. 참고로 프로그래머들끼리 암묵적으로 상수는 대문자로 정의한다.
예를 들어 게임에서 몬스터의 건강 상태를 상수로 나타내면 아래와 같다.

class Monster :
# 상수 값 정의
WEAK = 0
NORMAL = 10
STRONG = 20
VERY STRONG = 30
# 생성자
def __init__(self) :
	self.__health = Monster.NORMAL
# 만일 몬스터가 강해졌다면 건강상태를 STRONG으로 변경한다.
def eat(self) :
	self.__health = Monster.STRONG
# 만일 약해졌다면 건강상태를 WEAK으로 변경한다.
def attack(self):
	self.__health = Monster.WEAK

클래스 상속

상속이란?

클래스의 상속(Inheritance)은 기존 클래스에 있는 필드와 메서드를 그대로 물려받는 새로운 클래스를 만드는 것이다. 이 때 생성자는 예외가 된다. 상속받은 후에는 새로운 클래스에서 추가로 필드나 메서드를 만들어 사용해도 된다.
상속은 클래스 사이의 직접적인 관계를 만들어 준다.

예를 들어 승용차의 클래스와 트럭 클래스를 생성한다고 가정하자. 그렇다면 아래와 같이 생성할 수 있다.

위의 그림에서 확인할 수 있드시 공통적인 필드와 메서드가 있다. 이럴 때 따로 전부 코드를 작성하는 것 보다, 승용차, 트럭의 공통된 특징을 자동차라는 클래스로 생성한 후 승용차와 트럭은 자동차 클래스의 특징을 그대로 물려받고, 필요한 필드와 메서드만 추가하는 편이 더 효율적일 것이다.

위의 그림에서는 승용차와 트럭의 공통되는 필드인 색상, 속도와 공통되는 메서드인 속도 올리기()와 속도 내리기()를 자동차 클래스에 정의했다. 그리고 자동차 클래스를 상속받아서 승용차와 트럭 클래스를 만들었다. 승용차 클래스에서는 자동차 클래스의 필드와 메서드를 그대로 이어받고, 좌석 수 필드와 좌석 수 알아보기() 메서드를 추가했다. 트럭 클래스도 마찬가지로 자동차 클래스의 필드와 메서드를 그대로 이어받고, 적재량 필드와 적재량 알아보기() 메서드를 추가했다.

이런 식으로 계속 추가를 하기에 하위 클래스로 내려갈수록 필드와 메서드의 양은 점점 늘어난다.

공통된 내용을 자동차 클래스에 두고 상속을 받음으로써 일관되고 효율적인 프로그래밍이 가능해진다. 이 때 상위 클래스(자동차)를 슈퍼 클래스 ,부모 클래스, 조상 클래스라고 하며, 하위 클래스(승용차, 트럭)를 서브 클래스, 자식 클래스, 자손 클래스라 칭한다.

상속을 구현 하는 문법은 아래와 같다.

class 서브 클래스(슈퍼 클래스):
# 서브 클래스의 코딩

메서드 오버라이딩

메서드 오버라이딩(Overriding)은 상위 클래스의 메서드를 서브 클래스에서 재정의하는 것이다.

아래 그림은 메서드 오버라이딩을 잘 보여 준다. 트럭의 속도에는 제한이 없지만, 승용차는 안전을 이유로 속도를 최대 150km로 제한해야 한다고 가정하자.

슈퍼 클래스를 상속 받은 서브 클래스는 따로 '속도 올리기' 메서드를 다시 만들 필요가 없다. 하지만 '속도 올리기' 라는 명칭의 메소드를 다시 만들어야 할 경우도 있다.
만일 승용차 클래스의 속도를 제한한다면 if문을 넣어 메소드를 재정의 해야한다.
이렇게 슈퍼 클래스에 있는 메서드를 서브 클래스에서 다시 만들어 사용하는 것을 메서
드 오버라이딩 또는 재정의라고 한다.

# 클래스 선언 부분 #
class Car :
	speed = 0
	def upSpeed(self, value) :
		self.speed += value
		print("현재 속도(슈퍼 클래스) : %d" % self.speed)

class Sedan(Car) :
	def upSpeed(self, value) :
		self.speed += value
		if self.speed > 150 :
			self.speed = 150
		print("현재 속도(서브 클래스) : %d" % self.speed)
class Truck(Car) :
	pass 	# 여기서 정의할 것이 없다면 pass를 넣는다.
	    
# 변수 선언 부분 #
sedan1, truck1 = None, None

# 메인 코드 부분 #
truck1 = Truck()
sedan1 = Sedan()
print("트럭 --> ", end = "")
truck1.upSpeed(200)
print("승용차 --> ", end = "")
sedan1.upSpeed (200)

출력 결과:
트럭 --> 현재 속도(슈퍼 클래스) : 200
승용차 --> 현재 속도(서브 클래스) : 150

서브 클래스(Sedan)의 upSpeed() 메서드를 재정의 한다.그리고 Sedan 인스턴스의 upSpeed() 메서드를 호출하면 재정의된 upSpeed() 메서드를 호출한다. 서브 클래스(Truck)에는 아무런 내용도 없으므로 슈퍼 클래스(Car)의 메서드를 모두 그대로 상속받는다. 그리고 Truck 인스턴스의 upSpeed()를 호출하면 슈퍼 클래스(Car)의 upSpeed() 메서드를 호출한다.

super() 메소드

서브 클래스에서 메서드 오버라이딩을 할 때, 슈퍼 클래스의 메서드나 속성을 사용해야 하는 경우가 있다. 이럴 때는 super() 메서드를 사용한다.

class Car :
	value = '슈퍼 값’
	def carMethod(self):
		print('슈퍼 클래스 메서드 실행~~)

class Sedan(Car) :
	value = '서브 값’
	def carMethod(self) :
	super().carMethod() 		# Car 클래스의 carMethod()가 실행됨
	print('서브 클래스 메서드 실행’)
	print(super().value) 		# Car 클래스의 value가 출력됨

sedan1 = Sedan()
sedan1.carMethod()

예. Turtle

## Shape.py
# 조상클래스 Shape 을 정의
import turtle
import random
class Shape:
    myTurtle = None
    # Center x,y 도형의 중심점
    cx, cy = 0, 0

    # 기본생성자
    def __init__(self):
        # Turtle 인스턴스 생성
        self.myTurtle = turtle.Turtle()

    # 펜의 색상과 두께 무작위로 뽑기
    def setPen(self):
        r = random.random() # 0.0 <= 값 < 1.0
        g = random.random() # 0.0 <= 값 < 1.0
        b = random.random() # 0.0 <= 값 < 1.0
        print('r: ',r,'g: ',g,'b: ',b)
        self.myTurtle.pencolor((r,g,b)) # 펜 색상 지정
        penSize = random.randrange(1,20) # 펜의 굵기를 임의로 얻는다.
        self.myTurtle.pensize(penSize)  # 펜의 굵기를 지정
    # Shape 클래스를 상속받는 클래스들은 플요에 의해서 drawShpae()를 오버라이딩
    # 할 수 있도록 선언부 선언과 아무런 내용이 없는 구현부를 만들어 두었다.
    def drawShape(self):
        pass
        
## Rentangle.py
from Shape import *

# 자손클래스 Rectangle 정의
class Rentangle(Shape):
    width = 0
    height = 0
    def __init__(self, cx, cy):
        Shape.__init__(self) # 조상클래스의 생성자 호출
        self.cx = cx
        self.cy = cy
        # 사각형의 너비 ,폭을 임의의 수로 결정
        self.width = random.randrange(20,100)
        self.height = random.randrange(20,100)
        print("width: ",self.width," height: ",self.height)

    # 조상클래스 drawShape() 를 오버라이딩
    def drawShape(self):
        # 사각형 그리기
        sx1, sy1 = 0, 0 # 좌측 상단의 좌표값
        sx2, sy2 = 0, 0 # 우측 하단의 좌표값

        sx1 = self.cx - self.width/2
        sy1 = self.cy - self.height/2
        sx2 = self.cx + self.width/2
        sy2 = self.cy + self.height/2
        print("cx: ",self.cx,"cy: ",self.cy)
        print("sx1 sy1 sx2 sy2 = ",sx1, sy1, sx2, sy2)

        self.setPen() # 팬의  색상과 두께를 정하는 조상클래스의 메서드를 호출
        self.myTurtle.penup() # 팬을 드는 메서드
        self.myTurtle.goto(sx1, sy1) # 팬을 좌측 상단으로 이동
        self.myTurtle.pendown() # 팬을 내리는 메서드
        self.myTurtle.goto(sx1, sy2)
        self.myTurtle.goto(sx2, sy2)
        self.myTurtle.goto(sx2, sy1)
        self.myTurtle.goto(sx1, sy1)

## ShapeExample.py
# Shape 클래스의 실해파일
from Rectangle import *

# 왼쪽 마우스를 클릭하면  호출되는 함수
def screenLeftClick(x,y):
    rect = Rentangle(x,y)
    rect.drawShape()

if __name__ == '__main__':
    turtle.title('클래스를 이용한 사각형 그리기')
    # 아래의 코드는 터틀 그래픽 판에서 마우스 왼쪽 버튼이 클릭이 되는 것을
    # 감지하는 리스너 메소드이다.
    # 1은 왼쪽버튼, 2는 휠, 3번은 오른쪽버튼
    turtle.onscreenclick(screenLeftClick, 1)
    turtle.done() # 터틀 그래픽 창을 닫히지 않게끔 한느 메서드이다.

0개의 댓글

관련 채용 정보