Python cho người Việt

Python cho người Việt

Entries in the Category “Bài viết”

Grinder - Java load testing framework

written by Lê Ngọc Hiếu, on Nov 30, 2007 4:14:00 AM.

Những người làm phần mềm hẳn ai cũng đã biết đến công đoạn rất quan trọng sau khi thành phẩm là "kiểm thử" (test). Một trong những kiểu kiểm thử (test case) cần phải làm là kiểm thử khả năng chịu đựng của hệ thống (load test). Có một công cụ rất mạnh để tự động hóa quá trình này, tiết kiệm thời gian và công sức cho đội kiểm thử đó là Grinder.

Giới thiệu

Tại sao lại là Grinder mà không phải là một công cụ nào khác? Đơn giản là vì Grinder sử dụng Jython - Python chạy trên nền Java - một sự kết hợp tuyệt vời của một ngôn ngữ rất mềm dẻo, mạnh mẽ (Python) với một khung nền có thể chạy trên mọi hệ điều hành (Java).

Grinder bao gồm 3 thành phần:

  • Console: đây là thành phần đóng vai trò điều khiển và tiếp nhận kết quả kiểm thử trả về.

  • Agent process: đây là thành phần nhận lệnh từ Console và điều khiển các worker process.

  • Worker process: đây là các kịch bản kiểm thử do người dùng định nghĩa.

Như vậy, tại máy dùng để theo dõi và hiển thị kết quả kiểm thử, chạy Console. Tại các máy trạm chịu trách nhiệm chạy các kịch bản kiểm thử, chạy Agent process. Các kịch bản kiểm thử sẽ được tự động chuyển về cho các Agent process từ Console khi người dùng ra lệnh chạy.

Chuẩn bị

Để chạy được Grinder cần phải cài đặt các biến môi trường trỏ về đúng thư mục cài đặt Grinder và các thành phần liên quan.

Đối với Windows

  • Tạo tập tin kịch bản gọi mõi khi chạy setGrinderEnv.cmd:

    set GRINDERPATH=(full path to grinder install directory)
    set GRINDERPROPERTIES=(full path to grinder.properties)\grinder.properties
    set CLASSPATH=%GRINDERPATH%\lib\grinder.jar;%CLASSPATH%
    set JAVA_HOME=(full path to java install directory)
    PATH=%JAVA_HOME%\bin;%PATH%
  • Hoặc gán trực tiếp biến môi trường mỗi khi khởi động bằng cách:

    • Vào System Properties chọn tab Advanced chọn tiếp Environment Variables

    • Chọn New và thêm vào tên biến và đường dẫn như trên.

Đối với Unix

  • Tạo tập tin kịch bản setGrinderEnv.sh:

    #!/usr/bin/ksh
    GRINDERPATH=(full path to grinder install directory)
    GRINDERPROPERTIES=(full path to grinder.properties)/grinder.properties
    CLASSPATH=$GRINDERPATH/lib/grinder.jar:$CLASSPATH
    JAVA_HOME=(full path to java install directory)
    PATH=$JAVA_HOME/bin:$PATH
    export CLASSPATH PATH GRINDERPROPERTIES
  • Hoặc gán trực tiếp mỗi khi đăng nhập vào người dùng bằng cách thêm vào trong tập tin /home/$USER/.bashrc:

    export GRINDERPATH=(full path to grinder install directory)
    export GRINDERPROPERTIES=(full path to grinder.properties)/grinder.properties
    export CLASSPATH=$GRINDERPATH/lib/grinder.jar:$CLASSPATH
    export JAVA_HOME=(full path to java install directory)

Cấu hình

Tập tin thuộc tính grinder.properties thường gồm các tham số sau:

Tên tham số

Giải thích

Giá trị mặc định

grinder.processes

Số lượng tiến trình worker mà một agent khởi tạo

1

grinder.threads

Số lượng tiểu trình mà mỗi tiến trình worker sinh ra

1

grinder.runs

Số lần chạy kịch bản kiểm thử mà mỗi tiểu trình chạy. 0 nghĩa là chạy lặp đi lặp lại. Tham số này nên để là 0 khi sử dụng Console để điều khiển hoạt động của các agent.

1

grinder.processIncrement

Nếu đặt, agent sẽ tăng số lượng tiếng trình worker lên sau mỗi nhịp grinder.processIncrementInterval.

0 (Khởi động tất cả các tiến trình worker cùng lúc.)

grinder.processIncrementInterval

Sử dụng cùng với tham số grinder.processIncrement, tham số này xác định nhịp tăng tiến trình worker tính theo mili giây.

60000

grinder.initialProcesses

Sử dụng cùng với tham số grinder.processIncrement, tham số này xác định số lượng tiến trình worker khởi động.

Giá trị của grinder.processIncrement.

grinder.duration

Thời gian mỗi tiến trình worker chạy. Tham số này có thể xác định cùng với tham số grinder.runs, điều đó có nghĩa là tiến trình worker sẽ chấm dứt nếu hết thời gian chạy hoặc số lượng tiến trình worker đạt tối đa.

0 (Không kết thúc.)

grinder.script

Tên tập tin kịch bản sẽ chạy.

grinder.py

grinder.jvm

Sử dụng máo ảo Java (JVM) khác. Mặc định là java nên không cần phải khai báo nếu đã có sẵn trong đường dẫn (PATH).

java

grinder.jvm.classpath

Dùng để xác định đường dẫn đến các lớp sử dụng cho tiến trình worker. Bất cứ đường dẫn nào xác định ở đây sẽ được thêm vào trong đường dẫn classpath sử dụng để khởi động tiến trình Grinder.

grinder.jvm.arguments

Các tham số phụ thêm cho tiến trình worker.

grinder.logDirectory

Thư mục lưu tập tin log. Tự động tạo nếu chưa có.

Thư mục cục bộ

grinder.numberOfOldLogs

Số lượng nhật kí được lưu lại từ lần chạy trước đó.

1

grinder.hostID

Thay thế chuỗi host sử dụng trong tên tập tin nhật kí và trong nhật kí.

Tên máy

grinder.consoleHost

Địa chỉ IP hoặc tên máy sử dụng cho kết nối giữa các agent, tiến trình worker và console.

Tất cả các giao tiếp mạng của máy.

grinder.consolePort

Cổng để agent và tiến trình worker sử dụng để kết nối đến console.

6372

grinder.useConsole

Nếu đặt là false, agent và tiến trình worker sẽ không sử dụng console.

true

grinder.reportToConsole.interval

Khoảng thời gian mỗi tiến trình gửi cập nhật về cho console. Tham số này cũng xác định khoảng thời gian các tập tin dữ liệu được làm mới. Giá trị tính theo mili giây.

500

grinder.initialSleepTime

Khoảng thời gian tối đa mỗi tiểu trình đợi trước khi bắt đầu. Không giống như thời gian ngừng trong kịch bản, tham số này thay đổi ngẫu nhiên giữa 0 và giá trị được đặt. Bị ảnh hưởng bởi tham số grinder.sleepTimeFactor, còn tham số grinder.sleepTimeVariation thì không. Giá trị tính theo mili giây.

0

grinder.sleepTimeFactor

Thêm một hệ số vào thời gian chờ đã đặt, thông qua một thuộc tính hay trong kịch bản. Đặt giá trị này là 0.1 kịch bản sẽ chạy nhanh hơn mười lần.

1

grinder.sleepTimeVariation

Thông thường, Grinder thay đổi thời gian chờ xác định trong kịch bản tùy theo sự phân chia bình thường. Tham số này xác định một dải phân đoạn mà đa số (chiếm 99.75%) là sẽ rơi vào trong dải này. Ví dụ, nếu thời gian chờ xác định là 1000 và sleepTimeVariation đặt là 0.1, khi đó 99.75% các giá trị thời gian chờ sẽ nằm trong khoảng 900 và 1100 milli giây.

0.2

grinder.logProcessStreams

Đặt là false để tắt tính năng ghi nhật kí những thông báo và lỗi hiển thị của tiến trình worker. Dùng tham số này để giảm tải cho các tiểu trình tại máy trạm.

true

grinder.reportTimesToConsole

Đặt là false để tắt tính năng phản hồi thông tin thời gian về cho console, các thông tin thống kê khác vẫn được phản hồi.

true

grinder.debug.singleprocess

Nếu đặt là true, agent sẽ phát sinh tiểu trình để chạy kịch bản thay vì tiến trình, sử dụng một bộ nạp lớp đặc biệt để cô lập engine ra. Điều này cho phép engine dễ gỡ rối hơn. Mục đích chính của công cụ này là gỡ rối Grinder engine, tuy nhiên nó cũng có thể dùng cho người dùng chuyên nghiệp vào các mục đích khác.

false

grinder.useNanoTime

Nết đặt là true, System.nanoTime() sẽ được dùng để đo thời gian thay vì System.currentTimeMills(). Grinder sẽ vẫn phản hồi thời gian tính bằng mili giây. Độ chính xác của những phương pháp này phụ thuộc vào sự hiện thực hóa JVM và hệ điều hành. Tham số true chỉ dùng cho J2SE 5 hoặc lớn hơn.

false

Ví dụ

Thông thường, một tập tin cấu hình đơn giản chỉ cần các thông số sau:

grinder.process 1
grinder.threads 1
grinder.runs 1
grinder.script sample.py

Nếu chạy ở chế độ có Console thì cần thêm:

grinder.useConsole true
grinder.consoleHost x.x.x.x (IP hoặc tên máy Console)
grinder.consolePort xxx (cổng quy định bởi Console)

Chạy

Để chạy Grinder có thể đơn giản như sau:

  • Chạy Console:

    java net.grinder.Console
  • Chạy Agent:

    java net.grinder.Grinder

Hoặc tạo tập tin kịch bản như sau:

Đối với Windows

startAgent.cmd

call (path to setGrinderEnv.cmd)\setGrinderEnv.cmd
echo %CLASSPATH%
java -cp %CLASSPATH% net.grinder.Grinder %GRINDERPROPERTIES%

startConsole.cmd

call (path to setGrinderEnv.cmd)\setGrinderEnv.cmd
java -cp %CLASSPATH% net.grinder.Console

Đối với Unix

startAgent.sh

#!/usr/bin/ksh
. (path to setGrinderEnv.sh)/setGrinderEnv.sh
java -cp $CLASSPATH net.grinder.Grinder $GRINDERPROPERTIES

startConsole.sh

#!/usr/bin/ksh
. (path to setGrinderEnv.sh)/setGrinderEnv.sh
java -cp $CLASSPATH net.grinder.Console

Công việc tiếp theo chỉ là... viết kịch bản kiểm thử và chạy.

Tự động hóa

Đến đây hẳn nhiều người, nhất là những lập trình viên không (chưa) biết ngôn ngữ Python sẽ tiếc nuối vì không biết viết mã cho kịch bản kiểm thử như thế nào. Nhưng có một cách để những lập trình viên này vẫn có thể dùng công cụ kiểm thử mạnh mẽ Grinder mà không cần phải biết viết một dòng mã Python nào. Từ phiên bản 3.0 trở lên, Grinder cung cấp một môi trường proxy kiểm soát dữ liệu vào ra để tự động tạo mã kịch bản tương ứng. Điều đó có nghĩa là, kịch bản sẽ tự động được tạo ra theo đúng những thao tác chuột và bàn phím mà những người kiểm thử làm. Và tất nhiên cách này chỉ dùng cho những kiểm thử đối với các ứng dụng web.

Để khởi động Grinder proxy:

Đối với Windows

startProxy.cmd

call (path to setGrinderEnv.cmd)\setGrinderEnv.cmd
java -cp %CLASSPATH% net.grinder.TCPProxy -console -http > script_name.py

Đối với Unix

startProxy.sh

#!/usr/bin/ksh
. (path to setGrinderEnv.sh)/setGrinderEnv.sh
java -cp $CLASSPATH net.grinder.TCPProxy -console -http > script_name.py

Sau đó thiết đặt trình duyệt cho trỏ đến proxy là localhost với cổng 8081 và bắt đầu các thao tác kiểm thử.

Tổng kết

Trên đây là những bước cơ bản nhất để có thể làm quen với một công cụ kiểm thử vô cùng mạnh mẽ, đơn giản, dễ dùng và đặc biệt là hoàn toàn miễn phí. Mặc dù chạy trên nền Java nhưng trong quá trình thử nghiệm cũng như sử dụng để kiểm thử hệ thống Online Trading của công ty Chứng khoán VNDirect, Grinder chạy rất nhẹ nhàng, không tốn nhiều RAM như tôi lo ngại, thậm chí tôi sử dụng chính các máy của người dùng làm agent trong khi mọi người vẫn làm việc mà không hề thấy hệ thống bị kéo chậm lại hay có vấn đề gì xảy ra. Việc làm quen với Grinder cũng không mất quá nhiều thời gian, kể cả với một tay ngang như tôi. Grinder thật sự rất ấn tượng. Và điểm nổi trội nhất của Grinder đó là nó cho phép người dùng tự định nghĩa kịch bản kiểm thử bằng một ngôn ngữ mạnh mẽ và mềm dẻo - Python.

Python S60 cho người mới bắt đầu

written by vithon, on Aug 18, 2007 7:54:00 PM.

Xin chào các bạn, mình là thành viên mới của forum. Mình thực sự rất yêu thích Python, và cũng mới bắt đầu nghiên cứu về Python trong thời gian gần đây. Đặc biệt mình có nghiên cứu thêm về phát triển ứng dụng Python cho điện thoại Nokia S60. Trong quá trình học mình có ghi chép lại những gì đã học thành tài liệu Python S60 cho người mới bắt đầu. Tài liệu này mình vẫn viết, được cập nhật hàng ngày và có thể xem cũng như download tại http://my.opera.com/noname00/forums/topic.dml?id=198943 (Khuyến cáo nên down bản PDF, vì nếu có thay đổi thì bản PDF sẽ được thay đổi chứ mình không thay đổi trên forum :D)

Mình nghĩ nó thích hợp cho cả những người mới bắt đầu làm quen với Python (như mình) cũng như những người thích lập trình Python cho điện thoại di động.

Mong các bạn cho ý kiến

(chép lại từ diễn đàn, do bạn Trịnh Thành Trung đóng góp)

Lập trình như một Pythonista: Thành ngữ Python

written by Nguyễn Thành Nam, on Jul 28, 2007 4:57:00 AM.

Bản lược dịch của bài nói chuyện "Code Like a Pythonista: Idiomatic Python" do David Goodger trình bày tại PyCon 2007 và OSCON 2007.

Ngày 26 tháng 07 năm 2007, David Goodger gửi bài nói chuyện Code Like a Pythonista: Idiomatic Python mà ông đã trình bày ở PyCon 2007 và OSCON 2007 lên trang nhà. Đây là bài lược dịch những ý chính.

Trước khi vào bài, xin được giới thiệu qua về hai từ mà chúng ta hay gặp là pythonistapythoneer. Pythonista chỉ một người theo đuổi ngôn ngữ Python cuồng nhiệt, như là một cổ động viên bóng đá trung thành. Pythoneer chỉ một người luôn đi đầu và nắm bắt những điểm mới của ngôn ngữ Python, như là nhà khoa học tiên phong trong lĩnh vực của họ. Thông thường, hai từ này đều dùng để chỉ một người rất yêu thích Python, và có thể được hoán chuyển.

Kiểu lập trình: Dễ đọc là quan trọng

Programs must be written for people to read, and only incidentally for machines to execute.

Abelson & Sussman, Structure and Interpretation of Computer Programs

Tạm dịch: Chương trình phải được viết ra để cho người đọc, và chỉ là sự trùng hợp để cho máy thực thi.

Khoảng trắng

  • Bốn (4) khoảng trắng ở mỗi nấc thụt vào

  • Không dùng tab

  • Không bao giờ lẫn lộn tab và khoảng trắng

  • Cách một dòng giữa các hàm

  • Cách hai dòng giữa các lớp

  • Chừa một khoảng trắng sau dấu phẩy , trong từ điển, danh sách, bộ, danh sách tham số, và sau dấu hai chấm : trong từ điển nhưng không phải trước nó

  • Chừa một khoảng trắng trước và sau phép gán và so sánh (trừ khi trong danh sách tham số)

  • Không chừa khoảng trắng trong ngoặc tròn, hoặc ngay trước các danh sách tham số

  • Không chừa khoảng trắng trong các chuỗi tài liệu

Cách đặt tên

  • joined_lower cho hàm, phương thức và thuộc tính

  • joined_lower hoặc ALL_CAPS cho hằng

  • CapWords cho lớp

  • camelCase chỉ dùng để hợp với những thói quen đã có

  • Với các thuộc tính: public, _internal, và __private nhưng hạn chế dùng kiểu __private

Những câu lệnh ghép

Tốt:

if foo == 'blah':
    do_something()
do_one()
do_two()
do_three()

Xấu:

if foo == 'blah': do_something()
do_one(); do_two(); do_three()

Việc thiếu thụt vào ở mã "Xấu" che mất câu lệnh if. Sử dụng nhiều câu lệnh trên cùng một dòng là mang tội lớn.

Hoán đổi giá trị

Trong các ngôn ngữ khác:

temp = a;
a = b;
b = temp;

Trong Python:

b, a = a, b

Dấu _ tương tác

Đây là một chức năng thật sự hữu dụng mà ít người biết.

Trong môi trường thông dịch tương tác, khi bạn định giá một biểu thức hoặc gọi một hàm, kết quả sẽ được gán vào một tên tạm, _ (dấu gạch chân).

>>> 1 + 1
2
>>> _
2

_ chứa biểu thức được in cuối cùng. Khi kết quả là None, không có gì được in ra nên _ không thay đổi. Dấu gạch chân chỉ có tác dụng trong môi trường tương tác, không có tác dụng trong một mô-đun.

Tạo chuỗi từ các chuỗi con

Dùng:

colors = ['red', 'blue', 'green', 'yellow']
result = ''.join(colors)

Không dùng:

colors = ['red', 'blue', 'green', 'yellow']
result = ''
for s in colors:
    result += s

Trường hợp hay gặp khi danh sách có nhiều hơn một phần từ:

colors = ['red', 'blue', 'green', 'yellow']
print 'Choose', ', '.join(colors[:-1]), 'or', colors[-1]

In ra:

Choose red, blue, green or yellow

Dùng in khi có thể

Tốt:

for key in d:
    print key
  • in thường là nhanh hơn

  • có thể dùng được với danh sách, bộ, từ điển, và tập hợp

Xấu:

for key in d.keys():
    print key

Chỉ dùng được cho các đối tượng có phương thức keys().

Nhưng sẽ cần dùng keys() khi sửa đổi từ điển:

for key in d.keys():
    d[str(key)] = d[key]

keys() tạo ra một danh sách các khóa riêng để lặp. Nếu không, bạn sẽ gặp phải lỗi RuntimeError vì từ điển bị thay đổi trong khi lặp.

Để thống nhất, hãy dùng key in dict thay vì dict.has_key(key).

Phương thức get() của từ điển

Chúng ta thường khởi tạo các phần tử từ điển trước khi dùng. Đây là cách không hay:

navs = {}
for (portfolio, equity, position) in data:
    if portfolio not in navs:
        navs[portfolio] = 0
    navs[portfolio] += position * prices[equity]

Dùng dict.get(key, default) sẽ tránh được việc kiểm tra:

navs = {}
for (portfolio, equity, position) in data:
    navs[portfolio] = navs.get(portfolio, 0) + position * prices[equity]

Phương thức setdefault() của từ điển

Cách dở để khởi tạo một từ điển:

equities = {}
for (portfolio, equity) in data:
    if portfolio in equities:
        equities[portfolio].append(equity)
    else:
        equities[portfolio] = [equity]

Dùng dict.setdefault(key, default) nhanh gọn hơn nhiều:

equities = {}
for (portfolio, equity) in data:
    equities.setdefault(portfolio, []).append(equity)

dict.setdefault() tương đương với lấy, hoặc thiết lập rồi lấy. Nó rất hiệu quả nếu khóa từ điển cần nhiều thời gian để tính, hoặc vì nó dài nên khó nhập. Tuy nhiên giá trị default luôn luôn được tính cho dù có cần dùng hay không.

Phương thức setdefault() cũng có thể được dùng riêng vì hiệu quả phụ của nó:

navs = {}
for (portfolio, equity, position) in data:
    navs.setdefault(portfolio, 0)
    navs[portfolio] += position * prices[equity]

defaultdict

Trong Python 2.5, defaultdict là một phần của mô-đun collections. Nó giống như từ điển thường nhưng với hai đặc điểm khác:

  • Nó nhận một tham số là một hàm nhà máy mặc định (default factory functions) và

  • Khi khóa không thể tìm thấy trong từ điển, hàm này sẽ được gọi và kết quả trả về sẽ được dùng để khởi tạo khóa đó

Đây là ví dụ trước, mà mỗi phần tử trong từ điển là một danh sách rỗng, nhưng dùng defaultdict:

from collections import defaultdict

equities = defaultdict(list)
for (portfolio, equity) in data:
    equities[portfolio].append(equity)

Trong ví dụ này, hàm nhà máy mặc định là list. Để tạo từ điển giá trị mặc định là 0 thì dùng int:

navs = defaultdict(int)
for (portfolio, equity, position) in data:
    navs[portfolio] += position * prices[equity]

Vì khóa mới luôn được tạo nên bạn sẽ không gặp KeyError với defaultdict. Bạn phải dùng in để kiểm tra xem một khóa đã có trong từ điển hay chưa.

Kiểm tra giá trị đúng

# dùng:           # không dùng:
if x:             if x == True:
    pass              pass

Nếu là một danh sách:

# dùng:           # không dùng:
if items:         if len(items) != 0:
    pass              pass

                  # and definitely not this:
                  if items != []:
                      pass

Để điều khiển giá trị đúng của các trường hợp của một lớp người dùng định nghĩa, sử dụng các phương thức đặc biệt __nonzero__ hoặc __len__. Dùng __len__ nếu lớp đó là một lớp chứa (container) có chiều dài:

class MyContainer(object):

    def __init__(self, data):
        self.data = data

    def __len__(self):
        """Return my length."""
        return len(self.data)

Nếu lớp đó không phải là một lớp chứa thì dùng __nonzero__:

class MyClass(object):

    def __init__(self, value):
        self.value = value

    def __nonzero__(self):
        """Return my truth value (True or False)."""
        # This could be arbitrarily complex:
        return bool(self.value)

Trong Python 3.0, __nonzero__ được đổi tên thành __bool__ để đồng nhất với kiểu bool có sẵn. Thêm dòng sau vào lớp của bạn cho nó tương hợp hơn:

__bool__ = __nonzero__

Chỉ mục và phần tử

Dùng enumerate() để lặp:

for (index, item) in enumerate(items):
    print index, item

# thay vì:              # thay vì:
index = 0               for i in range(len(items)):
for item in items:          print i, items[i]
    print index, item
    index += 1

enumerate() trả về một bộ lặp (iterator) (một bộ sinh, generator, là một kiểu bộ lặp):

>>> enumerate(items)
<enumerate object at 0xb73ee0f4>
>>> e = enumerate(items)
>>> e.next()
(0, 'zero')
>>> e.next()
(1, 'one')
>>> e.next()
(2, 'two')
>>> e.next()
(3, 'three')
>>> e.next()
Traceback (most recent call last):
  File "", line 1, in ?
StopIteration

Các ngôn ngữ khác có biến

Trong các ngôn ngữ khác, gán vào một biến là đưa giá trị vào một hộp.

int a = 1;

a1box

Hộp a bây giờ chứa một số nguyên 1.

Gán một giá trị khác vào cùng biến đó sẽ thay thế những gì đã có trong hộp.

a = 2;

a2box

Gán một biến vào một biến khác sẽ tạo một bản sao của giá trị trong hộp này và đặt nó vào hộp mới.

int b = a;

b2box

a2box

a1boxa2boxb2box

b là một hộp mới, có giá trị là bản sao của số nguyên 2. Trong khi hộp a có một bản sao riêng.

Python có tên

Trong Python, tên hoặc định danh giống như thẻ tên gắn vào một đối tượng.

a = 1

a1tag

Ở đây, số nguyên 1 được gắn một thẻ tên a.

Nếu chúng ta gán lại vào a, chúng ta chỉ chuyển thẻ a vào một đối tượng khác.

a = 2

a2tag

1

Giờ đây tên a được gắn vào số nguyên 2. Số nguyên 1 có thể vẫn còn tồn tại nhưng nó không còn thẻ tên a.

Nếu chúng ta gán một tên vào một tên khác, chúng ta chỉ là gắn một thẻ tên khác vào đối tượng đã có.

b = a

ab2tag

a1taga2tag1ab2tag

Tên b chỉ là một thẻ tên thứ hai được gắn vào cùng một đối tượng như thẻ tên a.

Trong Python, biến là những thẻ tên, không phải là những hộp được đánh tên.

Gộp danh sách (list comprehension, hay listcomp)

Thông thường:

new_list = []
for item in a_list:
    if condition(item):
        new_list.append(fn(item))

Với listcomp:

new_list = [fn(item) for item in a_list
            if condition(item)]

Listcomp rõ ràng và xúc tích. Listcomp có thể chứa nhiều vòng for hoặc câu lệnh if nhưng nếu nhiều hơn 2 hay 3 thì tốt nhất là nên dùng cách thông thường. Ví dụ danh sách bình phương của các số lẻ từ 0 tới 9:

>>> [n ** 2 for n in range(10) if n % 2]
[1, 9, 25, 49, 81]

Biểu thức bộ sinh (generator expression, genexp)

Để tính tổng bình phương từ 1 đến 100 ta có thể dùng vòng lặp for:

total = 0
for num in range(1, 101):
    total += num * num

Hoặc dùng hàm sum() với listcomp:

total = sum([num * num for num in range(1, 101)])

Hoặc sum() với genexp:

total = sum(num * num for num in xrange(1, 101))

Biểu thức bộ sinh giống như gộp danh sách nhưng nó lười (lazy). Listcomp tạo danh sách ngay lập tức, còn genexp tạo từng giá trị một. Khi chúng ta không cần một danh sách mà chỉ cần từng giá trị của danh sách đó, genext rất hữu dụng. Ví dụ như để tính tổng bình phương từ 1 tới 1000000000, chúng ta sẽ dùng hết bộ nhớ nếu ta dùng listcomp, nhưng với genexp thì chuyện này có thể được thực hiện (mặc dù hơi lâu):

total = sum(num * num
            for num in xrange(1, 1000000000))

Sắp xếp với DSU

DSU là Decorate-Sort-Undecorate (trang hoàng, sắp xếp, khử trang hoàng).

Thay vì tạo một hàm so sánh riêng, ta có thể tạo một danh sách tạm sẽ được sắp xếp. Ví dụ để sắp xếp một danh sách các chuỗi theo thứ tự chữ cái thường của chúng:

a_list = "Mot Hai Ba".split()
# a_list = ["Mot", "Hai", "Ba"]

# Decorate:
to_sort = [(lower(x), x)
           for x in a_list]
# to_sort = [("mot", "Mot"), ("hai", "Hai"), ("ba", "Ba")]

# Sort:
to_sort.sort()
# to_sort = [("ba", "Ba"), ("hai", "Hai"), ("mot", "Mot")]

# Undecorate:
a_list = [item[-1] for item in to_sort]
# a_list = ["Ba", "Hai", "Mot"]

Đây là sự đổi chác giữa bộ nhớ và thời gian. Đơn giản và nhanh hơn, nhưng tốn bộ nhớ hơn vì chúng ta cần tạo một danh sách mới.

Bộ sinh (generator)

Chúng ta đã gặp biểu thức bộ sinh. Chúng ta cũng có thể tạo những bộ sinh phức tạp riêng như là những hàm:

def my_range_generator(stop):
    value = 0
    while value < stop:
        yield value
        value += 1

for i in my_range_generator(10):
    do_something(i)

Từ khóa yield biến một hàm thành một bộ sinh. Khi bạn gọi một hàm bộ sinh (generator function), thay vì thực thi mã ngay lập tức, Python trả về một đối tượng bộ sinh (generator object), cũng là một bộ lặp vì nó có phương thức next(). Vòng for gọi phương thức next() của bộ lặp cho đến khi biệt lệ StopIteration được nâng. Bạn có thể tự nâng StopIteration hoặc nó sẽ được nâng khi đến cuối bộ sinh.

Vòng for có một vế else sẽ được thực thi khi mà bộ lặp chạy xong, nhưng không được thực thi khi thoát khỏi bằng câu lệnh break. Ví dụ nếu chúng ta muốn kiểm tra xem điều kiện nào đó có thỏa với một phần tử bất kỳ của một dãy:

for item in sequence:
    if condition(item):
        break
    else:
        raise Exception('Condition not satisfied.')

Ví dụ để lọc các dòng trắng khỏi bộ đọc CSV hoặc các phần tử của một danh sách:

def filter_rows(row_iterator):
    for row in row_iterator:
        if row:
            yield row

data_file = open(path, 'rb')
irows = filter_rows(csv.reader(data_file))

Hoặc đọc từng dòng từ tập tin văn bản:

datafile = open('datafile')
for line in datafile:
    do_something(line)

Điều này làm được là vì các đối tượng tập tin hỗ trợ phương thức next() y như các bộ lặp khác: danh sách, bộ, từ điển (cho các khóa của nó), và các bộ sinh.

Một điều cần lưu ý là bạn không thể dùng lẫn lộn next()read() ở các đối tượng tập tin trừ khi bạn dùng Python 2.5 trở lên.

EAFP v.s. LBYL

Easier to Ask Forgiveness than Permission: Dễ xin sự tha thứ hơn là sự cho phép. Ý là cứ làm đi và tìm sự tha thứ sau nếu làm sai, còn hơn là tìm sự cho phép trước khi làm.

Look Before You Leap: Nhìn trước khi nhảy. Ý là phải xem xét hết các trường hợp có thể xảy ra trước khi làm.

Thông thường EAFP được ưa chuộng hơn.

Ví dụ như để ép kiểu một biến thành kiểu chuỗi, ta có thể gói đoạn mã trong một câu lệnh try thay vì dùng isinstance(). Và thông thường thì bạn sẽ nhận ra giải pháp tổng quát hơn là nếu bạn cố tìm ra mọi trường hợp có thể.

try:
    return str(x)
except TypeError:
    ...

Luôn luôn chỉ rõ kiểu biệt lệ! Không bao giờ dùng vế except đơn giản vì nó sẽ chụp luôn cả những biệt lệ không lường trước, làm cho mã của bạn khó gỡ rối.

Không dùng from module import *

Thay vào đó, tham chiếu tới tên qua tên mô-đun:

import module
module.name

Hoặc dùng tên mô-đun ngắn:

import long_module_name as mod
mod.name

Hoặc tự nhập vào các tên bạn cần:

from module import name
name

Mô-đun và kịch bản

Để vừa tạo một mô-đun và một kịch bản chạy được:

if __name__ == '__main__':
    # script code here

Trừ trường hợp rất cần thiết, bạn không nên đặt mã thực thi ở mức cao nhất mà hãy đặt chúng ở trong các hàm, các lớp, hoặc các phương thức rồi dùng nó trong if __name__ == '__main__'.

Cấu trúc mô-đun

Một mô-đun nên có cấu trúc như sau:

"""module docstring"""

# imports
# constants
# exception classes
# interface functions
# classes
# internal functions & classes

def main(...):
    ...

if __name__ == '__main__':
    status = main()
    sys.exit(status)

Gói

package/
    __init__.py    # <-- Lưu ý
    module1.py
    subpackage/
        __init__.py
        module2.py
  • Dùng để quản lý dự án

  • Giảm các mục trong đường dẫn nạp (load-path)

  • Giảm xung đột tên

Ví dụ:

import package.module1
from packages.subpackage import module2
from packages.subpackage.module2 import name

Trong Python 2.5 chúng ta có nhập tuyệt đối (absolute import) và nhập tương đối (relative import):

from __future__ import absolute_import

Đơn giản tốt hơn phức tạp

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Brian W. Kernighan, cùng tác giả của The C Programming Language và là chữ K trong AWK

Tạm dịch: Gỡ rối khó gấp hai lần viết mã. Cho nên, nếu bạn viết mã lanh lợi nhất có thể, thì bạn, theo định nghĩa, không đủ thông minh để gỡ rối. Nói một cách khác, hãy giữ cho chương trình của bạn đơn giản.

Đừng sáng tạo lại bánh xe

Trước khi viết mã, hãy:

  • Xem qua thư viện chuẩn của Python

  • Xem qua chỉ mục các gói Python (còn được biết đến là Cửa hàng phô mai, Cheese Shop)

  • Tìm qua mạng

Cập nhật tình hình Python 3000

written by vithon, on Jun 22, 2007 9:02:00 AM.

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.