Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 616 – 접두사와 접미사를 제거하는 문자열 메서드

Author:
Dennis Sweeney <sweeney.dennis650 at gmail.com>
Sponsor:
Eric V. Smith <eric at trueblade.com>
Status:
Final
Type:
Standards Track
Created:
19-Mar-2020
Python-Version:
3.9
Post-History:
20-Mar-2020

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이는 Python의 다양한 문자열 객체 API에 두 가지 새 메서드인 removeprefix()removesuffix()를 추가하자는 제안입니다. 이러한 메서드는 문자열에 접두사 또는 접미사가 있는 경우 각각 이를 제거하며, 유니코드 str 객체, 이진 bytesbytearray 객체, collections.UserString에 추가됩니다.

근거

기존 str.lstripstr.rstrip메서드에 대한 사용자의 혼란과 관련하여 Python-Ideas [2] [3], Python-Dev [4] [5] [6] [7], 버그 트래커 및 StackOverflow [8]에서 문제가 반복적으로 제기되었습니다. 이러한 사용자는 일반적으로 removeprefixremovesuffix의 동작을 기대하지만, lstrip의 매개변수가 부분 문자열이 아니라 문자 집합으로 해석된다는 점에 놀랍니다. 이 문제가 반복된다는 사실은 이러한 메서드가 유용하다는 증거입니다. 새 메서드를 사용하면 사용자를 원하는 동작으로 더욱 깔끔하게 안내할 수 있습니다.

이러한 메서드의 유용성을 보여 주는 또 다른 증언으로, Python-Ideas [2]의 여러 사용자가 생산성을 위해 자신의 코드에 유사한 함수를 자주 포함한다고 보고했습니다. 구현에는 빈 문자열 처리와 관련된 미묘한 실수가 자주 포함되었으므로, 충분히 테스트된 내장 메서드가 유용할 것입니다.

원하는 동작을 만드는 기존 방법은 아래의 Specification과 같이 메서드를 구현하거나, re.sub('^' + re.escape(prefix), '', s)와 같은 정규 표현식을 사용하는 것입니다. 후자는 발견하기 어렵고, 모듈을 가져와야 하며, 코드의 가독성도 떨어집니다.

사양

내장 str 클래스에는 두 가지 새 메서드가 추가되며, type(self) is type(prefix) is type(suffix) is str인 경우 다음과 같이 동작합니다.:

def removeprefix(self: str, prefix: str, /) -> str:
    if self.startswith(prefix):
        return self[len(prefix):]
    else:
        return self[:]

def removesuffix(self: str, suffix: str, /) -> str:
    # suffix='' should not call self[:-0].
    if suffix and self.endswith(suffix):
        return self[:-len(suffix)]
    else:
        return self[:]

인수가 str 하위 클래스의 인스턴스인 경우, 해당 인수들을 먼저 기본 str 객체로 강제 변환한 것처럼 메서드가 동작해야 하며, 반환값은 항상 기본 str이어야 합니다.

이에 상응하는 의미 체계를 갖는 메서드가 내장 bytesbytearray 객체에 추가됩니다. bbytes 또는 bytearray 객체 중 하나인 경우, b.removeprefix()b.removesuffix()는 인수로 모든 바이트 유사 객체를 허용합니다. 두 메서드는 유사한 동작으로 collections.UserString에도 추가됩니다.

Python 표준 라이브러리에서의 동기를 부여하는 예

다음 예는 제안된 메서드를 사용하면 코드가 다음 특성 중 하나 이상을 갖게 될 수 있음을 보여 줍니다.

  1. 더 견고함:

    코드는 리터럴의 길이를 사용자가 세는 데 의존하지 않습니다.

  2. 더 뛰어난 성능:

    코드는 Python 내장 len함수를 호출할 필요가 없으며, 더 비용이 큰 str.replace()메서드도 필요로 하지 않습니다.

  3. 더 명확한 표현:

    이 메서드는 기존의 문자열 슬라이싱 방식보다 코드 가독성을 위해 더 높은 수준의 API를 제공합니다.

find_recursionlimit.py

  • 현재:
    if test_func_name.startswith("test_"):
        print(test_func_name[5:])
    else:
        print(test_func_name)
    
  • 개선:
    print(test_func_name.removeprefix("test_"))
    

deccheck.py

이는 접두사만 제거하려는 상황에서 작성자가 str.replace메서드를 사용하기로 선택했다는 점에서 흥미로운 사례입니다.

  • 현재:
    if funcname.startswith("context."):
        self.funcname = funcname.replace("context.", "")
        self.contextfunc = True
    else:
        self.funcname = funcname
        self.contextfunc = False
    
  • 개선됨:
    if funcname.startswith("context."):
        self.funcname = funcname.removeprefix("context.")
        self.contextfunc = True
    else:
        self.funcname = funcname
        self.contextfunc = False
    
  • 더 개선되었다고 할 수도 있음:
    self.contextfunc = funcname.startswith("context.")
    self.funcname = funcname.removeprefix("context.")
    

cookiejar.py

  • 현재:
    def strip_quotes(text):
        if text.startswith('"'):
            text = text[1:]
        if text.endswith('"'):
            text = text[:-1]
        return text
    
  • 개선됨:
    def strip_quotes(text):
        return text.removeprefix('"').removesuffix('"')
    

test_i18n.py

  • 현재:
    creationDate = header['POT-Creation-Date']
    
    # peel off the escaped newline at the end of string
    if creationDate.endswith('\\n'):
        creationDate = creationDate[:-len('\\n')]
    
  • 개선됨:
    creationDate = header['POT-Creation-Date'].removesuffix('\\n')
    

표준 라이브러리에는 이와 같은 예가 더 많이 있습니다.

거부된 아이디어

lstrip 및 rstrip API를 확장하십시오

lstrip은 문자열을 인자로 받으므로, 길이가 1인 문자열의 이터러블을 받는 것으로 볼 수 있습니다. 따라서 API를 일반화하여 모든 문자열 이터러블을 허용하고, 해당 문자열을 접두사로서 차례로 제거할 수 있습니다. 이 동작은 일관되지만, 단일 접두사를 사용하는 일반적인 경우에 사용자가 'foobar'.lstrip(('foo',))를 호출해야 한다는 점은 분명하지 않을 수 있습니다.

접두사의 여러 복사본 제거

이는 앞서 언급한 lstrip/rstrip API 확장과 일관되는 동작으로, 인자가 변경되지 않을 때까지 함수를 반복해서 적용합니다. 이 동작은 다음과 같은 방식으로 제안된 동작을 통해 달성할 수 있습니다.:

>>> s = 'Foo' * 100 + 'Bar'
>>> prefix = 'Foo'
>>> while s.startswith(prefix): s = s.removeprefix(prefix)
>>> s
'Bar'

찾지 못한 경우 예외 발생

s.removeprefix(pre)not s.startswith(pre)인 경우 예외를 발생시켜야 한다는 제안이 있었습니다. 그러나 이는 다른 문자열 메서드의 동작 및 사용감과 일치하지 않습니다. required=False 키워드를 추가할 수도 있지만, 이는 KISS 원칙에 위배됩니다.

접미사 튜플 허용

위의 test_concurrent_futures.py 예를 name.removesuffix(('Mixin', 'Tests', 'Test'))로 작성하면 편리할 수 있으므로, 새로운 메서드가 startswith() API와 유사하게 문자열 튜플을 인자로 받을 수 있어야 한다는 제안이 있었습니다. 튜플 내에서는 일치하는 첫 번째 접미사만 제거됩니다. 다음과 같은 이유로 이 제안은 거부되었습니다.

  • 특히 한 접두사가 비어 있거나 다른 접두사의 부분 문자열인 경우, 예를 들어 'FooBar'.removeprefix(('', 'Foo')) == 'FooBar' 또는 'FooBar text'.removeprefix(('Foo', 'FooBar ')) == 'Bar text'와 같이 이 동작은 예상 밖이거나 시각적으로 혼란스러울 수 있습니다.
  • str.replace() API는 대체 문자열 한 쌍만 허용하지만, 모호한 다중 대체 상황에서 추측하려는 유혹을 거부함으로써 오랜 시간 동안 그 가치를 입증해 왔습니다.
  • 향후에는 이러한 기능에 대한 설득력 있는 사용 사례가 생길 수 있지만, 기본 기능이 실제 환경에서 사용되기 전에 일반화하면 영구적으로 잘못 설계하기 쉽습니다.

대체 메서드 이름

몇 가지 대체 메서드 이름이 제안되었습니다. 아래에 그중 일부와 함께, removeprefix를 채택하고 removesuffix에도 동일한 주장을 적용해야 하는 이유에 대한 설명을 제시합니다.

  • ltrim, trimprefix 등:

    다른 언어(예: JavaScript, Java, Go, PHP)에서 “Trim”은 Python에서 strip 메서드가 하는 일을 합니다.

  • lstrip(string=...)

    이렇게 하면 새 메서드를 추가하지 않아도 되지만, 동작이 다르다면 동작을 선택하는 키워드 인자를 받는 하나의 메서드보다 서로 다른 두 메서드를 두는 편이 낫습니다.

  • remove_prefix:

    문자열 API의 다른 모든 메서드(예: str.startswith())는 lower_case_with_underscores보다 lowercase를 사용합니다.

  • removeleft, leftremove, 또는 lremove:

    “prefix”를 명시적으로 드러내는 방식이 선호됩니다.

  • cutprefix, deleteprefix, withoutprefix, dropprefix 등:

    이러한 이름 중 상당수도 허용 가능했을 수 있지만, “remove”는 모호하지 않으며 영어로 “remove the prefix” 동작을 설명하는 방식과 일치합니다.

  • stripprefix:

    사용자는 “strip”이 문자 집합을 대상으로 작업한다는 뜻인 반면 다른 메서드는 부분 문자열을 대상으로 작업한다는 점을 기억하면 도움이 될 수 있으므로, 여기에서 “strip”을 재사용하는 것은 피해야 합니다.

이것을 가르치는 방법

partition(), startswith(), 및 split() 문자열 메서드나 enumerate() 또는 zip() 내장 함수를 사용하는 경우에 공통적으로 드러나는 주제는, 초보자가 문자열을 수동으로 인덱싱하거나 슬라이싱하고 있다면 코드가 단지 어떻게 작업을 수행해야 하는지가 아니라 무엇을 수행해야 하는지를 더 잘 전달하는 상위 수준의 메서드가 있는지 고려해야 한다는 점입니다. 제안된 removeprefix()removesuffix() 메서드는 상위 수준 문자열 “도구 상자”를 확장하며, 수동 슬라이싱에 대해 이러한 종류의 재고를 더 가능하게 합니다.

사용자가 혼동할 주된 여지는 lstrip/rstripremoveprefix/removesuffix를 혼동하는 것입니다. 따라서 (문서에서도 그렇게 하듯이) 이 메서드들 간의 다음과 같은 차이점을 강조하는 것이 도움이 될 수 있습니다:

  • (l/r)strip:
    • 인자는 문자 집합으로 해석됩니다.
    • 문자열의 해당 끝에서 문자가 반복적으로 제거됩니다.
  • remove(prefix/suffix):
    • 인자는 연속된 부분 문자열로 해석됩니다.
    • 접두사/접미사는 최대 한 번만 제거됩니다.

참조 구현

GitHub의 풀 리퀘스트를 참조하십시오 [1].

주요 개정 이력

  • 버전 3: 튜플 동작을 제거합니다.
  • 버전 2: 이름을 removeprefix/removesuffix로 변경하고, 인자로 튜플을 지원하도록 추가했습니다
  • 버전 1: cutprefix/cutsuffix를 사용한 초안

참고 문헌