아래 코드는 필자가 소개한 평균 길이, 중간 길이, 전체 분포를 보여주는 코드이다.
def get_stats(numbers):
minimum = min(numbers)
maximum = max(numbers)
count = len(numbers)
average = sum(numbers) / count
sorted_numbers = sorted(numbers)
middle = count // 2
if count % 2 == 0:
lower = sorted_numbers[middle - 1]
upper = sorted_numbers[middle]
median = (lower + upper) / 2
else:
median = sorted_numbers[middle]
return minimum, maximum, average, median, count
minimum, maximum, average, median, count = get_stats(lengths)
print(f'Min: {minimum}, Max: {maximum}')
print(f'Average: {average}, Median: {median}, Count {count}')
>>>
Min: 60, Max: 73
Average: 67.5, Median: 68.5, Count 10아
이 코드에는 2가지 문제점이 존재하는데
1.
return values가 모두 numeric해서 reorder 하기 쉽다. -> 버그를 발생시키기 좋다
따라서 minimum, maximum, average, median순으로 이를 구하는 것이 좋다.
2.
함수를 call 하는 line이 다양한 방법으로 wrapped 될 필요가 있어보인다.
minimum, maximum, average, median, count = get_stats(
lengths)
minimum, maximum, average, median, count = \
get_stats(lengths)
(minimum, maximum, average,
median, count) = get_stats(lengths)
(minimum, maximum, average, median, count
) = get_stats(lengths)
사실 없어보인다
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
# 이 함수는 다음과 같이 활용해볼 수 있다.
result = divide(x, y)
if result is None:
print('Invalid inputs')
위의 코드를 보면 없어보인다
그러나
result = divide(x, y)
if not result:
print('Invalid inputs')
위와 같은 활용식을 사용한다면 error가 발생한다.
def divide(a, b):
try:
return True, a / b # 튜플로 결과를 반환
except ZeroDivisionError:
return False, None # 튜플로 결과를 반환
success, result = divide(x, y)
if not success:
print('Invalid inputs')
이렇게 해주면 또다른 문제를 야기시킨다
def divide(a, b):
try:
return a / b
except ZeroDivisionError as e:
raise ValueError('Invalid inputs') from e
x, y = 5, 2
try:
result = divide(x, y)
except ValueError:
print('Invalid inputs')
else:
print('Result is {:.2f}'.format(result))
>>> Result is 2.50
라고 예외를 처리하면 더 깔끔하다고 한다.
def sort_priority(values, group):
def helper(x):
if x in group:
return (0, x)
return (1, x)
values.sort(key=helper)
numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_priority(numbers, group)
print(numbers)
>>> [2, 3, 5, 7, 1, 4, 6, 8]
여기에는 3가지 이유가 있다
1.
파이썬은 클로저를 지원한다
2.
함수는 파이썬에서 일급 객체이다
3.
파이썬에는 이터러블의 대소 관계를 비교하는 특정 규칙이 있다.
따라서 함수에서 우선순위가 높은 아이템을 발견했는지 여부를 반환해서 사용자 인터페이스 코드가 그에 따라 동작하게 하면 좋다고 한다.
필자는 아래와 같이 예시로 보여주었다
def sort_priority2(numbers, group):
found = False
def helper(x):
if x in group:
found = True
return 0, x
return 1, x
numbers.sort(key=helper)
return found
found = sort_priority2(numbers, group)
print('Found', found)
>>> False
또한, 함수의 표현식에서 변수를 reference한다면 파이썬 인터프리터는 이를 해결하려고 아래와 같은 순서로 탐색한다
1.
The current function's scope
2.
Any enclosing scopes. (such as other containing functions)
3.
The scope of the module that contains the code (also called the global scope).
4.
The built-in scope(that contains functions like len and str).
그래서 이 중 4개중에 하나라도 없으면 NameError exception이 발생한다.
스코핑 버그라는 문제들이 발생하는데 이것은 뉴비들에게 굉장히 놀랍다.
sort 메서드의 key에 sorter클래스를 넣고 있으면 instance를 호출할 수 있기 위해서 call 메서드를 정의하여야 한다.
class Sorter:
def __init__(self, group):
self.group = group
self.found = False
def __call__(self, x):
if x in self.group:
self.found = True
return 0, x # 튜플은 ( )로 안 감싸도 된다.
return 1, x
sorter = Sorter(group)
numbers.sort(key=sorter)
assert sorter.found is True
함수롤 호출할 때 인자를 위치로 전달할 수 있다.
# 정수를 다른 정수로 나눈 나머지를 반환하는 함수
def remainder(n, d):
return n % d
>>> print(remainder(20, 7))
6
위에서는 단순히 함수 호출 시에 값만 전달됐으나, 인자의 이름과 값을 쌍으로 전달할 수도 있다.
remainder(20, 7)
remainder(20, d=7)
remainder(n=20, d=7)
remainder(d=7, n=20)
import datetime
import time
# Use Python 3.6 boy
def log(message, when=datetime.datetime.now()):
print(f"{when}: {message}")
그런데 필자가 만약 now 함수를 0.1초 정도 뒤에 호출할 경우 기록된 시간이 다름을 예상할 수 있었다고 하는데 now함수가 정의될 때의 시간이 고정되어있음을 확인할 수 있다. (A default argument value is evalutated only once!)
따라서 이를 해결하기 위해 기본값을 None 로 설정한 후 docstring으로 실제 동적을 문서화한다
def log(message, when=None):
"""Log a message with a timestamp
:input:
message: Message to print
when: datetime of when the message occurred.
Defaults to the present time.
"""
if when is None:
when = datetime.datetime.now()
print(f"{when}: {message}")
log("Greetings!")
time.sleep(0.1)
log("Greetings again")
2019-05-17 12:27:54.804982: Greetings!
2019-05-17 12:27:54.906290: Greetings again
그리고 교재를 통해서 필자가 여러가지 코드를 제시했는데 모두 확인해보면 시간이 알아서 바뀌었음을 확인할 수 있다.
def safe_division(number, divisor,
ignore_overflow,
ignore_zero_division):
try:
return number / divisor
except OverflowError:
if ignore_overflow:
return 0
else:
raise
except ZeroDivisionError:
if ignore_zero_division:
return float('inf')
else: raise
필자는 저 위의 함수에서 몇 가지 오류를 무시하고 무한대 값을 반환하도록 설계하였는데, 이것도 또한 그렇게 좋은 방법이 아닌 것을 교재에서 설명하고 있다.
def safe_division_c(n, d, *,
ignore_overflow=False, ignore_zero_division=False):
...
위와 같이 활용하는 것이다. *뒤에 있는 인수들은 위치 인자로 보내면 도작하지 않는다고 한다.