내가 (최소한) 가장 좋아하는 Python 단점

14319 단어 python
몇 년 동안 Python을 작성하는 동안 문제를 일으킨 몇 가지 언어 인체 공학 문제.

선언이 실행됩니다



이것은 무슨 일이 일어나고 있는지 생각한 후에는 의미가 있지만 처음에는 틀리기 쉽다고 생각합니다.

한 가지 예는 다음과 같습니다.

class MyClass:
  def __init__(self, data={}):
    self._data = data

  def set(self, k, v):
    self._data[k] = v

  def get(self, k):
    return self._data.get(d)

  def remove(self, k):
    self._data.pop(k, None)

  def get_count(self):
    return len(self._data.keys())

A = MyClass()
A.get_count() # 0
A.set('a', 1)
A.get_count() # 1

B = MyClass()
B.get_count() # 1

A.remove('a')
A.get_count() # 0
B.get_count() # 0


추가로 실험해 보면 _data 의 내용이 MyClass 의 두 인스턴스 간에 동기화된 상태로 유지되고 있음을 알 수 있습니다.

하지만 흥미롭게도:

C = MyClass({})
C.get_count() # 0


일어난 일은 파이썬이 파일을 한 줄씩 실행하여 클래스 및 함수 정의를 가져오는 것입니다. 즉, data 메서드의 기본값 __init__이 실제로 인스턴스화됩니다. 기본값을 사용하는 클래스의 모든 인스턴스는 동일한 객체에서 변경 및 읽어서 효과적으로 클래스 변수가 됩니다.

이것은 다음에 의해 확인됩니다.

A._data is B._data # True


이것을 피하려면 다음과 같은 둔한 일을 해야 합니다.

class MyClass:
  def __init__(self, data=None):
    if data is None:
      self._data = {}
    else:
      self._data = data

...


루프 변수의 범위



많은 언어와 달리 Python의 "루프 변수"는 루프가 종료된 후에도 로컬 범위에 유지됩니다. 이로 인해 예기치 않은 동작이 발생할 수 있습니다.

def fun():
  i = 0
  arr = [1, 2, 3]

  # lots of code

  for i in range(len(arr)):
    # do some stuff

  # more code

  return arr[i]

fun() # returns 3


이 예제는 상당히 인위적이지만, 일반적인 아이디어는 루프 변수의 이러한 동작이 이상하고 추적하기 어려운 버그(이전에 선언된 로컬 변수를 섀도잉하는 이 예제 포함)로 이어질 수 있다는 것입니다.

또 다른 합병증: 반복할 항목이 없으면 루프 변수가 전혀 할당되지 않습니다. 예를 들어 아래 main()의 첫 번째 줄은 오류 없이 실행되지만 두 번째 줄은 NameError: name 'x' is not defined가 발생합니다.

def fun(l):
  for x in l:
    print(x)
  return x

def main():
  fun([1, 2, 3])
  fun([])


다른 해석 및 중괄호가 아닌 언어조차도 이러한 문제를 피합니다. 루비:

3.times do |x|
  print x
end

x # NameError (undefined local variable or method `x' for main:Object)


함수 체이닝은 어렵다



데이터 구조 작업과 관련하여 Python에서 제공하는 함수와 메서드는 매우 일관적이지 않습니다. 목록을 예로 들어 보겠습니다. List 클래스에는 제자리에서 변경하는 메서드가 있지만 많은 작업의 경우 새 목록을 반환하고 고유한 구문을 갖는 목록 이해를 사용해야 합니다. 다른 작업의 경우 filtermap (목록 클래스의 메서드가 아님) 함수를 사용할 수 있습니다. 그리고 목록 작업을 위한 함수인 join 가 목록 메서드가 아닌 문자열 메서드라는 사실과 같이 다른 흩어져 있는 불일치가 있습니다.

예로서:

def slugify(string, bad_words):
  words = string.split()
  words = [w.lower() for w in words if w.isalpha() and w not in bad_words]
  return "-".join(words)

slugify("My test 1 string", set(["dang", "heck"])) # "my-test-string"


Python에 익숙한 사람들에게는 이것이 자연스러워 보일 수 있지만 여전히 코드는 함수가 수행하는 각 변환을 이해하기 위해 주의 깊게 읽어야 합니다(그리고 이것은 간단한 예를 위한 것입니다).

다른 언어에서는 이런 종류의 코드가 더 자연스럽습니다. 예를 들어 Ruby에는 연결을 허용하는 보다 일관된 방법이 있습니다.

def slugify(string, bad_words)
  string.split()
    .map(&:downcase)
    .select { |w| w.match(/^[[:alpha:]]+$/) }
    .select { |w| !bad_words.include? w }
    .join("-")
end

slugify("My dang test 1 string", Set["dang", "heck"]) # "my-test-string"


기능적 세계의 예로서, Clojure는 일관된 API와 스레딩 매크로->->>를 사용하여 기능 체인을 강력하게 지원합니다.

(defn slugify [string bad-words]
  (->> (str/split string #" ")
    (filter #(re-matches #"^[a-zA-Z]*$" %))
    (remove bad-words)
    (map str/lower-case)
    (str/join "-")
  )
)

(slugify "My dang test 1 string" (set '("dang" "heck"))) ; "my-test-string"

좋은 웹페이지 즐겨찾기