Transcript
Page 1: Python Programming: Class and Object Oriented Programming

Python Intermediate Programming

임찬식 ([email protected])

1

Page 2: Python Programming: Class and Object Oriented Programming

Python Intermediate Programming타입과 객체

함수와 함수형 프로그래밍

클래스와 객체지향 프로그래밍

데이터 구조

튜닝과 최적화

2

Page 3: Python Programming: Class and Object Oriented Programming

클래스와 객체지향 프로그래밍class

클래스 인스턴스

유효 범위 규칙

상속

다형성 동적 바인딩

3

Page 4: Python Programming: Class and Object Oriented Programming

class 문

클래스(class) 는 인스턴스(instance) 라고 부르는 객체에 연결되고이들 객체 사이에 공유되는 속성 정의

일반적으로 클래스는함수(메서드), 변수(클래스 변수), 계산된 속성(프로퍼티) 을 모아둔 것

클래스는 class 문으로 정의하고 클래스 몸체는클래스가 정의되는 도중에 실행할 문장을 담고 있음

class 문 자체는 클래스의 인스턴스를 생성하지 않음

클래스 안에서 정의되는 함수를 인스턴스 메서드라 부름인스턴스 메서드는 첫 번째 인수로 넘어오는 클래스 인스턴스에�해서 동작하는 함수

첫 번째 인수 이름은 관례적으로 self 로 사용

4

Page 5: Python Programming: Class and Object Oriented Programming

class 문

class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance Account.num_accounts += 1 def __del__(self): Account.num_accounts -= 1 def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return self.balance

num_accounts 같은 클래스 변수는 클래스의 모든 인스턴스에서 공유

5

Page 6: Python Programming: Class and Object Oriented Programming

클래스 인스턴스

클래스의 인스턴스는 클래스 객체를 함수로서 호출해 생성

새로운 인스턴스는 생성되고 클래스의 __init__() 메서드에 생성한인스턴스를 전달해 초기화 진행

>>> a = Account("Guido", 1000.00)>>> a.balance1000.0>>> a.deposit(100.00)>>> a.balance1100.0>>> Account.deposit(a, 100.00)>>> a.balance1200.0

6

Page 7: Python Programming: Class and Object Oriented Programming

클래스 인스턴스

__init__() 안에서 self 에 인스턴스 속성 추가하고점(.) 연산자를 사용해 인스턴스나 클래스 속성에 접근

def __init__(self, name, balance): self.name = name # name 속성을 인스턴스에 추가

name 속성에 접근

>>> b = Account("Bill", 2000.00)>>> b.name'Bill'

점(.) 연산자를 사용해 속성에 접근할 때 속성 값을 여러 곳에서 찾음

속성에 접근하면 먼저 인스턴스를 검사

인스턴스에 없을 경우에는 클래스를 검사

7

Page 8: Python Programming: Class and Object Oriented Programming

유효 범위 규칙

클래스는 새로운 네임스페이스를 정의하지만 메서드 안에서 참조하는이름에 �한 유효 범위를 생성하지는 않음

클래스 메서를 작성할 때 속성이나 메서드에 �한 참조는 인스턴스를명확하게 지정하여야 함

>>> class Foo(object):... def bar(self):... print("bar!")... def spam(self):... self.bar()... Foo.bar(self)... bar(self)...>>> Foo().spam()bar!bar!Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in spamNameError: name 'bar' is not defined 8

Page 9: Python Programming: Class and Object Oriented Programming

유효 범위 규칙

클래스에서 유효 범위가 생성되지 않는 점은 C++ 혹은 자바와 다른 점

C++ 이나 자바에서 this 포인터가 self 매개변수와 같음

파이썬에서 변수를 명시적으로 선언할 수 있는 방법이 없기 때문에메서드에서 변수에 값을 �입할 때 self 를 이용

self.a = 10 # 인스턴스 변수 사용a = 20 # 지역 변수 사용

9

Page 10: Python Programming: Class and Object Oriented Programming

상속

상속(inheritance)은 기존 클래스 작동 방식을 특수화하거나 변경하여새로운 클래스를 생성하는 방법

원본 클래스: 기반 클래스(base class) 또는 상위 클래스(superclass)새 클래스: 파생 클래스(derived class) 또는 하위 클래스(subclass)

클래스가 상속을 통해 생성되면 기반 클래스에서 정의된 속성을 그�로 가짐파생 클래스는 상속받은 속성을 제정의하거나 자신만의 새로운 속성을 정의

상송 관계를 지정하려면 class 문에서 기반 클래스 이름을 콤마로 구분해 명시특별한 기반 클래스가 없는 경우에는 object 로 부터 상속

object모든 파이썬 객체의 조상 클래스이며 공통 메서드의 기본 구현 제공

10

Page 11: Python Programming: Class and Object Oriented Programming

상속

상속을 기존 메서드 작동 방식을 재정의하는 데 사용

>>> class SavingAccount(Account):... def withdraw(self, amt):... if self.balance - amt >= 0:... self.balance -= amt... else:... raise ValueError("balance - amt < 0")...>>> s = SavingAccount("John", 10.00)>>> s.deposit(10.0) # Account.deposit(s, 10.0)>>> s.withdraw(30.0) # SavingAccount.withdraw(s, 30.0)Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in withdrawValueError: balance must be greater than zero

11

Page 12: Python Programming: Class and Object Oriented Programming

상속

점(.) 연산자 작동 방식을 약간 개선하여 상속이 구현됨

속성 검색을 수행할 때 인스턴스나 클래스에서 부합하는 속성을 찾지 못하면더 찾아볼 기반 클래스가 없을 때까지 기반 클래스에서 속성 검색을 진행

s.withdraw() 는 SavingAccount 클래스에 정의된 메서드 호출

s.deposit() 은 Account 클래스에 정의된 deposit() 메서드 호출

하위 클래스에서 __init__() 를 정의해 인스턴스에 새로운 속성을추가하는 것이 가능

하위 클래스에서 __init__() 정의하면기반 클래스 __init__() 는 자동으로 호출되지 않음

상위 클래스 __init__() 를 호출해 기반 클래스를 적절하게초기화하는 것은 하위 클래스의 역할

12

Page 13: Python Programming: Class and Object Oriented Programming

상속

class SavingAccount(Account): def __init__(self, name, balance, interest_rate): Account.__init__(self, name, balance) self.interest_rate = interest_rate

Account.__init__() 를 호출해 기반 클래스 초기화 진행

기반 클래스에서 __init__() 를 정의하Ü는지 잘 모를 경우에는단순히 인수 없이 __init__() 를 호출

‑> 항상 아무 일도 하지 않는 기본 구현이 제공

13

Page 14: Python Programming: Class and Object Oriented Programming

상속

파생 클래스에서 어떤 메서드를 다시 구현하Ü지만 원래 메서드 구현을호출할 때는 기반 클래스 메서드를 직접 호출하면서 self 전달

deposit() 메서드는 Account 클래스에서 구현되어 있고이 메서드를 호출하면서 인스턴스 self 를 전달해 호출

class BasicSavingAccount(SavingAccount): def deposit(self, amt): self.withdraw(1.00) # 수수료 차감 SavingAccount.deposit(self, amt) # 기반 클래스 메서드

14

Page 15: Python Programming: Class and Object Oriented Programming

상속

기반 클래스 메서드를 super() 함수를 이용해 호출하는 방법

Python 3

class BasicSavingAccount(SavingAccount): def deposit(self, amt): self.withdraw(1.00) super().deposit(amt)

Python 2

class BasicSavingAccount(SavingAccount): def deposit(self, amt): self.withdraw(1.00) super(BasicSavingAccount, self).deposit(amt)

super(cls, instance) 는 기반 클래스에 �해 속성 검색을 수행하는특수한 객체를 반환해 어느 기반 클래스인지에 상관없이 메서드 호출 가능

15

Page 16: Python Programming: Class and Object Oriented Programming

다중 상속

클래스에 여러 기반 클래스를 지정해 다중 상속(multiple inheritance) 사용

class DepositCharge(object): fee = 1.00 def deposit_fee(self): self.withdraw(self.fee) # self.fee 는 1.00 인가?

class WithdrawCharge(object): fee = 2.00 def withdraw_fee(self): self.withdraw(self.fee) # self.fee 는 2.00 인가?

class FreeSavingAccount(BasicSavingAccount, DepositCharge, WithdrawCharge): def deposit(self, amt): self.deposit_fee() super().deposit(amt) def withdraw(self, amt): super().withdraw(amt)

16

Page 17: Python Programming: Class and Object Oriented Programming

다중 상속

>>> f = FreeSavingAccount("John", 100.0, 0.10)>>> f.deposit_fee()>>> f.balance99.0 # 사용한 self.fee 는 1.00>>> f.withdraw_fee()>>> f.balance98.0 # 사용한 self.fee 는 1.00

withdraw_fee() 메서드에서 사용한 self.fee 는 WithdrawCharge() 클래스정의에 있는 값이 아니라 DepositCharge() 클래스에 정의된 값

17

Page 18: Python Programming: Class and Object Oriented Programming

다중 상속

어떤 클래스의 기반 클래스 접근 순서를 확인하려면 __mro__ 속성 확인(Method Resolution Order)

>>> FreeSavingAccount.__mro__(<class '__main__.FreeSavingAccount'>, <class '__main__.BasicSavingAccount'>, <class '__main__.SavingAccount'>, <class '__main__.Account'>, <class '__main__.DepositCharge'>, <class '__main__.WithdrawCharge'>, <class 'object'>)

기반 클래스 순서는 일반적으로

파생 클래스는 항상 기반 클래스보다 먼저 검사

다중 상속인 경우에는 클래스 정의에 나와 있는 순서�로 검사

18

Page 19: Python Programming: Class and Object Oriented Programming

다중 상속

기반 클래스 검사 순서를 명확하게 하기 힘든 경우에는 클래스 생성 불가

>>> class X(object):... pass...>>> class Y(X):... pass...>>> class Z(X, Y):... pass...Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: Cannot create a consistent method resolutionorder (MRO) for bases X, Y

19

Page 20: Python Programming: Class and Object Oriented Programming

다중 상속

클래스 Z 에서 기반 클래스 순서를 제�로 정할 수 없기 때문에 TypeError

클래스 X 는 클래스 Y 보다 먼저 나타나기 때문에 먼저 검사

클래스 Y 는 클래스 X 에서 상속을 받았기 때문에 더 특화된 클래스

X 가 먼저 검사된다면 Y 에서 특수화된 메서드를 검색하는 건 불가능함

일반적으로 이러한 문제가 발생한다는 것은 프로그램 설계에문제가 있다는 신호일 경우가 많으니 설계 부분을 다시 검토하는 것이 좋음

다중 상속 자체는 가급적 사용하지 않는 것이 좋으나혼합 클래스(mixin class) 를 정의할 때 종종 사용함

DepositCharge, WithdrawCharge 에서는 또 다른 메서드가 있다고가정하고 그 메서드들을 이용하는 형태로 작성

20

Page 21: Python Programming: Class and Object Oriented Programming

다형성 동적 바인딩과 오리 타입화

동적 바인딩(dynamic binding)은 타입에 신경 쓰지 않고 인스턴스를사용하는 객체 지향 언어의 특징

동적 바인딩은 상속과 관련된 속성 검색 과정을 통해 이루어짐

obj.attr 을 통해 속성에 접근하면

먼저 인스턴스 자체에서 attr 검색못 찾을 경우 인스턴스의 클래스 정의에서 검색

그래도 못 찾을 경우에는 기반 클래스에서 검색

이 중에서 가장 먼저 검색된 속성이 반환되어 이용

21

Page 22: Python Programming: Class and Object Oriented Programming

다형성 동적 바인딩과 오리 타입화

동적으로 속성을 바인딩하는 과정은 객체가 실제로 어떤 타입인지 상관없음

obj.name 같은 속성 검색은 name 속성을 갖고 있는 객체라면어떤 객체인지에 관계없이 동작하고 이것을 오리 타입화라 부름

오리 타입화(duck typing)"오리처럼 생겼고, 오리처럼 울고, 오리처럼 걸으면 그것은 오리이다"

기존 객체를 수정하려면 기존 객체에서 상속을 받아 변경하는 것도 가능하고기존 객체와 관련이 없지만 기존 객체처럼 보이는 객체를 생성하는 것도 가능

오리 타입화를 이용해 구성 요소들 사이에 느슨한 결합을 가진 형태로동작하는 프로그램을 설계하는 것이 가능

파일처럼 동작하는 객체들은 내장 파일 객체와 아무런 관계가 없이겉에서 보면 파일처럼 사용할 수 있음

22

Page 23: Python Programming: Class and Object Oriented Programming

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

정적 메서드(static method) 는 클래스에 의해 정의되는 네임스페이스에포함되어 있는 일반 함수를 나타냄

정적 메서드는 인스턴스에 �해서 동작하지 않음

@staticmethod 장식자를 이용해 정의

인스턴스를 받지 않으므로 self 인수를 정의하지 않음

>>> class Foo(object):... @staticmethod... def add(x, y):... return x + y...>>> Foo.add(1, 2)3>>> Foo.add(3, 4)7>>>

정적 메서드는 다양한 방식으로 인스턴스를 생성하는 클래스를 작성할 때 활용 23

Page 24: Python Programming: Class and Object Oriented Programming

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

>>> import time>>> class Date(object):... def __init__(self, year, month, day):... self.year = year... self.month = month... self.day = day... @staticmethod... def now():... t = time.localtime()... return Date(t.tm_year, t.tm_mon, t.tm_mday)... @staticmethod... def tomorrow():... t = time.localtime(time.time() + 86400)... return Date(t.tm_year, t.tm_mon, t.tm_mday)...>>> d = Date(2017, 1, 1)>>> d.year, d.month, d.day(2017, 1, 1)>>> now = Date.now()>>> now.year, now.month, now.day(2017, 1, 17)

24

Page 25: Python Programming: Class and Object Oriented Programming

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

클래스 메서드(class method) 는 클래스 자체를 객체로 보고클래스 객체에 �해 동작하는 메서드

@classmethod 장식자를 사용해서 정의

첫 번째 인수로 cls 라는 이름을 가진 클래스가 전달

>>> class Times(object):... factor = 1... @classmethod... def mul(cls, x):... return cls.factor * x...>>>>>> class TwoTimes(Times):... factor = 2...>>> TwoTimes.mul(2) # Times.mul(TwoTimes, 2) 호출4>>> TwoTimes.mul(4) # Times.mul(TwoTimes, 4) 호출8

25

Page 26: Python Programming: Class and Object Oriented Programming

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

Date 클래스를 상속받아 새로운 EuroDate 클래스 생성

>>> class EuroDate(Date):... def __str__(self):... return "%02d/%02d/%04d" % (self.day, ... self.month, ... self.year)...

>>> e = EuroDate(2017, 1, 1)>>> type(e)<class '__main__.EuroDate'>>>> print(e)01/01/2017>>> now = EuroDate.now()>>> type(now)<class '__main__.Date'>>>> print(now)<__main__.Date object at 0x10be039e8>

26

Page 27: Python Programming: Class and Object Oriented Programming

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

EuroDate.now(), EuroDate.tomorrow() 에서 반환하는 객체는EuroDate 가 아닌 Date 클래스 객체

이러한 문제를 클래스 메서드를 사용해 해결

>>> class Date(object):... def __init__(self, year, month, day):... self.year = year... self.month = month... self.day = day... @classmethod... def now(cls):... t = time.localtime()... return cls(t.tm_year, t.tm_mon, t.tm_mday)... @classmethod... def tomorrow(cls):... t = time.localtime(time.time() + 86400)... return cls(t.tm_year, t.tm_mon, t.tm_mday)...

27

Page 28: Python Programming: Class and Object Oriented Programming

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

>>> class Date(object):... def __init__(self, year, month, day):... self.year = year... self.month = month... self.day = day... def __str__(self):... return "%04d-%02d-%02d" % (self.year, self.month, self.day)... @classmethod... def now(cls):... t = time.localtime()... return cls(t.tm_year, t.tm_mon, t.tm_mday)... @classmethod... def tomorrow(cls):... t = time.localtime(time.time() + 86400)... return cls(t.tm_year, t.tm_mon, t.tm_mday)...>>> print(Date.now())2017-01-17>>> print(EuroDate.now())17/01/2017

28

Page 29: Python Programming: Class and Object Oriented Programming

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

파이썬에서는 정적 메서드와 클래스 메서드를 인스턴스 메서드와별도 네임스페이스로 관리하지 않음

네임스페이스가 다르지 않기 때문에 인스턴스에서정적, 클래스 메서드를 호출하는 것이 가능

>>> d = Date(2017, 1, 1)>>> now = d.now()>>> print(now)2017-01-18

29

Page 30: Python Programming: Class and Object Oriented Programming

프로퍼티

프로퍼티(property) 는 접근하는 순간 값을 계산하는 특수한 속성

>>> import math>>> class Circle(object):... def __init__(self, radius):... self.radius = radius... @property... def area(self):... return math.pi * self.radius ** 2... @property... def perimeter(self):... return 2 * math.pi * self.radius...>>> c = Circle(2.0)>>> c.radius2.0

30

Page 31: Python Programming: Class and Object Oriented Programming

프로퍼티

장식자 @property 를 이용해 바로 다음에 나오는 메서드를"()" 없이 단순 속성인 것처럼 접근

>>> c.area12.566370614359172>>> c.perimeter12.566370614359172>>> c.area = 10Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: can't set attribute

속성을 재정의하려고 시도할 경우 AttributeError 발생

31

Page 32: Python Programming: Class and Object Oriented Programming

프로퍼티

균일 접근 원칙(uniform access principle)

클래스를 정의할 때 단순 속성과 메서드를 이용한 속성이 모두 존재할 경우

단순 속성에 접근할 때는 c.radius메서드 속성에 접근할 때는 c.area()

이렇게 언제 "()"를 붙여주어야 하는지 기억하는 불편함을 줄이기 위해프로퍼티를 사용할 수 있음

32

Page 33: Python Programming: Class and Object Oriented Programming

프로퍼티

프로퍼티를 설정하거나 삭제하는 메서드

>>> class Foo(object):... def __init__(self, name):... self.__name = name... @property... def name(self):... return self.__name... @name.setter... def name(self, value):... if not isinstance(value, str):... raise TypeError("Must be a string")... self.__name = value... @name.deleter... def name(self):... raise TypeError("Can't delete name")...

33

Page 34: Python Programming: Class and Object Oriented Programming

프로퍼티

>>> f = Foo("John")>>> f.name = "Jane">>> f.name = 42Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 10, in nameTypeError: Must be a string>>> del f.nameTraceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 14, in nameTypeError: Can't delete name

속성 name 은 @property 장식자를 이용해 읽기 전용 프로퍼티로 정의

@name.setter 는 설정을 위한 추가 메서드 연결

@name.deleter 는 삭제를 위한 추가 메서드 연결

추가 메서드는 원본 프로퍼티 이름과 동일34

Page 35: Python Programming: Class and Object Oriented Programming

데이터 캡슐화와 private 속성

파이썬에서는 클래스의 모든 속성과 메서드가 그�로 노출되어파생 클래스와 기반 클래스 사이에 이름 충돌이 발생 가능

이름 변형(name mangling)이 문제를 해결하기 위해 클래스에서 __Foo 같은 이중 밑줄로 시작하는모든 이름을 자동으로 _클래스이름__Foo 의 형태로 변환

파생 클래스에서 사용하는 private 이름과 기반 클래스에서 사용하는이름이 충돌하지 않음

클래스에서 사용하는 private 속성과 메서드를 정의하는 것이 가능

35

Page 36: Python Programming: Class and Object Oriented Programming

데이터 캡슐화와 private 속성

class A(object): def __init__(self): self.__X = 3 # self._A__X 로 변형 def __spam(self): # _A__spam() 으로 변형 print(self.__X) def bar(self): self.__spam()

class B(A): def __init__(self): A.__init__(self) self.__X = 37 # self._B__X 로 변형 def __spam(self): # _B__spam() 으로 변형 print(self.__X) def bar(self): self.__spam()

36

Page 37: Python Programming: Class and Object Oriented Programming

데이터 캡슐화와 private 속성

속성과 메서드 이름을 변형하여도 클래스 private 속성에 접근하는 것을근본적으로 막을 방법은 없음

클래스 이름과 속성을 알고 있으면 변형된 이름을 사용해 접근 가능

>>> a = A()>>> a.bar()3>>> b = B()>>> b.bar()37>>> dir(b)['_A__X', '_A__spam', '_B__X', '_B__spam', '__class__', ... >>> b._A__X3

37

Page 38: Python Programming: Class and Object Oriented Programming

객체 메모리 관리

인스턴스 생성은 새로운 인스턴스를 생성하는 특수 메서드 __new__() 와생성된 인스턴스를 초기화하는 __init__() 메서드를 통해 수행

>>> class Circle(object):... def __init__(self, radius):... self.radius = radius...>>> c = Circle(2.0)>>> >>> d = Circle.__new__(Circle, 2.0)>>> if isinstance(c, Circle):... Circle.__init__(c, 2.0)>>>>>> c.radius2.0>>> d.radius2.0...

38

Page 39: Python Programming: Class and Object Oriented Programming

객체 메모리 관리

클래스의 __new__() 메서드를 사용자 코드에서 재정의하는 경우는 매우 희박메서드를 재정의할 때는 일반적으로 __new__(cls, *args, **kwargs) 형태

클래스에서 __new__() 를 정의하여 사용하는 경우

변경이 불가능한 기반 클래스로부터 상속을 받는 경우정수, 문자열, 튜플 등과 같은 내장 타입에서 상속을 받은 객체 정의

인스턴스가 생성되기 전에 실행되는 __new__() 에서 값을 변경

>>> class UpperStr(str):... def __new__(cls, value=""):... return str.__new__(cls, value.upper())...>>> s = UpperStr("Hello, World!")>>> s'HELLO, WORLD!'

메타클래스를 정의하기 위해39

Page 40: Python Programming: Class and Object Oriented Programming

객체 메모리 관리

인스턴스를 파괴하기 전에 인터프리터는 먼저 인스턴스에__del__() 메서드가 있는지 확인 후 존재하면 호출

del 문이 참조 횟수를 줄이고 이 값이 0이 되면 __del__() 메서드 호출

close() 같은 메서드를 두어서 명시적인 마무리 작업을직접 수행할 수 있는 형태로 구성하는 것이 바람직

40

Page 41: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

인스턴스는 내부적으로 __dict__ 속성으로 접근할 수 있는 사전을 이용해 구현

__dict__ 속성에는 각 인스턴스가 가지고 있는 고유한 데이터를 저장

>>> a = Account('Bill', 2000.0)>>> a.__dict__{'name': 'Bill', 'balance': 2000.0}>>> a.number=1>>> a.__dict__{'name': 'Bill', 'balance': 2000.0, 'number': 1}

언제든지 인스턴스에 새로운 속성을 추가하는 것이 가능하고이 변화는 항상 내부 __dict__ 에 반Ý

41

Page 42: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

인스턴스는 특수한 속성 __class__ 를 통해 자신의 클래스에 연결

클래스 자체도 __dict__ 속성을 가지고 있고 이 속성 안에클래스 자체에 �한 정보와 메서드 정보를 저장

>>> a.__class__<class '__main__.Account'>>>> Account.__dict__.keys()dict_keys(['__module__', 'num_accounts', '__init__', '__del__', 'deposit', 'withdraw', 'inquiry', '__dict__', '__weakref__', '__doc__'])

42

Page 43: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

클래스는 기반 클래스들을 담는 __bases__ 튜플 속성을 통해자신의 기반 클래스에 연결됨

>>> FreeSavingAccount.__bases__(<class '__main__.BasicSavingAccount'>, <class '__main__.DepositCharge'>, <class '__main__.WithdrawCharge'>)

43

Page 44: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

obj.name = value 로 속성을 설정할 때마다 특수 메서드obj.__setattr__("name", value) 가 호출

del obj.name 으로 속성을 삭제하면 특수 메서드obj.__delattr__("name") 가 호출

obj.name 같은 속성을 검색할 때에는 특수 메서드obj.__getattribute__("name") 가 호출

obj.__getattribute__() 메서드 검색 순서

내부 프로퍼티

내부 __dict__클래스 __dict__기반 클래스들 내부 검색

클래스의 __getattr__() 메서드이 모든 곳에서 찾지 못하면 AttributeError 발생 44

Page 45: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

사용자 정의 클래스에서 속성 검색 함수를 이용한 구현 방법 예

class Circle(object): def __init__(self, radius): self.radius = radius def __getattr__(self, name): if name == 'area': return math.pi * self.radius ** 2 elif name == 'perimeter': return 2 * math.pi * self.radius else: return object.__getattribute__(self, name) def __setattr__(self, name, value): if name in ['area', 'perimeter']: raise TypeError("%s is readonly" % name) object.__setattr__(self, name, value)

45

Page 46: Python Programming: Class and Object Oriented Programming

객체 표현과 속성 바인딩

>>> c = Circle(2.0)>>> c.area12.566370614359172>>> c.perimeter12.566370614359172>>> c.name = "Circle">>> c.area = 20Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 13, in __setattr__TypeError: area is readonly

46

Page 47: Python Programming: Class and Object Oriented Programming

__slots__

클래스에서 __slots__ 특수 변수를 정의해 속성 이름을 제한할 수 있음

class Stock(object): __slots__ = ("name", "qty") def __init__(self, name, qty): self.name = name self.qty = qty

지정된 속성 이외의 이름을 사용하면 AttributeError 발생

>>> s = Stock("pen", 100)>>> s.tag = "pen"Traceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: 'Stock' object has no attribute 'tag'

47

Page 48: Python Programming: Class and Object Oriented Programming

__slots__

일반적으로 __slots__ 은 속성 보호 기능 보다는 메모리와 실행 속도를최적화하기 위한 용도로 사용

__slots__ 을 사용하는 클래스 인스턴스는 인스턴스 데이터를 저장하는데사전을 사용하지 않고 배열에 기초한 간결한 데이터 구조를 사용

__slots__ 을 사용하는 기반 클래스로부터 상속을 받으면자신 또한 __slots__ 을 정의할 필요가 있음

인스턴스 내부 __dict__ 속성을 직접 사용하는 코드가 있다면제�로 동작하지 않을 수 있음

48

Page 49: Python Programming: Class and Object Oriented Programming

연산자 오버로딩

연산자 오버로딩을 위해서는 클래스에 __add__() 같은 특수 메서드를 정의

복소수를 나타내는 클래스 예 (내장 객체에 복소수는 이미 있음)

class Complex(object): def __init__(self, real, imag=0): self.real = float(real) self.imag = float(imag) def __repr__(self): return "Complex(%s, %s)" % (self.real, self.imag) def __str__(self): return "(%g+%gj)" % (self.real, self.imag) def __add__(self, other): return Complex(self.real + other.real, self.imag + other.imag) def __sub__(self, other): return Complex(self.real - other.real, self.imag - other.imag)

49

Page 50: Python Programming: Class and Object Oriented Programming

연산자 오버로딩

__repr__()평가될 경우 객체를 다시 만들기 위한 문자열 생성

__str__()

print() 문에 의해서 출력될 문자열 생성

__add__()복소수가 "+" 연산자 왼쪽에 나올 때 호출되는 메서드

__sub__()복소수가 "‑" 연산자 왼쪽에 나올 때 호출되는 메서드

앞에서 구현한 연산자 메서드는 연산자의 오른쪽에 위치하거나제일 왼쪽 피연산자가 Complex 가 아닐 경우에는 제�로 동작하지 않음

50

Page 51: Python Programming: Class and Object Oriented Programming

연산자 오버로딩

>>> a = Complex(3, 2)>>> b = Complex(2, 3)>>> a + bComplex(5.0, 5.0)>>> a + 4.0Complex(7.0, 2.0)>>> 4.0 + aTraceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: unsupported operand type(s) for +: 'float' and 'Complex'

파이썬 내장 숫자에는 .real, .imag 속성이 존재하기 때문에 "+" 연산이 동작Complex 구현이 다른 객체와도 동작하도록 하기 위해서는 other 객체 타입에따라 필요한 속성을 추출하는 코드가 더 필요

51

Page 52: Python Programming: Class and Object Oriented Programming

연산자 오버로딩

>>> 4.0 + a

위 코드에서 내장 부동 소수점 타입은 Complex 클래스를 전혀 모르기 때문에제�로 동작하지 않음

이러한 문제를 해결하기 위해 Complex 클래스에 역피연산자 메서드 추가

... def __radd__(self, other): return Complex(self.real + other.real, self.imag + other.imag) def __rsub__(self, other): return Complex(self.real - other.real, self.imag - other.imag) ...

>>> a = Complex(3, 2)>>> 4.0 + aComplex(7.0, 2.0)

52

Page 53: Python Programming: Class and Object Oriented Programming

타입과 클래스 멤버 검사

isinstance(obj, cname)특정 인스턴스가 어떤 클래스에 속하는지를 검사하는 내장 함수객체 obj 가 클래스 cname 이나 cname 에서 파생된 클래스에 속하면 True

class A(object): pass

class B(A): pass

class C(object):pass

>>> type(a)<class '__main__.A'>>>> isinstance(a, A)True>>> isinstance(b, A)True>>> isinstance(b, C)False

53

Page 54: Python Programming: Class and Object Oriented Programming

타입과 클래스 멤버 검사

issubclass(A, B)클래스 A 가 클래스 B 의 하위 클래스일 경우 True 를 반환

>>> issubclass(B, A)True>>> issubclass(C, A)False

54

Page 55: Python Programming: Class and Object Oriented Programming

타입과 클래스 멤버 검사

실제 상속 관계를 가지지 않고 동일한 메서드를 구현하여 다른 객체를흉내내는 방식으로 구성한 클래스의 타입 검사 방법

class Foo(object): def spam(self, a, b): pass

class FooProxy(object): def __init__(self, f): self.f = f def spam(self, a, b): return self.f.spam(a, b)

위 두 클래스는 기능적으로 동일하지만 타입 시스템상에서는 서로 다름

>>> f = Foo()>>> g = FooProxy(f)>>> isinstance(g, Foo)False

55

Page 56: Python Programming: Class and Object Oriented Programming

타입과 클래스 멤버 검사

어떤 객체가 Foo 와 동일한 인터페이스를 가지고 있다면 Foo 처럼사용될 수 있도록 비슷한 객체들을 그룹 지어 검사할 수 있도록isinstance(), issubclass() 를 재정의

class IClass(object): def __init__(self): self.implementors = set() def register(self, C): self.implementors.add(C) def __subclasscheck__(self, sub): return any(c in self.implementors for c in sub.mro()) def __instancecheck__(self, x): return self.__subclasscheck__(type(x))

56

Page 57: Python Programming: Class and Object Oriented Programming

타입과 클래스 멤버 검사

클래스 IClass 는 집합 자료 구조를 사용해 클래스들을 그룹 짓는 객체 생성

register(): 집합에 새로운 클래스 추가

__instancecheck__(): isinstance 연산을 수행할 때 호출

__subclasscheck__(): issubclass 연산을 수행할 때 호출

IFoo 객체와 등록된 객체들을 이용해 타입 검사 수행

>>> IFoo = IClass()>>> IFoo.register(Foo)>>> IFoo.register(FooProxy)>>> f = Foo()>>> g = FooProxy(f)>>> isinstance(f, IFoo)True>>> isinstance(g, IFoo)True>>> issubclass(FooProxy, IFoo)True

57

Page 58: Python Programming: Class and Object Oriented Programming

추상 기반 클래스

추상 기반 클래스(abstract base class)객체들을 계층으로 구성하거나 필수 메서드를 강제하는 작업 가능

abc 모듈을 사용해 추상 기반 클래스 정의

ABCMeta, @abstractmethod, @abstractproperty 등을 이용

class Foo: # Python 2 __metaclass__ = ABCMeta

from abc import ABCMeta, abstractmethod, abstractpropertyclass Foo(metaclass=ABCMeta): # Python 3 @abstractmethod def spam(self, a, b): pass @abstractproperty def name(self): pass

58

Page 59: Python Programming: Class and Object Oriented Programming

추상 기반 클래스

추상 클래스를 정의할 때는 ABCMeta 를 클래스의 메타 클래스로 설정

추상 클래스 안에서 @abstractmethod, @abstractproperty 장식자를이용해 Foo 하위 클래스에서 반드시 구현하도록 강제

>>> f = Foo()Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: Can't instantiate abstract class Foo with abstract methods name, spam

Foo 클래스 인스턴스는 바로 생성할 수 없고 하위 클래스에서 필요한메서드와 프로퍼티를 구현해야 생성 가능

59

Page 60: Python Programming: Class and Object Oriented Programming

추상 기반 클래스

추상 기반 클래스는 반드시 구현해야 하는 메서드나 프로퍼티에 �한규칙을 검사하지만 인수나 반환 값에 �해서는 검사하지 않음

하위 클래스 메서드 인수가 추상 메서드 인수와 동일한지

하위 클래스 프로퍼티가 추상 프로퍼티와 동일한 연산을 지원하는지

두 가지 경우 모두 검사를 진행하지 않음

60

Page 61: Python Programming: Class and Object Oriented Programming

추상 기반 클래스

추상 기반 클래스는 기존 클래스를 등록하는 기능 제공

class Grok(object): def spam(self, a, b): print("Grok.spam")

register() 메서드를 이용해 클래스 등록

>>> Foo.register(Grok)<class '__main__.Grok'>>>> g = Grok()>>> isinstance(g, Foo)True

클래스가 추상 클래스에 등록되면 실제로 추상 메서드나 프로퍼티를구현하고 있는지를 검사하지 않고 단순히 타입 검사에만 Ý향

61

Page 62: Python Programming: Class and Object Oriented Programming

메타클래스

파이썬에서 클래스를 정의하면 클래스 정의 자체도 객체로 취급

>>> class Foo(object):... pass...>>> isinstance(Foo, object)True

메타클래스(metaclass)클래스를 어떻게 생성하고 관리하는지를 알고 있는 객체를 뜻함

위에 예에서는 Foo 생성을 제어하는 메타클래스는 type 클래스

>>> type(Foo)<class 'type'>

62

Page 63: Python Programming: Class and Object Oriented Programming

메타클래스

class 문으로 새로운 클래스를 정의할 때 일어나는 일

클래스 몸체 안에 있는 문장이 private 사전 안에서 실행

private 멤버 이름 변형

클래스 객체를 생성하기 위해 이름, 기반 클래스 목록, 생성된 사전이메타클래스 생성자에 전달

메타클래스 type() 이 호출되는 마지막 단계를 변경하는 방법

클래스를 정의할 때 metaclass 지정

class Foo(metaclass=type) # Python 3class Foo: __metaclass__ = type # Python 2

기반 클래스 목록 튜플에 metaclass 키워드 인수 전달

63

Page 64: Python Programming: Class and Object Oriented Programming

메타클래스

메타클래스를 명시적으로 지정하지 않을 경우

기반 클래스들의 튜플에서 첫 번째 항목 선택 (존재할 경우)

기반 클래스를 지정하지 않으면 __metaclass__ 전역 변수 탐색

__metaclass__ = typeclass Foo: pass

아무것도 못 찾으면 기본 메타클래스 사용Python 2: types.ClassTypePython 3: type()

64

Page 65: Python Programming: Class and Object Oriented Programming

메타클래스

메타클래스는 객체를 정의하는 방식에 제약을 가하고자할 때 주로 사용

모든 메서드에 문서화 문자열이 존재해야 하는 것을 강제하는 메타클래스 예

class DocMeta(type): def __init__(self, name, bases, attrs): for key, value in attrs.items(): # 특수 메서드와 private 메서드는 넘어감 if key.startswith("__"): continue # 호출 가능하지 않은 것은 넘어감 if not hasattr(value, "__call__"): continue # 문서화 문자열을 가지고 있는지 검사 if not getattr(value, "__doc__"): raise TypeError( "%s must have a docstring" % key) type.__init__(self, name, bases, attrs)

65

Page 66: Python Programming: Class and Object Oriented Programming

메타클래스

앞에서 정의한 메타클래스를 이용한 기반 클래스 정의

Python 3

class Documented(metaclass=DocMeta): pass

Python 2

class Documented: __metaclass__ = DocMeta

66

Page 67: Python Programming: Class and Object Oriented Programming

메타클래스

문서화가 필요한 클래스 기반 클래스로 Documented 지정

>>> class Foo(Documented):... def spam(self, a, b):... print("spam")...Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 11, in __init__TypeError: spam must have a docstring

67

Page 68: Python Programming: Class and Object Oriented Programming

클래스 장식자

클래스 장식자(decorator)클래스 생성 과정 전체를 관여하기 보다 단순히 클래스 정의 후에단순하게 몇 가지 추가 작업을 수행하고자 할때 사용

registry = {}def register(cls): registry[cls.__clsid__] = cls return cls

클래스 정의 앞에 장식자 이용

@registerclass Foo(object): __clsid__ = "111-111" def bar(self): pass

68

Page 69: Python Programming: Class and Object Oriented Programming

클래스 장식자

클래스를 정의하며 registry 객체에 등록

>>> @register... class Foo(object):... __clsid__ = "111-111"... def bar(self):... pass...>>> registry{'111-111': <class '__main__.Foo'>}

69