Chương 2: Biến, biểu thức và câu lệnh

Trở về Mục lục cuốn sách

Giá trị và kiểu

Giá trị là một trong những cái cơ bản mà chương trình cần dùng đến, chẳng hạn như một chữ cái hoặc một con số. Các giá trị mà ta đã thấy đến giờ bao gồm 1, 2, và 'Hello, World!'.

Các giá trị này thuộc về hai kiểu khác nhau: 2 là một số nguyên, còn 'Hello, World!' là một chuỗi, được gọi như vậy vì nó là một chuỗi các kí tự ghép lại với nhau. Bạn (và trình thông dịch) có thể nhận ra các chuỗi vì chúng được đặt trong cặp dấu nháy.

Câu lệnh print cũng có tác dụng với các số nguyên.

>>> print 4
4

Nếu bạn không chắc rằng kiểu của một giá trị là gì, trình thông dịch có thể cho bạn biết.

>>> type('Hello, World!')
<type 'str'>
>>> type(17)
<type 'int'>

Thật không ngạc nhiên rằng chuỗi kí tự (string) thuộc về kiểu str và các số nguyên (integer) thuộc về kiểu int. Điều ít hiển nhiên là các số có phần thập phân thuộc về một kiểu có tên là float, vì những số này được biểu diễn dưới một dạng được gọi là dấu phẩy động (floating-point) [ta sẽ tạm gọi là số có phần thập phân trong cuốn sách này].

>>> type(3.2)

<type 'float'>

Thế còn các giá trị như '17''3.2'? Trông chúng giống như số, nhưng chúng được đặt trong cặp dấu nháy như các chuỗi.

>>> type('17')
<type 'str'>
>>> type('3.2')
<type 'str'>

Vậy chúng là các chuỗi.

Khi bạn gõ vào những số nguyên lớn, có thể bạn muốn dùng những dấu phẩy để nhóm từng lớp ba chữ số lại với nhau, như 1,000,000. Đây không phải là một số nguyên hợp lệ trong Python, nhưng vẫn đúng về mặt cú pháp:

>>> print 1,000,000
1 0 0

À, đó không phải là điều chúng ta mong muốn! Python dịch 1,000,000 như một danh sách các số nguyên được phân cách bởi các dấu phẩy, và khi in ra thì đặt dấu cách giữa các số này.

Đây là ví dụ đầu tiên mà chúng ta thấy một lỗi ngữ nghĩa: đoạn mã chạy mà không có lỗi được thông báo, nhưng nó không thực hiện điều “đúng”.

Biến

Một trong những tính năng mạnh nhất của một ngôn ngữ lập trình là khả năng thao tác với các biến. Biến là một tên gọi tham chiếu đến một giá trị.

Một lệnh gán tạo ra biến mới và đặt giá trị cho nó:

>>> message = 'And now for something completely different'
>>> n = 17
>>> pi = 3.1415926535897931

Ví dụ này có ba lệnh gán. Lệnh thứ nhất gán một chuỗi cho một biến có tên là message; lệnh thứ hai gán số nguyên 17 cho n; lệnh thứ ba gán giá trị (gần đúng) của π cho pi.

Một cách chung để biểu diễn các biến trên giấy là viết ra tên kèm theo một mũi tên chỉ đến giá trị của biến. Dạng hình vẽ này được gọi là sơ đồ trạng thái vì nó cho thấy trạng thái của mỗi biến hiện tại là như thế nào (hãy hình dung nó như trạng thái trí não của biến đó). Sơ đồ dưới đây cho thấy kết quả của ví dụ trước:

sơ đồ trạng thái

Để hiển thị giá trị của một biến, bạn có thể dùng lệnh print:

>>> print n
17

>>> print pi
3.14159265359

Kiểu của một biến là kiểu của giá trị mà biến đó tham chiếu đến.

>>> type(message)
<type 'str'>
>>> type(n)
<type 'int'>
>>> type(pi)
<type 'float'>

Nếu bạn gõ vào một số nguyên mà bắt đầu với chữ số 0, có thể bạn sẽ nhận được một thông báo lỗi khó hiểu:

>>> zipcode = 02492
                  ^
SyntaxError: invalid token

Với các số khác có vẻ như mọi việc bình thường, nhưng kết quả rất quái lạ:

>>> zipcode = 02132
>>> print zipcode
1114

Bạn có thể hình dung điều gì đang xảy ra không? Gợi ý: hãy in các giá trị 01, 010, 010001000.

Tên biến và từ khoá

Thông thường các lập trình viên chọn tên biến có nghĩa— tự nó nói lên rằng biến được dùng vào việc gì.

Tên biến có độ dài tuỳ ý. Chúng có thể gồm cả chữ cái và số, nhưng bắt buộc phải bắt đầu bằng một chữ cái. Dùng các chữ in cũng được, nhưng tốt nhất là bạn nên bắt đầu tên biến với chữ thường (sau này bạn sẽ biết tại sao).

Dấu gạch dưới (_) có thể xuất hiện trong một tên. Nó thường được dùng trong các tên gồm có nhiều từ, như my_name hoặc airspeed_of_unladen_swallow.

Nếu bạn đặt một tên biến không hợp lệ, sẽ có lỗi cú pháp:

>>> 76trombones = 'big parade'
SyntaxError: invalid syntax

>>> more@ = 1000000
SyntaxError: invalid syntax
>>> class = 'Advanced Theoretical Zymurgy'
SyntaxError: invalid syntax

76trombones không hợp lệ vì nó không bắt đầu bằng một chữ cái. more@ không hợp lệ vì nó có chứa một kí tự không hợp lệ, @. Nhưng còn class tại sao lại sai?

Hoá ra vì class là một trong những từ khoá của Python. Trình thông dịch sử dụng từ khoá để nhận ra cấu trúc của chương trình, và chúng không thể được dùng để đặt tên biến.

Python có 31 từ khoá1:

and       del       from      not       while    
as        elif      global    or        with     
assert    else      if        pass      yield    
break     except    import    print              
class     exec      in        raise              
continue  finally   is        return             
def       for       lambda    try

Bạn có thể ghi lại danh sách trên đây. Nếu trình thông dịch phàn nàn về một tên biến mà bạn không biết tại sao, hãy tra xem nó có nằm trong danh sách này không.

Câu lệnh

Câu lệnh là một đơn vị của mã lệnh mà trình thông dịch Python có thể thực hiện được. Chúng ta đã gặp hai loại câu lệnh: print và lệnh gán.

Khi bạn gõ một câu lệnh ở trong chế độ tương tác, trình thông dịch sẽ thực hiện nó và hiển thị kết quả, nếu có.

Một văn lệnh thường gồm một loạt các câu lệnh hợp thành. Nếu có hơn một câu lệnh thì kết quả của từng câu lệnh sẽ lần lượt được xuất hiện khi câu lệnh đó được thực thi.

Chẳng hạn, đoạn văn lệnh

print 1
x = 2
print x

cho ta kết quả

1
2

Câu lệnh gán không tạo ra kết quả.

Toán tử và toán hạng

Toán tử là các kí hiệu đặc biệt để biểu diễn các phép tính như cộng và nhân. Toán tử được áp dụng cho các giá trị được gọi là toán hạng.

Các toán tử +, -, *, /** biểu thị phép cộng, trừ, nhân, chia, và luỹ thừa như trong ví dụ sau:

20+32   hour-1   hour*60+minute   minute/60   5**2   (5+9)*(15-7)

Trong một số ngôn ngữ lập trình khác, ^ được dùng để tính luỹ thừa, nhưng với Python đó là một toán tử tính cho bit có tên là XOR. Tôi sẽ không trình bày các toán tử để tính cho bit trong sách này, nhưng bạn có thể đọc thêm về chúng ở trang wiki.python.org/moin/BitwiseOperators.

Toán tử chia có thể không thực hiện điều mà bạn mong đợi:

>>> minute = 59
>>> minute/60
0

Giá trị của minute là 59, và trong đại số thông thường thì 59 chia cho 60 bằng 0.98333, chứ không phải 0. Lí do của sự khác biệt ở đây là Python đã thực hiện phép chia làm tròn xuống2.

Khi cả hai toán hạng đều là số nguyên, kết quả cũng sẽ là một số nguyên; phép chia làm tròn xuống cắt bỏ phần thập phân, vì vậy trong ví dụ này kết quả được làm tròn xuống 0.

Nếu một trong hai toán hạng là một số có phần thập phân, Python sẽ thực hiện phép chia thập phân, và kết quả là một số thập phân (float):

>>> minute/60.0
0.98333333333333328

Biểu thức là một tổ hợp các giá trị, biến, và toán tử. Một giá trị bản thân nó cũng được coi như là một biểu thức, và một biến cũng vậy; vì thế tất cả những cái dưới đây đều là các biểu thức hợp lệ (giả sử rằng biến x đã được gán một giá trị):

17
x
x + 17

Nếu bạn gõ một biểu thức ở trong chế độ tương tác, trình thông dịch sẽ định lượng nó và hiển thị kết quả:

>>> 1 + 1
2

Nhưng trong một văn lệnh, một biểu thức bản thân nó không có tác dụng gì cả! Đây là một điểm dễ gây nhầm lẫn cho người mới học.

Hãy gõ những câu lệnh dưới đây vào trình thông dịch Python để xem chúng có tác dụng gì:

5
x = 5
x + 1

Bây giờ đưa chính các câu lệnh đó vào trong một văn lệnh và chạy nó. Kết quả là gì? Sửa lại văn lệnh bằng cách thay mỗi biểu thức bằng một câu lệnh print tương ứng và chạy lại.

Thứ tự thực hiện

Khi trong biểu thức có nhiều hơn một toán tử, thứ tự định lượng sẽ tuân theo quy tắc ưu tiên. Với các toán tử toán học, Python dựa vào quy ước chung trong môn toán. Chữ viết tắt PEMDAS là một cách nhớ quy tắc này:

  • Cặp ngoặc đơn (Parentheses) có thứ tự ưu tiên cao nhất và có thể được dùng để buộc việc lượng giá một biểu thức theo đúng thứ tự mà bạn mong muốn. Vì các biểu thức trong cặp ngoặc đơn được lượng giá trước tiên, 2 * (3-1) bằng 4, và (1+1)**(5-2) bằng 8. Bạn cũng có thể dùng cặp ngoặc đơn để biểu thức trở nên dễ đọc, như với (minute * 100) / 60, ngay cả khi không có nó thì kết quả cũng không đổi.
  • Phép luỹ thừa (Exponentiation) có thứ tự ưu tiên kế tiếp, vì vậy 2**1+1 bằng 3 chứ không phải 4, và 3*1**3 bằng 3 chứ không phải 27.
  • Các phép nhân (Multiplication) và chia (Division) có cùng độ ưu tiên, cao hơn các phép cộng (Addition) và trừ (Subtraction), hai phép sau cũng có cùng độ ưu tiên. Vì vậy 2*3-1 bằng 5 chứ không phải 4, và 6+4/2 bằng 8 chứ không phải 5.
  • Các toán tử có cùng độ ưu tiên được định lượng từ trái sang phải. Vì vậy, trong biểu thức degrees / 2 * pi, phép chia được thực hiện trước và kết quả sẽ được nhân với pi. Để chia cho , bạn có thể dùng cặp ngoặc đơn hoặc viết degrees / 2 / pi.

Các thao tác với chuỗi

Nói chung, bạn không thể thực hiện các phép toán đối với chuỗi, ngay cả khi chuỗi trông giống như những con số. Vì vậy các biểu thức sau đây đều không hợp lệ:

'2'-'1'    'eggs'/'easy'    'third'*'a charm'

Toán tử + có tác dụng với chuỗi, nhưng nó có thể sẽ không hoạt động theo cách bạn mong đợi: nó có nhiệm vụ nối, nghĩa là ghép nối tiếp các chuỗi lại với nhau. Chẳng hạn:

first = 'throat'
second = 'warbler'
print first + second

Kết quả của chương trình trên là throatwarbler.

Toán tử * cũng có tác dụng đối với chuỗi; nó có nhiệm vụ lặp lại. Chẳng hạn, 'Spam'*3'SpamSpamSpam'. Nếu một trong các toán hạng là chuỗi, toán hạng còn lại phải là một số nguyên.

Công dụng của +* cũng có nghĩa tương tự như với phép cộng và phép nhân. Giống như việc 4*3 tương đương với 4+4+4, chúng ta trông đợi 'Spam'*3 tương đương 'Spam'+'Spam'+'Spam', và thật vậy. Mặt khác, có một sự khác biệt đáng kể giữa kết nối và lặp chuỗi so với các phép cộng và nhân số nguyên. Bạn có thể nghĩ ra một thuộc tính của phép cộng mà phép nối chuỗi không có không?

Chú thích

Khi chương trình trở nên lớn và phức tạp hơn, chúng cũng đồng thời khó đọc hơn. Các ngôn ngữ hình thức rất cô đặc, và nhìn vào một đoạn mã lệnh ta thường khó hình dung ra nó để làm gì, hoặc tại sao.

Vì lí do này, ta nên thêm các ghi chú vào chương trình để giải thích rằng chương trình làm gì bằng ngôn ngữ tự nhiên. Các ghi chú này được gọi là chú thích, và đều bắt đầu bằng kí hiệu #:

# compute the percentage of the hour that has elapsed
percentage = (minute * 100) / 60

Trong trường hợp này, chú thích xuất hiện riêng trên một dòng. Bạn cũng có thể đặt chú thích ở cuối một dòng:

percentage = (minute * 100) / 60     # percentage of an hour

Mọi thứ từ dấu # về cuối dòng đều được bỏ qua—nó không làm ảnh hưởng đến tác dụng của chương trình.

Các chú thích rất cần thiết khi chúng đưa thông tin về những tính năng không dễ thấy của đoạn mã lệnh. Thường ta có thể coi rằng người đọc đều hình dung được mã lệnh làm làm gì; và tốt hơn là hãy dùng chú thích vào việc giải thích tại sao.

Trong đoạn mã lệnh sau, chú thích là thừa và vô dụng:

v = 5     # assign 5 to v

Chú thích sau chứa thông tin hữu dụng hơn mà mã lệnh không có:

v = 5     # velocity in meters/second. 

Việc đặt tên biến hợp lý có thể làm giảm nhu cầu dùng chú thích, nhưng những tên biến dài có thể làm các biểu thức khó đọc, vì vậy nó luôn có sự được–mất giữa hai mặt.

Gỡ lỗi

Cho đến giờ lỗi cú pháp mà bạn thường gặp nhất có lẽ là tên biến không hợp lệ, như classyield, vốn trùng với các từ khoá, hoặc odd~jobUS$, vốn có chứa các kí tự không hợp lệ.

Nếu bạn đặt một dấu cách về phía trước một tên biến, Python sẽ nghĩ rằng đó là hai toán hạng mà không kèm theo toán tử nào:

>>> bad name = 5
SyntaxError: invalid syntax

Với các lỗi cú pháp, dòng chữ thông báo lỗi không giúp được gì nhiều. Những thông báo lỗi thường gặp nhất là SyntaxError: invalid syntax (cú pháp không hợp lệ) và SyntaxError: invalid token (nguyên tố không hợp lệ), cả hai đều không mang thông tin đáng kể.

Loại lỗi khi chạy chương trình mà có lẽ bạn thường gặp nhất là “use before def”; nghĩa là bạn đã thử dùng một biến trước khi gán cho nó một giá trị. Điều này có thể xảy ra nếu bạn viết nhầm tên biến:

>>> principal = 327.68
>>> interest = principle * rate
NameError: name 'principle' is not defined

Các tên biến đều phân biệt chữ in và chữ thường, vì vậy, LaTeX khác với latex.

Cho đến giờ, nguyên do thường gặp nhất gây ra lỗi ngữ nghĩa là thứ tự thực hiện phép tính. Chẳng hạn, để định lượng 1/ 2π, có thể bạn đã toan viết

>>> 1.0 / 2.0 * pi

Nhưng phép chia lại được thực hiện trước, vì vậy bạn sẽ được π/2, vốn không giống kết quả đúng! Vì Python không có cách nào đoán biết ý của bạn khi viết chương trình nên trong trường hợp này bạn không thấy có thông báo lỗi; bạn chỉ thu được đáp số sai.

Thuật ngữ

giá trị:
Một trong những đơn vị cơ bản của dữ liệu, cũng như số hoặc chuỗi, mà chương trình thao tác với.

kiểu:
Loại riêng của các giá trị. Những kiểu mà ta đã gặp bao gồm kiểu số nguyên (int), số có phần thập phân (float), và chuỗi (str).

số nguyên:
Kiểu dùng để biểu diễn loại số tương ứng.

số có phần thập phân:
Kiểu dùng để biểu diễn loại số tương ứng.

chuỗi:
Kiểu dùng để biểu diễn một danh sách các kí tự.

biến:
Tên được tham chiếu đến một giá trị.

câu lệnh:
Đoạn mã biểu diễn một lệnh hoặc một hành động. Cho đến giờ, các câu lệnh mà ta đã gặp gồm có lệnh gán và lệnh print.

lệnh gán:
Lệnh để gán một giá trị cho một biến.

sơ đồ trạng thái:
Đồ thị biểu diễn một tập hợp các biến và các giá trị mà chúng tham chiếu tới.

từ khoá:
Từ dành riêng cho trình biên dịch để phân tách một chương trình; bạn không thể dùng những từ khoá như if, def, và while để đặt tên biến.

toán tử:
Kí hiệu đặc biệt để biểu diễn một phép tính đơn nhất như cộng, nhân, hoặc nối chuỗi.

toán hạng:
Một trong những giá trị mà toán tử thực hiện với.

phép chia làm tròn xuống:
Phép toán chia hai số và cắt bỏ phần thập phân.

biểu thức:
Tập hợp các biến, toán tử, và giá trị nhằm biểu diễn một giá trị kết quả duy nhất.

định lượng:
Giản hoá một biểu thức bằng cách thực hiện các phép tính nhằm thu được một giá trị duy nhất.

quy tắc ưu tiên:
Tập hợp các quy tắc chi phối thứ tự mà những biểu thức bao gồm nhiều toán tử và toán hạng được định lượng.

nối:
Ghép nối tiếp hai toán hạng.

chú thích:
Thông tin trong một chương trình; thông tin này có ích đối với các lập trình viên khác (hoặc người khác đọc mã nguồn) nhưng không làm ảnh hưởng đến việc thực hiện chương trình.

Bài tập

Giả sử ta thực hiện những câu lệnh gán dưới đây:

width = 17
height = 12.0
delimiter = '.'

Với mỗi biểu thức sau, hãy cho biết giá trị của biểu thức cùng với kiểu của giá trị đó.

  1. width/2
  2. width/2.0
  3. height/3
  4. 1 + 2 * 5
  5. delimiter * 5

Dùng trình thông dịch Python để kiểm tra kết quả.

Tập luyện cách dùng trình thông dịch Python thay cho máy tính tay:

  1. Thể tích của một hình cầu có bán kính r4/3 πr3. Thể tích của một hình cầu có bán kính bằng 5 là bao nhiêu? Gợi ý: 392.6 là đáp số sai!
  2. Coi rằng giá bìa của một cuốn sách là $24.95, nhưng các hiệu sách được mua giảm giá 40%. Tiền vận chuyển là $3 với cuốn sách đầu và 75 xu với mỗi cuốn sách thêm. Tổng số tiền bán sỉ cho 60 bản sách là bao nhiêu?
  3. Nếu tôi rời nhà lúc 6:52 sáng và chạy chậm 1 dặm (mỗi dặm hết 8:15), sau đó chạy mức trung bình 3 dặm (mỗi dặm hết 7:12) và tiếp tục chạy chậm 1 dặm, thì lúc mấy giờ tôi sẽ về đến nhà để ăn sáng?

  1. Trong Python 3.0, exec không còn là một từ khoá, nhưng lại có thêm từ khoá nonlocal.
  2. Trong Python 3.0, kết quả của phép chia này là một số có phần thập phân (float). Một toán tử mới // thực hiện phép chia làm tròn.

9 bình luận

Filed under Sách, Think Python

9 responses to “Chương 2: Biến, biểu thức và câu lệnh

  1. Pingback: Think Python: Cách tư duy như nhà khoa học máy tính | Blog của Chiến

  2. Pingback: Phụ lục: Lumpy | Blog của Chiến

  3. Ẩn danh

    hữu ích lắm anh ơi

  4. Ẩn danh

    em cảm ơn anh đã dịch cuốn sách này 🙂

  5. MayaJiyong

    Cam on anh da dich sach nay. ❤
    May qua, em dang co y dinh hoc Python va duoc goi y tim doc sach nay. Hihi
    Anh co the cho em dap an cua bai tap 3 duoc khong a?
    (Em xin loi vi khong danh may bang tieng Viet duoc.)

Bình luận về bài viết này