Programing

"if x : return x"문을 피하는 파이썬 방식

lottogame 2020. 4. 25. 09:49
반응형

"if x : return x"문을 피하는 파이썬 방식


특정 조건을 확인하기 위해 4 개의 다른 메소드를 순서대로 호출하고 Truthy를 반환 할 때마다 즉시 (다음 중 하나를 확인하지 않음) 반환하는 메소드가 있습니다.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

이것은 많은 수하물 코드처럼 보입니다. 각 2 줄 if 문 대신 다음과 같이하십시오.

x and return x

그러나 그것은 잘못된 파이썬입니다. 여기에 간단하고 우아한 솔루션이 누락 되었습니까? 또한이 상황에서 4 가지 검사 방법이 비쌀 수 있으므로 여러 번 호출하고 싶지 않습니다.


루프를 사용할 수 있습니다 :

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

이는 이제 조건 수를 변수로 만들 수 있다는 이점이 있습니다.

당신은 사용할 수 map()+ filter()(사용, 파이썬 3 버전을 future_builtins버전 최초의 일치하는 값을 얻기 위해 파이썬 2 일) :

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

그러나 이것이 더 읽기 쉬운 것은 논쟁의 여지가 있습니다.

또 다른 옵션은 생성기 표현식을 사용하는 것입니다.

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)

Martijn의 정답 대신 체인을 연결할 수 or있습니다. 첫 번째 진실 된 값을 반환하거나 None진실 된 가치가없는 경우 :

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

데모:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True

바꾸지 마

다양한 다른 답변이 보여주는 것처럼이 작업을 수행하는 다른 방법이 있습니다. 원래 코드만큼 명확한 것은 없습니다.


효과적으로 timgeb와 같은 대답이지만 더 나은 형식을 위해 괄호를 사용할 수 있습니다.

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

Curly의 법칙 에 따르면 두 가지 우려를 나누면이 코드를 더 읽기 쉽게 만들 수 있습니다.

  • 무엇을 확인해야합니까?
  • 한 가지 사실이 돌아왔습니까?

두 가지 기능으로

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

이것은 피합니다 :

  • 복잡한 논리 구조
  • 정말 긴 줄
  • 되풀이

... 선형적이고 읽기 쉬운 흐름을 유지하면서.

특정 상황에 따라 더 나은 기능 이름을 얻을 수도 있습니다.


이것은 Martijns의 첫 번째 예의 변형입니다. 또한 단락을 허용하기 위해 "콜 러블 컬렉션"스타일을 사용합니다.

루프 대신 내장을 사용할 수 있습니다 any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

any부울 반환하므로 확인의 정확한 반환 값이 필요한 경우이 솔루션이 작동하지 않습니다. any구분하지 않습니다 14, 'red', 'sharp', 'spicy'반환 값으로, 그들은 모두 반환됩니다 True.


if x: return x한 줄에 모두 쓰는 것을 고려해 보셨습니까 ?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

이것은 당신이 가진 것보다 반복적 이지 는 않지만 IMNSHO는 꽤 부드럽게 읽습니다.


any이 목적을 위해 만들어진 내장 기능을 언급 한 사람이 아무도 없습니다 .

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

이 구현은 아마도 가장 분명하지만 첫 번째 검사가하더라도 모든 검사를 평가합니다 True.


첫 번째 실패한 검사에서 실제로 중지해야하는 경우 reduce목록을 간단한 값으로 변환하기 위해 사용 되는 것을 사용하십시오 .

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): 반복 가능한 항목에 왼쪽에서 오른쪽으로 두 인수의 함수를 누적 적용하여 반복 가능한 값을 단일 값으로 줄입니다. 왼쪽 인수 x는 누적 값이고 오른쪽 인수 y는 iterable의 업데이트 값입니다. 선택적 이니셜 라이저가 있으면 계산에서 이터 러블 항목 앞에 배치됩니다.

귀하의 경우 :

  • lambda a, f: a or f()검사 어큐뮬레이터 어느 것이 그 함수 a또는 현재 체크 f()된다 True. 경우에 유의 a하다 True, f()평가되지 않습니다.
  • checks점검 기능 포함 ( f람다 항목)
  • False 초기 값입니다. 그렇지 않으면 검사가 수행되지 않으며 결과는 항상 True

anyreduce기능 프로그래밍을위한 기본적인 도구이다. 나는 당신이 이것들을 훈련시키는 것도 강력히 권장합니다 map.


동일한 코드 구조를 원한다면 삼항 문장을 사용할 수 있습니다!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

나는 당신이 그것을 보면 잘 보이고 분명하다고 생각합니다.

데모:

실행중인 스크린 샷


나에게 가장 좋은 대답은 @ phil-frost의 @ wayne-werner 's입니다.

내가 흥미로운 점은 함수가 많은 다른 데이터 유형을 반환한다는 사실에 대해 아무도 말하지 않았기 때문에 추가 작업을 수행하기 위해 x 자체의 유형을 확인해야한다는 것입니다.

따라서 @PhilFrost의 응답을 단일 유형을 유지한다는 아이디어와 혼합합니다.

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

공지 사항 x또한 인수로 전달되지만,이 all_conditions모두가 얻을 어디에 기능을 확인하는 통과 발전기로 사용되는 x확인하고, 반환 True또는 False. 사용하여 funcall_conditions같은 기본 값, 당신은 사용할 수 있습니다 assessed_x(x), 또는 당신은을 통해 더욱 개인화 된 발전기를 전달할 수 있습니다 func.

이렇게하면 x한 번의 확인이 통과 되더라도 항상 같은 유형이됩니다.


이상적으로는 check_반환 True이나 False값이 아닌 함수를 다시 작성합니다 . 그런 다음 수표가됩니다

if check_size(x):
    return x
#etc

당신 x이 불변이 아니라고 가정하면 , 함수는 여전히 그것을 재 할당 할 수는 없지만 수정할 수는 있지만 호출 된 함수는 check실제로 수정해서는 안됩니다.


위의 Martijns 첫 번째 예제에서 약간의 변형으로 루프 내부의 if를 피할 수 있습니다.

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status

이 방법은 상자 밖에서는 약간이지만 최종 결과는 간단하고 읽기 쉽고 멋지게 보입니다.

기본 아이디어는 raise함수 중 하나가 진실로 평가되고 결과를 반환 할 때 예외입니다. 다음과 같이 보일 수 있습니다.

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

assertFalsey호출 된 함수 인수 중 하나가 진실로 평가 될 때 예외를 발생 시키는 함수가 필요합니다 .

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

위의 내용은 평가할 기능에 대한 인수를 제공하도록 수정 될 수 있습니다.

그리고 물론 당신은 TruthyException그 자체 가 필요합니다 . 이 예외는 예외 object를 트리거 한 것을 제공합니다 .

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

물론 원래 기능을 좀 더 일반적인 것으로 바꿀 수 있습니다.

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

if명령문을 사용 하고 예외를 처리 하므로 약간 느려질 수 있습니다 . 그러나 예외는 최대 한 번만 처리되므로 확인을 실행하고 True수천 번 값을 얻을 것으로 예상하지 않는 한 성능에 대한 적중은 적어야합니다 .


pythonic 방법은 reduce (이미 언급 한 것처럼) 또는 itertools (아래에 표시된 것처럼)를 사용하지만 연산자의 단락을 사용 or하면 더 명확한 코드가 생성 되는 것으로 보입니다.

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

나는 @timgeb를 좋아한다. 그 동안 내가 표현하는 것을 추가 할 Nonereturn문 것은의 컬렉션으로 필요하지 않습니다 or분리 문이 평가하는 첫 번째 없음 제로는, 아무도 비어가 없음 - 없음 반환 된 후 어떤 존재하지 않는 경우 None반환됩니다 유무에 관계없이 None!

check_all_conditions()기능은 다음과 같습니다.

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

timeit함께 사용 하면서 number=10**7여러 제안의 실행 시간을 보았습니다. 비교를 위해 방금 random.random()문자열을 반환하거나 None임의의 숫자를 기반으로 함수를 사용했습니다 . 전체 코드는 다음과 같습니다.

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

결과는 다음과 같습니다.

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031

나는 여기서 뛰어 들어 한 줄의 Python을 작성하지 않았지만 if x = check_something(): return x유효 하다고 가정 합니다.

그렇다면:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None

또는 사용 max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None

나는 과거에 dicts로 switch / case 문을 흥미롭게 구현 하여이 답변을 얻었습니다. 제공 한 예제를 사용하면 다음을 얻을 수 있습니다. (광기 using_complete_sentences_for_function_names이므로 check_all_conditions이름이로 변경됩니다 status. (1) 참조)

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

select 함수는 각각 check_FUNCTION두 번 호출 할 필요가 없습니다. 즉, check_FUNCTION() if check_FUNCTION() else next다른 함수 레이어를 추가하여 피할 수 있습니다 . 장기 실행 기능에 유용합니다. dict의 람다는 while 루프까지 값 실행을 지연시킵니다.

보너스로, 실행 횟수를 수정 k하고 테스트 수를 변경 하고 s예를 들어 k='c',s={'c':'b','b':None}원래 처리 순서를 반대로하여 일부 테스트를 건너 뛸 수도 있습니다 .

timeit동료는 찾아 볼하지만 당신은 더 코드의 귀여움에 관심을 보인다 별도의 층 또는 스택 2 개, DICT에 대한 비용을 추가 비용을 통해 흥정 수 있습니다.

또는 더 간단한 구현은 다음과 같습니다.

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. 나는 이것을 pep8이 아니라 문장 대신 하나의 간결한 설명 단어를 사용한다는 의미입니다. OP가 일부 코딩 규칙에 따라 기존 코드베이스 중 하나를 작동하거나 코드베이스의 간결한 용어를 신경 쓰지 않을 수 있습니다.

참고 URL : https://stackoverflow.com/questions/36117583/pythonic-way-to-avoid-if-x-return-x-statements

반응형