# Ex1
import contextlib
import time
@contextlib.contextmanager
def my_file_writer(file_name, method):
# class 형과 달리, 현재 지점부터 __enter__로 진입
f = open(file_name, method) # 여기서도 굳이 __init__할 필요없고,
yield f # yield f가 __enter__에 해당
f.close() # __exit__ 에 해당
#실행해보자.
with my_file_writer('../testfile4.txt', 'w') as f: # __enter__에서 넘어온 f
f.write('Context Manager Test4. \n Contextlib Test4')
# 같은 내용을 클래스 형과 비교하면,
class MyFileWriter():
# class에는 초기 함수가 있어야한다.
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print('Logging exception {}'.format((exc_type, exc_val, exc_tb)))
self.file_obj.close()
with MyFileWriter('../testfile3.txt', 'w') as f:
f.write('Context Manager Test3\nContextlib Test3.')
Annotation을 사용하면 함수형으로 context manager 구현이 가능함을 확인할 수 있다. 뿐만 아니라 클래스형의
__exit__
,__enter__
,__init__
의 구현없이, 곧 바로 generator - yield를 사용함으로 직관적인 코드 작성이 가능해진다.
# Ex2
# Use decorator
@contextlib.contextmanager
def ExecuteTimerDc(msg):
start = time.monotonic()
try: # __enter__ (yield 명령어로 __enter__ 대신 진입)
yield start
except BaseException as e:
print('Logging exception: {}: {}'.format(msg, e))
raise
else: #__exit__
print('{}: {}s'.format(msg, time.monotonic() - start))
with ExecuteTimerDc('Start job') as v:
print('Received start monotonic2 : {}'.format(v))
#execute job
for i in range(10000000):
pass
# raise ValueError('occured')
>>> Received start monotonic2 : 0.053906368
>>> Start job: 0.340590548s
raise Exception('에러여') # 예외 발생시킴 raise # 에러 메시지는 생략가능
- underscore
- getter, setter
우선 underscore (밑줄) 의 용도는 크게,
1. 인터프리터에서 사용되는 경우
2. 값을 무시할 경우
3. 특별한 네이밍(국제화, 자릿수)의 경우 로 나뉜다.
Python Interpreter에선 마지막으로 실행된 결과값이 (underscore)에 저장된다.
아래의 코드에서 확인할 수 있듯 이전의 실행값이 저장되어 에 저장된 것을 볼 수 있다.
>>> 1
1
>>> _
1
>>> _ * 5
5
>>> _ * 20
100
# Unpacking - packing(*)
x, _, y = (1,2,3)
print(x,y)
>>> 1, 3
a, *_, b = (1,2,3,4,5)
print(a, b)
>>> 1, 5
print('Ex 1 > ', x, y, a, b)
>>> 'Ex 1 > 1, 3, 1, 5
for _ in range(10): #무시할 때 사용
pass
# 10번의 공회전
for _, val in enumerate(range(10)):
pass
# 마찬가지로 10번의 공회전이지만, val에는 각 회전마다 0에서 9까지의 변수가 할당된다.
# _에는 아무 값(본래라면 인덱스가 할당)도 할당되지 않는다.
파이썬에서 underscore가 가장 많이 쓰이는 경우가 접근지정자일 때다.
_single_leading_underscore
: 변수앞의 하나의 Underscore(_Variable)는 쉽게 말해 protected 속성을 부여한다. (많이 사용되지 않는 컨벤션이다)
_double_leading_underscore
: 더블 Underscore는 ('__Variable') 속성명을 Mangling하여 클래스간 속성명의 충돌을 방지하는 용도로 쓰인다. (mangling: 컴파일러나 인터프리터가 변수/함수명을 그대로 사용하지 않고 일정한 규칙에 의해 변형시키는 것)
타언어(ex. Java)라면 엄격히 접근제어를 지키기에, private 접근제어자를 부여받는 변수는 타 클래스에서 사용시 에러가 발생한다.
파이썬도 마찬가지로 attributeError가 발생하지만, 해당 클래스 내부를 뜯어보면 (by using dir()메서드) 해당 변수는 Naming Mangling을 통해 변수명이 바뀌어 있다. 즉 실질적인 접근 제어가 이뤄지지 않는 것이다.
이는 파이썬의 특징으로, 접근을 허용함으로 프로그램의 흐름을 개발자가 원하는 방향으로 조작하거나 제어할 수 있는 활용의 기회를 부여한다.
# __name: private (instance)
class SampleA:
def __init__(self):
self.__y = 0 # private instance
a = SampleA()
# print('Ex 2 > y:{}'.format((a.__y))) # attributeError발생
# --> private하기 때문에 클래스 외부에서 선언 불가
# 클래스 SampleA()의 내부를 까보면 __y앞에 _SampleA라는 prefix가 붙어있다 => ['_SampleA__y', ...]
# 이것이 Naming Mangling : 충돌 방지를 위해 변수명을 바꿔버리는 것, 즉 __y는 형태가 바뀌어 저장됨.
print('Ex 2 > ', dir(a))
>>> ['_SampleA__y', ...]
a._SampleA__y = 2 # 수정 가능(강제성이 없다 but 룰로써는 지켜야한다 )
print('Ex 2 > y : {}'.format(a._SampleA__y))
>>> 2
a._SampleA__y = 2 는 private 변수의 수정 가능(private에 대한 강제성 X)을 뜻한다 => 즉 파이썬은 접근제어자를 엄격히 지키지 않으나 rule로써 위와 같이 (클래스) 외부에서 변수 변경은 자제한다.
# 캡슐화
class SampleA:
def __init__(self):
self.x = 0
self.__y = 0 # private
# 만약 y뿐만 아니라 다수의 변수가 private으로 선언된다면, 각각의 변수에 일일이 getter/setter 라는 이름을 붙여야한다.
# 그런 귀찮은 일 대신 프로퍼티 어노테이션을 사용
@property # 언더스코어를 제외한 변수 이름
def y(self): # 이게 getter
return self.__y
@y.setter
def y(self, value):
self.__y = value
@y.deleter # 메모리 절약
def y(self):
del self.__y
# 위 세 메소드의 형태가 파이선 getter/setter의 표본
a = SampleA()
a.y = 2 # 자동으로 세터를 호출할 타이밍
print('Ex 1 > y: {}'.format(a.y))
>>> 2
## deleter
print('Ex 1 - 1 >', dir(a))
del a.y
print('Ex 1 - 2 >', dir(a)) # _SampleA__y 메소드가 사라진다.
결국 getter, setter라는 거추장스러운 이름 대신, 어노테이션을 사용하여 직관적으로 y 변수의 메서드를 호출할 수 있다.(직관적으로 사용할 수 있음이 포인트)
class Lim:
def __init__(self):
self.__age = 0
@property
def age(self):
return self.age
@age.setter
def age(self, value):
self.age = value
특히 @property와 @age.setter를 붙이면 Lim.age 메서드를 속성처럼 사용할 수 있다. 값을 저장할 때는 Lim.age = 20처럼 메서드에 바로 값을 할당하면 되고, 값을 가져올 때도 Lim.age처럼 메서드에 바로 접근할 수 있다.
참조: https://dojang.io/mod/page/view.php?id=2476
메소드 오버라이딩
- 서브클래스(자식)에서 슈퍼(부모)클래스를 호출 후 사용
- 메소드 재 정의 후 사용가능 (실제 오버라이딩 발생)
- 부모 클래스의 메소드를 추상화 후 사용가능 (구조적 접근 가능)
- 확장 가능 + 다형성
(다양한 방식으로 동작 -> 부모에서 하나를 만들었지만, 사용하는 자식에 따라 다양하게 사용될 수 있다)- 가독성 증가, 오류가능성 감소, 메소드 이름 절약(부모가 메소드 이름을 이미 정의해놨기에)
# Ex 1
# 기본 Overriding 예제
class ParentEx1():
def __init__(self):
self.value = 5
def get_value(self):
return self.value
class ChildEx1(ParentEx1): # 상속 갱 <=> 메소드 오버라이딩 일어나지 않았다.
pass
c1 = ChildEx1()
p1 = ParentEx1()
print('Ex 1 >', c1.get_value())
# c1의 모든 속성 출력
print('Ex 1 >', dir(c1)) # get_value, value를 가지고 있다.
# 부모 & 자식의 모든 속성값 출력
print('Ex 1 > ', dir(ParentEx1))
print('Ex 1 > ', dir(ChildEx1)) # 부모 클래스와 자식 클래스의 메소드가 같다.
print('Ex 1 >', ParentEx1.__dict__)
print('Ex 1 >', ChildEx1.__dict__)
# 딕셔너리로 네임스페이스내의 속성들을 확인하는데, 부모에게는 있지만, 자식에게는 없다.
# 인스턴스가 되는 시점에 자식에게 담기는 것이다. 그렇기 때문에 dir()과 __dict__로 찍을때 다르게 나온다.
# dir(), __dict__ 객체 내부 검사 메서드
# dir() : 클래스와 인스턴스 내부에서 사용할 수 있는 정보를 확인 / __dict__ : 인스턴스 내부에 어떤 속성이 있는지 확인
# Ex 2
# 기본 메소드 Overriding, 재정의
class ParentEx2():
def __init__(self):
self.value = 5
def get_value(self):
return self.value
class ChildEx2(ParentEx2):
def get_value(self): # 메소드 변경 => 메소드 오버라이딩
return self.value * 10
c2 = ChildEx2()
print("Ex 2 >", c2.get_value())
# Ex3 - 오버라이딩 다형성 예제
import datetime
class Logger():
def log(self, msg):
print(msg)
class TimestampLogger(Logger):
def log2(self, msg):
message = '{ts} {msg}'.format(ts=datetime.datetime.now(), msg=msg)
super().log(message)
# 자식클래스의 프로토타입, 인스턴스를 super()의 인자로 넘긴 것
# super(TimestampLogger, self).log(message) # 위 코드와 다를 바 없지만 더 명확하다, # fm style 로 코딩한 것
# def log()는 결국 부모에게서 상속받은 메서드 => 같은 이름을 사
class DateLogger(Logger):
def log2(self, msg):
message = "{ts} {msg}".format(ts=datetime.datetime.now().strftime('%Y-%m-%d'), msg=msg)
super().log(message)
# super(TimestampLogger, self).log(message) # 에러 나니까 위 코드로 실행 -> 편하다
# 하나의 부모를 정하고 다양한 형태로 상속을 받는다.
# 뼈대는 부모가 제공 (출력 기능) => 출력할 메세지의 형태는 자식이 결정
l = Logger()
t = TimestampLogger()
d = DateLogger()
# 메소드 재정의 실습
print('========= Ex 3 =========')
print('Ex 3 > ', l.log('Called logger'))
print('Ex 3 > ', t.log('Called Timestamp logger'))
print('Ex 3 > ', d.log('Called Datetime logger'))
# l.log('test1')
# t.log('test2')
# d.log('test3')
모두를 위한 파이썬 : 필수 문법 배우기 Feat. 오픈소스 패키지 배포 (Inflearn Original)