펭찐이의 블로그

안녕하세요오오...

펭찐이의 블로그 자세히보기

찐따의 프로그래밍 독학/찐따의 파이썬 독학

찐따의 파이썬 독학 - 문제점과 해결 (2)

펭찐 2022. 3. 30. 21:38
반응형

제발 좀 도와주세요, 코딩 소녀... ㅠㅅㅠ

지난 글

2022.03.28 - [찐따의 프로그래밍 독학/찐따의 파이썬 독학] - 찐따의 파이썬 독학 - 문제점과 해결

 

저번에 이어서 오늘도 내가 파이썬 프로그래밍 독학을 하면서 겪었던 문제들과 그것을 해결하는 것을 적고자 한다.

이 포스팅은 내가 파이썬으로 프로그래밍을 하면서 겪었던 문제점들과 그에 대한 해결법들을 정리해놓은 문서이다.
더 자세한 내용은 그동안 내가 독학하면서 정리해놓은 나의 깃허브(GitHub) 저장소를 참고하면 된다.

 

GitHub - iam-jjintta/python-tutorial: 흔한 찐따의 파이썬 튜토리얼 (Python Tutorial)

흔한 찐따의 파이썬 튜토리얼 (Python Tutorial). Contribute to iam-jjintta/python-tutorial development by creating an account on GitHub.

github.com

 

 

중복되는 변수명

이 문제는 내가 파이썬 프로그래밍을 하다가 가끔씩 함수나 클래스를 작성할 때 혼동하는 경우이다.

파이썬에서는 변수나 모듈, 패키지명을 중복해서 사용하는 경우가 많다.

이를테면, 파이썬 표준 라이브러리인 sys 에 있는 pathos에 있는 path 를 대표적으로 꼽을 수 있겠다.

이것은 내가 예전에 프로그래밍 패러다임을 공부하면서 익혔던 개념이었는데,
이 개념 자체가 그리 어렵지 않은 개념임에도 불구하고 자주 헷갈려서 혼동이 왔던 개념이었다.

 

문제

예를 들어, 아래와 같이 간단한 덧셈 연산을 해주는 함수를 정의한다고 해보자.

일반적으로 생각할 수 있는 코드는 다음과 같다.

 

def add(x, y):
    return x + y

lambda 함수로 정의할 경우, 더 간단하게 만들 수도 있다.

 

lambda x, y: x + y

 

여러 매개 변수를 받아서 처리하는 함수를 작성하고 싶은 경우,
파이썬 표준 라이브러리인 functools 에서 제공하는 reduce 를 사용하여 가변 인자 args 로 받게한 뒤에 다음과 같이 만들 수도 있다.

 

from functools import reduce

def add(*args):
    return reduce(lambda x, y: x + y, args)

 

이것 마저도 코드가 길어져서 복잡하고 귀찮다고 느껴진다면 그냥 내장 함수 sum 을 사용해서 구현할 것이다.

 

def add(*args):
    return sum(args)

 

이제 변수 xy 를 선언한 후에 미리 구현한 함수 add 를 사용해서 변수 xy 의 값을 더해본다.

 

x = 1
y = 1

def add(x, y):
    return x + y

z = add(x, y)
print(z)

 

위의 예시들은 전혀 문제가 없다.
지금부터가 이제 혼동이 생기는 부분이다.

 

# 1번 예시
x = 1

def print_x():
    print('x:', x)

print_x()

 

위의 코드는 변수 x 를 단순히 출력해주는 함수 print_x 를 정의한 것이다.

 

x: 1

 

위의 코드는 에러 없이 정상적으로 잘 동작한다.

 

# 2번 예시
x = 1

def print_x():
    print(x)
    x += 10
    print(x)

print_x()

 

그러나 위의 코드는 에러가 발생한다.

 

Traceback (most recent call last):
  File "C:/Users/iamjjintta/Desktop/공부/test.py", line 8, in <module>
    print_x()
  File "C:/Users/iamjjintta/Desktop/공부/test.py", line 4, in print_x
    print(x)
UnboundLocalError: local variable 'x' referenced before assignment

 

에러의 내용을 해석해보면 "지역 변수 x 가 할당되기 전에 참조되었습니다." 라는 에러이다.

왜 1번 예시의 코드는 에러가 나지 않고 2번 예시에서는 에러가 발생하는 것일까?

이것이 개념이 잘 잡히지 않은 나에게 프로그래밍을 하면서 종종 혼동을 야기한 문제였다.

 

원인

나는 이것의 원인을 분석해보기 위해서
예전에 열심히 독학하면서 내용을 정리했던 문서들을 찾아보았다.

함수에 대해 공부했을때 내가 필기해둔 부분에서 이에 대해 적혀있었다.

 

파이썬에서는 global 키워드와 nonlocal 키워드를 통해서 전역 변수와 지역 변수를 구별한다.

 

우선 나는 global 키워드와 nonlocal 키워드에 대해 살펴보기 전에,
대체 전역 변수와 지역 변수를 나누는 기준이 무엇인지부터 다시 한번 공부해보기로 했다.

파이썬에서는 이름 공간이라는 개념이 있다.

이 개념은 파이썬 공식 문서의 용어집에서 namespace (이름 공간) 항목에 이에 대한 내용이 서술되어 있었다.

 

파이썬 공식 문서의 용어집에 따르면 이름 공간에 대해 다음과 같이 정의하고 있다.

 

  • 이름 공간이란, 변수가 저장되는 장소를 의미한다.
  • 이름 공간은 딕셔너리( dict )로 구현되어 있다.
  • 객체에 중첩된 이름 공간 (메서드 에서) 뿐만 아니라 지역, 전역, 내장 이름 공간이 있다.
  • 이름 공간은 이름 충돌을 방지해서 모듈성을 지원한다.
  • 예를 들어, 함수 builtins.openos.open() 은 그들의 이름 공간에 의해 구별된다.
  • 또한, 이름 공간은 어떤 모듈이 함수를 구현하는지를 분명하게 만들어서 가독성과 유지 보수성에 도움을 준다.

 

즉, 파이썬에서는 이름 공간을 지원하는데,
공간이라는 특징을 가졌기 때문에 변수가 저장되는 곳을 의미하며,
변수의 영향력을 미치는 범위를 의미하는 것이다.

그래서 나는 global 키워드와 local 키워드에 대해 좀 더 살펴보기로 했다.

먼저, 2번 예시 코드에서 global 키워드를 사용해봤다.

 

x = 1

def print_x():
    global x
    print(x)
    x += 10
    print(x)

print_x()

 

위의 코드를 실행하면 이제 정상적으로 작동한다.

 

1
11

 

그리고 nonlocal 키워드를 사용해보았다.

 

x = 1

def print_x():
    nonlocal x
    print(x)
    x += 10
    print(x)

print_x()

 

이 코드는 문법적으로 아예 틀렸다는 에러인 SyntaxError 에러가 발생해서 실행조차 되지 않는다.

왜냐하면 x 가 애시당초 지역 변수로 선언된 적이 없기 때문이다.

이렇게만 이해하고 나니 도저히 감이 잡히지 않았다.
그래서 아래의 코드를 직접 코드를 작성해서 실행시켜 보았다.

 

x = 1

def f():
    x = 2

    def g():
        nonlocal x
        print(x)
        x += 10
        print(x)

    g()

f()

 

뭔가 복잡해 보이는 위의 코드를 실행시켜 본다면 아래와 같은 결과를 확인할 수 있다.

 

2
12

 

위의 코드는 전역 변수에 선언된 x 와 내부 함수 f 에 선언된 변수 x 가 있다.

이 변수 x 라는 명칭이 서로 중복되어 사용되고 있음을 알 수 있는데, 이때 지역 변수가 아니라는 것을 명시해주는 키워드가 바로 nonlocal 키워드이다.

뿐만 아니라, nonlocal 키워드는 전역 변수에 영향을 미치지 못하도록 하기 때문에 처음과 같이 사용할 경우 에러가 발생하는 것이다.

따라서 아래처럼 사용할 경우에도 에러가 발생한다.

 

x = 1

def f():
    # 전역 변수 'x'로써 사용할 것임을 명시
    global x
    x = 2

    def g():
        # 함수 'f'에 선언된 변수 'x'는 지역 변수가 아님
        # 전역 변수에 영향을 미치지 못하므로, 에러 발생
        nonlocal x
        print(x)
        x += 10
        print(x)

    g()

f()

 

즉, global 키워드와 nonlocal 키워드의 의미는 다음과 같다.

 

  • global 키워드는 global 키워드로 선언된 변수가 전역 변수라는 것을 알려주는 키워드이다.
  • nonlocal 키워드는 nonlocal 로 선언된 변수가 지역 변수가 아님을 알려주는 키워드이다.

 

위의 키워드들은 모두 파이썬에서의 이름 공간이라는 개념 때문에 탄생한 키워드라고 볼 수 있다.

프로그래밍을 하다보면 같은 이름 공간에 정의된 변수명이 서로 중첩될 수 있기 때문에 이를 구분하고자 하는 키워드라고 할 수 있다.

 

해결

이를 해결하는 방법은 프로그래머가 애초에 중첩되지 않도록 변수명을 잘 생각해서 지어야 하며,
프로그래머가 함수를 설계할 때 제대로 설계해야 하는 것이다.

이는 다시 말해, global 키워드와 nonlocal 키워드를 쓰지 않는 것이 좋다는 의미가 된다.

다시 한번 아래의 코드를 확인해보자.

 

x = 1

def f():
    # 전역 변수 'x'로써 사용할 것임을 명시
    global x
    x = 2

    def g():
        # 함수 'f'에 선언된 변수 'x'는 지역 변수가 아님
        # 전역 변수에 영향을 미치지 못하므로, 에러 발생
        nonlocal x
        print(x)
        x += 10
        print(x)

    g()

f()

 

위의 코드는 예시로 사용된 코드임을 감안하더라도,
과연 보기 좋은 코드라고 할 수 있을까?

파이썬 용어집에서도 명시되어 있는 이름 공간의 존재 의의가 퇴색되어 버린다.

 

또한, 이름 공간은 어떤 모듈이 함수를 구현하는지를 분명하게 만들어서 가독성과 유지 보수성에 도움을 준다.

 

분명 가독성과 유지 보수성에 도움을 주도록 설계된 것인데,
되려 가독성을 망치고 유지 보수성도 나빠지게 되는 셈이다.

게다가 함수는 무언가에 의존되어서 사용되기 시작하면 코드가 굉장히 난잡해지고 복잡해진다.
위의 코드같은 경우, 전역 변수 x 에 의존성을 띄고 있다는 것을 알 수 있다.

따라서 함수는 함수 그 자체로만 독립적으로 운용될 수 있도록 순수 함수(pure function)로써의 의미를 퇴색시켜서는 안 된다.

 

그래서 결론은 다음과 같다.

 

  • global 키워드와 nonlocal 키워드를 가급적 사용하지 않을 것
  • 함수는 독립적으로 설계할 것 (즉, 순수 함수로 만들 것)
  • 변수명은 잘 생각해서 지을 것
  • 만약 어쩔 수 없이 변수명을 중복해서 사용해야 하는 경우라면, 파이썬의 언더바 _ 기호로 맹글링 기법을 활용할 것

 

참고

반응형