###2019.03.07 TIL
(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)
대답해보기
========================
- 전역변수, 지역변수
- 함수에서 mmutable, immutable에 대해서
개발자가 하는 일
컴퓨터 사이언스 부트캠프 with 파이썬이라는 책을 쓰신 양태환 강사님께서 요즘 강의를 해주시고 계신다. 그리고 나는 TIL에 양태환 강사님께 배운 내용들을 바탕으로 글을 적고 있다.
양태환 강사님이 말씀하신 프로그래머가 하는 단 하나의 일은 추상화(abstraction)된 것(예를 들면 기획, 생각 등)을
인터페이스로 어떻게 설계할 것인지 그리고 그것을 어떻게 implementation(구현) 할 것인지이다.
인터페이스(interface)는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면이다. 즉, 사용자가 기기를 쉽게 동작시키는데 도움을 주는 시스템을 의미한다.
아직 명확히 글의 뜻은 이해 못하겠지만 구글링으로 관련 자료를 가지고 와 보았다.
인터페이스와 구현
객체 지향 디자인의 목표 중에 하나는 소프트웨어를 더 유지보수 가능하게 만드는 것이다. 다시 말해서 시스템의 다른 부분이 변경됐을 때나 프로그램을 새로운 요구사항에 맞춰 수정했을 때도 프로그램이 동작해야 한다는 뜻이다.
이러한 목표를 이루기 위한 디자인 원칙은 인터페이스와 구현을 분리하여 유지하는 것이다. 객체라면 클래스에서 제공하는 메서드가 속성의 표현 방식에 의존하지 않아야 한다는 뜻이다.
예를 들어 이 장에서 우리는 시간을 나타내는 클래스를 개발했다. 이 클래스가 제공하는 메서드에는 time_to_int, is_after, add_time이 있다.
이들 메서드는 몇 가지 방법으로 구현할 수 있다. 세부 구현은 시간을 어떻게 표현하는가에 따라 다르다. 이 장에서 Time 객체의 속성은 hour, minute, second가 있다.
대안으로 이들 속성을 자정부터의 경과 시간을 초로 표현한 정수 하나로 대체할 수 있다. 이러한 구현은 is_after 같은 메서드를 더 쉽게 작성할 수 있지만, 어떤 메서드는 구현하기가 더 어려워지기도 한다.
새 클래스를 배포한 후에 더 나은 구현을 발견할 수도 있다. 프로그램의 다른 부분에서 이 클래스를 사용하고 있다면 이 인터페이스를 변경하는 데 시간이 오래 걸리고 오류가 발생할 수 있다.
인터페이스를 주의 깊게 설계한다면 인터페이스를 변경하지 않아도 구현을 변경할 수 있다. 즉, 프로그램의 다른 부분은 변경하지 않아도 된다.
출처 https://thebook.io/006878/ch17/10/ [바로가기] (https://thebook.io/006878/ch17/10/)
아무튼 인터페이스 설계에서 한 가지의 예를 들면 함수 시그니쳐를 들 수 있다.
함수 시그니쳐
함수의 이름 - 함수의 기능을 명확하게 표현하는 함수 이름을 설정해야 한다.
파라미터 - 매개 변수를 어떻게 설정하고 받을 것인지
결과값 - 어떤 결과값을 반환해줄 것인지
위와 같은 정보가 함수를 설계할 때 꼭 들어가야 한다는 것이다.
예)
뒤늦게 정리하다 보니 선생님이 말씀해주신 좋은 설계와 나쁜 설계가 떠오른다.
open-closed
###확장에 대해서는 open 되어 있고 기존 코드의 수정에 대해서는 closed되어 있다.
클래스 간의 계층 구조를 만들고 설계하는 것
좋은 설계와 나쁜 설계
- 다 크게 만들어 놨는데 클라이언트가 특정 기능을 추가해달라고 했는데 미친듯이 클래스가 변경해야되면 나쁜 설계이다.
- 좋은 설계는 (궁극적인 목표) 기능이 추가되는 그 클래스 한개만 변경이 되고 나머지는 완벽히 닫혀져 있는 상태(Open-closed)
- 이것을 위해 design pattern이 필요하고 이게 GOF(gang of Four) - 20개 이렇게 하면 open-closed에 근접해 갈 수 있다.
- 이것을 가기 위해 가장 밑바닥의 지식이 상속이다.
packing와 unpacking
container = 1, 2, 3, 4, 5 라고 예를 들어보자.
type(container) == tuple 로 나온다.
container라는 이름에 여기에 할당된 값 객차가 5개가 있어 자연스럽게 튜플로 묶어 준다. 이를
packing이라고 한다.
이것을 a와 b라는 이름에 나누어 다시 할당하기 위해서는 특별한 기호가 필요하다
a, *b = container 라고 해주면
a = 1 ===> type(a) == int
b = [2,3,4,5]가 들어가게 된다. === > type(b) == list (한개를 제외한 나머지 전부, 뒤에 c가 있으면 [2,3,4]만 받게 된다.
2가의 타입은 다르지만(왜 다르게 들어가는지는 알 수 없지만) 나누어 할당이 가능하다.
그럼 이것은 내부적으로 어떻게 되는 것인지? 새로운 값 객체가 할당되는 것인가?
이부분에 대해서는 선생님도 새로운 값 객체가 형성되는 것인지 아니면 그냥 값 객체를 나누게 되는지는 잘 알 수 없다고 하신다.
단 한가지 알게 된 것은
container = 1,2,3
a = container 이라고 하면 파이썬에서 이중 할당이 되지 않기 때문에
a도 1,2,3이라는 값 객체를 가르키게 되는 것이다.
a, b, *c = container 역시 가능하다. ===> a=1, b=2, c=[3,4,5]가 된다.
이를 unpacking (다시 풀어준다) 이라고 한다.
unpacking의 한가지 예를 더 들어보면
>>> dic = {"a" : 1, "b" : 2}
>>> for k, v in dic.items():
>>> print(k,v)
>>> # a 1
>>> # b 2
>>>
이것 역시 unpacking의 한 종류로 볼 수 있다.
함수에서 가변인자를 받도록 인터페이스 설계하기
- 만약에 a,b 2개가 아닌 정해져 있지 않은 숫자의 인자를 받아서 모두 더하게 하려면 어떻게 해야 할까?
>>> def sum_int(a,b):
>>> s = a+b
>>> return s
>>>
즉 위와 같이 단순한 형태가 아닌 여러개의 parameter를 받을 때는 어떻게 해야할까?
ex) sum_int(1,2,3) / sum_int(1,2,3,4) 등.
이것을 가능하게 하기 위해서는 가변인자를 사용해야 한다.
>>> def sum_int(*args):
>>> print(args)
>>>
>>> sum_int(1,2,3,4,5)
>>> #(1,2,3,4,5) 즉 튜플로 된 (1,2,3,4,5)를 얻게 된다. 따라서 우리가 원하는 위의 문제를 해결하기 위해서는
>>> # 튜플로 된 것을 unpacking 작업을 거쳐야 한다.
>>>
- 해결방안
>>> def sum_int(*args):
>>> s = 0
>>> for i in args:
>>> s += i
>>> return s
>>>
>>> sum_int(1,2,3,4)
>>> # 10이라는 원하는 값을 얻을 수 있다.
>>>
만약에 li = [1,2,3,4] 를 만들어서 sum_int(li)를 하게 되면 어떻게 될까?
>>> li = [1,2,3,4]
>>> def sum_int(*args):
>>> s = 0
>>> for i in args:
>>> s += i
>>> return s
>>> sum_int(li)
>>>
>>> #TypeError: unsupported operand type(s) for +=: 'int' and 'list'
위와 같이 테입에러가 발생하게 된다. 왜냐하면 위의 식을 다시 가져와
>>> def sum_int(*args):
>>> print(args)
>>>
>>> print(li) #를 해보면 args가 ([1,2,3,4])로 packing 된 것을 볼 수 있다.
>>>
위의 문제를 해결하기 위해서는 li를 다시 unpacking하도록 해줘야하는데 굉장히 간단하다.
>>> li = [1,2,3,4]
>>> def sum_int(*args):
>>> s = 0
>>> for i in args:
>>> s += i
>>> return s
>>> sum_int(*li) *li를 unpacking해주면 된다.
>>> #10
위와 같이 li에 * 하나만을 추가하여 해결 할 수 있다.
그럼 여기서 sum_int(1, 2, 3, age = 100, weight = 100) 와 같이 특수 한 경우를 처리하기 위해서는 어떻게 해야 할까?
실행 해보면 TypeError: sum_int() got an unexpected keyword argument ‘age’
타입에러가 나오게 되면서 기대하지 않았던 keyword argument인 ‘age’가 나왔다는 메시지를 접하게 된다.
이 방법을 해결하기 위해 keyword argument를 받아 줄수 있는 가변인자를 한개 더 설정해줘야 한다. **kwargs로 가변인자를 설정해준다.
- 인자에 대한 설명은 https://suwoni-codelab.com/python%20%EA%B8%B0%EB%B3%B8/2018/03/05/Python-Basic-argument/ 를 참고하여 주말에 추가적으로 공부한다.
>>> def sum_int(*args, **kwargs):
>>> print(args)
>>> print(kwargs)
>>>
>>> sum_int(1,2,3, age = 100, weight = 100)
>>> #(1,2,3)
>>> #{"age" = 100, "weight" = 100} 로 프린트 해준다.
>>>
다시 위의 식을 가지고 와서
>>> def sum_int(*args, **kwargs):
>>> s = 0
>>> for i in args:
>>> s += i
>>> return s
>>> sum_int(1,2,3, age=100, weight=100)
>>> #6 을 잘 반환해준다.
>>>
위에서 보듯이 6을 return 하지만 age와 weight는 사라진 것이 아니라 출력되지 못한 것이다. 함수안에서 print로 kwargs를 해주면 볼 수 있다.
단 예외가 있는데
sum_int(1, 2, 3, age = 100, weight = 100, 4)를 해보면
SyntaxError: positional argument follows keyword argument
센텍스 에러가 뜨면서 positional argument가 keyword argument를 따라왔다는 오류 메시지가 나온다.
- 추가적으로 고민해보면 좋은 것
>>> li = [3,4,5,6]
>>> dic = {"a" : 1, "b" : 2}
>>>
>>> def sum_int(*args, **dic):
>>> print(args)
>>> print(dic)
>>>
>>> sum_int(li, dic)
>>> #([3,4,5,6] ,{"a":1, "b":2}), {}
>>>
>>> sum_int(*li,*dic)
>>> # (3,4,5,6) , {"a":1, "b":2}
>>>
왜 위와 같이 되는지 고민해 보면 좋을 것 같다.
주의할 점
sum_int(* li, ** dic) —> 함수를 호출할 때 * 와 ** 은 언패킹을 의미한다.
삼항 연산자
- 사용방법
참인경우 값 if 조건 else 거짓인경우 값
연산 대상의 개수에 따라 연산자를 분리하면 단항 연산자, 이항 연산자, 삼항 연산자로 분리 합니다.
단항 연산자는 부호(+, -), not 등이 있으며 +, -, *, / …. 등 대부분의 연산자가 이항 연산자 입니다.
삼항 연산자는 1개가 존재한다.
[참고] (https://wikidocs.net/20701) (https://wikidocs.net/20701)
- 말은 어렵지만 예를 보면 간단해진다.
>>> a = 10
>>> if a > 10:
>>> print("good!")
>>> else:
>>> print("bad!")
>>>
>>> # 삼항 연산자
>>> print("good!") if a>10 else print("bad!")
>>>