Logo

파이썬의 내장 함수 any() 사용법

이번 포스팅에서는 파이썬에서 하나라도 참인지 확인할 때 사용하는 any() 내장 함수에 대해서 알아보겠습니다.

반복문으로 하나라도 참인지 확인하기

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

예를 들어, 여러 숫자 중에서 하나라도 양의 짝수인지를 확인하는 함수를 다음과 같이 구현할 수 있을 것입니다.

def any_positive_even(nums):
    for num in nums:
        if num > 0 and num % 2 == 0:
            return True
    return False

이 함수에 양의 짝수가 하나라도 들어있는 리스트를 넘겨서 호출하면 True를 반환하겠죠?

any_positive_even([-2, 4, 5, -8]) # True

반면에 인자로 넘긴 리스트에 양의 짝수가 하나도 없으면 False가 반환될 것입니다.

any_positive_even([-2, 3, 5, -8]) # False

사실 별 거 아닌 로직인데 이처럼 반복문을 사용해서 구현하니 코드가 괜히 길어지는 것 같지 않나요? 과연 좀 더 소위 Pythonic, 즉 파이썬🐍스럽게 하나라도 참인지 확인하는 방법은 없을까요? 🧐

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

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

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

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

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

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

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

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

any([False, False, False, False, False]) # False

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

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

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

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

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

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

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

표현식과 함께 활용하기

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

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

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

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

any(num > 0 for num in [-1, -4, 7, 9]) # True (7과 9는 양수)

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

any(num % 2 == 0 for num in [-1, -4, 7, 9]) # True (-4는 짝수)

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

any(num > 0 and num % 2 == 0 for num in [-1, -4, 7, 9]) # False
any(num > 0 and num % 2 == 0 for num in [-1, 4, 7, 9]) # True (4는 양의 짝수)

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

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

any() 함수 들여다보기

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

any([]) # False

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

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

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

위 구현에 따르면 비어있는 리스트나 튜플이 인자로 넘어오면 for 문이 자체가 실행될 일이 없으므로 당연히 False가 반환되겠죠?

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

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

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

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

그리고 제네레이터 표현식을 사용하여 양의 짝수가 하나도 없는 리스트를 대상으로 이 함수를 호출한 결과를 가지고 any() 함수를 호출해보겠습니다.

any(positive_even(num) for num in [-1, -4, 7, 9]) # False

자, 그럼 positive_even() 함수가 총 4번 호출되는 것을 볼 수가 있죠? 양의 짝수가 하나도 없으므로 마지막 원소까지 확인하지 않으면 결과를 얻을 수 없기 때문입니다.

positive_even(-1)
positive_even(-4)
positive_even(7)
positive_even(9)

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

any(positive_even(num) for num in [-1, 4, 7, 9]) # True

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

positive_even(-1)
positive_even(4)

or 연산자와 비교

파이썬의 any() 함수는 or 연산자와 동작 방식이 유사하기 때문에 서로 대체적으로 사용 가능한 경우가 많은데요.

예를 들어, 아래 any() 함수로 작성된 코드는

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

다음과 같이 or 연산자를 사용해서 작성할 수도 있습니다.

False or True or False # True

하지만 or 연산자는 any() 함수처럼 항상 불리언(boolean) 타입의 결과를 반환하지 않습니다.

0 or "TEST" or 1 < 2 # "TEST"
any([0, "TEST", 1 < 2]) # True

그래서 반드시 True 또는 False를 반환해야한다면 any() 함수를 사용하거나, or 연산자의 결과를 불리언으로 형변환을 해줘야 합니다.

bool(0 or "TEST" or 1 < 2) # True

이러한 특징 때문에 or 연산자는 변수에 기본값을 할당해주기 위해서 많이 활용됩니다.

예를 들어, 사용자가 지정한 페이지 번호가 있다면 그 것을 사용하고 아니라면 1을 사용하고 싶다면 다음과 같이 코드를 짜야할텐데요.

DEFAULT_PAGE = 1

def paginate(user_page):
    if user_page:
        page = user_page
    else:
        page = DEFAULT_PAGE
    print(f"{page=}")
paginate(5) # page=5
paginate(None) # page=1

or 연산자를 사용하면 if 조건문 없이 한 줄로 훨씬 깔끔하게 처리할 수 있습니다.

DEFAULT_PAGE = 1

def paginate(user_page):
    page = user_page or DEFAULT_PAGE
    print(f"{page=}")

전체코드

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

https://dales.link/mdv

마치면서

지금까지 파이썬에서 하나라도 참인지를 알아내기 위해서 사용하는 any() 함수에 대해서 살펴보았습니다. 참고로 파이썬에서는 any() 함수와 짝꿍인 all() 함수도 내장하고 있는데요. 이 녀석에 대해서는 관련 포스팅을 참고 바라겠습니다.