0
tập trung vào
0
Người theo dõi

Python: Hãy cẩn thận ở những nơi này

Được tạo ra trong: 2016-12-29 13:46:12, cập nhật trên:
comments   0
hits   1638

Python: Hãy cẩn thận ở những nơi này

Python là một ngôn ngữ rất thú vị. Nó cung cấp nhiều thư viện chuẩn rất tiện dụng và nhiều lệnh tích hợp để chúng ta dễ dàng thực hiện nhiệm vụ. Nhưng quá nhiều thứ tốt là sự sợ lựa chọn, đến nỗi chúng ta không thể sử dụng thư viện chuẩn và cơ sở cơ bản của nó tốt. Dưới đây là một số bẫy đơn giản và hiệu quả cho người mới học Python.

  • Bỏ qua phiên bản Python

Đây là một vấn đề liên tục được đưa ra trên StackOverflow. Khi bạn chạy mã hoàn hảo của bạn trên máy tính của người khác, bạn sẽ gặp phải một số lỗi, vì vậy bạn cần kiểm tra phiên bản Python của bạn có phù hợp không. Hãy chắc chắn rằng mã chạy trên phiên bản Python mà bạn biết.

  $ python --version
  Python 2.7.9

python quản lý phiên bản

pyenv là một công cụ quản lý phiên bản Python hay. Thật không may, nó chỉ hoạt động trên*trên hệ thống nix. trên Mac OS, bạn có thể cài đặt pyenv bằng cách cài đặt brew. trên hệ thống Linux, có một trình cài đặt tự động (automatic installer)

  • Bị mắc kẹt trong việc giải quyết tất cả các vấn đề bằng một dòng mã

Nhiều người nói rằng tôi đã giải quyết tất cả mọi vấn đề bằng một dòng mã, ngay cả khi mã của họ kém hiệu quả hơn so với mã thông thường, và mã đó khó đọc hơn, thậm chí có thể gây hiểu nhầm.

  l = [m for a, b in zip(this, that) if b.method(a) != b for m in b if not m.method(a, b) and reduce(lambda x, y: a + y.method(), (m, a, b))]

Thật ra thì tôi đã viết code trên để giải thích điều này. Nhưng tôi đã thấy rất nhiều người đã làm như vậy. Nếu bạn chỉ đơn giản là thêm một cái gì đó vào một danh sách hoặc một bộ để tự mình giải quyết các vấn đề phức tạp, thì bạn có thể sẽ không được đền đáp.

Kiểm soát một dòng mã không phải là một thành tựu lớn, mặc dù đôi khi nó có vẻ đặc biệt thông minh. Mã tốt là đơn giản nhưng tập trung nhiều hơn vào hiệu quả và dễ đọc.

  • Set khởi tạo sai

Đây là một vấn đề tinh tế hơn, và đôi khi bạn sẽ không nhận ra điều đó.

  >>> { n for n in range(10) if n % 2 == 0 }
  {0, 8, 2, 4, 6}
  >>> type({ n for n in range(10) if n % 2 == 0 })

Ví dụ trên cho thấy điều này. set giống như list trong container. Khác biệt giữa chúng là set không có giá trị lặp lại và không có trật tự. Người ta thường coi {} là một set trống, nhưng nó không phải là, nó là một dict trống.

  >>> {}
  {}
  >>> type({})

Vì vậy, nếu chúng ta muốn khởi tạo một tập hợp trống, chúng ta chỉ cần sử dụng tập hợp.

  >>> set()
  set()
  >>> type(set())

Lưu ý rằng một set trống có thể được biểu diễn là set (), nhưng một tập hợp chứa các phần tử sẽ được định nghĩa là set ().[1, 2])

  • GIL bị hiểu lầm

GIL (global interpreter lock) có nghĩa là chỉ có một luồng trong một chương trình Python có thể chạy bất cứ lúc nào. Điều này có nghĩa là khi chúng ta không thể tạo một luồng và mong đợi nó chạy song song. Những gì mà trình diễn Python thực sự làm là nhanh chóng chuyển đổi các luồng khác nhau đang chạy.

Nhiều người sẽ cố gắng biện minh cho Python rằng đây là các threads thực sự. Điều này là đúng, nhưng không thay đổi một thực tế: Python xử lý các threads khác với cách bạn mong đợi.

Giải pháp được đưa ra là sử dụng mô-đun đa xử lý. Các lớp quá trình được cung cấp bởi mô-đun đa xử lý về cơ bản có thể che đậy sự khác biệt tốt. Tuy nhiên, sự khác biệt có chi phí cao hơn nhiều so với đường dây. Vì vậy, chạy song song không phải lúc nào cũng tốt.

Tuy nhiên, vấn đề này không phải là vấn đề mà mọi chương trình Python gặp phải. PyPy-stm là một ví dụ về việc triển khai Python không bị ảnh hưởng bởi GIL. Các triển khai được xây dựng trên các nền tảng khác như JVM (Jython) hoặc CLR (IronPython) không có vấn đề GIL.

Nói tóm lại, hãy cẩn thận khi sử dụng các loại thread, bạn có thể không nhận được những gì bạn muốn.

  • Sử dụng các loại kiểu lỗi thời

Trong Python 2 có hai loại lớp, lớp phế kiểu cũ và lớp phế kiểu mới. Nếu bạn đang sử dụng Python 3, bạn đang sử dụng lớp phế kiểu mới mặc định. Để đảm bảo bạn sử dụng lớp phế kiểu mới trong Python 2, bạn cần kế thừa object hoặc bất kỳ lớp mới nào bạn tạo không phải lúc nào cũng kế thừa lệnh int hoặc list được xây dựng. Nói cách khác, lớp cơ sở của bạn nên luôn luôn kế thừa object.

  class MyNewObject(object): # stuff here

Các lớp mới này đã khắc phục một số vấn đề rất cơ bản trong các lớp cũ, bạn có thể xem tài liệu nếu bạn quan tâm.

  • Sự lặp lại sai lầm

Những sai lầm sau đây rất phổ biến đối với những người mới bắt đầu:

  for name_index in range(len(names)):
    print(names[name_index])

Rõ ràng là không cần phải sử dụng len, và thực tế là bạn có thể thực hiện nó bằng một câu nói rất đơn giản:

  for name in names:
     print(name)  

Ngoài ra, có rất nhiều công cụ khác để bạn xử lý đơn giản hóa sự lặp đi lặp lại. Ví dụ, zip có thể được sử dụng để đi qua hai danh sách:

  for cat, dog in zip(cats, dogs):
     print(cat, dog)

Nếu chúng ta muốn xem xét các biến chỉ mục và danh sách giá trị, chúng ta có thể sử dụng enumerate

  for index, cat in enumerate(cats):
     print(cat, index)

Trong itertools có rất nhiều tính năng để lựa chọn. Nếu bạn muốn sử dụng một tính năng trong itertools, bạn có thể dễ dàng sử dụng nó. Nhưng đừng sử dụng nó quá nhiều vì nó.

Lạm dụng itertools đã khiến một trong những vị thần lớn của StackOverflow mất rất nhiều thời gian để giải quyết nó.

Sử dụng các tham số mặc định có thể thay đổi

Tôi đã thấy rất nhiều thứ như:

  def foo(a, b, c=[]):
     # append to c
     # do some more stuff

Không sử dụng các tham số mặc định có thể thay đổi, thay vì sử dụng:

  def foo(a, b, c=None):
   if c is None:
     c = []
     # append to c
     # do some more stuff 

Ví dụ sau đây sẽ giúp chúng ta hiểu vấn đề một cách trực quan:

  In[2]: def foo(a, b, c=[]):
  ...     c.append(a)
  ...     c.append(b)
  ...     print(c)
  ...
  In[3]: foo(1, 1)
  [1, 1]
  In[4]: foo(1, 1)
  [1, 1, 1, 1]
  In[5]: foo(1, 1)
  [1, 1, 1, 1, 1, 1]

Cùng một c được trích dẫn một lần nữa và một lần nữa mỗi khi hàm được gọi. Điều này có thể tạo ra một số hậu quả rất không cần thiết.