Logo

파이썬의 내장 함수: all()

이번 포스팅에서는 파이썬에서 모든 것이 참인지 확인할 때 사용하는 all() 함수에 대해서 알아보겠습니다.

반복문으로 모두 참인지 확인하기

여러 개의 데이터가 모두 어떤 조건을 만족하는지 확인을 해야할 때 보통 어떻게 접근을 하시나요? 많은 분들이 자연스럽게 반복문을 떠올릴 것 같은데요.

예를 들어, 여러 숫자가 모두 양의 짝수인지 확인하는 함수를 다음과 같이 구현할 수 있을 것입니다.

def all_positive_even(nums):
    for num in nums:
        # 음수 확인
        if num <= 0:
            return False
        # 짝수 확인
        if num % 2 == 1:
            return False
    return True

이 함수에 양의 짝수로만 이루어진 리스트를 넘겨서 호출하면 True를 반환하겠죠?

all_positive_even([2, 4, 6, 8]) # True

반면에 인자로 넘긴 리스트에 음수가 하나라도 있다면 False가 반환될 것입니다.

all_positive_even([2, -4, 6, 8]) # False

인자로 넘긴 리스트에 홀수가 하나라도 있어도 False가 반환될 테지요.

all_positive_even([2, 4, 7, 8]) # False

사실 별 거 아닌 로직인데 이처럼 반복문을 사용해서 구현하니 코드가 괜히 길어지는 것 같지 않나요? 과연 파이썬🐍답게 모두 참인지 확인하는 방법은 없을까요? 🧐

all() 내장 함수 사용해보기

파이썬에서는 굳이 반복문이 없이도 모든 것이 참인지를 확인할 수 있도록 all()이라는 내장 함수를 제공하고 있습니다.

all() 함수는 반복문으로 순회할 수 있는(iterable) 모든 객체를 인자로 받을 수 있는데요. 쉽게 말해 여러 데이터를 담을 수 있는 리스트(list), 터플(tuple)과 같은 파이썬에서 흔히 볼 수 있는 자료구조를 생각하시면 됩니다.

엄밀히 얘기하면 여러 데이터를 제공할 수 있는 이터레이터(iterator)나 제너레이터(generator)도 인자로 넘길 수 있지만 이 부분은 본 포스팅에서 다루고자하는 범위에서 벗어나는 고급 주제이므로 나중에 기회가 되면 다뤄보도록 하겠습니다.

기본적으로 all() 함수는 인자로 넘어온 자료 구조 내의 모든 요소가 참일 때만 True를 반환합니다. 이 말은 인자로 넘어온 자료 구조 내에 거짓인 요소가 하나라도 있으면 False를 반환한다는 뜻이기도 하지요.

그럼, 가장 단순한 예제로 설명을 해보겠습니다.

아래와 깉이 True만을 담고있는 리스트를 all() 함수에 인자로 넘기면 True를 반환합니다.

all([True, True, True, True, True]) # True

아래 리스트는 두 번째 요소로 False를 포함하고 있기 때문에 all() 함수에 인자로 넘기면 False를 반환합니다.

all([True, False, True, True, True]) # False

여기서 인자로 넘기는 자료구조가 담고 있는 모든 요소가 구지 불리언 자료형(boolean type)일 필요는 없습니다. 불리언 자료형이 아닌 값(value)이나 식(expression)으로 되어 있는 요소는 all() 함수가 내부적으로 불리언으로 형변환을 해서 참/거짓 여부를 판단하기 때문입니다.

예를 들어, 다음 리스트에 담겨 있는 요소들은 모두 불리언으로 형변환을 하면 True가 되기 때문에 all() 함수는 True를 반환합니다.

all([1, "TEST", 1 < 2, 2 + 3 == 5]) # True

반면에 인자로 넘어온 리스트나 터플이 불리언으로 형변환을 했을 때 False가 될 수 있는 값이나 식을 하나라도 포함하고 있다면 all() 함수는 False를 반환하게 됩니다.

all([0, "", {}, [], None, 1 > 2, 2 + 3 == 4]) # False

표현식과 함께 활용하기

지금까지 all() 함수를 사용하는 기본적인 방법에 대해서 살펴보았는데요. 사실 이렇게 단순한 리스트나 터플을 인자로 넘겨서는 all() 진가를 느끼기가 어려울 수도 있어요.

왜냐하면 파이썬에서는 모든 것이 결국 객체이고 대부분의 객체는 불리언으로 형변환을 하면 참으로 판단되기 때문입니다.

하지만 all() 함수의 인자로 리스트 표현식(list comprehension)이나 제너레이터 표현식(generator comprehension)을 넘기면 어떨까요?

예를 들어, 리스트 안에 숫자가 모두 양수인지 확인하는 코드를 all() 함수와 제너레이터 표현식을 이용해서 작성해볼께요.

all(num > 0 for num in [1, 2, 4, 8]) # True

마찬가지는 방법으로 이번에는 리스트 안에 숫자가 모두 짝수인지를 확인하는 코드를 작성해볼까요?

all(num % 2 == 0 for num in [1, 2, 4, 8]) # False (1은 홀수)

그러면 우리가 맨 처음에 반복문으로 작성했던 모든 숫자가 양의 짝수인지를 확인하는 코드는 어떻게 짤 수 잇을까요?

all(num > 0 and num % 2 == 0 for num in [2, 4, 6, 8]) # True

캬~ 어떤가요? 코드가 훨씬 간결하고 알아보기 쉬워지지 않았나요?

이렇게 파이썬의 🌸이라 불리는 리스트 표현식이나 제너레이터 표현식과 함께 all() 함수를 사용하면 무궁무진한 활용 가능성이 열리게 됩니다!

파이썬의 제너레이터에 대한 자세한 설명은 관련 포스팅를 참고바랍니다.

all() 함수 들여다보기

혹시 all() 함수에 빈 리스트나 빈 터플을 넘기면 무엇이 반환될 지 생각해보셨나요? 뭐, 직접 호출을 해볼까요? True가 반환이 되네요!

all([]) # True

all() 함수를 처음 사용하실 때 이 부분이 상당히 햇갈릴 수가 있는데요. 사실 이러한 현상은 all() 내장 함수가 내부적으로 어떻게 구현되었는지를 알면 충분히 예상할 수가 있답니다.

파이썬 공식 문서를 확인해보면 all() 함수는 다음과 같은 방식으로 구현이 되어있습니다.

def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True

위 구현에 따르면 비어있는 리스트나 터플이 인자로 넘어오면 for 문이 자체가 실행될 일이 없으므로 당연히 True가 반환되겠죠? 따라서 all() 함수에 대해 생각하실 때, 모든 것이 참일 때 True를 반환한다고 사고하시는 것보다는, 하나라도 거짓이면 False를 반환한다라고 사고하시는 것이 혼선을 줄이는데 도움이 될 거에요.

이러한 all() 함수가 내부적으로 어떤 식으로 구현되었는지 깨닫고 나면 또 다른 중요한 특징도 알아챌 수가 있는데요. 바로 루프를 돌다가 거짓으로 판별되는 요소가 있다면 나머지 요소에 대한 체크를 생략하고 즉시 False를 반환할 거라는 것입니다.

프로그래밍 언어론에서는 이러한 성능 최적화 기법을 소위 단락 평가(short-circuit evaluation)라고도 하죠? 주어진 데이터 중에서 하나라도 거짓이라면 어차피 최종 결과는 False가 되기 때문에 상당히 합리적인 구현이라고 볼 수 있겠네요.

이 현상을 관찰해보기 위해서 숫자가 양의 짝수인지를 판별하는 함수를 하나 작성하고 해당 함수가 어떤 인자와 함께 호출되었는지 출력하도록 해볼께요.

def positive_even(num):
    print(f"positive_even({num})")
    return num > 0 and num % 2 == 0

그리고 제네레이터 표현식을 사용하여 3번째 요소가 홀수인 리스트를 대상으로 이 함수를 호출한 결과를 가지고 all() 함수를 호출해보겠습니다.

all(positive_even(num) for num in [2, 4, 7, 8]) # False

자, 그럼 positive_even() 함수가 총 3번만 호출되는 것을 볼 수가 있죠? 이를 통해 all() 함수는 양의 짝수가 아닌 7을 만나자 마자 바로 False를 반환했다는 것을 알 수 있습니다.

positive_even(2)
positive_even(4)
positive_even(7)

이번에는 동일한 방법으로 2번째 요소가 음수인 리스트를 가지고 같은 실험을 해보겠습니다.

all(positive_even(num) for num in [2, -4, 6, 8]) # False

마찬가지로 all() 함수는 양의 짝수가 아닌 -4를 만나자 마자 바로 False를 반환하므로 이번에는 positive_even() 함수가 총 2번만 호출이 되었다는 것을 확인할 수 있네요.

positive_even(2)
positive_even(-4)

전체코드

본 포스팅에서 제가 작성한 코드는 아래에서 직접 확인하고 실행해보실 수 있습니다.

https://deepnote.com/project/Blog-Yd3-DsV_QeGqo4AUZ7FyHg/%2Fpython-all.ipynb

마치면서

파이썬에서는 all() 함수와 짝꿍인 any() 함수도 내장하고 있는데요. 이 녀석도 함께 알아두면 큰 도움이 될 것 같으니 다음 포스팅에서 다루도록 하겠습니다!