Python

[파이썬 코드 업] 4-1장. 프로그래밍 지름길

patrick-star 2023. 5. 5. 15:32
728x90

① 필요하다면 코드를 여러 줄에 걸쳐 작성한다

물리적으로 1줄보다 긴 문장을 작성할 때 여러 가지 방법을 이용해서 해결할 수 있다.

  • literal 문자열 다음에 literal 문자열 입력
    ex) my_str = 'I am Hen-er-y the Eighth,' ' I am!' 라고 입력
>>> my_str
'I am Hen-er-y the Eighth, I am!' 로 입력된다. 

# 'I am Hen-er-y the Eighth, ' 라는 literal 문자열 다음에 
# 한 칸 띄고
# ' I am!' literal 문자열을 입력함으로써 두 문자열을 연결했다. 
  • \를 이용하는 방법
my_str = 'I am Hen-er-y the Eighth,' \
         ' I am!'

# 이때, 역슬래시(\) 뒤에 아무런 공백없이 바로 줄 바꿈을 해줘야 한다. 

>>> my_str
'I am ready!  You are ready'
  • 소괄호, 중괄호, 대괄호를 이용하는 방법
my_str = ('I am ready! '
          ' You are ready'
          ' Living in the sunlight')

>>> my_str
'I am ready!  You are ready Living in the sunlight'

② for 루프 현명하게 사용하기

range 함수를 사용하는 건 C 언어 스타일의 for 루프이다.

  • C 언어 스타일의 for 루프
beat_list = ['John', 'Paul', 'George', 'Ringo']

for i in range(len(beat_list)) :
    print(beat_list[i])

위와 같이 for루프를 사용하지 말고 아래와 같이 for 루프를 사용하도록 하자.
Java에서 사용하는 enhanced-for문과 비슷해보인다.

beat_list = ['John', 'Paul', 'George', 'Ringo']

for guy in beat_list :
    print(guy)

인덱스 번호도 함께 생성하고 싶다면 enumerate 함수를 사용하자.

beat_list = ['John', 'Paul', 'George', 'Ringo']

for guy in beat_list :
    print(guy)

# 출력 결과 
1. John
2. Paul
3. George
4. Ringo

cf) 인덱스를 사용하지 않고 변수 값을 변경한다면 객체의 값이 변경되지 않고 새로운 객체가 생성되어 해당 변수에 대입된다.

무슨 말이냐면 다음 코드를 보자.

beat_list = ['John', 'Paul', 'George', 'Ringo']

for guy in beat_list :
    guy = 'Chris' # 인덱스를 사용하지 않고 변수 값을 변경 시도 

print(beat_list)

# 출력 결과 - 객체의 값이 변경되지 않음 
['John', 'Paul', 'George', 'Ringo']

for 문 안에서 beat_list의 각각의 값을 guy 라는 변수로 가리키도록 했다.
그런데, 가리키는 값을 Chris라는 새롭게 생성된 객체로 바꾼 것이다.

그러면, guy 변수가 가리키던 값이 변경될 뿐 beat_list의 0번째 값에는 아무런 변화가 생기지 않는다.

③ 메모리-값-변경(in place)과 신규-객체-생성(non-in-place)

문자열(String)과 리스트가 이런 차이를 분명하게 보여준다.

  • 문자열
s1 = s2 = 'A string' 

s1 += '...with more stuff!' 

>>> s1  
'A string...with more stuff!'
>>> s2
'A string'

여기서 주목할 점은 s1이 참조하던 값을 메모리에서 변경한 것이 아니다.
새로운 문자열 'A string...with more stuff!'을 생성해서 참조한 것이다.

그렇기 때문에 s1과 s2는 지금 서로 다른 값을 가리키고 있는 상태다.

  • 리스트
a_list = b_list = [10, 20] 

a_list += [30, 40] 

>>> a_list
[10, 20, 30, 40]
>>> b_list
[10, 20, 30, 40]

분명 a_list를 변경했음에도 불구하고 b_list의 값도 변경된 걸 확인할 수 있다.

왜냐하면, a_list와 b_list는 서로 같은 리스트를 가리켰기 때문에 a_list에서 변경한 리스트는 b_list가 가리킨 리스트와 동일하기 때문이다.

값을 직접 변경하는 연산신규 객체를 생성하는 연산보다 훨씬 효율적이다.
그래서 문자열에서 +=를 사용하는 것 보다는 join 메서드를 사용하는 것이 더 좋다.

ex)

str_list = []
n = ord('a')
for i in range(n, n+26) :
    str_list += chr(i); 

alpha_str = ''.join(str_list)

>>> alpha_str
'abcdefghijklmnopqrstuvwxyz'

④ 다중 대입

a = b = c = d = 0 # a, b, c, d 서로 다른 4개의 변수에 값은 값(0)을 대입할 수 있다

⑤ 튜플 대입

다중 대입은 서로 다른 변수에 같은 값을 대입할 떄 유용하다.
하지만, 서로 다른 값을 대입하고 싶다면 어떻게 해야 할까?

가장 먼저 생각할 수 있는 방법은 아래와 같다.

a = 1
b = 0

하지만, 튜플 대입을 사용하면 1줄로 표현할 수 있다.

a, b = 1, 0 

튜플 대입을 이용하면 swap 과정을 간편하게 구현할 수 있다

  • 다른 언어의 swap
temp = x
x = y
y = temp 
  • python에서의 swap
x, y = y, x  

⑥ 고급 튜플 대입

  • 튜플 unpack
tup = 10, 20, 30 # 3개의 값을 저장한 튜플

a, b, c = tup # 튜플에 있는 3개의 값을 3개의 변수에 저장 
              # 물론, 좌측 변수의 개수와 우측에 있는 튜플의 값 개수가 동일해야 한다. 
              # 그렇지 않으면 에러가 발생 

print(a, b, c) 
  • 1개의 값을 갖는 튜플 생성
my_tup = (1)

# my_tup의 타입 내용 - 그냥 정수 값으로 저장됨 
>> type(my_tup)
<class 'int'>

이렇듯 소괄호 기호만을 가지고 1개의 항목을 갖는 튜플을 생성할 수는 없다. 그래서 다음과 같이 입력해줘야 한다.

my_tup = (1, )

# my_tup의 타입 내용
>>> type(my_tup)   
<class 'tuple'>
  • * 기호를 사용한 튜플 대입

*기호를 사용하면 튜플 대입에 유연성을 더할 수 있다.

a, *b = 2, 4, 6, 8

>>> a
2
>>> b
[4, 6, 8]

위와 같이 변수 b가 첫 번째를 제외한 나머지 항목들의 리스트를 참조한다는 걸 알 수 있다.

*기호는 좌항의 위치 상관없이 놓을 수 있다. 다만, 1개만 사용할 수 있다.

a, *b, c = 10, 20, 30, 40, 50, 6000

>>> a
10
>>> b
[20, 30, 40, 50]
>>> c
6000

⑦ 리스트와 문자열 곱하기

  • 리스트 곱하기

C, Java 언어는 배열의 개수를 미리 정해서 선언한다. 하지만, python은 별도의 데이터 선언이 없다.
그래서 많은 개수의 리스트를 만드려면 일일이 초기값을 넣어줘야 한다.

list = [0, 0, 0, 0, .... ]

하지만, 초기값을 일일이 넣는다는 건 말이 안된다. 그래서 아래와 같은 방법을 사용하면 된다.

my_list = [0] * 10000 # 보이는 대로 0을 10000개 넣은 리스트가 생성됨 

>>> len(my_list)
10000
  • 문자열 곱하기

문자열에서도 동일한 기능을 위한 곱하기(*)를 사용할 수 있다.

div_str = '-' * 40

>>> div_str
'----------------------------------------'

⑧ 여러 개의 값 return

ex) 2차 방정식을 수행하는 함수. 전달하는 매개변수는 계수값.

>>> def quad(a, b, c) : 
...     det = (b * b - 4 * a * c) ** .5
...     x1 = (-b + det) / (2 * a)
...     x2 = (-b - det) / (2 * a)
...     return x1, x2
...
>>> quad(1, -2, 1)
(1.0, 1.0) # 함수의 return값이 이와 같이 2개 나오도록 할 수 있다. 

⑨ 루프와 else 키워드

else는 대부분 if 와 함께 쓰인다.

파이썬에서는 루프에서 사용하는 try-except 문법으로 사용될 수 있다.

반복문에서 사용한 else는 루프가 break문을 만나서 일찍 빠져나오지 않는다면 반복문 종료시 실행된다.

def find_divisor(n, max) : 
    for i in range(2, max + 1) : 
        if n % i == 0 : 
            print (i, 'divides evenly into', n)
            break

    else :
        print("No!!!!")

>>> find_divisor(49, 6) # break 문을 만나지 않으면서 반복문이 끝났으니까 else 부분 실행
No!!!!
>>> find_divisor(49, 7) # break 문을 만나서 반복문이 끝났으니까 else 부분 실행 X
7 divides evenly into 49

⑩ 필요없는 문자 제거 - replace

s = s.replace(' ', '') # 문자열 s에서 빈칸을 없애주는 코드 

만약에 모든 모음을 삭제하고 싶다면 다음과 같이 쓸 수 있다.

# 문자열 s의 각각의 문자를 c로 가리키고 
# 그 c가 모음이 아닌 경우에만 저장되도록 함 

a_list = [c for c in s if c not in 'aeiou'] 
s = ''.join(a_list) 

⑪ 연결된 비교 연산자

if 0 < x and x < 100 : 
    print('x is in range') 

# 짧게 변경 - 연결된 비교 연산자 사용 
if 0 < x < 100 : 
    print('x is in range') 
  • 여러 종류 섞어 쓰는 경우
a, b, c = 5, 10, 15 

# 0 < a <= c and c > b > 1 을 의미함 
if 0 < a <= c > b > 1 : 
    ~~~ 
# a, b, c, d, e가 모두 같은 값인 경우 
if a == b == c == d == e : 
    ~~ 

이처럼 파이썬에서는 여러개의 비교연산자를 한꺼번에 사용할 수 있다.

⑫ 함수 테이블로 switch문 모방하기

파이썬은 switch문을 제공하지 않는다. 그렇지만 if/elif를 여러 번 쓴다면 코드가 장황해보일 수 있다.

  • if/elif를 사용한 경우
if n == 1: 
    do_plot(stockdf) 
elif n == 2 : 
    do_highlow_plot(stockdf) 
elif n == 3 : 
    do_volumn_subplot(stockdf) 
elif n == 4 : 
    do_movingavg_plot(stockdf) 

장황하다. 그래서 다음과 같은 idea를 통해 switch문을 모방할 수 있다.

  • 리스트를 이용한 방식
fn = [sum, max, min]

>>> fn[0]([1, 2, 3]) # sum
6
>>> fn[1]([1, 2, 3]) # max
3
>>> fn[2]([1, 2, 3]) # min
1

함수를 저장하고 있는 리스트를 만들어서
원하는 함수에 해당하는 인덱싱을 해서 각각 다른 함수를 호출하는 방식을 생각할 수 있다.
파이썬 함수 역시 객체이기 때문에 리스트의 항목에 넣을 수 있기에 가능한 방식이다.

  • 딕셔너리를 이용한 방식
fn = {'SUM' : sum, 'MAX': max, 'MIN' : min}

fn['SUM']([1, 2, 3])
fn['MAX']([1, 2, 3])
fn['MIN']([1, 2, 3])

⑬ 동등 연산자(==) vs is 연산자

아래와 같은 상황을 보자.

s1 = "I am what I am and that is all that I am"
s2 = "I am what I am" + " and that is all that I am"

# 동등 연산자로 본다면 s1, s2는 같은 값을 가지기 때문에 true 
>>> s1 == s2 
True

# s1, s2는 동일한 값을 갖더라도 서로 다른 객체이기 때문에 is 연산자에서는 false를 반환 
>>> s1 is s2 
False

그래서 is 연산자를 사용하는 경우는

1) None, True, False와 같은 객체와 비교하는 경우
2) 동일한 객체의 값을 비교하는 경우

⑭ 여러 문장을 1줄로 줄이기

코드의 문장들이 충분히 짧다면 여러 문장을 1줄로 작성할 수 있다.

a = 1; b = 2; c = a+b; print(c) 

물론 1줄씩 작성할 때는 ;을 사용하지 않아도 되지만 공간을 절약할 수 있다는 측면에서 충분히 활용할 수 있는 문법이다.

⑮ 단일 행 if/then/else문

형식부터 설명하면 다음과 같다.

(참 값) if 조건문 else (거짓 값) 

# 조건문이 참일 때 (참 값)을 반환
# 조건문이 거짓일 때 (거짓 값)을 반환

ex) 
cell = 'X' if turn % 2 else 'O' 

⑯ range와 함께 enum 생성

각각의 변수를 숫자로 표현하는게 편한 경우가 있다.

red = 0; blue = 1; green = 2; black = 3; white = 4 

위 코드는 잘 동작하지만 자동화 할 수 있는 방법이 있다. 아래의 방법을 보자.

red, blue, green, black, white = range(5) # 위 코드랑 동일한 내용이다. 
                                          # 1부터 시작하고 싶다면 range(1, 6)을 쓰면 된다. 

⑰ IDLE 안에서 print 함수 사용 줄이기

ex) 별표 기호(*) 40 * 20 블록으로 출력하기

  • 가장 느린 방법 - print 함수를 40 * 20번 호출한다
for i in range(20) : 
    for j in range(40) : 
        print('*', end = ' ') 
     print()
  • 한 번에 한 개 행의 별표 기호를 출력
row_of_aster = '*' * 40 
for i in range(20) : 
    print(row_of_aster) 
  • 여러 줄의 큰 문자열을 미리 만들고 print는 1번만 호출 - += 이용
row_of_aster = '*' * 40 
s = ''

for i in range(20) : 
    s += row_of_aster + '\n'

print(s) 

성능을 좀 더 개선하기 위해 join 메소드를 사용한다.

  • 여러 줄의 큰 문자열을 미리 만들고 print는 1번만 호출 - join 이용
row_of_aster = '*' * 40 

list_str = []

for i in range(20) : 
    list_str.append(row_of_aster)

print('\n'.join(list_str)) # 리스트를 합치는데 
                           # 구분되는 곳 마다 '\n'을 넣어서 합친다. 

위 코드를 1줄로 줄이면

print('\n'.join(['*' * 40] * 20))

⑱ 큰 숫자에 _ 삽입

보통 숫자를 구분할 때 콤마(,)를 쓴다. ex) 1,400,000

그렇지만, 파이썬에서는 콤마를 이용해서 값을 넣지 못한다. ex) n = 1,400,000 => 안되는 코드

그래서 _를 사용할 수 있다.

>>> n = 1_500_0000
>>> n
15000000