파이썬의 코딩의 기술(책) 간단 정리

런던행·2021년 7월 13일
0

Python 기초

목록 보기
11/11

4 f-string 통한 인터폴레이션을 사용하라

pantry = [
('abcd', 1.25),
('bbbb', 2.5)
]

for i, (item, count) in enumerate(pantry):
print(f'{i} {item} {count}')

key = 'my_var'
value = 1.234

formatted = f'{key!r:<20} = {value:.2f}'
print(formatted)

6 인덱스를 사용하는 대신 대입을 사용해 데이터를 언패킹하라

snaks = {
'감자침': 140,
'팝콘': 150
}

items = tuple(snaks.items())
print(items)
print(items[0][0], '&', items[0][1])

first, second = items[0] # 언팩
print(first, '&', second)

7 range보단 enumerate를 사용해라

flavor_list = ['바닐라', '초콜릿', '파칸', '딸']

for i, flavor in enumerate(flavor_list):
print(i, ' ', flavor)

8 여러 이터레이터에 대해 나란히 루프를 수행하려면 zip을 사용하라.

names = ['aaa', 'bbb', 'ccccc']
counts = [len(n) for n in names]
print(counts)

for name, count in zip(names, counts):
print(name, count)

for i in range(2):
print(i)
else:
print('end block')

11 시퀀스를 슬라이싱하는 방법을 익혀라

a = [1, 2, 3, 4, 5, 6]
print(a[:5])
print(a[1:])
print(a[:-1])
print(a[-1:])
print(a[2:-1])

12 스트라이드와 슬라이스를 한 식에 함께 사용하지 말라

일정한 간격을 두고 슬라이싱을 할수 있는 방법을 스트라이드(stride)라고 한다.

a = [1, 2, 3, 4, 5, 6]

odd = a[::2]
evens = a[1::2]
r = a[::-1]
print(odd)
print(evens)
print(r)

13 슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용해라

a = [1, 2, 3, 4, 5, 6]
descending = sorted(a, reverse=True)
try:
oldest, second_oldest = descending # 2개 이상 언팩이 필요하다.
except Exception as e:
print(str(e))

oldest, second_oldest, *others = descending # 별표식을 사용해 모든 값을 담는 언패킹을 할 수 있게 지원
print(oldest, second_oldest, others)

14 복잡한 기준을 사용해 정렬할 때는 key 파라미터를 사용하라.

class Tool:
def init(self, name, weight):
self.name = name
self.weight = weight

def __repr__(self):
    return f'{self.name} & {self.weight}'

tools = [
Tool('AAA', 3.5),
Tool('AA', 5),
Tool('AA', 10)
]

print(tools)
tools.sort(key=lambda x: x.name) # 정렬 키 한개
print(tools)
tools.sort(key=lambda x: (x.name, -x.weight)) # 정렬 키 여러개
print(tools)

15 딕셔너리 삽입 순서에 의존할 떄는 조심하라

파이썬 3.5 이전에는 딕셔너리에 대해 이터레이션을 수행하면 키를 임의의 순서로 반환하여

이터레이션 순서는 원소가 삽입된 순서와 일치하지 않았다. 그러나 3.6부터는 딕셔너리가 삽입 순서를 보존하도록 동작이 개선

baby_names = {
'cat': 'kitt',
'dog': 'potty'
}

print(list(baby_names.keys()))
print(list(baby_names.values()))
print(list(baby_names.items()))

16 in 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라.

counters = {
'apple': 2,
'banana': 1,
}

key = 'orange'
if key in counters.keys():
count = counters[key]
else:
count = 0
counters[key] = count + 1

count = counters.get(key, 0)
counters[key] = count + 1

17 내부 상태에서 원소가 없는 경우를 처리할 때는 setdefault보다 defaultdict를 사용하라.

:= 바다코끼리 연산자

a = 'dsfsdfsdfsdfsdf'

if (n := len(a)) > 10 :
print(f"List is too long ({n} elements, expected <= 10)")

n = len(a)
(n := len(a))

visits = {
'미국': {'newy', 'loast'},
'일본': {'하코네'}
}

visits.setdefault('프랑스', set()).add('칸')

if (japan := visits.get('일본')) is None:
visits['일본'] = japan = set()

visits.setdefault('일본', set()).add('칸')
visits.setdefault('일본', set()).add('칸2')
japan.add('도쿄')
print(visits)

class Visits:
def init(self):
self.data = {}

def add(self, country, city):
    city_set = self.data.setdefault(country, set())
    city_set.add(city)

visits = Visits()
visits.add('러시아', '1')
visits.add('러시아', '2')
print(visits.data)

from collections import defaultdict

class Visitor2:
def init(self):
self.data = defaultdict(set)

def add(self, country, city):
    self.data[country].add(city)

vvisits = Visitor2()
vvisits.add('영국', '바스')
vvisits.add('영국', '런던')
print(vvisits.data)

19 함수가 여러 값을 반환하는 경우 절대로 4 값 이상을 언패킹하지 마라

lengths = [1 ,2, 3, 4, 5, 6, 55, 100, 22]

def get_avg_ration(numbers):
average = sum(numbers) / len(numbers)

scaled = [x / average  for x in numbers]
scaled.sort(reverse=True)
return scaled

longest, *middle, shortest = get_avg_ration(lengths)
print(f'(longest {longest}, shortest {shortest})')

20 None을 반환하기보다는 예외를 발생시켜라

21 변수 영역과 클로저의 상호작용 방식을 이해하라

def sort_priority(values, group):
def helper(x):
if x in group:
return (0, x)

    return (1, x)  #  시쿼스 비교시 0번 인덱스에 있는 값이 같으면 다시 1번 인덱스에 있는 값을 비교한다.

values.sort(key=helper)

numbers = [4, 2, 1, 2]
group = { 2 }

sort_priority(numbers, group)
print(numbers)

22 변수 위치 인자를 사용해 시각적인 잡음을 줄여라

def log(message, *values):
if not values:
print(message)
else:
values_str = ','.join(str(x) for x in values)
print(message, ' ' , values_str)
log('aaa', 1, 2)

23 키워드 인자로 선택적인 기능을 제공하라

27 map과 filter 대신 컴프리헨션을 사용하라.

a = [1, 2, 3, 4, 5]
squares = []
for x in a:
squares.append(x**2)
print(squares)

squares2 = [x**2 for x in a]
print(squares2)
alt = map(lambda x: x ** 2, a)

even_squares = [x**2 for x in a if x % 2 == 0]
print(even_squares)

28 컴프리센션 내부에 제어 하위 식을 세 개 이상 사용하지 마라

matrix = [[1, 2], [4, 5, 6, 7]]
squared = [[x**2 for x in row] for row in matrix]
print(squared)

a = [1, 2, 3, 5]
b = [x for x in a if x > 4 if x % 2 == 0]

29 대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라

stock = {
'못': 125,
'나사못': 35,
'와셔': 24,
'나비너트': 8
}

order = ['나사못', '나비너트']

def get_batches(count, size):
return count // size

result = {}
for name in order:
count = stock.get(name, 0)
batches = get_batches(count, 8)
if batches:
result[name] = batches

print(result)
found = {name: get_batches(stock.get(name,0), 8) for name in order if get_batches(stock.get(name, 0), 8)}
print(found)

30 리스트를 반환하기보다는 제너레이터를 사용하라

address = '컴퓨터(영어: Computer)'
def index_words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1

it = index_words_iter(address)
print(next(it))
print(next(it))

32 긴 리스트 컴프리헨션보다는 제너레이터 식을 사용하라

value = [len(x) for x in open('my_file.txt')] # 파일에서 읽은 각 줄은 개행문자가 있다.
print(value)

it = (len(x) for x in open('my_file.txt'))
print(next(it))
print(next(it))

36 이터레이터나 제너레이터를 다룰 때는 itertools를 사용하라.

import itertools

chain

it = itertools.chain([1, 2, 3], [4, 5, 6])
print(list(it))

repeat

it = itertools.repeat('hi', 3)

cycle

it = itertools.cycle([1, 2])
result = [next(it) for _ in range(10)]
print(result)

it1, it2 = itertools.tee(['aa', 'bb'], 2)
print(list(it1))
print(list(it2))

zip_longest

keys = [1, 2, 3]
values = [11, 22, 33]

normal = list(zip(keys, value))
print('zip', normal)

it = itertools.zip_longest(keys, values, fillvalue='None')
longest = list(it)
print(longest)

이터레이터에서 원소 거르기

values = [1, 2, 3, 4, 5, 6]
first_five = itertools.islice(values, 5)
print('앞에서 다섯 개:', list(first_five))

middle_odds = itertools.islice(values, 2, 8, 2) # 시작, 끝, 증가값
print('중간의 홀수들:', list(middle_odds))

takewhile

less_than_sevent = lambda x: x < 7
it = itertools.takewhile(less_than_sevent, values)
print(list(it))

dropwhile

values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

less_than_sevent = lambda x: x < 7
it = itertools.dropwhile(less_than_sevent, values)
print(list(it))

filterfalse

values = [1, 2, 3, 4, 5, 6]
evens = lambda x: x % 2 == 0

37 내장 타입을 여러 단계로 내포시키기보다는 클래스를 합성하라

class SimpleGradebook:
def init(self):
self._grades = {}

def add_student(self, name):
    self._grades[name] = []

def report_grade(self, name, score):
    self._grades[name].append(score)

def average_grade(self, name):
    grades = self._grades[name]
    return sum(grades) / len(grades)

book = SimpleGradebook()
book.add_student('new ton')
book.report_grade('new ton', 50)
book.report_grade('new ton', 20)
print(book.average_grade('new ton'))

class BySubjectGradebook:
def init(self):
self._grades = {} # 외부 dict

def add_student(self, name):
    self._grades[name] = defaultdict(list)  # 내부 dict

def report_grade(self, name, subject, grade):
    by_subject = self._grades[name]
    grade_list = by_subject[subject]
    grade_list.append(grade)

def average_grade(self, name):
    by_subject = self._grades[name]
    total, count = 0, 0

    for grades in by_subject.values():
        total += sum(grades)
        count += len(grades)

    return total / count

38 간단한 인터페이스의 경우 클래스 대신 함수를 받아 드려라

names = ['abcdef', '아르키메데스', '플라톤', '아리스토텔레스']
names.sort(key=len)
print(names)

39 객체를 제너릭하게 구성하려면 @classmethod를 통한 다형성을 활용하라

40 super로 부모 클래스를 초기화 하다

다중 상속을 사용하는 경우 생기는 문제 중 하나는 모든 하위 클래스에서 init 호출의 순서가 정해져 있지 않다는 것이다.

class MyBaseClass:
def init(self, value):
self.value = value
print(f'MyBaseClass {value}')

class TimesTwo:
def init(self, value):
print(f'TimesTwo {value}')

class PlusFive:
def init(self, value):
print(f'PlusFive {value}')

class OneWay(MyBaseClass, TimesTwo, PlusFive):
def init(self, value):
MyBaseClass.init(self, value)
TimesTwo.init(self, value)
PlusFive.init(self, value)

foo = OneWay(5)

class AnotherWay(MyBaseClass, PlusFive, TimesTwo ):
def init(self, value):
MyBaseClass.init(self, value)
TimesTwo.init(self, value)
PlusFive.init(self, value)

클래스 정의에 나열한 부모 클래스의 순서와 부모 생성자를 호출한 순서가 달라서 문제가 생긴다.

foo = AnotherWay(5)

super()는 다이아몬드 계층의 공통 상위 클래스를 단 한번만 호출하도록 보장한다.

class TimesSevenCorrect(MyBaseClass):
def init(self, value):
super().init(value)
self.value *= 7

class PlusCorrect(MyBaseClass):
def init(self, value):
super().init(value)
self.value += 9

class GoodWay(TimesSevenCorrect, PlusCorrect):
def init(self, value):
super().init(value)

foo = GoodWay(5)
print(f'good way : {foo.value}')

호출 순서는 MRO 정의를 따른다

mro_str = '\n'.join(repr(cls) for cls in GoodWay.mro())
print(mro_str)

41 기능을 합성할 떄는 믹스인 클래스를 사용하라

믹스인은 자식 클래스가 사용할 메서드 몇개만 정의하는 클래스다. 믹수인 클래스에는 자체 애트리뷰트 정의가 없으므로 믹스인 클래스의 init 메서드를 호출할 필요도 없다.

42 비공개 애트리뷰트보다는 공개 애트리뷰트를 사용하라

애트리뷰트 이름 앞에 밑줄을 두 개 (__) 붙이면 비공개 필드가 된다.

43 커스텀 컨테이너 타입은 collections.abc를 상속하라

class FrequencyList(list):
def init(self, members):
super().init(members)

def frequency(self):
    counts = {}
    for item in self:
        counts[item] = counts.get(item, 0) + 1

    return counts

foo = FrequencyList(['a', 'b', 'c', 'a'])
print(len(foo))

print(foo.frequency())

44 세터와 게터 메서드 대신 평범한 대트리뷰트를 사용하라

class Resistor:
def init(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0

r1 = Resistor(50e3)
r1.ohms = 10e3

class VoltageResistor(Resistor):
def init(self, ohms):
super().init(ohms)

    self._voltage = 0

@property
def voltage(self):
    return self._voltage

@voltage.setter
def voltage(self, voltage):
    self._voltage = voltage
    self.current = self._voltage / self.ohms

r2 = VoltageResistor(1e3)
print(f'{r2.current:.2f}')
r2.voltage = 10
print(f'{r2.current:.2f}')

45 애트리뷰트를 리팩터링하는 대신 @property를 사용하라

46 재사용 가능한 @property 메서드를 만들려면 디스크립터를 사용하라

class Grade:
def init(self):
self._value = 0

def __get__(self, instance, owner):
    return self._value

def __set__(self, instance, value):
    if not (0 <= value <= 100):
        raise ValueError('fail')

    self._value = value

class Exam:

# 클래스 애트리뷰트
math_grade = Grade()
writing_grade = Grade()

first_exam = Exam()
first_exam.writing_grade = 99
first_exam.math_grade = 80

second_exam = Exam()
second_exam.math_grade = 70

print(first_exam.math_grade)
print(second_exam.math_grade)

class Exam2:

# 클래스 애트리뷰트
def __init__(self):
    self.math_grade = Grade()
    self.writing_grade = Grade()

first_exam = Exam2()
first_exam.writing_grade = 99
first_exam.math_grade = 80

second_exam = Exam2()
second_exam.math_grade = 70

print(first_exam.math_grade)
print(second_exam.math_grade)

47 지연 계산 애트리뷰트가 필요하면 getattr, getattribute, setattr을 사용해

어떤 클래스안에 getattr 메서드 정의가 있으면, 이 객체의 인스턴스 딕셔너리에서 찾을 수 없는 애트리뷰트에 접근할 때마다 getattr이 호출된다.

class LazyRecord:
def init(self):
self.exists = 5

def __getattr__(self, item):
    value = f'{item}GOGO'
    setattr(self, item, value)
    return value

data = LazyRecord()
print(data.dict)
print(data.foo)
print(data.dict)

48 init_subclass를 사용해 하위 클래스를 검증하라

메타클래스는 type을 상속해 정의된다. 기본적인 경우 메타클래스는 new 메서드를 통해 자신과 연관된 클래스의 내용을 받는다.

class Meta(type):
def new(meta, name, bases, class_dict):

    print(name, meta.__new__, bases, class_dict)

    return type.__new__(meta, name, bases, class_dict)

class MyClass(metaclass=Meta):
stuff = 123

def foo(self):
    pass

class MySubclass(MyClass):
other = 567

def bar(self):
    pass

class ValidatePolygon(type):

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__()
    if cls.sides < 3:
        raise ValueError('check_color')

class Polygon():
sides = None

@classmethod
def angles(cls):
    return (cls.sides - 2) * 180

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__()
    if cls.sides < 3 :
        raise ValueError('check_color')

class Triangle(Polygon):
sides = 3

assert Triangle.angles() == 180

class Line(Polygon):

sides = 2

assert Line.angles() == 180

class ValidateFilled(type):
def new(meta, name, bases, class_dict):

    if bases:
        if class_dict['color'] not in ('red', 'green'):
            raise ValueError('check_color')

    return type.__new__(meta, name, bases, class_dict)

class Filled:
color = None

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__()
    if cls.color not in ('red', 'green'):
        raise ValueError('check_color')

class RedPentagon(Filled, Polygon):
color = 'red'
sides = 5

49 init_subclass를 사용해 클래스 확장을 등록하라

50 set_name으로 클래스 애트리뷰트를 표시하라

51 합성 가능한 클래스 확장이 필요하면 메타클래스보다는 클래스 데코레이터를 사용하라

def my_class_decorator(klass):
klass.extra_param = 'hello'
return klass

@my_class_decorator
class MyClass:
pass

print(MyClass.extra_param)

profile
unit test, tdd, bdd, laravel, django, android native, vuejs, react, embedded linux, typescript

0개의 댓글