인프런 리프 - 파이썬 고급 week2

pyhoo·2021년 3월 18일
0

Inflearn Leaf 2nd

목록 보기
3/5
post-thumbnail

1. @contextlib.contextmanager

  • @contextlib.contextmanager 어노테이션을 사용하면 context manager를 클래스형태가 아닌 함수형으로 구현할 수 있다.
  • 이전 클래스형에서 context manager를 구현한 것과 비교하면, 코드가 더 직관적이며 예외처리가 용이해진다.

# 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는 예외를 발생시키는 명령어로 raise하위의 코드를 실행하지 않고 곧 바로 except로 넘어간다.
  • raise 예외가 발생한 코드블럭에서 except가 없다면, except가 나올 때 까지 상위코드로 올라간다. 만약 상위코드에도 except구문이 없다면, 코드 실행은 중지되고 에러 표시가 발생된다.
raise Exception('에러여') # 예외 발생시킴
raise # 에러 메시지는 생략가능

2. Property

  1. underscore
  2. getter, setter

1. Underscore

우선 underscore (밑줄) 의 용도는 크게,
1. 인터프리터에서 사용되는 경우
2. 값을 무시할 경우
3. 특별한 네이밍(국제화, 자릿수)의 경우 로 나뉜다.

1. 인터프리터에서 사용되는 경우

Python Interpreter에선 마지막으로 실행된 결과값이 (underscore)에 저장된다.
아래의 코드에서 확인할 수 있듯 이전의 실행값이 저장되어
에 저장된 것을 볼 수 있다.

>>> 1
1
>>> _
1
>>> _ * 5
5
>>> _ * 20
100

2. 값을 무시할 경우

# 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까지의 변수가 할당된다.
# _에는 아무 값(본래라면 인덱스가 할당)도 할당되지 않는다.    

3. 특별한 네이밍

파이썬에서 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로써 위와 같이 (클래스) 외부에서 변수 변경은 자제한다.

2. Getter, Setter

  • 위에서 double underscore가 붙은 변수의 직접적인 접근은 attributeError를 발생시키지만, 새로운 값의 할당은 가능했다(권장하진 않지만).
  • getter, setter 메서드는 올바른 변수의 접근을 허용한다(이는 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

3. Method Overriding

메소드 오버라이딩

  1. 서브클래스(자식)에서 슈퍼(부모)클래스를 호출 후 사용
  2. 메소드 재 정의 후 사용가능 (실제 오버라이딩 발생)
  3. 부모 클래스의 메소드를 추상화 후 사용가능 (구조적 접근 가능)
  4. 확장 가능 + 다형성
    (다양한 방식으로 동작 -> 부모에서 하나를 만들었지만, 사용하는 자식에 따라 다양하게 사용될 수 있다)
  5. 가독성 증가, 오류가능성 감소, 메소드 이름 절약(부모가 메소드 이름을 이미 정의해놨기에)
# 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)

0개의 댓글