Python의 추상화 두번째 시간입니다. 우리 눈에 보이지 않는 Python의 실행 순서와 변수, 파라미터 지정 등과 같은 다양한 추상화 사례를 이번 시간에도 함께 살펴보도록 합시다.

🦋 Scope(범위)

Scope는 초심자 분들이 많이 어려워하는 개념입니다. 그럼에도 이 개념을 소개하는 이유는 코딩을 작성할 때 이 개념을 알면 발생할 수 있는 많은 오류를 줄일 수 있기 때문입니다. 어렵지만 천천히 한 번 이해해 보도록 하죠.

def my_function():
    x = 2
    print(x)
    
my_function()

위 코드를 실행하면 어떤 결과가 나올까요? my_function이라는 함수를 호출했고 파라미터에 들어갈 값을 제공하지 않았습니다. 함수가 실행됨에 따라 2라는 수가 변수 x에 지정이 되고 print()문에 의해 2가 출력됩니다.

이번에는 my_function() 호출문 밑에 print(x)를 추가해볼까요? 우선 위 코드와 같이 2가 출력되는 것은 동일합니다. 그러나 그 이후 print(x)를 실행하면 오류가 발생합니다. x라는 이름이 정의된 적 없다고 말이죠. 분명히 함수 내에서 x를 정의했는데 왜 이런 결과가 나온 걸까요?

코드를 유심히 살펴 봅시다. 변수 x는 my_function 함수 내에서 정의되었습니다. 이처럼 함수 내에서 정의된 변수로컬 변수(local variable)라고 합니다. 로컬 변수는 해당 변수가 정의된 함수 내에서만 사용할 수 있습니다. 따라서, 변수 x의 스코프 즉, 변수 x가 유효한 범위는 my_function이라는 함수 안입니다.

print(x)는 my_function 밖에 있는 독립된 함수이므로 x의 정의가 효력을 잃는 것이죠. 그래서 정의된 값이 없다는 오류가 뜬 것입니다.

x = 3

def my_function():
    print(x)
    
my_function()
print(x)

그럼 위 코드와 같이 함수 정의 전에 변수를 먼저 선언하면 어떻게 될까요? 이렇게 함수 밖에서 정의한 변수글로벌 변수(Global variable)라고 합니다. 글로벌 변수는 로컬 변수와 다르게 모든 곳에서 사용이 가능합니다. 즉, 변수 x가 유효한 범위는 프로그램 전체인 것이죠.

따라서, 위 코드를 실행하면 변수 x에 지정된 값 3이 my_function 안의 print(x)에 들어가 3을 출력하고 my_function 아래의 print(x)에도 마찬가지로 3이 들어가 3을 출력합니다.

3
3

이번에는 x를 함수 밖에서도 정의하고 함수 내에서도 정의해보겠습니다.

x = 2

def my_function():
    x = 3
    print(x)
   
my_function()
print(x)

변수는 가장 최근에 지정한 값을 기준으로 정해집니다. 즉, 글로벌 변수가 2로 지정되었다 하더라도 함수 내에서 선언된 변수가 3이라는 값을 갖기 때문에 my_function()의 호출 결과로 3을 먼저 출력합니다. my_function() 아래 print(x)는 글로벌 변수 2의 영향을 받아 2를 출력하게 됩니다. 로컬 변수의 유효 범위가 함수 안이기 때문에 my_function 밖 print(x)에 대해서는 효력을 잃기 때문이죠.

def square(x):
    return x * x
    
print(square(3))

앞서 함수에 파라미터를 넘겨주는 사례를 여러 번 봤었는데요. 파라미터터도 사실 로컬 변수라고 볼 수 있습니다. square(3)에서의 3이라는 값은 오직 square()라는 함수 내에서만 유효하기 때문이죠.

정리하자면 scope란, 변수가 사용 가능한 범위를 말합니다. 정의한 함수 내에서만 사용 가능한 변수는 로컬 변수, 모든 곳에서 사용 가능한 변수를 글로벌 변수라 합니다.

함수에서 변수를 사용하면, 로컬 변수를 먼저 찾고 나서 글로벌 변수를 찾습니다. 앞으로 코딩할 때 scope에 유의해서 사용하시길 바랍니다.

🦋 상수(constant)

학창시절에 수학을 배울 때, 변수에 대응하는 개념으로 상수라는 것을 배웠습니다. 상수란, 변하지 않는 수를 말합니다. 코딩에서 상수가 어떻게 사용되는지 한 번 알아봅시다.

다음 코드는 원의 넓이를 계산합니다.

# 원주율 '파이'
PI = 3.14

# 반지름을 받아서 원의 넓이 계산
def calculate_area(r):
    return PI * r * r    

코드 맨 앞에 PI라는 변수에 3.14를 선언했습니다. 함수 밖에 있는 변수이니 글로벌 변수겠네요.

다음으로 원의 넓이를 계산하는 calculate_area(r) 함수를 정의했습니다. 이 함수는 원의 반지름을 나타내는 r이라는 파라미터를 가지고 있네요. 이 함수를 호출하면 r 값을 받아 주어진 연산을 실행한 뒤 결괏값을 반환합니다.

이제 이 함수를 차례대로 호출 해 보겠습니다.

radius = 1 # 반지름
print("반지름이 {}면, 넓이는 {}".format(radius, calculate_area(radius)))

radius = 2 # 반지름
print("반지름이 {}면, 넓이는 {}".format(radius, calculate_area(radius)))

radius = 3 # 반지름
print("반지름이 {}면, 넓이는 {}".format(radius, calculate_area(radius)))

변수 radius에는 반지름 값이 지정되었습니다. 각각의 변수 아래에는 print()함수를 호출하는데요. 호출할 때마다 위에 정의한 radius 값을 사용해서 결과를 출력합니다.

요약하자면 이 코드에는 변수가 총 2개 있습니다. 하나는 원주율을 나타내는 PI, 다른 하나는 반지름을 나타내는 radius이죠. 그런데 radius는 1, 2, 3으로 값이 계속해서 바뀌는 반면 처음 선언한 pi의 값은 3.14로 고정되어 있습니다. 원주율은 수학에서 정의한 수로 절대 변하지 않습니다. 따라서, 원주율은 상수인죠.

코딩에서 상수의 규칙은 대문자를 사용하는 것입니다. 이는 프로그램 실행에 있어 아무런 영향을 주지 않습니다. 그럼에도 이러한 표기를 사용하는 이유는 코드를 보는 입장에서 이해하기 쉽게 하기 위해서 입니다.

이렇게 하면 일반 변수와 상수를 쉽게 구분할 수 있고 값을 변경하는 실수를 방지할 수 있습니다. 상수로 선언했다는 것은 절대로 수정할 일이 없다는 뜻이니까요. 누가 강제한 것도 아니고 프로그램 상으로도 수정이 가능하지만 상호 간의 약속이기 때문에 지켜야 하는 것입니다.

이런 부분은 매우 사소할 수 있지만 습관을 들여야 좋은 코드를 작성할 수 있기 때문에 익숙해지도록 노력해야 합니다.

🦋 스타일

글을 쓸 때처럼 코딩을 할 때도 스타일이 있습니다. 스타일은 주관적인 영역이긴 하지만 좋은 문장은 정해져 있기 마련입니다. 코딩도 마찬가지인데요. 주로 이해하기 쉬운 코드가 좋은 스타일을 가진 좋은 코드라고 인정받고 있습니다.

이번에는 안 좋은 코드의 사례를 보고 좋은 코드로 고쳐보는 연습을 해봅시다.

print(6.28*2)
print(3.14*2*2)
print(6.28*4)
print(3.14*4*4)

위 코드는 문법적으로 아무런 문제가 없는 코드입니다. 출력값 또한 의도한대로 나오죠. 그럼에도 위 코드는 절대로 좋은 코드라고 볼 수 없습니다.

이 코드를 읽는 입장에서 생각해봅시다. 한 눈에 봤을 때, 어떤 목적으로 쓰였는지 파악이 불가능합니다. 가독성이 떨어지는 굉장히 안 좋은 코드이죠.

이 프로그램의 목적은 원의 둘레와 원의 넓이를 계산하는 것이었습니다. 이 목적이 잘 드러나기 위해서는 코드의 스타일을 개선해야 합니다. 한 단계씩 수정해 볼까요?

x=3.14
y=2
print(2*x*y)
print(x*y*y)
y=4
print(2*x*y)
print(x*y*y)

처음 코드와 차이점은 변수를 사용했다는 것입니다. 처음보다는 낫긴 하지만 여전히 각 변수가 뭘 의미하는 지 알 수 없습니다. 이를 해결하기 위해 코멘트를 넣어보겠습니다.

x=3.14 #원주율(파이)
y=2    #반지름
print(2*x*y)
print(x*y*y)
y=4   #반지름
print(2*x*y)
print(x*y*y)

코멘트가 들어가니 한 눈에 봤을 때는 이해하기 어렵진 않을 겁니다. 그럼에도 불편함이 여전히 존재하는데요. 변수명을 아무렇게나 지었기 때문입니다.

pi=3.14 #원주율(파이)
radius=2    #반지름
print(2*pi*radius)
print(pi*radius*radius)
radius=4   #반지름
print(2*pi*radius)
print(pi*radius*radius)

변수명을 직관적으로 정해주니 이해가 좀 더 빠를 겁니다. 자, 그런데 앞서 pi는 변하지 않는 상수라고 했었죠? 혼선을 방지하기 위해 대문자로 고쳐야 합니다.

PI=3.14 #원주율(파이)
radius=2    #반지름
print(2*PI*radius)
print(PI*radius*radius)
radius=4   #반지름
print(2*PI*radius)
print(PI*radius*radius)

이렇게 하면 PI가 상수라는 것을 누구나 알 수 있겠네요. 여기까지 보면 확실히 처음 코드보다는 많이 나아진 것 같습니다. 그럼에도 여전히 보기 좋은 코드라고 볼 수는 없습니다. 제일 거슬려 보이는 부분은 공백이 없다는 점이네요. 가독성을 높이기 위해 띄어쓰기를 해봅시다.

PI = 3.14 #원주율(파이)

radius = 2    #반지름
print(2 * PI * radius)
print(PI * radius * radius)

radius = 4   #반지름
print(2 * PI * radius)
print(PI * radius * radius)

문단을 나누기 위해 빈 줄을 만들었고 연산자 사이에도 띄어쓰기를 했습니다. 이러한 공백들을 화이트 스페이스라고 합니다. 화이트 스페이스를 활용하면 가독성 좋은 코드를 작성할 수 있습니다.

print안에 있는 연산들이 무엇을 의미하는지 이해하지 못하는 사람들도 분명히 있을 것 같습니다. 이를 알기 쉽게 하기 위해 쓸 수 있는 방법이 있는데요.

PI = 3.14 #원주율(파이)

# 반지름이 r인 원의 둘레 계산
def calculate_circumference(r):
    return 2 * PI * r

# 반지름이 r인 원의 넓이 계산
def calculate_area(r):
    return PI * r * r

radius = 2    #반지름
print(2 * PI * radius)
print(PI * radius * radius)

radius = 4   #반지름
print(2 * PI * radius)
print(PI * radius * radius)

바로 함수를 정의하면서 이 연산이 어떤 것인지 알려주는 방법이죠. 이제 이 공식들에 이름이 붙어있으니 어떤 식을 계산하는지 쉽게 파악할 수 있습니다. 코멘트를 추가하니 좀 더 직관적으로 파악이 가능하겠죠?

이 정도로만 작성하면 훨씬 좋은 코드라고 인정받을 수 있습니다. 가독성이 매우 좋기 때문이죠. 전세계 누가 보더라도 이해하기 쉬운 코드입니다.

참고로 코드 스타일은 나 하나만 보기 편하게 작성하는 것이 아닙니다. 개발에서는 협업이 주로 이루어지기 때문에 함께 작업하는 사람들이 보기 편한 코드가 되어야 하죠. 회사에서는 그 회사만의 스타일 가이드를 제공하곤 합니다.

Python에서 가장 많이 활용되는 스타일 가이드PEP8입니다. 굉장히 오랜 기간 많은 개발자들이 정리해둔 규칙입니다. 이 중 가장 중요하다고 생각하는 부분만 추려서 소개해 드리겠습니다.

🦋 Python 스타일 가이드(PEP8)

❗ 이름

  • 이름 규칙

모든 변수와 함수 이름소문자로 작성
여러 단어일 경우 _(언더바)로 나누기

❌ bad example

SomeVariableName = 1
someVariableName = 1

def someFunctionName():
    print("Hello")

⭕ good example

some_variable_name = 1

def some_function_name():
    print("Hello")

모든 상수 이름은 대문자

❌ bad example

SomeConstant = 3.14
someConstant = 3.14
some_constant = 3.14

⭕ good example

SOME_CONSTANT = 3.14
  • 의미 있는 이름
❌ bad example

a = 2
b = 3.14
print(b * a * a)

⭕ good example

radius = 2
PI = 3.14
print(PI * radius * radius)
❌ bad example

def do_something():
    print("Hello, World!")

⭕ good example

def say_hello():
    print("Hello, World!")

❗ 화이트 스페이스

  • 들여쓰기

    들여쓰기는 무조건 스페이스 4개를 사용

❌ bad example(스페이스 2개)

def do_something():
  print("Hello, World!")

❌ bad example(스페이스 8개)

i = 0
while i < 10:
        print(i)

⭕ good example(스페이스 4개)

def say_hello():
    print("Hello, World!")
  • 함수 정의

    위아래로 빈 줄 두 개씩
    단, 파일의 첫 줄이 함수인 경우, 해당 함수 위에는 빈 줄 X

❌ bad example

def a():
    print('a')
def b():
    print('b')
def c():
    print('c')

⭕ good example

def a():
    print('a')
    
def b():
    print('b')
    
def c():
    print('c')
  • 괄호 안

    괄호 바로 안에는 띄어쓰기 X

❌ bad example

coffee( latte[ 1 ], { cafe_latte: 2 } )

⭕ good example

coffee(latte[1], {cafe_latte: 2})
  • 함수 괄호

    함수를 정의하거나 호출 시, 함수 이름과 괄호 사이에 띄어쓰기 X

❌ bad example

def coffee (x):
    print (x + 2)
    
coffee (1)

⭕ good example

def coffee(x):
    print(x + 2)
    
coffee(1)
  • 쉼표

    쉼표 앞에는 띄어쓰기 X

❌ bad example

print(x , y)

⭕ good example

print(x, y)
  • 지정 연산자

    지정 연산자 앞뒤로 하나씩 띄어쓰기

❌ bad example

x=1
x    = 1

⭕ good example

x = 1
  • 연산자

    연산자 앞뒤로 하나씩 띄어쓰기

❌ bad example

i=i+1
submitted +=1

⭕ good example

i = i + 1
submitted += 1

단, 연산의 "우선순위"를 강조하기 위해 연산자 앞뒤로 붙여쓰기 권장

❌ bad example

x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

⭕ good example

x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
  • 코멘트

    일반 코드와 같은 줄에 코멘트 쓸 때, 코멘트 앞 최소 2개 띄어쓰기

❌ bad example

x = x + 1# 코멘트

⭕ good example

x = x + 1  # 코멘트

이 외에도 더 많은 스타일 가이드를 참고하고 싶다면 원본 링크에 접속해서 확인해 보시길 바랍니다.


지금까지 Python에서 간결하고 깔끔한 코드를 작성하도록 돕는 여러 가지 추상화 개념에 대해 배워봤습니다. 좋은 코드를 작성하는 방법을 익히면 후에 훌륭한 개발자로 인정받을 수 있습니다. 함수의 실행 원리와 스타일 가이드 등을 파악하여 좋은 코드를 쓸 수 있도록 노력해봅시다.

* 이 자료는 CODEIT의 프로그래밍 기초 in Python 강의를 기반으로 작성되었습니다.
profile
There's Only One Thing To Do: Learn All We Can

0개의 댓글