클래스

객체 지향 프로그래밍

객체와 클래스

객체 지향 프로그래밍에서의 객체는 다음과 같이 정의된다.

객체(Object) = 속성(Attribute) + 기능(Method)

속성은 사물의 특징을 말한다.

자동차로 예를 들면, 몸체의 색, 바퀴의 크기, 엔진의 배기량 등이 자동차의 속성이라 할 수 있다. 기능은 어떤 것의 특징적인 동작을 말한다.

다시 자동차로 예를 들면, 전진, 후진, 좌회전, 우회전등이 자동차의 기능이라고 할 수 있다!

  • 속성을 ‘변수’, 기능을 ‘함수’로 바꿔 ‘객체 = 변수 + 함수’라고 할 수 있다.
class Car: # Car 클래스의 정의 시작을 알린다
	def __init__(self):
		self.color = 0xFF0000 # Car 클래스 안에 차의 색, 바퀴 크기, 배기량을 나타내는 변수를 정의한다.
		self.wheel_size = 16
		self.displacement = 2000
		
	def forward(self): # 전진
		pass
		
	def backward(self): # 후진
		pass
		
	def turn_left(self): # 좌회전
		pass
		
	def turn_right(self): # 우회전 # Car 클래스 안에 전진, 후진, 좌회전, 우회전 함수를 정의
		pass
  • 클래스는 자료형이고, 객체는 변수이다!

  • 객체 대신에 인스턴스(Instance)라는 용어를 사용하기도 한다. 인스턴스는 실체, 사례라는 뜻으로 클래스가 설계도라면, 객체는 그 설계를 바탕으로 실체화 한 것이라는 뜻에서 유래한다.

객체 지향 프로그래밍을 해야하는 이유!

한번 만들어서 세상에 내놓으면 고칠 일이 별로 없는 하드웨어와는 달리 소프트웨어는 세상에 내놓는 순간부터 챙겨줘야 할 것이 매우 많아진다. 이때 소프트웨어의 결합도가 높으면 코드를 고칠 때마다 부작용이 발생할 가능성이 높아진다. 결합도가 낮은 소프트웨어는 어느 한 부분을 수정하더라도 부작용에 대한 걱정을 줄일 수 있다.

같은 목적과 기능을 위해 객체로 묶인 코드 요소(변수, 함수)들은 객체 내부에서만 강한 응집력을 발휘하고 객체 외부에 주는 영향은 줄이게 된다.

  • 코드가 객체 위주로 이루어질 수 있도록 지향하는 프로그래머의 노력은 코드의 결합도를 낮추는 결과는 낳게 된다. 객체 지향 프로그래밍의 장점은 여기에 있다.

클래스의 정의

클래스는 class 키워드를 이용하여 정의한다.

class 클래스이름:
	코드블록
  • 파이썬에서는 콜론으로 끝나는 문장의 다음 줄에는 코드 블록이 위치할 것을 요구하는데, 클래스의 코드블록은 변수와 메소드(Method)로 이루어진다. 여기서 메소드는 클래스에 속해있는 함수를 말한다.

클래스 안에 정의되어 있는 변수와 메소드는 클래스의 멤버라고도 한다. 메소드는. 이미 용어 자체로 함수와 달리 클래스에 소속되어 있음을 알 수 있지만 변수의 경우에는 일반 변수와 클래스의 멤버 변수를 구분하기 어려우므로 ‘데이터 속성(Data Attribute)’이라는 용어가 사용된다.

class Car:
    def __init__(self):
        self.color = 0xFF0000    # 바디의 색
        self.wheel_size = 16     # 바퀴의 크기
        self.displacement = 2000 # 엔진 배기량

    def forward(self): # 전진
        pass

    def backward(self): # 후진
        pass

    def turn_left(self): # 좌회전
        pass

    def turn_right(self): # 우회전
        pass

if __name__ == '__main__':        
    my_car = Car() # 앞에서 정의한 Car클래스의 객체 my_car를 생성한다.

    print('0x{:02X}'.format(my_car.color)) # my_car의 정보를 출력
    print(my_car.wheel_size)
    print(my_car.displacement)

    my_car.forward() # my_car의 메소드를 호출
    my_car.backward()
    my_car.turn_left()
    my_car.turn_right()

ex) 실행 결과

> Car.py
0xFF0000
16
2000

__init__() 메소드를 이용한 초기화

클래스의 생성자가 호출되면 내부적으로 또 다른 두 개의 메소드가 호출된다. 하나는 __new__()이고 또 다른 하나는 우리가 알고 있는 __init__()이다. 이들은 마법(Magic)메소드 또는 특별(Special)메소드라고 불리운다.

데이터 속성을 __init__()메소드가 아닌 클래스에 직접 정의한다면 어떤 일이 일어날까?

class ClassVar: # text_list는 ClassVar클래스의 정의 시점에 함께 메모리에 할당된다.
	text_list = []
	
obj = ClassVar() # obj객체가 생성되기 전부터 text_list는 메모리에 적재되어 있다.

ex) 왜 __init__()메소드를 사용해야하는가?

class ClassVar:
    text_list = []

    def add(self, text):
        self.text_list.append(text)
    
    def print_list(self):
        print(self.text_list)

if __name__ == '__main__':        
    a = ClassVar()
    a.add('a')
    a.print_list() # ['a'] 출력을 기대
    
    b = ClassVar()
    b.add('b')
    b.print_list() # ['b'] 출력을 기대

ex) 실행 결과

> ClassVar.py
['a'] # 객체 a를 통해 'a'를 입력하고 a의 text_list 내용을 출력.
['a','b']	# 객체 b를 통해 'b'를 입력하고 b의 text_list 내용을 출력했다. 하지만 객체 'b'에서는 입력하지 않은 'a'가 들어 있다. 이것은 클래스 속성으로 정의된 text_list변수를 ClassVar의 모든 인스턴스가 공유하게 되어 생긴 문제이다!
  • 인스턴스가 생성될 때에만 __init__()메소드가 실행되므로 이 메소드 안에 변수 정의/초기화 코드를 넣어 놓으면 모든 인스턴스가 변수를 공유하게 되는 불상사는 피할 수 있다.
class InstanceVar:
    def __init__(self):
        self.text_list = []

    def add(self, text):
        self.text_list.append(text)
    
    def print_list(self):
        print(self.text_list)

if __name__ == '__main__':        
    a = InstanceVar()
    a.add('a')
    a.print_list() # ['a'] 출력을 기대
    
    b = InstanceVar()
    b.add('b')
    b.print_list() # ['b'] 출력을 기대

ex) 실행 결과

> InstanceVar.py
['a']
['b'] # __init__()메소드 안에서 정의한 text_list는 각 객체마다 고유한 인스턴스로 사용하므로 ClassVar.py에서와 같은 부작용이 사라졌다!!!
  • __init__() 메소드의 이름은 ‘초기화하다’는 뜻의 initailize를 줄여서 붙여진 것이다.

self에 대해

self는 ‘자아’또는 ‘자신’이라는 뜻이다. 파이썬의 메소드에 사용되는 self는 메소드가 소속되어 있는 객체이다.

class ContactInfo:
	def __init__(self, name, email):
		self.name = name
		self.email = email
		
	kyeonghan = ContactInfo('윤경한', 'zizou0812@gmail.com')
  • ContactInfo 외부에서는 kyeonghan 이라는 이름으로 객체를 다룰 수 있다. 내부에서는 kyeonghan처럼 객체를 지칭할 수 있는 이름이 없다. 그래서 self를 도입한 것이다!

정적 메소드와 클래스 메소드

인스턴스 메소드는 인스턴스(객체)에 속한 메소드를 말한다

인스턴스 메소드가 “인스턴스에 속한다”라는 표현은 “인스턴스를 통해 호출가능하다”라는 뜻이다. 그렇다면 클래스에 속한(클래스를 통해 호출 가능한) 메소드도 있을까? 있다. 정적 메소드(Static Method)와 클래스 메소드(Class Method)가 바로 그런 메소드 이다.

ex) 정적메소드의 예

class Calculator:
	
	@staticmethod
	def plus(a, b): # 당연히 정적 메소드도 매개변수를 받는다. self는 제외!
		return a+b

ex) 사칙연산 함수를 활용하는 정적메소드 예제

class Calculator:

    @staticmethod # 클래스 안에 정의되었지만 self를 매개변수로 받지 않는다.
    def plus(a, b):
        return a+b

    @staticmethod
    def minus(a, b):
        return a-b

    @staticmethod
    def multiply(a, b):
        return a*b

    @staticmethod        
    def divide(a, b):
        return a/b
        
if __name__ == '__main__':        
    print("{0} + {1} = {2}".format(7, 4, Calculator.plus(7, 4))) # 정적 메소드는 인스턴스 대신 클래스 이름을 통해 메소드에 접근한다. "클래스이름.메소드()"의 형태!
    print("{0} - {1} = {2}".format(7, 4, Calculator.minus(7, 4)))
    print("{0} * {1} = {2}".format(7, 4, Calculator.multiply(7, 4)))
    print("{0} / {1} = {2}".format(7, 4, Calculator.divide(7, 4)))

ex) 실행 결과

> Calculator.py
7 + 4 = 11
7 + 4 = 3
7 + 4 = 28
7 + 4 = 1.75
  • 정적 메소드는 self 매개변수를 전달받을 방법이 없으므로(self 매개변수는 인스턴스 메소드에게만 파이썬이 전달해준다) 객체/인스턴스의 변수에 접근 할 수 없다.

  • 따라서, 정적 메소드는 객체의 데이터 속성과는 관계가 없는 코드로 구현되는 것이 일반적이다.

ex) 클래스 메소드의 예

class 클래스이름:
	# ...  
	
	@classmethod # 클래스 메소드를 정의하기 위해서는 1. @classmethod 데코레이터를 앞에 붙여준다. 
	def 메소드이름(cls): # 2. 메소드의 매개변수를 하나 이상 정의한다.
		pass

ex) cls 매개변수를 통해 클래스 속성에 접근하는 방법 예제

class InstanceCounter:
    count = 0
    def __init__(self): # 인스턴스가 만들어질 때마다 __init__()메소드가 실행된다. 인스턴스의 수를 세기에는 이곳이 적절하다!
        InstanceCounter.count += 1
    
    @classmethod
    def print_instance_count(cls): # print_instance_count() 메소드는 InstanceCounter의 클래스 속성인 count를 출력한다.
        print(cls.count)

if __name__ == '__main__':        
    a = InstanceCounter()
    InstanceCounter.print_instance_count()
    
    b = InstanceCounter()
    InstanceCounter.print_instance_count()
    
    c = InstanceCounter()
    c.print_instance_count()

ex) 실행 결과

> InstanceCounter.py
1
2
3

클래스 내부에게만 열려있는 프라이빗 멤버

클래스의 안에서만 접근이 가능한 멤버를 일컬어 ‘프라이빗(Private)’멤버라고 한다. 반대로 안과 밖 모두에서 접근이 가능한 멤버는 ‘퍼블릭(Public)’멤버라고 한다.

파이썬은 다른 프로그래밍 언어들과는 달리 작명법(Naming)으로 둘을 구분한다!

프라이빗 멤버의 명명 규칙은 다음과 같다!

  1. 두 개의 밑줄 __이 접두사여야 한다. 예) _number

  2. 접미사는 밑줄이 한 개까지만 허용된다. 예) _number_

    • 접미사의 밑줄이 두 개 이상이면 퍼블릭 멤버로 간주한다. 예) __number__

상속

클래스끼리 데이터를 물려주는 것을 일컬어 ‘상속(Inheritance)’이라고 한다.

객체 지향 프로그래밍에서는 물려 받는 클래스(파생 클래스(Derived Class) 또는 자식 클래스라고 한다)가 유산을 물려줄 클래스(기반 클래스(Base Class)또는 부모 클래스라고 한다)를 지정한다.

ex) 상속 개념

class 기반 클래스:
	# 멤버 정의
	
class 파생 클래스(기반 클래스)
	# 아무 멤버를 정의하지 않아도 기반 클래스의 모든 것을 물려받아 갖게 된다.
	# 단, 프라이빗 멤버(_로 시작하는 이름을 갖는 멤버)는 제외

super()

super()는 부모 클래스의 객체 역할을 하는 프록시(Proxy)를 반환하는 내장 함수이다!

  • 사실상, super()함수의 반환값을 상위 클래스의 객체로 간주하고 코딩을 해도 된다.

super()를 사용함으로써, 기반 클래스가 다른 클래스로 교체되거나 수정되어도 파생 클래스가 받는 영향을 최소화 할 수 있다.

다중 상속

파이썬에서는 파생 클래스를 정의할때 기반 클래스의 이름을 콤마(,)로 구분해서 쭉 적어주면 다중 상속이 이루어진다.

ex) 다중 상속의 예

class A:
	pass
	
class B:
	pass
	
class C:
	pass
	
class D(A, B, C): # 클래스 D는 A, B, C로부터 상속받는다.
	pass

오버라이딩

오버 라이딩(Overriding)은 영어로 짓밟다. (결정 등을) 무효로 하다. ~에 우선하다 등의 뜻을 갖고 있다. OOP에서 오버라이딩의 뜻은 “기반(부모) 클래스로부터 상속받은 메소드를 다시 정의하다”이다.

ex) 오버라이딩의 예

>>> class A:
     	def method(self):
     		print("A")
     		
>>> class B(A):
    	def method(self):
    		print("B")
    		
>>> class C(A):
    	def method(self):
    		print("C")
    		
>>> A().method()
A
>>> B().method()
B
>>> C().method()
C
  • 위의 코드에서 클래스 B와 C는 A를 상속하고 있기 때문에 가만히 있어도 method()를 물려받을 수 있었다. 하지만 그렇게 하지 않았다. 자신만의 method()를 새로 정의했다. 이것이 오버라이딩이다!

데코레이터: 함수를 꾸미는 객체

데코레이터는 __call__() 메소드를 구현하는 클래스

__call__() 메소드는 객체를 함수 호출 방식으로 사용하게 만드는 마법 메소드이다!

ex) __call__()

class MyDecorator:
	def __init__(self, f): # __init__() 메소드의 매개변수를 통해 함수를 받아들이고 데이터 속성에 저장해둔다.
		print("Initializing MyDecorator...")
		self.func = f
		
	def __call__(self):
		print("Begin :{0}".format( self.func.__name__))
		
		self.func() # __call__() 메소드가 호출되면서 생성자에서 저장해둔 함수(데이터 속성)를 호출한다!
		
		print("End :{0}".format(self.func.__name__))

데코레이터를 생성하는 방법 1: 생성자

앞서 정의한 MyDecorator 클래스의 경우는 다음과 같이 데코레이터의 인스턴스를 만들어 사용한다.

def print_hello():
	print("Hello")
	
print_hello = MyDecorator(print_hello)

ex) 데코레이터의 예

class MyDecorator:
    def __init__(self, f):
        print("Initializing MyDecorator...")
        self.func = f # MyDecorator의 func 데이터 속성이 print_hello를 받아둔다.

    def __call__(self):
        print ("Begin :{0}".format( self.func.__name__))
        self.func()
        print ("End :{0}".format(self.func.__name__))

def print_hello():
    print("Hello.")

print_hello = MyDecorator(print_hello) # MyDecorator의 인스턴스가 만들어지며 __init__()메소드가 호출된다. print_hello 식별자는 앞에서 정의한 함수가 아닌 MyDecorator의 객체이다.
    
print_hello() # __call__()메소드 덕에 MyDecorator 객체를 호출하듯 사용 할 수 있다.

ex) 실행 결과

> decorator.py
Initializing MyDecorator ...
Begin :print_hello
Hello.
End :print_hello

데코레이터를 사용하는 방법 2: @ 기호

데코레이터(장식)이라는 이름답게 사용하려면 @데코레이터의 꼴로 사용하는 것이 바람직하다. 앞에서는 생성자를 호출해서 데코레이터의 인스턴스를 만들었지만, @ 기호로 시작하는 데코레이터 전용 문법을 이용하면 함수에 꽃장식을 꽂아놓듯 간단하게 데코레이터의 인스턴스를 만들 수 있다.

ex) @ 데코레이터 예

class MyDecorator:
    def __init__(self, f):
        print("Initializing MyDecorator...")
        self.func = f

    def __call__(self):
        print ("Begin :{0}".format( self.func.__name__))
        self.func()
        print ("End :{0}".format(self.func.__name__))

@MyDecorator        
def print_hello():
    print("Hello.")

print_hello()

ex) 실행 결과

> decorator2.py
Initializing MyDecorator ...
Begin :print_hello
Hello.
End :print_hello

for문으로 순회를 할 수 있는 객체 만들기

이터레이터와 순회 가능한 객체

파이썬에서 for문을 실행할 때 가장먼저 하는 일은 순회하려는 객체의 __iter__()메소드를 호출하는 것이다. __iter__()메소드는 이터레이터(Iterator)라고 하는 특별한 객체를 for문에게 반환한다.

이터레이터는 __next__()메소드를 구현하는 객체를 말하는데, for문은 매 반복을 수행할 때마다 바로 이 __next__()메소드를 호출하여 다음 요소를 얻어낸다.

ex) 이터레이터 예

class MyDecorator:
    def __init__(self, f):
        print("Initializing MyDecorator...")
        self.func = f

    def __call__(self):
        print ("Begin :{0}".format( self.func.__name__))
        self.func()
        print ("End :{0}".format(self.func.__name__))

@MyDecorator        
def print_hello():
    print("Hello.")

print_hello()

ex) 실행 결과

0
1
2
3
4

제네레이터

제네레이터(Generator)는 이터레이터처럼 동작하는 함수이다. 하지만 이터레이터보다 훨씬 더 간편하게 구현가능하다.

클래스를 정의하지 않아도 되고 __init__()메소드나 __next__()메소드를 구현 할 필요도 없다. 그저 함수 안에서 yield문을 이용하여 값을 반환하면 된다.

  • yeild문은 상당히 독특하다. return문처럼 함수를 실행하다가 값을 반환하지만, return문과는 달리 함수를 종료시키지는 않고 중단시켜 놓기만 한다.

ex) 제네레이터 예

def YourRange(start, end):
    current = start
    while current < end:
        yield current
        current += 1
    return

for i in YourRange(0, 5):
    print(i)

ex) 실행 결과

0
1
2
3
4

상속의 조건: 추상 기반 클래스

“미운 오리 새끼”에서 아기 오리가 엄마 오리에게 미움을 받았던 이유는 단 하나.

외모이다. 미운 아기 오리는 사실 오리가 아니라 백조였다. 엄마 오리는 아무리 봐도 오리라고는 할 수 없는 아기 백조를 내치고 다른 아기 오리들만을 챙겼다.

프로그래머도 종종 엄마 오리와같은 입장이 될 때가 있다. 부모 클래스를 정의할 때 자식 클래스가 갖춰야 하는 모습을 규약으로 정의해 두고 이 규약을 따르지 않는 자식은 자식으로 인정하지 않는것이다. 추상 기반 클래스(Abstract Base Class)는 자식 클래스가 갖춰야 할 특징(메소드)을 강제하는 기능을 한다. 만약 추상 기반 클래스가 요구하는 메소드를 자식 클래스가 구현하지 않는다면, 자식 클래스의 인스턴스를 생성하고자 할 때 파이썬은 TypeError 예외를 일으킨다!

from abc import ABCMeta
from abc import abstractmethod

class AbstractDuck(metaclass=ABCMeta):
	@abstractmethod
	def Quack(self):
		pass
  • 파이썬에서는 모든 것이 객체이다. 심지어 클래스도 객체이다. 그러니 이 객체의 자료형인 클래스가 존재해야 한다. 메타 클래스는 ‘클래스에 대한 정보를 갖고 있는 클래스’를 말한다. 클래스를 정의할 때 metaclass에 별도의 메타 클래스를 지정하지 않으면 type 클래스가 기본적으로 사용된다.

오류를 어떻게 다뤄야 할까?

예외

파이썬에서 예외(Exception)는 문법적으로는 문제가 없는 코드를 실행하는 중에 발생하는 오류를 말한다.

try ~ except로 예외 처리하기

파이썬의 예외 처리는 try ~ except구문을 이용한다.

  • try 절 안에는 문제가 없을 경우, 정상적인 경우에 실행할 코드블록을 배치한다.

  • except 절에는 문제가 생겼을 때 뒤처리를 하는 코드 블록을 배치한다.

try: 
	# 문제가 없을 경우 실행할 코드
except:
	# 문제가 생겼을 때 실행할 코드

여러 개의 except절 사용하기

프로그램이 하는 일이 많아지고 코드의 양이 늘어나면 처리해야 할 예외도 늘어난다. 늘어난 예외의 종류에 따른 예외처리를 구현하려면 하나 이상의 except절을 사용해야 한다.

try:
	# 문제가 없을 경우 실행할 코드
except 예외형식1:
	# 문제가 생겼을 때 실행할 코드
except 에외형식2:
	# 문제가 생겼을 때 실행할 코드

try절을 무사히 실행하면 만날 수 있는 else

try와 함께 사용하는 else는 if문에서 사용하는 else와는 다르다. 이 else절은 try절에 있는 코드블록 실행중에 아무런 예외가 일어나지 않으면 실행된다.

  • 말하자면, try에 대한 else가 아닌 ‘except절에 대한 else’인 것이다.
try:
	# 실행할 코드블록
except: 
	# 예외 처리 코드블록
else:
	# except절을 만나지 않았을 경우 실행하는 코드블록

ex) else 예

my_list = [1, 2, 3]

try:
    print("첨자를 입력하세요:")
    index = int(input())
    print("my_list[{0}]: {1}".format(index, my_list[index]))
except Exception as err:
    print("예외가 발생했습니다 ({0})".format(err))
else:
    print("리스트의 요소 출력에 성공했습니다.")

ex) 실행 결과

> try_except_else.py
첨자를 입력하세요:
1
my_list[1]: 2
리스트의 요소 출력에 성공했습니다.

> try_except_else.py
첨자를 입력하세요:
10
예외가 발생했습니다 (list index out of range)

어떤 일이 있어도 반드시 실행되는 finally

finally는 else와 비슷하면서도 매우 다르다. else는 except절이 실행되면 실행되지 않고, except절이 실행되지 않으면 실행됐었다. finally는 예외가 발생했든 아무 일이 없든 간에 ‘무조건’실행된다.

Exception 클래스

파이썬은 오류 상황에 대한 정보를 담는 예외 형식을 다양하게 제공한다. 그 예외 형식들의 맨 위에는 BaseException 클래스가 있다. 파이썬의 모든 예외 형식은 BaseException 클래스로부터 상속받는다.

  • BaseException이 예외 클래스 족보의 시조이긴 하지만, 실질적인 시조로 간주되는 것은 그 밑에 있는 Exception클래스이다. 파이썬 코드를 작성하면서 사용되는 예외 형식은 거의 모두 Exception 클래스로부터 상속을 받는다.

우리도 예외 좀 일으켜보자

우리가 작성한 코드도 파이썬의 함수나 연산자처럼 예외를 일으킬 수 있다. 예외 객체를 매개변수로 넘겨 raise문을 실행하면 된다.

ex) raise 예

text = input()
if text.isdigit() == False:
	raise Exception("입력받은 문자열이 숫자로 구성되어 있지 않습니다."):

raise문을 통해 발생시킨 예외는 except문으로 받아 처리하지 않으면 자신을 받아주는 곳이 나올 때 까지 상위 코드로 나아간다.

  • 함수에서 일어난 예외를 함수 안에서 처리하지 않으면 그 예외는 함수의 호출자에게로 던져진다. 그 호출자도 처리를 안하고 그 호출자의 호출자도 처리를 안하다 보면 앞에서 본 것처럼 파이썬 인터프리터가 받나낸다.

ex) 함수 안에서 예외를 일으키고 이것을 호출자에서 받아 처리하는 예

def some_function():
    print("1~10 사이의 수를 입력하세요:")
    num = int(input())
    if num < 1 or num > 10:
        raise Exception("유효하지 않은 숫자입니다.: {0}".format(num))
    else:
        print("입력한 수는 {0}입니다.".format(num))

try:
    some_function()
except Exception as err: # 함수 안에서 일어난 예외가 except문으로 처리되지 않으면 함수 밖으로 다시 던져진다.
    print("예외가 발생했습니다. {0}".format(err))

ex) 실행 결과

> raise_in_function.py
1~10 사이의 수를 입력하세요:
5
입력한 수는 5입니다.

> raise_in_function.py
1~10 사이의 수를 입력하세요:
12
예외가 발생했습니다. 유효하지 않은 숫자입니다.: 12

내가 만든 예외 형식

필요한 예외 형식(사용자 정의 예외 형식)을 직접 만들어 사용하려면 Exception 클래스를 상속하는 클래스를 정의하면 된다.

class MyException(Exception):
	pass

ex) 직접 정의한 예외 형식 예

class InvalidIntException(Exception):
    def __init__(self, arg):
        super().__init__('정수가 아닙니다.: {0}'.format(arg))
 
def convert_to_integer(text):
    if text.isdigit(): # 부호(+, -) 처리 못함.
        return int(text)
    else:
        raise InvalidIntException(text)

if __name__ == '__main__':
    try:
        print('숫자를 입력하세요:')
        text = input()
        number = convert_to_integer(text)        
    except InvalidIntException as err:
        print('예외가 발생했습니다 ({0})'.format(err))
    else:
        print('정수 형식으로 변환되었습니다 : {0}({1}'.format(number, type(number)))

ex) 실행 결과

> InvalidIntException.py
숫자를 입력하세요:
123
정수 형식으로 변환되었습니다 : 123(<class 'int'>

> InvalidIntException.py
숫자를 입력하세요:
abc
예외가 발생했습니다. (정수가 아닙니다.: abc)