Ngày 19 tháng 06 năm 2007, Guido van Rossum, tác giả ngôn ngữ Python, gửi lên mạng một bài cập nhật về tình hình phát triển Python 3000. Bài lược dịch (từ trang http://www.artima.com/weblogs/viewpost.jsp?thread=208549) này tóm tắt về khoảng trượt kế hoạch 2 tháng và nhiều tính năng mới.

Nhìn chung về kế hoạch

Lịch sử

Guido nghĩ đến ý tưởng về Python 3000 (tên được đặt ra nhằm để chế giễu Windows 2000) vào khoảng năm 2000, trong một hội thảo Python. Theo đó, Python 3000 sẽ là bản đầu tiên bỏ qua tính tương thích ngược để tạo ra một ngôn ngữ tốt nhất cho sau này.

Diễn biến gần đây

Khoảng một năm rưỡi trước, Guido thật sự bắt đầu thiết kế Python 3000. Hàng loạt đề xuất cải tiến Python (Python Enhancement Proposal, PEP) ra đời được đặt số thứ tự từ 3000 trở lên (PEP 3000 trước đó được đổi thành PEP 3100). PEP 3000 trở thành tài liệu chính về tư tưởng và kế hoạch của Python 3000.

Từ đó, việc phát triển đã chảy dưới chân cầu python-dev, và sau đó được chuyển hẳn qua python-3000.

Kế hoạch dự tính

Lịch trình được thông báo khoảng một năm trước và nhắm vào cuối tháng 06 năm 2007 sẽ có bản alpha đầu tiên và vào một năm sau có bản cuối cùng (Python 3.0 là dấu hiệu phiên bản khi nó được phát hành; "Python 3000" hay "Py3k" là tên mã của dự án).

Kế hoạch này đã bị trễ một chút. Hiện tại chúng ta dự tính khoảng cuối tháng 08 mới có bản alpha, và dĩ nhiên bản cuối cùng cũng bị trễ hai tháng. Lý do chính cho sự trễ này chủ yếu là do việc chuyển qua sử dụng Unicode toàn bộ.

Python 2.6

Phiên bản “đồng hành” 2.6 sẽ ra lò vài tháng trước 3.0. Nếu bạn không thích sử dụng phiên bản mới nhất, thì 2.6 sẽ là phiên bản bạn sẽ dùng, và nó sẽ không khác gì mấy so với 2.5.

Tính tương thích và sự chuyển đổi

Tính tương thích

Python 3.0 sẽ phá vỡ tính tương thích ngược. Toàn bộ.

Ngược lại, Python 2.6 sẽ giữ vững tính tương thích ngược với Python 2.5 (và các phiên bản khác có thể), và nó cũng sẽ hỗ trợ tương thích tiến, theo những lối sau:

  • Python 2.6 sẽ hỗ trợ chế độ cảnh báo Py3k và sẽ cảnh báo khi chạy những tính năng mà sẽ không còn hoạt động trong Python 3.0, ví dụ như khi nghĩ hàm range() trả về một danh sách.
  • Python 2.6 sẽ có những chức năng từ Py3k, kích hoạt bằng __future__ hoặc sử dụng cả cú pháp mới lẫn cú pháp cũ (nếu cú pháp không hợp lệ trong 2.5).
  • Cùng với tính tương thích tiến, sẽ có một công cụ chuyển đổi mã nguồn. Công cụ này có thể dịch từ-nguồn-tới-nguồn theo chế độ phi ngữ cảnh. Lấy một ví dụ thật đơn giản, nó có thể dịch apply(f, args) thành f(*args). Tuy nhiên, công cụ này không thể khảo sát dữ liệu hoặc phỏng đoán kiểu cho nên nó chỉ đơn giản giả định rằng apply trong ví dụ này là hàm có sẵn trong phiên bản cũ.

Việc chuyển đổi

Mô hình phát triển một kế hoạch mà cần hỗ trợ cả 2.6 và 3.0 cùng lúc được khuyến khích như sau:

  1. Bắt đầu bằng hàng loạt các kiểm tra đơn vị (unit test), tốt nhất là bao trùm toàn bộ.
  2. Chuyển kế hoạch qua Python 2.6.
  3. Bật chế độ cảnh báo Py3k.
  4. Kiểm tra và chỉnh sửa cho đến khi không còn cảnh báo.
  5. Dùng công cụ 2to3 để chuyển mã nguồn này sang cú pháp 3.0. Không sửa kết quả bằng tay!.
  6. Kiểm tra mã nguồn đã đổi ở 3.0.
  7. Nếu phát hiện vấn đề thì sửa mã cho phiên bản 2.6 và quay lại bước 3.
  8. Khi cần phát hành, phát hành hai bản 2.6 và 3.0 riêng biệt.

Nếu công cụ chuyển đổi và tính tương thích tiến trong Python 2.6 làm việc tốt, các bước từ 2 đến 6 sẽ không cần nhiều hơn những gì cần thiết để chuyển từ Python 2.x lên 2.(x+1).

Tình hình của những tính năng riêng lẻ

Có rất nhiều thay đổi để liệt kê hết ra ở đây nên chỉ những tính năng quan trọng, hoặc có mâu thuẫn sẽ được nhắc đến.

Unicode, Codec và I/O

Chúng ta đang chuyển qua mô hình được biết đến ở Java: các chuỗi (bất biến, immutable) là Unicode, và dữ liệu nhị phân được biểu diễn bởi một kiểu byte dữ liệu khả biến (mutable) riêng. Hơn nữa, trình phân tích cú pháp sẽ sử dụng được cả Unicode: bảng mã mặc định của mã nguồn sẽ là UTF-8, các ký tự không nằm trong bảng ASCII có thể được dùng trong tên chỉ định (identifier). Vẫn còn nhiều vấn đề về chuẩn hóa, một vài ký tự cụ thể, và việc hỗ trợ các ngôn ngữ từ phải qua trái. Dù vậy, bộ thư viện chuẩn sẽ tiếp tục dùng ASCII cho các tên chỉ định và hạn thế các ký tự không ASCII trong ghi chú và các chuỗi trong kiểm tra đơn vị cho các tính năng Unicode và tên tác giả.

Chúng ta dùng "..." hay '...' cho các chuỗi Unicode, và b"..." hay b'...' cho các byte. Ví dụ, b'abc' có nghĩa là tạo một đối tượng byte y như kết quả của bytes([97, 98, 99]).

Chúng ta sẽ theo một hướng khác với các codec: trong Python 2, các codec có thể nhận Unicode hoặc 8-bit như đầu nhập và xuất, trong Py3k, mã hóa (encoding) luôn luôn là từ một chuỗi Unicode thành một mảng byte, và giải mã (decoding) luôn luôn đi theo chiều ngược lại. Điều này có nghĩa là chúng ta sẽ bỏ một vài codec không tương thích với mô hình ví dụ như rot13, base64bz2 (những chuyển đổi này sẽ vẫn còn được hỗ trợ, chỉ có điều là không phải qua API mã hóa/giải mã).

Thư viện I/O mới

Sự phân biệt giữa byte và chuỗi dẫn đến một thay đổi nhỏ trong API. Trong thư viện mới, các dòng nhị phân (binary stream) (khi mở tập tin theo rb hoặc wb) và dòng văn bản (text stream) được phân biệt rõ ràng. Các dòng văn bản có một thuộc tính mới, encoding, có thể được thiết lập khi dòng được mở; nếu encoding không được cho biết trước, mặc định của hệ thống sẽ được dùng.

Việc đọc từ dòng nhị phân sẽ trả về mảng byte, trong khi đọc từ dòng văn bản sẽ trả về chuỗi Unicode; và tương tự cho việc viết. Viết một chuỗi văn bản vào dòng nhị phân hoặc viết mảng byte vào dòng văn bản sẽ dẫn đến ngoại lệ (exception).

Mặc dù vẫn còn hàm open(), định nghĩa đầy đủ của thư viện I/O mới được đặt trong môđun io. Môđun này cũng chứa các lớp khác cho các loại dòng khác nhau, một cài đặt mới của StringIO, và một lớp mới BytesIO giống như StringIO nhưng dùng cho dòng nhị phân, để đọc và viết mảng byte.</p>

In ấn và định dạng

Hai chức năng I/O khác: lệnh print trở thành hàm print(), và phép định dạng chuỗi % sẽ được thay bằng phương thức (method) format() ở các đối tượng chuỗi.

Chuyển print thành một hàm sẽ làm nhiều người suy nghĩ. Tuy nhiên, có một vài điểm tốt ở đây: nó sẽ rất dễ hơn để chuyển từ việc dùng hàm print() thành việc dùng gói logging; và cú pháp print luôn có một ít tranh cãi vì kiểu << file và ý nghĩa của dấu phẩy cuối cùng.

Tương tự, phương thức format() tránh được một vài điểm xấu của toán tử % cũ, đặc biệt là kết quả bất ngờ của "%s" % x khi x là tuple, và lỗi thường xảy ra khi vô tình bỏ quên s trong %(name)s. Các chuỗi định dạng mới sử dụng {0}, {1}, {2}, ... cho các tham số theo vị trí, và {a}, {b}, ... cho các tham số theo từ khóa. Tính năng khác bao gồm {a.b.c} cho thuộc tính và ngay cả {a[b]} cho dãy hoặc ánh xạ. Độ dài của trường có thể được chỉ định giống như {a:8}; cách viết như vậy cũng hỗ trợ các tùy chọn khác.

Phương thức format() có thể được mở rộng theo nhiều chiều: bằng cách định nghĩa một phương thức __format__() đặc biệt, các kiểu dữ liệu có thể quyết định cách định dạng, cách sử dụng các tham số; bạn cũng có thể tạo các lớp định dạng riêng nhằm để tự động cung cấp biến cục bộ như tham số cho cách định dạng.

Các thay đổi về lớp và hệ thống kiểu

Bạn có thể đoán được rằng các lớp cổ điển cuối cùng cũng ra đi. Lớp object sẽ là lớp cơ sở mặc định cho các lớp mới. Điều này tạo chỗ cho những tính năng sau:

  • Trang hoàng lớp (Class decorators). Chúng hoạt động như các trang hoàng hàm (function decorators):

    @art_deco
    class C:
      ...
    
  • Ký hiệu hàm và phương thức từ nay có thể được “ghi chú” (annotated). Ngôn ngữ gốc sẽ không đặt ý nghĩa vào các ghi chú này (ngoài việc cung cấp chúng cho việc tự xét, introspection), nhưng một vài thư viện chuẩn sẽ làm vậy; ví dụ, hàm tổng quát (generic function) (xem ở dưới) có thể dùng chúng. Cú pháp cũng dễ đọc:

    def foobar(a: Integer, b: Sequence) -> String:
      ...
    
  • Cú pháp siêu lớp (metaclass) mới. Thay vì đặt một biến __metaclass__ trong thân lớp, bây giờ bạn phải chỉ định siêu lớp bằng cách dùng thông số từ khóa trong định nghĩa lớp, ví dụ như:

    class C(bases, metaclass=MyMeta):
      ...
    
  • Từ điển lớp (class dictionary) riêng. Nếu siêu lớp định nghĩa một phương thức __prepare__(), nó sẽ được gọi trước khi vào thân lớp, và giá trị trả về sẽ được dùng thay cho từ điển chuẩn như là vùng tên (namespace) dùng trong thân lớp. Điểm này có thể được sử dụng để cài đặt kiểu cấu trúc (struct) khi thứ tự các phần tử có vai trò quan trọng.

  • Bạn có thể chỉ định lớp cơ sở lúc chạy, như:

    bases = (B1, B2)
    
    class C(*bases):
      ...
    
  • Các thông số từ khóa khác cũng được cho phép ở đầu đề lớp; chúng được truyền cho phương thức __new__ của siêu lớp.

  • Bạn có thể định nghĩa lại các kiểm tra isinstance()issubclass(), bằng cách định nghĩa các phương thức __instancecheck__()__subclasscheck__() của lớp. Khi chúng được định nghĩa, isinstance(x, C) thành tương tự như C.__instancecheck__(x), và issubclass(D, C)C.__subclasscheck__(D).

  • Lớp cơ sở trừu tượng tự chọn (Voluntary Abstract Base Classes). Nếu bạn muốn định nghĩa một lớp hoạt động như một ánh xạ chẳng hạn, bạn có thể tự giác kế thừa (voluntarily inherit) từ lớp abc.Mapping. Một mặt, lớp này cung cấp các cách thức có ích thay thế gần hết tính năng của các lớp UserDictDictMixin. Mặt khác, sử dụng các ABC một cách có hệ thống có thể giúp các khung (framework) lớn thực hiện nhiều việc đúng đắn với ít “phỏng đoán” hơn: trong Python 2 khó mà nói rằng một đối tượng là một dãy, hay là một ánh xạ khi nó định nghĩa phương thức __getitem__(). Các ABC sau được cung cấp cho người dùng: Hashable, Iterable, Iterator, Sized, Container, Callable; Set, MutableSet; Mapping, MutableMapping; Sequence, MutableSequence; Number, Complex, Real, Rational, Integer. Môđun io cũng định nghĩa một số ABC cho nên đây là lần đầu tiên Python có một đặc tả cho khái niệm file-like (như kiểu tập tin) u ám. Sức mạnh của khung ABC nằm ở khả năng (ability) (mượn từ các giao tiếp Zope, Zope interface) đăng ký một lớp cụ thể (concrete class) X như kế thừa ảo từ một ABC Y, trong khi XY được viết bởi các tác giả khác nhau và có mặt trong các gói khác nhau. (Để làm rõ, khi dùng kế thừa ảo, cách thức của lớp Y không được cung cấp cho lớp X; hiệu quả duy nhất là issubclass(X, Y) sẽ trả về True.)

  • Để hỗ trợ việc định nghĩa ABC mà cần các lớp cụ thể thật sự cài đặt đầy đủ giao tiếp, trang hoàng @abc.abstractmethod có thể được dùng để khai báo các phương thức trừu tượng (chỉ dùng trong các lớp mà siêu lớp là, hoặc, kế thừa từ abc.ABCMeta).

  • Hàm tổng quát (generic functions). Việc thêm vào chức năng này, được trình bày trong PEP 3124, có vẻ chưa rõ lắm, vì PEP này có vẻ đã dậm chân tại chỗ. Hy vọng rằng nó sẽ được tiếp tục. Nó hỗ trợ sự chuyển hàm (function dispatch) dựa vào kiểu của các tham số, thay vì kiểu thông thường chỉ dựa vào lớp của đối tượng đích (target object).

Các thay đổi lớn khác

Chỉ một vài điểm sáng.

Sửa đổi về ngoại lệ

  • Các ngoại lệ chuỗi (string exception) không còn dùng.
  • Mọi ngoại lệ phải bắt nguồn từ BaseException và tốt hơn là từ Exception.
  • Chúng ta sẽ bỏ StandardException.
  • Ngoại lệ không còn hoạt động như dãy nữa. Thay vào đó chúng có một thuộc tính args chứa tham số được truyền vào hàm khởi tạo (constructor).
  • Cú pháp except E, e: được đổi thành except E as e. Điều này tránh những lẫn lộn hay gặp như except E1, E2:.
  • Tên biến đằng sau as trong câu except bị ép xóa thẳng ngay khi thoát khỏi câu except.
  • sys.exc_info() trở nên dư thừa (hoặc có thể biến mất): thay vào đó, e.__class__ là kiểu ngoại lệ, và e.__traceback__ là vết ngược.
  • Những thuộc tính tùy chọn như __context__ được thiết lập là ngoại lệ trước khi một ngoại lệ xảy ra trong câu except hay finally; __cause__ có thể được thiết lập trực tiếp khi nâng ngoại lệ (raise exception) bằng cách dùng raise E1 from E2.
  • Các biến thể cũ của cú pháp raise như raise E, eraise E, e, tb biến mất.

Sửa đổi về số nguyên

  • Sẽ chỉ có một kiểu số nguyên tên là int với cách thức của long trong Python 2. Hậu tố L biến mất.
  • 1/2 sẽ trả về 0.5, không phải 0. (Dùng 1//2 nếu cần điều đó.)
  • Cú pháp của số bát phân đổi thành 0o777, để tránh gây ra khó hiểu cho người mới.
  • Số nhị phân: 0b101 == 5, bin(5) == '0b101'.

Bộ lặp (Iterator) hoặc khả lặp (Iterable) thay thế danh sách (List)

  • dict.keys()dict.items() trả về tập hợp; dict.values() trả về một cách nhìn khả lập (iterable view). Các phương thức iter*() khác biến mất.
  • range() trả về kiểu đối tượng mà xrange() đã từng làm; xrange() biến mất.
  • zip(), map(), filter() trả về các đối tượng khả lặp (như những hàm tương tự trong itertools).

Các điểm khác

  • So sánh hơn kém (<, <=, >, >=) sẽ nâng TypeError thay vì trả về kết quả bất định. So sánh bằng (==, !=) mặc định sẽ so sánh tính đồng nhất của hai đối tượng (is, is not).
  • Câu lệnh nonlocal cho phép bạn gán vào các biến ở vòng ngoài (không phải toàn cục) (outer, non-global scope).
  • Lệnh gọi super() mới: Gọi super() không tham số tương đương với super(<this_class>, <first_arg>). super() có thể dùng trong phương thức đối tượng hoặc phương thức của lớp.
  • Tập hợp: {1, 2, 3} và ngay cả bao hàm tập hợp (set comprehension): {x for x in y if P(x)}. Chú ý rằng tập hợp rỗng là set(), vì {} là một từ điển rỗng!
  • reduce() biến mất (thật ra là chuyển vào functools). Nó cho thấy rằng hầu hết các mã dùng reduce() có thể trở nên dễ đọc hơn khi được viết lại bằng vòng lặp for. (Ví dụ.)
  • lambda vẫn tồn tại.
  • Cú pháp dấu phẩy ngược (backtick), thường khó đọc, biến mất (thay vào đó dùng repr()), cũng như toán tử <> (thay vào đó nên dùng !=).
  • Ở mức C, sẽ có một API bộ đệm mới, tốt hơn nhiều và sẽ cung cấp kết nối tốt hơn cho numpy (PEP 3118).

Sửa đổi về thư viện

Guido không muốn nói nhiều về các thay đổi ở thư viện chuẩn vì nó sẽ chỉ được thực hiện khi 3.0a1 ra đời. Vẫn rõ ràng thấy được là chúng ta sẽ bỏ nhiều thứ hết hạn hoặc không còn được hỗ trợ (ví dụ như nhiều môđun chỉ chạy với SGI IRIX), và chúng ta sẽ cố đổi tên các môđun sử dụng TênHoa như StringIO hay UserDict, để tương hợp với chuẩn đặt tên của PEP 8 (yêu cầu tên ngắn, toàn chữ thường cho môđun).

Và cuối cùng

Guido muốn nhấn mạnh rằng lambda sẽ vẫn tồn tại.