Các hàm tích hợp của Python: Khám phá hoàn chỉnh
Python có nhiều hàm dựng sẵn mà bạn có thể sử dụng trực tiếp mà không cần nhập bất cứ thứ gì. Các hàm này bao gồm nhiều tác vụ lập trình phổ biến, bao gồm thực hiện các phép toán, làm việc với các kiểu dữ liệu tích hợp, xử lý dữ liệu lặp lại, xử lý đầu vào và đầu ra trong chương trình của bạn, làm việc với phạm vi, v.v.
Trong hướng dẫn này, bạn sẽ:
- Tìm hiểu các hàm tích hợp của Python
- Tìm hiểu về các trường hợp sử dụng phổ biến của các hàm tích hợp trong Python
- Sử dụng các hàm này để giải các bài toán thực tế
Để tận dụng tối đa hướng dẫn này, bạn cần phải làm quen với lập trình Python, bao gồm các chủ đề như làm việc với các kiểu dữ liệu, hàm, lớp, trình trang trí, phạm vi và hệ thống nhập tích hợp.
Các hàm dựng sẵn trong Python
Python có sẵn một số hàm để bạn sử dụng trực tiếp từ bất kỳ đâu trong mã của mình. Các hàm này được gọi là hàm dựng sẵn và chúng bao gồm nhiều vấn đề lập trình phổ biến, từ tính toán toán học đến các tính năng dành riêng cho Python.
Lưu ý: Nhiều hàm dựng sẵn của Python là các lớp có tên kiểu hàm. Các ví dụ điển hình là str
, tuple
, list
và dict
, là các lớp xác định các kiểu dữ liệu có sẵn . Các lớp này được liệt kê trong tài liệu Python dưới dạng hàm dựng sẵn và bạn sẽ tìm thấy chúng trong hướng dẫn này.
Trong hướng dẫn này, bạn sẽ tìm hiểu những kiến thức cơ bản về các hàm dựng sẵn của Python. Cuối cùng, bạn sẽ biết trường hợp sử dụng của chúng là gì và chúng hoạt động như thế nào. Để bắt đầu, bạn sẽ bắt đầu với các hàm dựng sẵn liên quan đến tính toán.
Sử dụng các hàm tích hợp liên quan đến toán học
Trong Python, bạn sẽ tìm thấy một số hàm dựng sẵn xử lý các phép toán thông thường, như tính giá trị tuyệt đối của một số, tính lũy thừa, v.v. Dưới đây là bản tóm tắt các hàm dựng sẵn liên quan đến toán học trong Python:
abs()
Tính giá trị tuyệt đối của một số
divmod()
Tính thương và số dư của phép chia số nguyên
max()
Tìm giá trị lớn nhất trong số các đối số hoặc mục đã cho trong một lần lặp
min()
Tìm đối số hoặc mục nhỏ nhất trong số các đối số hoặc mục đã cho trong một lần lặp
pow()
Tăng một số lên lũy thừa
round()
Làm tròn một giá trị dấu phẩy động
sum()
Tính tổng các giá trị trong một lần lặp
Trong các phần sau, bạn sẽ tìm hiểu cách các hàm này hoạt động và cách sử dụng chúng trong mã Python của bạn.
Lấy giá trị tuyệt đối của một số: abs()
Giá trị tuyệt đối hoặc môđun của số thực là giá trị không âm của nó. Nói cách khác, giá trị tuyệt đối là số không có dấu. Ví dụ: giá trị tuyệt đối của -5 là 5 và giá trị tuyệt đối của 5 cũng là 5.
Lưu ý: Để tìm hiểu thêm về abs()
, hãy xem hướng dẫn Cách tìm giá trị tuyệt đối trong Python.
Hàm abs()
tích hợp sẵn của Python cho phép bạn tính nhanh giá trị tuyệt đối hoặc mô đun của một số:
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> abs(-42)
42
>>> abs(42)
42
>>> abs(-42.42)
42.42
>>> abs(42.42)
42.42
>>> abs(complex("-2+3j"))
3.605551275463989
>>> abs(complex("2+3j"))
3.605551275463989
>>> abs(Fraction("-1/2"))
Fraction(1, 2)
>>> abs(Fraction("1/2"))
Fraction(1, 2)
>>> abs(Decimal("-0.5"))
Decimal('0.5')
>>> abs(Decimal("0.5"))
Decimal('0.5')
Trong các ví dụ này, bạn tính giá trị tuyệt đối của các loại số khác nhau bằng hàm abs()
. Đầu tiên, bạn sử dụng số nguyên, sau đó là số dấu phẩy động và số phức, cuối cùng là số phân số và số thập phân. Trong mọi trường hợp, khi bạn gọi hàm có giá trị âm, kết quả cuối cùng sẽ xóa dấu.
Để có một ví dụ thực tế, giả sử bạn cần tính tổng lãi và lỗ của công ty bạn từ các giao dịch trong một tháng:
>>> transactions = [-200, 300, -100, 500]
>>> incomes = sum(income for income in transactions if income > 0)
>>> expenses = abs(
... sum(expense for expense in transactions if expense < 0)
... )
>>> print(f"Total incomes: ${incomes}")
Total incomes: $800
>>> print(f"Total expenses: ${expenses}")
Total expenses: $300
>>> print(f"Total profit: ${incomes - expenses}")
Total profit: $500
Trong ví dụ này, để tính toán chi phí, bạn sử dụng hàm abs()
để nhận giá trị tuyệt đối của chi phí, kết quả là giá trị dương.
Tìm Thương và Số dư trong phép chia: divmod()
Python cung cấp một hàm dựng sẵn có tên divmod()
lấy hai số làm đối số và trả về một bộ có thương và số dư là kết quả của phép chia số nguyên của các số đầu vào:
>>> divmod(8, 4)
(2, 0)
>>> divmod(6.5, 3.5)
(1.0, 3.0)
Với các số nguyên làm đối số, kết quả sẽ giống như (a/b, a % b)
. Với số có dấu phẩy động, kết quả là (q, a % b)
, trong đó q
thường là math.floor(a/b)
, nhưng có thể 1
ít hơn thế.
Như một ví dụ thực tế về thời điểm sử dụng hàm này, giả sử bạn muốn mã hóa một hàm nhận giá trị thời gian tính bằng mili giây và trả về một chuỗi có định dạng "00:00:00"
. Đây là một cách triển khai khả thi bằng cách sử dụng hàm divmod()
:
>>> def hh_mm_ss(milliseconds):
... seconds = round(milliseconds / 1000)
... minutes, seconds = divmod(seconds, 60)
... hours, minutes = divmod(minutes, 60)
... return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
...
>>> hh_mm_ss(10000)
'00:00:10'
>>> hh_mm_ss(68000)
'00:01:08'
>>> hh_mm_ss(3680000)
'01:01:20'
Trong hàm này, trước tiên bạn chuyển đổi mili giây đầu vào thành giây và làm tròn kết quả thành số nguyên gần nhất.
Sau đó, bạn sử dụng divmod()
để chia tổng số giây cho 60 vì một phút có 60 giây. Tính toán này cung cấp cho bạn số phút và số giây còn lại. Cuối cùng, bạn sử dụng lại divmod()
để chia số phút cho 60 vì một giờ có 60 phút. Lần này, bạn nhận được số giờ và số phút còn lại.
Tìm giá trị tối thiểu và tối đa: min()
và max()
Đôi khi, bạn cần tìm các giá trị nhỏ nhất và lớn nhất trong một giá trị có thể lặp lại hoặc trong một chuỗi giá trị. Đây có thể là những phép tính phổ biến trong lập trình và Python có sẵn các hàm dành cho chúng.
Lưu ý: Để tìm hiểu thêm về các hàm min()
và max()
, hãy xem min()
của Python và max()
: Hướng dẫn Tìm Giá trị Nhỏ nhất và Lớn nhất.
Hàm min()
cho phép bạn tìm giá trị nhỏ nhất trong một lần lặp, trong khi hàm max()
giúp bạn tìm giá trị lớn nhất. Đây là chữ ký của cả hai chức năng:
min(iterable, *[, default, key])
max(iterable, *[, default, key])
Cả hai hàm đều lấy một đối số bắt buộc được gọi là iterable
và trả về giá trị tối thiểu và tối đa tương ứng. Họ cũng có hai đối số chỉ từ khóa tùy chọn:
default
Có thể giữ giá trị bạn muốn trả về nếu lần lặp đầu vào trống
key
Chấp nhận hàm một đối số để tùy chỉnh tiêu chí so sánh
Dưới đây là một số ví dụ nhanh về cách sử dụng hàm min()
và max()
với các nhóm đối số khác nhau:
>>> min([1, 2, 3, 4])
1
>>> max([1, 2, 3, 4])
4
>>> min(1, 2, 3, 4)
1
>>> max(1, 2, 3, 4)
4
>>> min([], default=0)
0
>>> max([], default=0)
0
>>> min([-2, 3, 4, -5, 1], key=abs)
1
>>> max([-2, 3, 4, -5, 1], key=abs)
-5
Trong hai ví dụ đầu tiên, bạn sử dụng min()
và max()
với danh sách các số. Bạn cũng có thể sử dụng các hàm này với một loạt các đối số vị trí.
Sau đó, bạn có hai ví dụ về cách sử dụng đối số mặc định
để trả về một giá trị phù hợp khi lần lặp đầu vào trống. Cuối cùng, bạn có hai ví dụ về cách sử dụng đối số key
. Trong những ví dụ này, bạn sử dụng hàm abs()
để cung cấp tiêu chí so sánh.
Sức mạnh tính toán: pow()
Khi cần tính toán các lũy thừa trong mã của mình, bạn có thể sử dụng hàm pow()
tích hợp sẵn. Hàm này lấy một số và nâng nó lên một lũy thừa nhất định. Đây là chữ ký của hàm:
pow(base, exp[, mod=None])
Khi bạn gọi pow()
, bạn sẽ nhận được base
sức mạnh của exp
. Với hai đối số này, pow()
tương đương với base**exp
:
>>> pow(2, 8)
256
>>> 2**8
256
Thao tác này tính toán 2
với lũy thừa của 8
, là 256
. Điều này tương đương với thao tác nguồn với toán tử **
mà bạn sẽ thấy thường xuyên hơn trong mã thực tế.
Đối số mod
cho phép bạn thực hiện một số việc như pow(base, exp) % mod
nhưng được tính toán hiệu quả hơn:
>>> import timeit
>>> base = 2
>>> exp = 1000000
>>> mod = 1000000
>>> timeit.timeit(
... "pow(base, exp, mod)", globals=globals(), number=10
... ) * 1000
0.021042011212557554
>>> timeit.timeit(
... "pow(base, exp) % mod", globals=globals(), number=10
... ) * 1000
61.956208024639636
Trong ví dụ này, bạn sử dụng hàm timeit()
từ mô-đun timeit
để đo tốc độ tính toán. Sau đó, bạn xác định một vài biến để thực hiện tính toán. Trong lệnh gọi timeit()
đầu tiên, bạn sử dụng đối số mod
. Trong lệnh gọi thứ hai, bạn sử dụng toán tử modulo (%
).
Khi so sánh mức tiêu thụ thời gian thu được, bạn có thể kết luận rằng việc sử dụng đối số mod
nhanh hơn nhiều so với tính toán công suất rồi áp dụng toán tử modulo như trong pow(base, exp) % mod< /mã>.
Làm tròn số: round()
Hàm round()
tích hợp sẵn của Python nhận một đối số dạng số và trả về đối số được làm tròn thành một số chữ số nhất định.
Lưu ý: Để tìm hiểu thêm về cách làm tròn số và hàm round()
, hãy xem hướng dẫn Cách làm tròn số trong Python.
Chữ ký của round()
được hiển thị trong đoạn mã bên dưới:
round(number[, ndigits])
Trong chữ ký này, number
thường là số có dấu phẩy động, trong khi ndigits
là đối số tùy chọn phải là số nguyên. Đối số sau này sẽ xác định độ chính xác hoặc số chữ số sau dấu thập phân.
Dưới đây là một vài ví dụ:
>>> from math import pi
>>> pi
3.141592653589793
>>> round(pi, 2)
3.14
>>> round(pi, 4)
3.1416
>>> round(pi, 6)
3.141593
Trong các ví dụ này, bạn sử dụng hằng số pi
từ mô-đun toán học và hàm round()
để biểu thị số với độ chính xác khác nhau.
Khi bạn sử dụng round()
với một đối số duy nhất, bạn có thể nhận được kết quả đáng ngạc nhiên:
>>> round(1.5)
2
>>> round(2.5)
2
Trong hai ví dụ này, hàm round()
làm tròn 1,5
lên 2
và 2,5
xuống 2
. Điều này là do round()
làm tròn đến bội số gần nhất của 10
lũy thừa trừ ndigits
. Nếu hai bội số gần bằng nhau thì làm tròn số theo hướng chọn số chẵn. Chiến lược làm tròn một nửa thành chẵn này giúp giảm thiểu sai lệch làm tròn. Đó là lý do tại sao 2,5
làm tròn thành 2
thay vì 3
.
Tính tổng: sum()
Hàm sum()
tích hợp sẵn của Python cung cấp một cách Pythonic và hiệu quả để tính tổng danh sách các giá trị số, đây cũng là một bước trung gian phổ biến trong nhiều phép tính. Vì vậy sum()
là một công cụ khá tiện dụng dành cho lập trình viên Python.
Lưu ý: Để tìm hiểu sâu hơn về cách sử dụng sum()
, hãy xem hướng dẫn sum()
của Python: Cách tính tổng các giá trị của Python.
Hàm sum()
cho phép bạn cộng một loạt các giá trị lại với nhau. Chữ ký của nó giống như sau:
sum(iterable[, start=0])
Bạn có thể gọi sum()
với hai đối số sau:
iterable
Đối số bắt buộc có thể chứa bất kỳ Python nào có thể lặp lại.
start
Đối số tùy chọn có thể chứa giá trị ban đầu.
Khi bạn gọi sum()
, hàm này sẽ thêm start
bên trong cùng với các giá trị trong iterable
. Các mục trong đầu vào iterable
thường là các giá trị số. Tuy nhiên, bạn cũng có thể sử dụng danh sách hoặc bộ dữ liệu. Đối số start
có thể chấp nhận một số, danh sách hoặc bộ dữ liệu, tùy thuộc vào nội dung iterable
của bạn.
Dưới đây là một số ví dụ về cách sử dụng sum()
với các đầu vào khác nhau:
>>> sum([])
0
>>> sum([1, 2, 3, 4, 5])
15
>>> sum([1, 2, 3, 4, 5], 100) # As a positional argument
115
>>> sum([1, 2, 3, 4, 5], start=100) # As a keyword argument
115
>>> num_lists = [[1, 2, 3], [4, 5, 6]]
>>> sum(num_lists, start=[])
[1, 2, 3, 4, 5, 6]
>>> num_tuples = ((1, 2, 3), (4, 5, 6))
>>> sum(num_tuples, start=())
(1, 2, 3, 4, 5, 6)
Khi bạn gọi sum()
với một iterable trống, kết quả là bạn nhận được 0
vì đó là giá trị mặc định của start
. Gọi hàm với danh sách các giá trị sẽ trả về tổng các giá trị được cung cấp.
Nếu bạn muốn sử dụng giá trị bắt đầu
khác với 0
thì bạn có thể cung cấp giá trị đó làm đối số vị trí hoặc từ khóa. Tuy nhiên, cách tiếp cận sau dễ đọc hơn.
Hai ví dụ cuối cùng cho thấy rằng bạn cũng có thể sử dụng sum()
để nối các danh sách và bộ dữ liệu. Lưu ý rằng để thủ thuật này hoạt động, bạn cần đặt start
thành đối tượng thích hợp. Nếu bạn muốn nối các danh sách thì start
phải giữ một danh sách, v.v. Mặc dù thủ thuật này có tác dụng nhưng cách thực hành này không hiệu quả và không phổ biến. Thay vào đó, bạn nên sử dụng toán tử dấu cộng (+
) để nối thông thường.
Một ví dụ cổ điển về việc sử dụng sum()
là khi bạn cần tính giá trị trung bình hoặc trung bình của một số giá trị số. Trong tình huống này, bạn cần tính tổng dữ liệu đầu vào như một bước trung gian. Đây là một ví dụ:
def mean(values):
try:
return sum(values) / len(values)
except ZeroDivisionError:
raise ValueError("mean() arg shouldn't be empty") from None
Trong hàm mean()
này, bạn sử dụng sum()
để tính tổng các giá trị đầu vào rồi chia kết quả cho số giá trị trong dữ liệu đầu vào.
Tạo và thao tác các kiểu dữ liệu cơ bản
Python có một số hàm dựng sẵn cho phép bạn thao tác các kiểu dữ liệu cơ bản, chẳng hạn như số nguyên và số dấu phẩy động, chuỗi và giá trị Boolean. Dưới đây là bản tóm tắt các hàm tích hợp giúp bạn xử lý các loại dữ liệu cơ bản:
int()
Xây dựng một đối tượng số nguyên từ một số hoặc chuỗi
bin()
Chuyển đổi một số nguyên thành chuỗi nhị phân
oct()
Chuyển đổi một số nguyên thành một chuỗi bát phân
hex()
Chuyển đổi một số nguyên thành chuỗi thập lục phân
float()
Xây dựng một đối tượng dấu phẩy động từ một số hoặc chuỗi
complex()
Xây dựng một số phức từ các đối số
str()
Tạo một đối tượng chuỗi
repr()
Tạo biểu diễn chuỗi thân thiện với nhà phát triển của một đối tượng
bool()
Chuyển đổi một đối số thành giá trị Boolean
ord()
Tra cứu điểm mã số nguyên của một ký tự
chr()
Tra cứu ký tự cho điểm mã số nguyên đã cho
bytes()
Tạo một đối tượng
byte
(tương tự nhưbytearray
, nhưng không thể thay đổi)bytearray()
Tạo một đối tượng của lớp
bytearray
Trong các phần sau, bạn sẽ tìm hiểu những kiến thức cơ bản về cách làm việc với các hàm này và cách sử dụng chúng trong mã Python của mình.
Biểu diễn số nguyên: int()
, bin()
, oct()
và hex()
Số nguyên khá hữu ích trong lập trình. Python có kiểu dữ liệu tích hợp được gọi là int
đại diện cho số nguyên. Khi làm việc với số nguyên, đôi khi bạn cần biểu diễn chúng theo các cơ số khác nhau như 2
, 8
hoặc 16
. Bạn cũng có thể cần chuyển đổi chuỗi hoặc các loại số khác thành số nguyên.
Đối với tác vụ sau, bạn có thể sử dụng hàm int()
tích hợp sẵn. Dưới đây là một số ví dụ về việc sử dụng nó:
>>> int()
0
>>> int(42.42)
42
>>> int("42")
42
>>> int("42.42")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: '42.42'
>>> int("one")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'one'
Không có đối số, int()
trả về 0
. Hành vi này đặc biệt hữu ích khi bạn cần một hàm xuất xưởng cho các lớp như defaultdict
từ mô-đun collections
. Với số có dấu phẩy động, int()
chỉ xóa phần thập phân và trả về toàn bộ phần. Cuối cùng, với một chuỗi làm đối số, int()
chỉ trả về số nguyên tương ứng nếu chuỗi đó biểu thị một số nguyên hợp lệ.
Bạn cũng có thể sử dụng int()
để chuyển đổi biểu diễn chuỗi nhị phân, bát phân hoặc thập lục phân thành số nguyên:
>>> int("0b10", base=2)
2
>>> int("0o10", base=8)
8
>>> int("0x10", base=16)
16
Trong ví dụ đầu tiên, bạn sử dụng int()
để chuyển đổi một chuỗi biểu thị một số ở định dạng nhị phân thành số nguyên thập phân tương đương. Lưu ý rằng để thao tác này hoạt động, bạn cần đặt đối số base
thành cơ sở thích hợp, đó là 2
cho số nhị phân. Tiếp theo, bạn thực hiện các chuyển đổi tương tự với chuỗi bát phân và thập lục phân. Một lần nữa, bạn phải đặt base
thành giá trị thích hợp.
Các hàm bin()
, oct()
và hex()
cho phép bạn thực hiện thao tác ngược lại. Với chúng, bạn có thể chuyển đổi một số nguyên đã cho thành biểu diễn nhị phân, bát phân hoặc thập lục phân:
>>> bin(42)
'0b101010'
>>> oct(42)
'0o52'
>>> hex(42)
'0x2a'
Trong những ví dụ này, bạn sử dụng số nguyên làm đối số cho bin()
, oct()
và hex()
. Kết quả là, bạn có được biểu diễn chuỗi của giá trị đầu vào ở định dạng nhị phân, bát phân và thập lục phân tương ứng.
Thao tác với các số khác: float()
và complex()
Python có các kiểu dựng sẵn cơ bản để biểu diễn các số dấu phẩy động và phức. Các loại này có các chức năng tích hợp liên quan cho mục đích chuyển đổi. Vì vậy, đối với số dấu phẩy động, bạn có hàm float()
và đối với số phức bạn có complex()
.
Lưu ý: Để tìm hiểu sâu hơn về số phức và hàm complex()
, hãy xem hướng dẫn Đơn giản hóa số phức bằng Python.
Dưới đây là chữ ký của cả hai chức năng:
float(number=0.0)
complex(real=0, imag=0)
complex(string)
Hàm float()
nhận một đối số duy nhất biểu thị một giá trị số. Đối số này chấp nhận số hoặc chuỗi đại diện cho số hợp lệ:
>>> float()
0.0
>>> float(42)
42.0
>>> float("42")
42.0
>>> float("3.14")
3.14
>>> float("one")
Traceback (most recent call last):
...
ValueError: could not convert string to float: 'one'
Không có đối số, float()
trả về 0,0
. Với số nguyên, nó trả về số dấu phẩy động tương đương với 0
là phần thập phân. Với các chuỗi biểu thị số, float()
trả về số dấu phẩy động tương đương. Tuy nhiên, sẽ không thành công nếu chuỗi đầu vào không biểu thị giá trị số hợp lệ.
Hàm complex()
cho phép bạn làm việc với các số phức. Hàm này có hai chữ ký khác nhau. Chữ ký đầu tiên có hai đối số:
real
Phần thực của số
imag
Phần ảo của số
Các đối số này chấp nhận các giá trị số, chẳng hạn như số nguyên hoặc số dấu phẩy động. Đây là cách hoạt động của biến thể complex()
này:
>>> complex(3, 6)
(3+6j)
>>> complex(1, 0)
(1+0j)
>>> complex(0, 1)
1j
>>> complex(3.14, -2.75)
(3.14-2.75j)
Bạn có thể gọi complex()
bằng các giá trị số, dẫn đến số phức. Lưu ý rằng Python sử dụng j
để xác định phần ảo.
Chữ ký thứ hai của complex()
nhận một đối số duy nhất phải là một chuỗi:
>>> complex("3+6j")
(3+6j)
>>> complex("1+0j")
(1+0j)
>>> complex("1j")
1j
>>> complex("3.14-2.75j")
(3.14-2.75j)
Khi bạn sử dụng chuỗi để tạo số phức bằng complex()
, bạn phải đảm bảo rằng chuỗi đầu vào có định dạng hợp lệ. Nó phải bao gồm phần thực, dấu và phần ảo. Bạn không thể thêm dấu cách để phân tách các thành phần này.
Xây dựng và biểu diễn chuỗi: str()
và repr()
Khi nói đến việc tạo và làm việc với chuỗi Python, bạn có hai hàm dựng sẵn cơ bản cần xem xét:
str()
repr()
Với hàm str()
, bạn có thể tạo chuỗi mới hoặc chuyển đổi các đối tượng hiện có thành chuỗi:
>>> str()
''
>>> str(42)
'42'
>>> str(3.14)
'3.14'
>>> str([1, 2, 3])
'[1, 2, 3]'
>>> str({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"
>>> str({"A", "B", "C"})
"{'B', 'C', 'A'}"
Trong ví dụ đầu tiên, bạn sử dụng str()
không có đối số để tạo một chuỗi trống. Trong các ví dụ khác, bạn nhận được các chuỗi có cách trình bày thân thiện với người dùng của đối tượng đầu vào.
Trong trường hợp sử dụng thực tế, giả sử bạn có một danh sách các giá trị số và muốn nối chúng bằng phương thức str.join()
, phương thức này chỉ chấp nhận các chuỗi lặp. Trong trường hợp này, bạn có thể làm một cái gì đó như sau:
>>> "-".join(str(value) for value in [1, 2, 3, 4, 5])
'1-2-3-4-5'
Trong ví dụ này, bạn sử dụng biểu thức tạo để chuyển đổi từng số thành biểu diễn chuỗi trước khi gọi .join()
. Bằng cách này, bạn sẽ tránh gặp lỗi trong mã của mình.
Về phần mình, hàm repr()
tích hợp sẵn cung cấp cho bạn sự thể hiện thân thiện với nhà phát triển về đối tượng trong tầm tay:
>>> repr(42)
'42'
>>> repr(3.14)
'3.14'
>>> repr([1, 2, 3])
'[1, 2, 3]'
>>> repr({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"
>>> repr({"A", "B", "C"})
"{'B', 'C', 'A'}"
Đối với các kiểu có sẵn, cách biểu diễn chuỗi bạn nhận được bằng repr()
giống với cách biểu diễn chuỗi bạn nhận được bằng hàm str()
.
Lưu ý: Đằng sau hàm str()
, bạn có phương thức đặc biệt .__str__()
. Tương tự, đằng sau repr()
, bạn có phương thức .__repr__()
. Để tìm hiểu thêm về các phương thức đặc biệt này, hãy xem Khi nào bạn nên sử dụng .__repr__()
và .__str__()
trong Python? hướng dẫn.
Để thấy sự khác biệt giữa str()
và repr()
, hãy xem xét ví dụ sau sử dụng mô-đun datetime
:
>>> import datetime
>>> today = datetime.datetime.now()
>>> repr(today)
'datetime.datetime(2024, 7, 1, 12, 38, 53, 180208)'
>>> str(today)
'2024-07-01 12:38:53.180208'
Phương thức repr()
cung cấp cho bạn biểu diễn chuỗi thân thiện với nhà phát triển của đối tượng datetime
. Lý tưởng nhất là bạn có thể tạo lại đối tượng bằng cách sử dụng biểu diễn này. Nói cách khác, bạn có thể sao chép và dán biểu diễn kết quả để tạo lại đối tượng. Đó là lý do tại sao cách biểu diễn chuỗi này được cho là thân thiện với nhà phát triển.
Ngược lại, cách trình bày chuỗi mà bạn nhận được khi gọi str()
phải nhằm mục đích dễ đọc và mang tính thông tin cho người dùng cuối.
Xử lý các giá trị Boolean: bool()
Hàm bool()
tích hợp của Python cho phép bạn xác định giá trị thực của bất kỳ đối tượng Python nào. Đây là một hàm vị ngữ vì nó luôn trả về True
hoặc False
. Để tìm hiểu xem một đối tượng có phải là falsy hay không, nói cách khác, liệu bool()
có trả về False
khi áp dụng cho đối tượng hay không, Python sử dụng cách sau quy định nội bộ:
- Các hằng số được xác định là sai:
None
vàFalse
- Số 0 của bất kỳ loại số nào:
0
,0.0
,0j
,Decimal(0)
,Fraction (0, 1)
- Trình tự và bộ sưu tập trống:
''
,()
,[]
,{}
,set( )
,phạm vi (0)
Các đối tượng còn lại được coi là trung thực trong Python. Các đối tượng tùy chỉnh được coi là trung thực theo mặc định trừ khi chúng cung cấp một phương thức đặc biệt .__bool__()
để xác định một hành vi khác.
Lưu ý: Để tìm hiểu thêm về logic và giá trị Boolean, hãy xem hướng dẫn Python Booleans: Sử dụng giá trị thực trong mã của bạn.
Dưới đây là một số ví dụ về cách hoạt động của bool()
:
>>> bool()
False
>>> bool(0)
False
>>> bool(42)
True
>>> bool(0.0)
False
>>> bool(3.14)
True
>>> bool("")
False
>>> bool("Hello")
True
>>> bool([])
False
>>> bool([1, 2, 3])
True
Trong ví dụ đầu tiên, bạn gọi bool()
mà không có đối số và kết quả là nhận được False
. Trong các ví dụ còn lại, bạn có thể xác nhận rằng Python áp dụng nhất quán các quy tắc được liệt kê ở trên. Trong thực tế, bạn chỉ cần sử dụng bool()
khi mã của bạn yêu cầu rõ ràng giá trị Boolean thay vì một đối tượng khác.
Ví dụ về cách sử dụng bool()
, giả sử bạn có cách triển khai cấu trúc dữ liệu ngăn xếp sau đây:
class Stack:
def __init__(self, items=None):
self.items = list(items) if items is not None else []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __bool__(self):
return bool(self.items)
Trong ví dụ này, lớp Stack
của bạn triển khai phương thức đặc biệt .__bool__()
để hỗ trợ các phép toán Boolean trên các đối tượng của nó. Phương thức này đảm bảo rằng khi một đối tượng Stack
nhất định trống, hàm bool()
sẽ trả về False
và True
nếu không . Đây là một ví dụ:
>>> from stack import Stack
>>> stack = Stack()
>>> bool(stack)
False
>>> stack.push(4)
>>> bool(stack)
True
Trong đoạn mã này, trước tiên bạn tạo một ngăn xếp trống. Khi bạn chuyển đối tượng này tới bool()
, bạn sẽ nhận được False
. Sau đó, bạn đẩy một giá trị vào ngăn xếp và gọi lại bool()
. Lần này, bạn nhận được True
vì ngăn xếp không còn trống nữa.
Chuỗi mã hóa: ord()
và chr()
Mã hóa ký tự là một chủ đề quan trọng trong hầu hết các ngôn ngữ lập trình. Trong Python, chuỗi sử dụng các ký tự Unicode được đặt theo mặc định. Mỗi ký tự Unicode có một điểm mã liên kết, là một số nguyên. Để lấy điểm mã của một ký tự nhất định, bạn có thể sử dụng hàm ord()
tích hợp sẵn:
>>> ord("A")
65
>>> ord("Z")
90
>>> ord("x")
120
>>> ord("ñ")
241
>>> ord("&")
38
Mỗi ký tự Unicode có một điểm mã liên quan để xác định duy nhất ký tự trong bảng Unicode. Trong những ví dụ này, bạn sử dụng hàm để lấy điểm mã của một vài ký tự.
Trong thực tế, bạn có thể sử dụng hàm ord()
để triển khai các kỹ thuật mã hóa cơ bản, sắp xếp chuỗi hoặc ký tự, xác thực các ký tự đầu vào, v.v. Dưới đây là ví dụ đồ chơi nhanh về hàm chỉ kiểm tra xem tất cả các ký tự trong chuỗi có phải là chữ in hoa của bảng chữ cái tiếng Anh hay không:
>>> def is_uppercase(text):
... for char in text:
... if not (65 <= ord(char) <= 90):
... return False
... return True
...
>>> is_uppercase("HELLO")
True
>>> is_uppercase("Hello")
False
Trong hàm này, bạn sử dụng ord()
để xác định xem các ký tự trong chuỗi có nằm trong khoảng từ 65
đến 90
hay không, tức là khoảng thời gian của mã điểm cho các chữ in hoa, từ A đến Z, trong bảng Unicode.
Đôi khi, bạn có thể cần xác định điểm mã xác định một ký tự Unicode nhất định. Trong trường hợp này, bạn có thể sử dụng hàm chr()
tích hợp sẵn:
>>> chr(65)
'A'
>>> chr(90)
'Z'
>>> chr(120)
'x'
>>> chr(241)
'ñ'
>>> chr(38)
'&'
Hàm chr()
thực hiện thao tác ngược lại với ord()
. Nó cho phép bạn tìm điểm mã liên quan đến một ký tự cụ thể.
Các hàm ord()
và chr()
là loại bổ sung cho nhau và do đó, bạn có thể thấy chúng được sử dụng song song cùng nhau.
Tạo byte và mảng byte: bytes()
và bytearray()
Các byte và mảng byte của Python là các kiểu dựng sẵn mà Python cung cấp sẵn để thao tác dữ liệu nhị phân, mã hóa và giải mã văn bản, xử lý đầu vào và đầu ra tệp cũng như giao tiếp qua mạng.
Kiểu dữ liệu byte
là bất biến, trong khi loại bytearray
có thể thay đổi. Để tạo các đối tượng bắt nguồn từ các kiểu dữ liệu này, bạn có thể sử dụng các hàm bytes()
và bytearray()
tích hợp sẵn.
Các hàm bytes()
và bytearray()
có các chữ ký sau:
bytes(source=b"")
bytes(source, encoding)
bytes(source, encoding, errors)
bytearray(source=b"")
bytearray(source, encoding)
bytearray(source, encoding, errors)
Cả hai hàm đều có ba chữ ký khác nhau. Chữ ký đầu tiên của cả hai hàm đều chấp nhận một hằng byte
làm đối số. Những ký tự này tương tự như chuỗi ký tự, nhưng chúng bắt đầu bằng b
và chỉ chấp nhận các ký tự ASCII.
Dưới đây là bản tóm tắt các lập luận và ý nghĩa của chúng:
source
Một chữ
byte
hoặc một chuỗiencoding
Mã hóa ký tự được sử dụng để giải mã
nguồn
nếu nó chứa một chuỗierrors
Trình xử lý lỗi mã hóa và giải mã
Đối số encoding
chỉ được yêu cầu nếu đối số source
là một chuỗi, trong trường hợp đó, bạn phải cung cấp mã hóa thích hợp để Python có thể chuyển đổi chuỗi thành byte.
Cuối cùng, các đối số errors
cũng là tùy chọn và phải chứa một trong các trình xử lý lỗi sau:
"strict"
Đưa ra ngoại lệ
UnicodeDecodeError
hoặcUnicodeEncodeError
khi xuất hiện sự cố mã hóa"ignore"
Bỏ qua các ký tự không thể mã hóa
"replace"
Thay thế các ký tự không thể mã hóa bằng dấu chấm hỏi (
?
)"xmlcharrefreplace"
Thay thế các ký tự không thể mã hóa bằng tham chiếu ký tự XML
"backslashreplace"
Thay thế các ký tự không thể mã hóa bằng chuỗi thoát dấu gạch chéo ngược chuỗi của Python
Bằng cách chọn trình xử lý lỗi thích hợp, bạn có thể thiết lập một chiến lược tốt cho những tình huống đó khi bạn gọi các hàm bytes()
và bytearray()
có dữ liệu sai.
Dưới đây là một số ví dụ về cách sử dụng hàm bytes()
và bytearray()
:
>>> bytes()
b''
>>> bytes(b"Using ASCII characters or bytes \xc3\xb1")
b'Using ASCII characters or bytes \xc3\xb1'
>>> bytes("Using non-ASCII characters: ñ Ł", encoding="utf-8")
b'Using non-ASCII characters: \xc3\xb1 \xc5\x81'
>>> bytearray()
bytearray(b'')
>>> bytearray(b"Using ASCII characters or bytes \xc3\xb1")
bytearray(b'Using ASCII characters or bytes \xc3\xb1')
>>> bytearray("Using non-ASCII characters: ñ Ł", encoding="utf-8")
bytearray(b'Using non-ASCII characters: \xc3\xb1 \xc5\x81')
Trong các ví dụ này, bạn tạo các đối tượng byte
và bytearray
bằng cách sử dụng các ký tự byte
và các chuỗi có mã hóa chính xác làm đối số. Lưu ý rằng bạn có thể gọi hàm bytes()
mà không cần đối số để tạo đối tượng bytes
trống.
Bây giờ hãy xem xét các ví dụ sau đây cho thấy cách sử dụng trình xử lý lỗi:
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii"
... )
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\xf1'
in position 52: ordinal not in range(128)
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii",
... errors="ignore"
... )
b'Using non-ASCII characters with the ASCII encoding: '
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii",
... errors="replace"
... )
b'Using non-ASCII characters with the ASCII encoding: ? ?'
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii",
... errors="xmlcharrefreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: ñ Ł'
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii",
... errors="backslashreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: \\xf1 \\u0141'
Trong những ví dụ này, bạn chỉ sử dụng bytes()
vì bytearray()
sẽ hoạt động tương tự. Sự khác biệt duy nhất là bytes()
trả về các đối tượng bất biến trong khi bytearray()
trả về các đối tượng có thể thay đổi.
Các ví dụ này sử dụng các ký tự không phải ASCII với mã hóa ASCII, điều này sẽ gây ra lỗi mã hóa mà bạn cần phải xử lý. Giá trị mặc định của đối số errors
là "strict"
. Đó là lý do tại sao bạn nhận được ngoại lệ UnicodeEncodeError
trong ví dụ đầu tiên ở trên.
Sau đó, bạn đặt lỗi
thành "bỏ qua"
để Python bỏ qua mọi lỗi mã hóa. Trong trường hợp này, các ký tự ñ
và Ł
sẽ bị loại bỏ. Nếu bạn đặt lỗi
thành "thay thế"
thì ñ
và Ł
đều được thay thế bằng dấu chấm hỏi.
Việc sử dụng "xmlcharrefreplace"
làm trình xử lý lỗi sẽ khiến Python thay thế các ký tự ñ
và Ł
bằng tham chiếu ký tự XML tương ứng của chúng. Cuối cùng, sử dụng "backslashreplace"
để thoát khỏi các ký tự có vấn đề bằng cách sử dụng chuỗi thoát thích hợp.
Tạo kiểu dữ liệu bộ sưu tập
Một tính năng cơ bản của Python là tập hợp các kiểu dữ liệu thu thập phong phú được tích hợp trong ngôn ngữ. Bạn sẽ có một số hàm dựng sẵn cho phép bạn thao tác các kiểu dữ liệu này, bao gồm danh sách, bộ dữ liệu, từ điển, bộ và byte.
Dưới đây là bản tóm tắt các hàm tích hợp giúp bạn xử lý các loại dữ liệu bộ sưu tập:
list()
Tạo một đối tượng
danh sách
từ một đối tượng có thể lặp lạituple()
Tạo một đối tượng
tuple
từ một đối tượng có thể lặp lạidict()
Tạo một đối tượng
dict
từ một loạt các cặp khóa-giá trị hoặc đối số từ khóaset()
Tạo một đối tượng
set
từ một iterablefrozenset()
Tạo một đối tượng
freezeset
từ một đối tượng có thể lặp lại
Trong các phần sau, bạn sẽ tìm hiểu những kiến thức cơ bản về cách làm việc với các hàm này cũng như cách sử dụng chúng để tạo và thao tác với các bộ sưu tập trong mã Python của bạn.
Tạo danh sách và bộ dữ liệu: list()
và tuple()
danh sách
của Python là kiểu dữ liệu tích hợp cơ bản với bộ tính năng ấn tượng. Danh sách có thể thay đổi và cho phép bạn tổ chức và thao tác hiệu quả dữ liệu có thể không đồng nhất nhưng thường đồng nhất. Ví dụ: bạn có thể sử dụng danh sách để lưu trữ một cột từ bảng cơ sở dữ liệu.
Lưu ý: Để tìm hiểu thêm về cách làm việc với danh sách và hàm list()
, hãy xem hướng dẫn danh sách
của Python: Hướng dẫn tìm hiểu sâu với các ví dụ .
Tương tự, tuple
của Python là một loại tích hợp khác. Không giống như danh sách, bộ dữ liệu là bất biến. Bạn có thể sử dụng chúng để sắp xếp dữ liệu có thể đồng nhất nhưng thường không đồng nhất. Ví dụ: bạn có thể sử dụng bộ dữ liệu để lưu trữ một hàng từ bảng cơ sở dữ liệu.
Lưu ý: Để tìm hiểu thêm về cách làm việc với các bộ dữ liệu và hàm tuple()
, hãy xem hướng dẫn về tuple
của Python: Hướng dẫn tìm hiểu sâu với các ví dụ .
Các hàm list()
và tuple()
tích hợp sẵn của Python cho phép bạn tạo các đối tượng list
và tuple
.
Hàm list()
lấy một đối số có thể lặp lại và trả về một đối tượng list
được tạo từ dữ liệu đầu vào. Vì vậy, chữ ký của nó trông giống như sau:
list([iterable])
Lưu ý rằng dấu ngoặc vuông xung quanh iterable
có nghĩa là đối số là tùy chọn, vì vậy dấu ngoặc không phải là một phần của cú pháp.
Lưu ý: Trong thực tế, list()
là hàm tạo lớp chứ không phải là hàm. Tuy nhiên, tài liệu Python gọi nó là một hàm.
Dưới đây là một số ví dụ về cách sử dụng list()
để tạo các đối tượng list
:
>>> list()
[]
>>> list("Hello")
['H', 'e', 'l', 'l', 'o']
>>> list((1, 2, 3, 4, 5))
[1, 2, 3, 4, 5]
>>> list({"circle", "square", "triangle", "rectangle", "pentagon"})
['square', 'rectangle', 'triangle', 'pentagon', 'circle']
>>> list({"name": "John", "age": 30, "city": "New York"})
['name', 'age', 'city']
>>> list({"name": "John", "age": 30, "city": "New York"}.keys())
['name', 'age', 'city']
>>> list({"name": "John", "age": 30, "city": "New York"}.values())
['John', 30, 'New York']
>>> list({"name": "John", "age": 30, "city": "New York"}.items())
[('name', 'John'), ('age', 30), ('city', 'New York')]
Khi bạn gọi list()
mà không có đối số, bạn sẽ tạo một danh sách trống mới. Khi bạn sử dụng một chuỗi làm đối số, bạn sẽ tạo một danh sách các ký tự. Khi bạn sử dụng một bộ, bạn chuyển đổi bộ đó thành một danh sách.
Lưu ý: Trong hầu hết các trường hợp, bạn sẽ sử dụng một cặp dấu ngoặc vuông []
để tạo danh sách trống. Tuy nhiên, trong một số trường hợp, việc sử dụng list()
có thể dễ đọc hoặc rõ ràng hơn.
Hàm list()
thậm chí còn chấp nhận các tập hợp, nhưng bạn cần nhớ rằng các tập hợp là cấu trúc dữ liệu không có thứ tự, vì vậy bạn sẽ không thể dự đoán thứ tự cuối cùng của các mục trong danh sách kết quả.
Khi nói đến việc sử dụng từ điển với list()
, bạn có bốn khả năng. Bạn có thể tạo danh sách các khóa bằng cách sử dụng từ điển trực tiếp hoặc sử dụng phương thức .keys()
của nó. Nếu bạn muốn tạo danh sách các giá trị, bạn có thể sử dụng phương thức .values()
. Cuối cùng, nếu bạn muốn tạo danh sách các cặp khóa-giá trị thì bạn có thể sử dụng phương thức .items()
.
Danh sách có nhiều trường hợp sử dụng trong mã Python. Chúng linh hoạt, mạnh mẽ và có đầy đủ tính năng nên bạn sẽ tìm thấy chúng trong hầu hết mọi đoạn mã Python.
Các bộ dữ liệu thường được sử dụng để lưu trữ dữ liệu không đồng nhất và không thể thay đổi. Hàm tuple()
cho phép bạn tạo các bộ dữ liệu một cách nhanh chóng. Đây là chữ ký:
tuple([iterable])
Dấu ngoặc vuông xung quanh iterable
có nghĩa là đối số là tùy chọn, vì vậy dấu ngoặc không phải là một phần của cú pháp.
Hãy xem xét các ví dụ sau về việc sử dụng tuple()
trong mã của bạn:
>>> tuple()
()
>>> tuple("Hello")
('H', 'e', 'l', 'l', 'o')
>>> tuple(["Jane Doe", 25, 1.75, "Canada"])
('Jane Doe', 25, 1.75, 'Canada')
>>> tuple({
... "manufacturer": "Ford",
... "model": "Mustang",
... "color": "Blue",
... }.values())
('Ford', 'Mustang', 'Blue')
Bạn có thể sử dụng tuple()
không có đối số để tạo một bộ dữ liệu trống. Điều này sẽ dễ đọc hơn so với việc sử dụng một cặp dấu ngoặc đơn trống ()
. Khi bạn chuyển một chuỗi vào tuple()
, bạn sẽ nhận được một bộ ký tự.
Trong ví dụ thứ ba, bạn sử dụng tuple()
để chuyển đổi danh sách dữ liệu không đồng nhất thành một bộ dữ liệu, đây sẽ là cấu trúc dữ liệu phù hợp hơn để lưu trữ loại dữ liệu này. Cuối cùng, bạn sử dụng các giá trị của từ điển để xây dựng một bộ dữ liệu.
Cũng giống như danh sách, bộ dữ liệu khá hữu ích trong Python. Bạn sẽ thấy chúng được sử dụng trong nhiều trường hợp sử dụng, đặc biệt là trong những trường hợp bạn cần lưu trữ dữ liệu không đồng nhất bất biến.
Xây dựng từ điển: dict()
Từ điển là cấu trúc dữ liệu tích hợp cơ bản trong Python. Chúng ở khắp mọi nơi và là một phần cốt lõi của ngôn ngữ. Bạn sẽ tìm thấy nhiều trường hợp sử dụng từ điển trong mã của mình. Đối với các bộ sưu tập tích hợp khác, Python cũng có một hàm tích hợp cho phép bạn tạo từ điển: hàm dict()
.
Lưu ý: Để tìm hiểu thêm về từ điển và hàm dict()
, hãy xem hướng dẫn Từ điển trong Python.
Hàm dict()
có các chữ ký sau:
dict(**kwargs)
dict(mapping, **kwargs)
dict(iterable, **kwargs)
Tất cả các chữ ký này chấp nhận những gì được gọi là đối số từ khóa (**kwargs
) hoặc đối số được đặt tên. Chữ ký thứ hai lấy một ánh xạ, có thể là một từ điển khác. Cuối cùng, chữ ký thứ ba chấp nhận một cặp khóa-giá trị có thể lặp lại, ví dụ: có thể là danh sách các bộ dữ liệu gồm hai mục.
Dưới đây là một số ví dụ nhanh về cách sử dụng hàm dict()
theo nhiều cách khác nhau:
>>> dict()
{}
>>> jane = dict(name="Jane", age="30", country="Canada")
>>> jane
{'name': 'Jane', 'age': '30', 'country': 'Canada'}
>>> dict(jane, job="Python Dev")
{'name': 'Jane', 'age': '30', 'country': 'Canada', 'job': 'Python Dev'}
>>> dict([("name", "Jane"), ("age", 30), ("country", "Canada")])
{'name': 'Jane', 'age': 30, 'country': 'Canada'}
Một lần nữa, khi tạo một từ điển trống, bạn có thể sử dụng hàm dict()
mà không cần đối số. Điều này ít phổ biến hơn so với việc sử dụng một cặp dấu ngoặc nhọn {
, nhưng một lần nữa, nó có thể dễ đọc và rõ ràng hơn trong một số ngữ cảnh.
Sau đó, bạn tạo một từ điển jane
bằng cách sử dụng các đối số từ khóa. Đây là một cách rõ ràng và tinh tế để xây dựng từ điển bằng Python.
Ví dụ thứ ba cho thấy cách bạn có thể kết hợp ánh xạ với các đối số từ khóa để tạo một đối tượng từ điển mới. Cuối cùng, trong ví dụ thứ tư, bạn tạo một từ điển mới từ danh sách các bộ dữ liệu.
Tạo Bộ và Bộ đông lạnh: set()
và frozenset()
bộ
của Python là kiểu dữ liệu tích hợp để tạo các bộ sưu tập các đối tượng duy nhất và có thể băm, thường được gọi là phần tử hoặc thành viên. Trong Python, các tập hợp hỗ trợ các phép toán được xác định cho các tập hợp toán học, bao gồm hợp, hiệu, sai phân đối xứng và các phép toán khác.
Python có hai loại tập hợp:
set
frozenset
Sự khác biệt giữa hai loại dữ liệu này là đối tượng set
có thể thay đổi và đối tượng frozenset
là bất biến.
Lưu ý: Để tìm hiểu thêm về tập hợp và hàm set()
, hãy xem hướng dẫn về Tập hợp trong Python.
Cũng như các loại dữ liệu khác, Python cũng cung cấp các hàm dựng sẵn để tạo tập hợp và tập hợp cố định. Bạn sẽ có các hàm set()
và frozenset()
tương ứng. Chữ ký cho các chức năng này được hiển thị dưới đây:
set([iterable])
frozenset([iterable])
Một lần nữa, dấu ngoặc vuông cho biết rằng lần lặp đầu vào là tùy chọn. Bây giờ hãy xem các ví dụ sau về cách tạo học phần và học phần cố định:
>>> set()
set()
>>> frozenset()
frozenset()
>>> set(["square", "rectangle", "triangle", "pentagon", "circle"])
{'square', 'triangle', 'circle', 'rectangle', 'pentagon'}
>>> frozenset(["square", "rectangle", "triangle", "pentagon", "circle"])
frozenset({'square', 'triangle', 'circle', 'rectangle', 'pentagon'})
>>> set(("red", "green", "blue", "red"))
{'green', 'red', 'blue'}
>>> frozenset(("red", "green", "blue", "red"))
frozenset({'green', 'red', 'blue'})
Khi bạn gọi set()
và frozenset()
mà không có đối số, bạn sẽ tạo một tập hợp trống hoặc tập hợp cố định tương ứng. Trong trường hợp tập hợp, bạn không có chữ mà bạn có thể sử dụng để tạo tập hợp trống vì một cặp dấu ngoặc nhọn ({}
) xác định một từ điển trống. Vì vậy, để tạo một tập hợp trống, bạn phải sử dụng hàm set()
.
Trong các ví dụ còn lại, bạn sử dụng các biến lặp, chẳng hạn như danh sách và bộ dữ liệu, để tạo các tập hợp và tập hợp cố định. Điều quan trọng cần lưu ý là khi iterable đầu vào có các phần tử lặp lại, tập cuối cùng sẽ có một phiên bản duy nhất của mục lặp lại. Ngoài ra, tập hợp là cấu trúc dữ liệu không có thứ tự, vì vậy bạn sẽ không thể dự đoán thứ tự cuối cùng của các mục trong tập hợp kết quả khi cung cấp danh sách.
Xử lý Iterables và Iterators
Trình lặp và trình lặp của Python là hai công cụ khác nhau nhưng có liên quan với nhau, rất hữu ích khi bạn cần lặp qua luồng dữ liệu hoặc tập hợp dữ liệu. Các trình vòng lặp cung cấp năng lượng và kiểm soát quá trình lặp, trong khi các trình lặp thường chứa dữ liệu có thể được lặp qua một giá trị tại một thời điểm.
Python có một số hàm dựng sẵn mà bạn có thể sử dụng để làm việc với các iterable và iterator. Dưới đây là tóm tắt về các chức năng này:
len()
Tính chiều dài của một vật có kích thước
reversed()
Xây dựng một trình vòng lặp ngược
sorted()
Tạo một danh sách được sắp xếp từ một iterable
all()
Kiểm tra xem tất cả các phần tử của iterable có đúng không
any()
Kiểm tra xem có phần tử nào của iterable là đúng không
range()
Tạo ra một phạm vi các giá trị số nguyên
enumerate()
Tạo một trình lặp gồm các bộ dữ liệu chứa các chỉ số và giá trị từ một trình lặp có thể lặp lại
slice()
Tạo một đối tượng
slice
zip()
Tạo một trình vòng lặp tổng hợp các phần tử từ các vòng lặp
iter()
Xây dựng một đối tượng iterator
next()
Lấy mục tiếp theo từ một iterator
filter()
Lọc các phần tử từ một iterable
map()
Áp dụng một hàm cho mọi mục của một lần lặp
Trong các phần sau, bạn sẽ tìm hiểu về tất cả các hàm dựng sẵn này và cách chúng có thể hữu ích khi xử lý các iterable và iterators trong mã Python của bạn.
Xác định số lượng mục trong một thùng chứa: len()
Một trong những thao tác phổ biến nhất mà bạn sẽ thực hiện trên các bộ sưu tập là xác định số lượng mục được lưu trữ trong một chuỗi hoặc bộ sưu tập hiện có. Để hoàn thành nhiệm vụ này, Python có hàm len()
tích hợp sẵn.
Lưu ý: Để tìm hiểu thêm về hàm len()
, hãy xem hướng dẫn Sử dụng hàm len()
trong Python.
Hàm len()
nhận một đối số duy nhất, có thể là một chuỗi, chẳng hạn như một chuỗi, bộ dữ liệu hoặc danh sách. Nó cũng có thể là một bộ sưu tập, chẳng hạn như từ điển, bộ hoặc bộ cố định. Hàm trả về độ dài hoặc số lượng mục của đối tượng đầu vào.
Dưới đây là một số ví dụ về cách sử dụng len()
với các đối tượng khác nhau:
>>> len("Python")
6
>>> len(("Jane Doe", 25, 1.75, "Canada"))
4
>>> len([1, 2, 3, 4, 5])
5
>>> len({"green", "red", "blue"})
3
>>> len({"name": "Jane", "age": 30, "country": "Canada"})
3
Trong ví dụ đầu tiên, bạn sử dụng len()
để lấy số ký tự trong một chuỗi. Sau đó, bạn sử dụng hàm này để xác định độ dài của bộ, danh sách và tập hợp. Cuối cùng, bạn sử dụng len()
với từ điển làm đối số. Trong trường hợp này, bạn nhận được số cặp khóa-giá trị trong từ điển đầu vào.
Lưu ý rằng len()
trả về 0
khi bạn gọi nó với vùng chứa trống:
>>> len("")
0
>>> len(())
0
>>> len([])
0
Trong những ví dụ này, bạn sử dụng len()
với một chuỗi, bộ dữ liệu và danh sách trống. Trong mọi trường hợp, kết quả là bạn nhận được 0
.
Hàm len()
có thể hữu ích trong một số trường hợp. Một ví dụ phổ biến là khi bạn cần tính giá trị trung bình của một chuỗi giá trị số:
>>> grades = [90, 97, 100, 87]
>>> sum(grades) / len(grades)
93.5
Trong ví dụ này, len()
cung cấp cho bạn số giá trị trong danh sách điểm. Sau đó, bạn sử dụng giá trị này để tính điểm trung bình.
Đảo ngược và sắp xếp các vòng lặp: reversed()
và sorted()
Đảo ngược và sắp xếp các giá trị trong một iterable là một thao tác hữu ích khác trong lập trình. Vì những thao tác này rất phổ biến nên Python có sẵn các hàm dành cho chúng. Khi bạn muốn đảo ngược một iterable, bạn có thể sử dụng hàm reversed()
tích hợp sẵn.
Lưu ý: Để tìm hiểu sâu hơn về danh sách đảo ngược bằng hàm reversed()
, hãy xem Danh sách Python đảo ngược: Beyond .reverse()
và hướng dẫn
reversed().
Hàm reversed()
lấy một đối số có thể lặp lại và trả về một trình vòng lặp mang lại các mục theo thứ tự ngược lại:
>>> reversed([0, 1, 2, 3, 4, 5])
<list_reverseiterator object at 0x107062b30>
>>> list(reversed([0, 1, 2, 3, 4, 5]))
[5, 4, 3, 2, 1, 0]
Trong ví dụ này, bạn gọi reversed()
với một danh sách các số. Hàm trả về một trình vòng lặp ngược. Để hiển thị nội dung của nó, bạn có thể sử dụng hàm list()
để tạo danh sách từ trình vòng lặp.
Lưu ý: Chuyển đổi các trình vòng lặp thành danh sách là trường hợp sử dụng phổ biến cho hàm list()
, đặc biệt là khi gỡ lỗi hoặc làm việc tương tác.
Tương tự, khi cần sắp xếp các giá trị trong một iterable, bạn có thể sử dụng hàm sorted()
. Hàm này có chữ ký sau:
sorted(iterable, key=None, reverse=False)
Đối số đầu tiên là một đối tượng có thể lặp lại, chẳng hạn như một chuỗi, danh sách, bộ dữ liệu hoặc từ điển. Sau đó, bạn có các đối số key
và reverse
, có ý nghĩa sau:
key
Chỉ định hàm một đối số trích xuất khóa so sánh từ mỗi phần tử trong lần lặp đầu vào
reverse
Là giá trị Boolean cho phép bạn sắp xếp các mục theo thứ tự ngược lại nếu bạn đặt thành
True
Điều quan trọng cần lưu ý là đối số key
chấp nhận các đối tượng hàm. Nói cách khác, các hàm hoạt động không có dấu ngoặc đơn gọi.
Lưu ý: Để tìm hiểu thêm về hàm sorted()
, hãy xem Cách sử dụng sorted()
và .sort()
trong hướng dẫn Python.
Dưới đây là một số ví dụ về cách sử dụng sorted()
với các vùng chứa tích hợp khác nhau làm đối số:
>>> sorted("bdeac")
['a', 'b', 'c', 'd', 'e']
>>> sorted([4, 2, 7, 5, 1, 6, 3])
[1, 2, 3, 4, 5, 6, 7]
>>> sorted([4, 2, 7, 5, 1, 6, 3], reverse=True)
[7, 6, 5, 4, 3, 2, 1]
Không giống như reversed()
, hàm sorted()
luôn trả về một đối tượng danh sách chứ không phải một trình vòng lặp. Trong ví dụ đầu tiên, bạn sử dụng một chuỗi làm đối số cho sorted()
và nhận danh sách các ký tự theo thứ tự bảng chữ cái. Dưới mui xe, Python sử dụng điểm mã Unicode của ký tự để so sánh các chuỗi.
Lưu ý: Để tìm hiểu sâu hơn về cách sắp xếp chuỗi, hãy xem hướng dẫn Cách sắp xếp chuỗi Unicode theo thứ tự bảng chữ cái trong Python.
Trong ví dụ thứ ba, bạn đặt đối số reverse
thành True
và nhận danh sách các số được sắp xếp theo thứ tự ngược lại.
Cuối cùng, đối số key
có thể hữu ích trong một số trường hợp. Trường hợp sử dụng phổ biến của đối số này là khi bạn cần sắp xếp các mục cũng là vùng chứa. Dưới đây là ví dụ về sắp xếp danh sách các bộ giá trị có hai giá trị:
>>> points = [(1, 2), (3, 1), (4, 0), (2, 1)]
>>> sorted(points, key=lambda point: point[0])
[(1, 2), (2, 1), (3, 1), (4, 0)]
>>> sorted(points, key=lambda point: point[1])
[(4, 0), (3, 1), (2, 1), (1, 2)]
Trong dòng được đánh dấu đầu tiên, bạn sử dụng hàm lambda
để lấy một điểm làm đối số và trả về tọa độ đầu tiên của điểm đó. Ví dụ này sẽ tạo ra kết quả tương tự nếu bạn gọi sorted()
mà không có key
do cách Python so sánh các bộ dữ liệu. Trong dòng được đánh dấu thứ hai, hàm lambda
trả về tọa độ thứ hai. Các hàm này cung cấp các khóa so sánh cho quá trình sắp xếp.
Vì vậy, trong ví dụ đầu tiên, bạn sắp xếp các điểm theo tọa độ đầu tiên, trong khi ở ví dụ thứ hai, bạn sắp xếp các điểm theo tọa độ thứ hai.
Xác định giá trị thực của các mục trong Iterables: all()
và any()
Đôi khi, bạn có thể cần xác định xem tất cả các mục trong một lần lặp có đúng hay không. Để làm điều này, Python có hàm all()
tích hợp sẵn. Vào những lúc khác, bạn có thể cần tìm hiểu xem liệu ít nhất một mục trong một lần lặp có đúng hay không. Với mục đích này, Python có sẵn hàm any()
.
Chữ ký của all()
và any()
được hiển thị bên dưới:
all(iterable)
any(iterable)
Cả hai hàm đều lấy một đối tượng có thể lặp lại làm đối số. Hàm all()
trả về True
khi tất cả các mục trong dữ liệu đầu vào có thể lặp lại là đúng và False
khi có ít nhất một mục là sai.
Lưu ý: Để tìm hiểu thêm về hàm all()
, hãy xem hướng dẫn all()
của Python: Kiểm tra tính trung thực của Iterables của bạn.
Dưới đây là một số ví dụ về cách hoạt động của all()
:
>>> all([1, 2, 3, 4])
True
>>> all([1, 2, 3, 4, 0])
False
>>> all(["Hello", ""])
False
>>> all(["Hello", "World"])
True
Trong hai ví dụ đầu tiên, bạn sử dụng all()
với danh sách các số. Danh sách đầu tiên chứa các giá trị số nguyên khác với 0
mà Python coi là đúng. Vì vậy, all()
trả về True
. Sau đó, trong ví dụ thứ hai, bạn thêm 0
vào danh sách và all()
trả về False
vì giá trị này được coi là sai trong Python .
Trong hai ví dụ cuối cùng, bạn sử dụng danh sách các chuỗi. Python coi một chuỗi trống là sai, vì vậy all()
trả về False
. Cuối cùng, bạn chuyển một danh sách có hai chuỗi không trống và all()
chạy lại True
.
Bạn có thể tìm thấy một số trường hợp sử dụng all()
trong mã thực tế. Có lẽ việc chuyển một biểu thức tạo làm đối số cho all()
là một trong những cách mạnh mẽ nhất để sử dụng hàm. Ví dụ: giả sử bạn muốn xác định xem tất cả các số trong danh sách có nằm trong một khoảng nhất định hay không. Trong tình huống này, bạn có thể sử dụng all()
như trong đoạn mã sau:
>>> numbers = [10, 5, 6, 4, 7, 8, 18]
>>> # Between 0 and 10
>>> all(0 <= x <= 10 for x in numbers)
False
>>> # Between 0 and 20
>>> all(0 <= x <= 20 for x in numbers)
True
Trong dòng được đánh dấu đầu tiên, bạn sử dụng biểu thức trình tạo làm đối số cho all()
. Trình tạo kiểm tra xem các số có nằm trong khoảng từ 0
đến 10
hay không và tạo ra các giá trị Boolean theo kết quả của điều kiện.
Hàm all()
kiểm tra xem tất cả các giá trị được tạo có phải là True
hay không. Nếu đúng như vậy thì bạn sẽ nhận được True
. Mặt khác, nếu ít nhất một trong các giá trị được tạo là False
thì bạn sẽ nhận được False
.
Ngược lại với all()
, hàm any()
trả về True
nếu có ít nhất một mục là đúng và Sai
nếu tất cả các mục đều sai. Vì vậy, bạn có thể sử dụng any()
khi cần xác định xem ít nhất một mục trong một lần lặp có đúng hay không.
Lưu ý: Để tìm hiểu thêm về hàm any()
, hãy xem hướng dẫn Cách sử dụng any()
trong Python.
Dưới đây là một số ví dụ về cách sử dụng any()
trong Python:
>>> any([0, 0.0, False, "", []])
False
>>> any([0, 0.0, False, "", [], 42])
True
Trong ví dụ đầu tiên, tất cả các đối tượng trong danh sách đầu vào đều sai theo quy tắc nội bộ của Python để xác định giá trị thật của một đối tượng. Bởi vì tất cả các đối tượng đều sai, any()
trả về False
. Trong ví dụ thứ hai, bạn thêm số 42
vào cuối danh sách đầu vào. Vì 42
là trung thực nên any()
trả về True
.
Một lần nữa, giống như all()
, hàm any()
có thể phát huy tác dụng khi bạn sử dụng nó với biểu thức tạo để kiểm tra một số điều kiện. Ví dụ: giả sử bạn muốn biết liệu có ít nhất một chữ cái trong một văn bản nhất định có viết hoa hay không. Trong tình huống này, bạn có thể làm một số việc như sau:
>>> any(letter.isupper() for letter in "hello, world!")
False
>>> any(letter.isupper() for letter in "Hello, World!")
True
Trong những ví dụ này, bạn sử dụng phương thức str.isupper()
để xác định xem một chữ cái có viết hoa hay không. Phương thức này trả về True
hoặc False
, do đó bạn nhận được giá trị Boolean có thể lặp lại. Công việc của any()
là xác định xem có bất kỳ giá trị nào trong số này là đúng hay không.
Trong ví dụ đầu tiên, bạn không có chữ in hoa, vì vậy any()
trả về False
. Trong ví dụ thứ hai, bạn có H
viết hoa, vì vậy any()
trả về True
.
Tạo dãy giá trị số nguyên: range()
Đôi khi, bạn cần xây dựng các phạm vi số để biểu thị một chuỗi giá trị nguyên trong một khoảng nhất định. Thông thường, bạn cần các số liên tiếp, nhưng bạn cũng có thể muốn chúng không theo thứ tự.
Ví dụ: bạn có thể tạo một phạm vi giá trị chứa tất cả các chữ số bằng danh sách Python:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Cách tiếp cận này sẽ hiệu quả nếu phạm vi của bạn tương đối nhỏ như trong ví dụ này. Tuy nhiên, điều gì sẽ xảy ra nếu phạm vi của bạn cần có một triệu giá trị? Xây dựng loại phạm vi đó bằng một danh sách sẽ là một nhiệm vụ khó khăn. Có một cách tốt hơn.
Python có hàm range()
tích hợp sẵn để tạo điều kiện thuận lợi cho việc tạo các phạm vi số:
>>> range(10)
range(0, 10)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1_000_000)
range(0, 1000000)
>>> list(range(1_000_000))
[1, 2, 3,
...,
999998, 999999]
Hàm range()
trả về một đối tượng range
thay vì một danh sách. Nếu muốn kiểm tra các giá trị trong một phạm vi cụ thể thì bạn có thể gói nó trong lệnh gọi list()
, như bạn đã làm trong ví dụ thứ hai. Bây giờ bạn có một danh sách các chữ số.
Trong ví dụ thứ ba, bạn tạo một dải ô có một triệu số nguyên từ 0
đến 999999
. Nếu bạn muốn xem nó hoạt động, hãy chuyển nó tới list()
. Bạn sẽ nhận được một cửa sổ terminal đầy những con số!
Lưu ý: Để tìm hiểu sâu hơn về cách làm việc với các phạm vi và hàm range()
, hãy xem hướng dẫn Python range()
: Trình bày dãy số.
Hàm range()
tích hợp có các chữ ký sau:
range(stop)
range(start, stop, step=1)
Bạn có biến thể một đối số và ba đối số của hàm. Đây là ý nghĩa của từng đối số:
start
Nó giữ giá trị ban đầu trong phạm vi. Nó mặc định là
0
và được bao gồm trong phạm vi.stop
Nó giữ giá trị tại đó phạm vi dừng lại. Đó là đối số bắt buộc và giá trị của nó không nằm trong phạm vi.
step
Nó giữ bước thông qua các giá trị liên tiếp. Đó là một đối số tùy chọn mặc định là
1
.
Cho đến nay, bạn đã sử dụng biến thể một đối số, bắt đầu phạm vi tại 0
và xây dựng một phạm vi các số nguyên liên tiếp. Dưới đây là một số ví dụ hiển thị biến thể ba đối số:
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(10, 101, 10))
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Lưu ý rằng trong biến thể ba đối số, đối số thứ ba là tùy chọn. Điều này có nghĩa là bạn có thể gọi hàm với hai đối số và dựa vào giá trị mặc định của bước
, là 1
. Đó là những gì bạn làm trong ví dụ đầu tiên, ví dụ này xây dựng một dải số nguyên liên tiếp từ 1
đến 11
. Một lần nữa, 11
không được bao gồm trong phạm vi cuối cùng vì đó là giá trị mà tại đó range()
ngừng phát hành giá trị.
Trong ví dụ thứ hai, bạn tạo một phạm vi
bắt đầu ở 10
và tăng lên 101
với bước 10
>.
Bạn cũng có thể sử dụng các giá trị âm cho các đối số của range()
. Trường hợp sử dụng phổ biến cho việc này là khi bạn cần tạo phạm vi số âm và phạm vi đếm ngược:
>>> list(range(-10, 0))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1]
>>> list(range(100, 0, -10))
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
Trong ví dụ này, phạm vi
đầu tiên chứa các số âm liên tiếp từ -10
cho đến 0
. Phạm vi
thứ hai đếm ngược từ 100
đến 0
với bước -10
.
Phạm vi có thể hữu ích trong một số tình huống. Trường hợp sử dụng tốt cho một phạm vi là khi bạn cần thực hiện một thao tác với số lần nhất định:
>>> for _ in range(3):
... print("Beep")
...
Beep
Beep
Beep
Vòng lặp này chạy ba lần và in thông báo ra màn hình. Tuy nhiên, vòng lặp không cần sử dụng biến vòng lặp trong phần thân của nó. Vì vậy, bạn sử dụng dấu gạch dưới để biểu thị rằng đây là biến dùng một lần.
Một cách sử dụng sai mục đích phổ biến của các đối tượng range
trong Python là sử dụng chúng như một cách để lặp qua một iterable hiện có:
>>> letters = ["a", "b", "c", "d"]
>>> for index in range(len(letters)):
... print(index, letters[index])
...
0 a
1 b
2 c
3 d
Loại vòng lặp for
này không phải là ví dụ điển hình nhất về cấu trúc Pythonic. Nó sử dụng range()
để lặp qua các chữ cái có chỉ số bằng số. Trong phần sau, bạn sẽ tìm hiểu về cách Pythonic để viết loại vòng lặp này.
Đếm các mục trong vòng lặp: enumerate()
Vòng lặp for
Pythonic lặp lại các mục trong một iterable mà không xem xét chỉ mục của một mục nhất định hoặc thứ tự xử lý các mục đó. Bạn sẽ thấy rằng hầu hết các vòng lặp for
của Python sẽ trông giống như sau:
>>> colors = ["red", "orange", "yellow", "green"]
>>> for color in colors:
... print(color)
...
red
orange
yellow
green
Cách viết vòng lặp trong Python này rất rõ ràng và trực quan, điều này ảnh hưởng sâu sắc đến khả năng đọc của vòng lặp. Tuy nhiên, đôi khi bạn sẽ cần một cách để truy cập vào chỉ mục nơi một mục nhất định nằm trong đầu vào có thể lặp lại.
Thông thường, khi bạn bắt đầu với Python và cần truy cập các chỉ mục trong một vòng lặp, bạn có thể sẽ viết một vòng lặp như sau:
>>> for index in range(len(colors)):
... print(index, colors[index])
...
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
Loại vòng lặp này hoạt động. Tuy nhiên, đó không phải là thứ bạn có thể gọi là vòng lặp Pythonic. Nó sử dụng một vài thủ thuật để bằng cách nào đó bắt chước việc lặp lại các chỉ mục. Python cung cấp một công cụ tốt hơn để thực hiện việc này và công cụ đó là hàm enumerate()
được tích hợp sẵn.
Lưu ý: Để tìm hiểu sâu hơn về cách sử dụng hàm enumerate()
, hãy xem hướng dẫn Python enumerate()
: Đơn giản hóa các vòng lặp cần bộ đếm.
Đây là cách bạn viết vòng lặp trên bằng hàm enumerate()
:
>>> for index, color in enumerate(colors):
... print(index, color)
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
Vòng lặp này có thể đọc được và rõ ràng. Hàm enumerate()
lấy một đối số có thể lặp lại và tạo ra các bộ dữ liệu gồm hai mục chứa chỉ mục số nguyên và mục được liên kết.
Đây là chữ ký của hàm:
enumerate(iterable, start=0)
Đối số đầu tiên là một đối tượng có thể lặp lại. Đối số thứ hai, start
, cung cấp cho bạn tùy chọn để xác định giá trị bắt đầu cho bảng liệt kê. Giá trị mặc định là 0
vì đó là giá trị bắt đầu thông thường của các chỉ mục trong lập trình. Tuy nhiên, trong một số trường hợp, việc sử dụng điểm bắt đầu khác, như 1
, có thể thuận tiện.
Để minh họa cách sử dụng đối số start
, giả sử bạn đang xây dựng một ứng dụng giao diện người dùng (TUI) dựa trên văn bản và muốn hiển thị một menu với một số tùy chọn. Các tùy chọn phải có số liên kết để người dùng có thể chọn hành động mong muốn. Trong tình huống này, bạn có thể sử dụng enumerate()
như trong đoạn mã bên dưới:
>>> def list_menu(options):
... print("Main Menu:")
... for index, option in enumerate(options, start=1):
... print(f"{index}. {option}")
...
>>> list_menu(["Open", "Save", "Settings", "Quit"])
Main Menu:
1. Open
2. Save
3. Settings
4. Quit
Từ quan điểm của người dùng cuối, việc bắt đầu danh sách menu tại 1
là cách tự nhiên. Bạn có thể đạt được hiệu ứng này bằng cách đặt start
thành 1
trong lệnh gọi enumerate()
. Bây giờ, menu bắt đầu tại 1
thay vì 0
.
Trích xuất các lát hoặc các phần của chuỗi: slice()
Khi làm việc với Python, bạn có thể cần trích xuất một phần hoặc một phần của chuỗi hiện có, chẳng hạn như chuỗi, danh sách hoặc bộ dữ liệu. Để thực hiện việc này, bạn thường sử dụng toán tử cắt ([]
). Tuy nhiên, bạn cũng có thể sử dụng hàm slice()
được tích hợp sẵn. Hàm slice()
có các chữ ký sau:
slice(stop)
slice(start, stop, step=None)
Hàm slice()
trả về một đối tượng slice
đại diện cho tập hợp các chỉ mục được chỉ định bởi range(start, stop, step)
. Các đối số ở đây có ý nghĩa tương tự như trong hàm range()
:
start
Nó giữ giá trị ban đầu trong lát cắt. Giá trị mặc định là
Không
, cho biết điểm bắt đầu của chuỗi.stop
Nó giữ giá trị tại đó slice dừng lại. Đó là một đối số bắt buộc và giá trị của nó không được bao gồm trong slice. Khi được đặt thành
Không
, điều đó có nghĩa là kết thúc chuỗi (len(sequence)
).step
Nó giữ bước thông qua các giá trị liên tiếp. Đó là một đối số tùy chọn mặc định là
Không
, nghĩa là một bước1
.
Các lát cắt không đại diện cho phạm vi số mà là tập hợp các chỉ số. Bạn có thể sử dụng các chỉ mục này để trích xuất một phần của danh sách. Dưới đây là một vài ví dụ:
>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> even = numbers[slice(1, None, 2)]
>>> even
[2, 4, 6, 8]
>>> odd = numbers[slice(None, None, 2)]
>>> odd
[1, 3, 5, 7, 9]
Trong phần đầu tiên, bạn bắt đầu tại 1
và đi lên cuối danh sách với bước 2
. Lát này cung cấp cho bạn một danh sách các số chẵn. Trong ví dụ thứ hai, bạn bắt đầu từ đầu danh sách và đi lên cuối danh sách với bước 2
. Với lát cắt này, bạn sẽ có được một danh sách các số lẻ.
Lưu ý rằng trong hầu hết mã Python, bạn sẽ không nhìn thấy hoặc sử dụng slice()
như cách bạn đã làm trong ví dụ trên. Trong hầu hết các trường hợp, bạn sẽ sử dụng toán tử cắt [start:stop:step]
. Đây là giao diện của toán tử này:
>>> even = numbers[1::2]
>>> even
[2, 4, 6, 8]
>>> odd = numbers[::2]
>>> odd
[1, 3, 5, 7, 9]
Trong những ví dụ này, bạn sử dụng toán tử cắt để lấy số chẵn và số lẻ từ danh sách ban đầu của mình. Lưu ý rằng việc bỏ qua một chỉ mục nhất định sẽ khiến chỉ mục đó dựa vào giá trị mặc định của nó. Ví dụ: khi bạn không cung cấp chỉ mục bắt đầu
thì nó sẽ mặc định ở đầu danh sách.
Nén các vòng lặp cho phép lặp song song: zip()
Hàm zip()
tích hợp sẵn của Python cho phép bạn lặp lại nhiều lần lặp song song. Hàm này tạo ra một trình lặp sẽ tổng hợp các phần tử từ hai hoặc nhiều lần lặp, tạo ra các bộ giá trị.
Lưu ý: Để tìm hiểu thêm về hàm zip()
, hãy xem hướng dẫn Sử dụng hàm zip()
của Python cho phép lặp song song.
Đây là chữ ký của hàm zip()
tích hợp sẵn:
zip(*iterables, strict=False)
Hàm này lấy số lần lặp không xác định làm đối số và tạo ra các bộ mục theo yêu cầu. Các bộ dữ liệu sẽ chứa một mục từ mỗi đầu vào có thể lặp lại, khiến nó trở nên lý tưởng cho việc lặp song song.
Dưới đây là một số ví dụ nhanh:
>>> letters = ["a", "b", "c"]
>>> numbers = [1, 2, 3]
>>> operators = ["*", "/", "+"]
>>> for characters in zip(letters, numbers, operators):
... print(characters)
...
('a', 1, '*')
('b', 2, '/')
('c', 3, '+')
>>> for l, n, o in zip(letters, numbers, operators):
... print(f"{l} {n} {o}")
...
a 1 *
b 2 /
c 3 +
Trong vòng lặp đầu tiên, bạn sử dụng một biến vòng lặp duy nhất để lưu trữ từng bộ dữ liệu mà bạn nhận được từ zip()
. Bộ dữ liệu đầu tiên chứa các mục đầu tiên của mỗi lần lặp đầu vào. Bộ dữ liệu thứ hai chứa các mục thứ hai, v.v. Trong vòng lặp thứ hai, bạn sử dụng ba biến vòng lặp để giải nén các mục của mỗi bộ dữ liệu được tạo.
Một trường hợp sử dụng hay của zip()
là khi bạn có hai danh sách và muốn tạo một từ điển từ chúng. Hãy xem xét ví dụ sau:
>>> keys = ["name", "age", "country"]
>>> values = ["Jane", "30", "Canada"]
>>> dict(zip(keys, values))
{'name': 'Jane', 'age': '30', 'country': 'Canada'}
Trong ví dụ này, bạn kết hợp hai danh sách hiện có bằng cách sử dụng zip()
và chuyển các bộ dữ liệu kết quả vào hàm dict()
để tạo một từ điển.
Đối số strict
cho zip()
đã được thêm vào trong Python 3.10 và là đối số chỉ chứa từ khóa cung cấp một cách an toàn để xử lý các vòng lặp có độ dài không bằng nhau. Giá trị mặc định của đối số là False
, có nghĩa là zip()
sẽ chỉ tạo ra nhiều bộ dữ liệu như các mục trong vòng lặp ngắn nhất.
Nếu bạn đặt strict
thành True
thì bạn sẽ nhận được ngoại lệ ValueError
khi các lần lặp đầu vào không có cùng độ dài:
>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
>>> list(zip(range(5), range(100), strict=True))
Traceback (most recent call last):
...
ValueError: zip() argument 2 is longer than argument 1
Trong đối số đầu tiên, bạn dựa vào giá trị mặc định của strict
và nhận được năm bộ dữ liệu vì phạm vi ngắn nhất chỉ có năm giá trị. Trong ví dụ thứ hai, bạn đặt strict
thành True
. Lần này, bạn gặp lỗi vì phạm vi đầu vào không có cùng số giá trị.
Xây dựng và sử dụng các Iterator: iter()
và next()
Trong Python, các trình vòng lặp triển khai mẫu thiết kế vòng lặp, cho phép bạn duyệt qua một vùng chứa và truy cập các phần tử của nó. Mẫu vòng lặp tách riêng các thuật toán lặp khỏi các vùng chứa, chẳng hạn như danh sách, bộ dữ liệu, từ điển và bộ.
Lưu ý: Để tìm hiểu thêm về iterator và iterables, hãy xem hướng dẫn Iterator và Iterables trong Python: Chạy các bước lặp hiệu quả.
Python có hai hàm dựng sẵn có thể giúp bạn khi làm việc với các trình vòng lặp. Hàm iter()
cho phép bạn xây dựng một trình vòng lặp từ một biến lặp và hàm next()
cho phép bạn sử dụng một trình vòng lặp một mục tại một thời điểm.
Hãy xem xét ví dụ về đồ chơi sau:
>>> colors = ["red", "orange", "yellow", "green"]
>>> colors_it = iter(colors)
>>> colors_it
<list_iterator object at 0x10566a170>
>>> next(colors_it)
'red'
>>> next(colors_it)
'orange'
>>> next(colors_it)
'yellow'
>>> next(colors_it)
'green'
>>> next(colors_it)
Traceback (most recent call last):
...
StopIteration
Trong ví dụ này, bạn sử dụng hàm iter()
để tạo đối tượng iterator từ danh sách màu hiện có. Không giống như iterables, iterators hỗ trợ hàm next()
. Khi bạn gọi hàm này với một trình vòng lặp làm đối số, bạn sẽ nhận được mục đầu tiên trong lệnh gọi đầu tiên. Khi bạn gọi lại hàm, bạn sẽ nhận được mục thứ hai, v.v.
Khi bạn duyệt tất cả các mục trong iterator, next()
sẽ đưa ra một ngoại lệ StopIteration
. Python sử dụng ngoại lệ này trong nội bộ để dừng quá trình lặp trong vòng lặp for
hoặc để hiểu. Lưu ý rằng bạn chỉ có thể duyệt qua một trình vòng lặp một lần. Sau đó, iterator sẽ cạn kiệt hoặc tiêu thụ.
Dưới đây là chữ ký của iter()
:
iter(iterable)
iter(object, sentinel)
Trong chữ ký đầu tiên, iterable
đại diện cho bất kỳ loại có thể lặp lại nào. Trong chữ ký thứ hai, đối số object
phải có thể gọi được. Bạn đã thấy chữ ký đầu tiên hoạt động. Bây giờ, đã đến lúc xem nhanh chữ ký thứ hai.
Để minh họa bằng một ví dụ, giả sử bạn đang làm việc trên ứng dụng giao diện dòng lệnh (CLI) và muốn lấy thông tin đầu vào của người dùng cho đến khi họ nhập từ "xong"
. Đây là cách bạn có thể thực hiện việc này bằng hàm iter()
:
>>> def read_user_input():
... print("Enter word (type 'done' to finish):")
... for word in iter(input, "done"):
... print(f"Processing word: '{word}'")
...
>>> read_user_input()
Enter word (type 'done' to finish):
Python
Processing word: 'Python'
Programming
Processing word: 'Programming'
Iterators
Processing word: 'Iterators'
done
Trong dòng được đánh dấu, bạn sử dụng iter()
với hai đối số. Đối với đối số đầu tiên, bạn sử dụng hàm input()
tích hợp sẵn, cho phép bạn lấy dữ liệu đầu vào từ người dùng. Lưu ý rằng bạn không gọi hàm mà truyền nó dưới dạng đối tượng hàm.
Sau đó, bạn có từ "xong"
, có tác dụng như một người canh gác. Nói cách khác, iter()
sẽ gọi input()
cho bạn và đưa ra một ngoại lệ StopIteration
nếu giá trị trả về của nó khớp với từ trọng điểm.
Khi bạn gọi hàm, bạn được yêu cầu nhập một từ, sau đó mã sẽ xử lý từ đó và cho phép bạn nhập một từ khác. Những hành động này lặp lại cho đến khi bạn nhập trọng điểm của mình, từ "xong"
.
Khi nói đến hàm next()
, bạn cũng sẽ có hai chữ ký khác nhau trông giống như thế này:
next(iterator)
next(iterator, default)
Một lần nữa, bạn đã thấy cách sử dụng next()
với đối số duy nhất là iterable. Bây giờ, bạn có thể tập trung vào việc sử dụng chữ ký thứ hai. Trong trường hợp này, bạn có đối số thứ hai được gọi là mặc định
. Đối số này cho phép bạn cung cấp giá trị mà bạn muốn trả về khi vòng lặp đầu vào đã hết hoặc dữ liệu của nó kết thúc:
>>> count_down = iter([3, 2, 1])
>>> next(count_down, 0)
3
>>> next(count_down, 0)
2
>>> next(count_down, 0)
1
>>> next(count_down, 0)
0
>>> next(count_down, 0)
0
Trong ví dụ này, bạn tạo một trình lặp từ danh sách các số. Sau đó, bạn sử dụng next()
để sử dụng mỗi lần một số của trình vòng lặp. Sau khi next()
sử dụng toàn bộ trình vòng lặp, kết quả là bạn nhận được 0
vì đó là giá trị bạn đã chuyển cho mặc định
, đối số vị trí thứ hai . Các lệnh gọi hàm liên tiếp cũng sẽ trả về 0
.
Lọc và ánh xạ các vòng lặp: filter()
và map()
Bạn đã nghe nói về lập trình chức năng chưa? Đó là một mô hình lập trình trong đó chương trình bị chi phối bởi các lệnh gọi đến các hàm thuần túy, là các hàm có giá trị đầu ra chỉ phụ thuộc vào giá trị đầu vào của chúng mà không có bất kỳ tác dụng phụ nào có thể quan sát được.
Python không phải là thứ bạn có thể gọi là ngôn ngữ lập trình chức năng. Tuy nhiên, nó có một số chức năng tích hợp là các công cụ chức năng cổ điển. Những công cụ này là các hàm filter()
và map()
được tích hợp sẵn.
Bạn có thể sử dụng hàm filter()
để trích xuất các giá trị từ các lần lặp, được gọi là thao tác lọc.
Lưu ý: Để tìm hiểu sâu hơn về cách sử dụng hàm filter()
, hãy xem hướng dẫn filter()
của Python: Trích xuất giá trị từ Iterables.
Chữ ký của filter()
trông giống như thế này:
filter(function, iterable)
Đối số đầu tiên, hàm
, phải là hàm một đối số, trong khi đối số thứ hai có thể là bất kỳ hàm Python nào có thể lặp lại. Dưới đây là mô tả ngắn gọn về những lập luận này:
function
Hàm vị ngữ hoặc hàm có giá trị Boolean chấp nhận một đối số duy nhất
iterable
Một Python có thể lặp lại
Đối số hàm
là một hàm quyết định, còn được gọi là hàm lọc. Nó cung cấp các tiêu chí để quyết định có nên giữ một giá trị nhất định hay không.
Trong thực tế, filter()
áp dụng hàm
cho tất cả các mục trong iterable
. Sau đó, nó tạo một trình vòng lặp chỉ mang lại các mục đáp ứng tiêu chí mà hàm
kiểm tra. Nói cách khác, nó mang lại các mục làm cho hàm
trả về True
.
Hàm filter()
cho phép bạn xử lý các lần lặp mà không cần vòng lặp hình thức. Dưới đây là ví dụ về cách sử dụng filter()
để trích xuất các số chẵn từ danh sách các giá trị:
>>> numbers = [1, 3, 10, 45, 6, 50]
>>> list(filter(lambda n: n % 2 == 0, numbers))
[10, 6, 50]
Trong ví dụ này, hàm lambda
nhận một số nguyên và trả về True
nếu giá trị đầu vào là số chẵn và False
nếu ngược lại. Lệnh gọi filter()
áp dụng hàm lambda
này cho các giá trị trong numbers
và lọc ra các số lẻ, trả về các số chẵn. Lưu ý rằng bạn sử dụng hàm list()
để tạo danh sách từ trình vòng lặp mà filter()
trả về.
Lưu ý: Hàm ẩn danh là công cụ phổ biến trong lập trình hàm. Python cho phép bạn tạo loại hàm này bằng từ khóa lambda
. Để tìm hiểu thêm về chúng, hãy xem Cách sử dụng Hàm Lambda của Python.
Một công cụ cơ bản khác trong lập trình hàm là reduce()
trước đây là một hàm tích hợp nhưng hiện có sẵn trong mô-đun functools
. Hãy xem reduce()
của Python: Từ Chức năng đến Phong cách Pythonic để biết thêm thông tin.
Hàm map()
là một công cụ phổ biến khác trong lập trình hàm. Hàm này cho phép bạn áp dụng hàm chuyển đổi cho tất cả các giá trị trong một lần lặp.
Lưu ý: Để tìm hiểu thêm về hàm map()
, hãy xem hướng dẫn map()
của Python: Xử lý các vòng lặp không có vòng lặp.
map()
của Python có chữ ký sau:
map(function, iterable, *iterables)
Hàm map()
áp dụng hàm
cho từng mục trong iterable
trong một vòng lặp và trả về một trình vòng lặp mới mang lại các mục được chuyển đổi theo yêu cầu.
Dưới đây là bản tóm tắt các lập luận và ý nghĩa của chúng:
function
Hàm Python nhận một số đối số bằng với số lượng đầu vào. lặp đi lặp lại
iterable
Đối số bắt buộc có thể chứa bất kỳ Python nào có thể lặp lại.
*iterables
Một số lần lặp Python khác nhau.
Đối số function
được gọi là hàm chuyển đổi. Nó áp dụng một phép biến đổi cụ thể cho các đối số của nó và trả về một giá trị được chuyển đổi.
Để minh họa cách hoạt động của map()
, giả sử bạn có hai danh sách. Danh sách đầu tiên chứa một loạt các giá trị mà bạn muốn sử dụng làm cơ sở trong tính toán lũy thừa. Danh sách thứ hai chứa số mũ mà bạn muốn áp dụng cho mỗi cơ số. Bạn có thể sử dụng hàm map()
để xử lý các danh sách này và lấy một hàm lặp lũy thừa:
>>> bases = [8, 5, 2]
>>> exponents = [2, 3, 4]
>>> list(map(pow, bases, exponents))
[64, 125, 16]
Trong ví dụ này, bạn sử dụng hàm pow()
tích hợp làm đối số đầu tiên cho map()
. Như bạn đã học, pow()
lấy cơ số và số mũ làm đối số và trả về lũy thừa. Sau đó, bạn chuyển cơ số và số mũ cho map()
để nó tính lũy thừa mong muốn.
Xử lý đầu vào và đầu ra
Nếu bạn cần lấy đầu vào từ người dùng hoặc tệp và hiển thị đầu ra cho người dùng thì bạn nên biết rằng ngôn ngữ này có một số hàm tích hợp có thể giúp bạn thực hiện các tác vụ sau:
input()
Đọc đầu vào từ bảng điều khiển
open()
Mở một tệp và cung cấp quyền truy cập vào một đối tượng tệp
print()
In ra luồng văn bản hoặc bảng điều khiển
format()
Chuyển đổi một giá trị thành một biểu diễn được định dạng
Trong các phần sau, bạn sẽ đi sâu vào việc sử dụng các hàm này để xử lý các hoạt động đầu vào và đầu ra trong mã Python của mình.
Chấp nhận đầu vào từ người dùng: input()
Lấy đầu vào từ người dùng của bạn là một thao tác phổ biến trong các ứng dụng CLI và giao diện dựa trên văn bản (TUI). Python có một hàm tích hợp dành riêng cho loại hoạt động này. Hàm này được gọi thuận tiện là input()
.
Lưu ý: Để tìm hiểu sâu hơn về cách sử dụng hàm input()
, hãy xem hướng dẫn Cách đọc dữ liệu nhập của người dùng từ bàn phím trong Python.
Hàm input()
tích hợp sẽ đọc dữ liệu đầu vào của người dùng và lấy nó dưới dạng chuỗi. Đây là chữ ký của hàm:
input([prompt])
Dấu ngoặc vuông xung quanh prompt
là dấu hiệu cho thấy đối số này là tùy chọn. Đối số này cho phép bạn đưa ra lời nhắc để yêu cầu người dùng cung cấp thông tin đầu vào được yêu cầu hoặc mong muốn.
Ví dụ về cách sử dụng input()
, giả sử bạn muốn tạo một trò chơi đoán số. Trò chơi sẽ nhắc người dùng nhập một số từ 1 đến 10 và kiểm tra xem giá trị đầu vào có khớp với số bí mật hay không.
Đây là mã của trò chơi:
from random import randint
LOW, HIGH = 1, 10
secret_number = randint(LOW, HIGH)
clue = ""
while True:
guess = input(f"Guess a number between {LOW} and {HIGH} {clue} ")
number = int(guess)
if number > secret_number:
clue = f"(less than {number})"
elif number < secret_number:
clue = f"(greater than {number})"
else:
break
print(f"You guessed it! The secret number is {number}")
Trong mã này, bạn xác định một vòng lặp vô hạn trong đó bạn nhắc người dùng đoán bằng cách nhập một số từ 1 đến 10. Dòng đầu tiên trong vòng lặp là lệnh gọi đến input()
tích hợp sẵn chức năng. Bạn đã sử dụng lời nhắc mang tính mô tả để thông báo cho người dùng những việc cần làm.
Hãy tiếp tục và chạy tập lệnh từ dòng lệnh của bạn để dùng thử:
$ python guess.py
Guess a number between 1 and 10 2
Guess a number between 1 and 10 (greater than 2) 3
Guess a number between 1 and 10 (greater than 3) 8
Guess a number between 1 and 10 (less than 8) 6
You guessed it! The secret number is 6
Mát mẻ! Trò chơi của bạn yêu cầu người dùng nhập một số, so sánh số đó với số bí mật và cho người dùng biết khi nào họ đoán đúng. Hàm input()
đóng vai trò cốt lõi trong luồng của trò chơi, cho phép bạn nhận và xử lý thông tin đầu vào của người dùng.
Mở tập tin: open()
Đọc và ghi vào tập tin là những tác vụ lập trình phổ biến. Trong Python, bạn có thể sử dụng hàm open()
tích hợp sẵn cho những mục đích này. Bạn thường sử dụng hàm open()
trong câu lệnh with
.
Ví dụ nhanh, giả sử bạn có một tệp văn bản có nội dung sau:
apple
banana
cherry
orange
mango
Bạn muốn mở tệp và đọc nội dung của nó trong khi in nó ra màn hình. Để làm điều này, bạn có thể sử dụng đoạn mã sau:
>>> with open("fruits.txt") as file:
... print(file.read())
...
apple
banana
cherry
orange
mango
Trong câu lệnh with
này, bạn gọi open()
với tên tệp làm đối số. Cuộc gọi này sẽ mở tập tin để đọc. Hàm open()
trả về một đối tượng tệp mà câu lệnh with
gán cho các biến file
với as
người chỉ định.
Lưu ý: Để tìm hiểu thêm về cách làm việc với tệp, hãy xem hướng dẫn Đọc và Viết tệp trong Python (Hướng dẫn).
Hàm open()
có chữ ký sau:
open(
file,
mode="r",
buffering=-1,
encoding=None,
errors=None,
newline=None,
closefd=True,
opener=None,
)
Hàm có thể có tới tám đối số. Đối số đầu tiên, file
, là đối số bắt buộc duy nhất. Các đối số còn lại là tùy chọn. Dưới đây là bản tóm tắt ý nghĩa của các đối số:
Argument | Description | Comment |
---|---|---|
file |
A path-like object holding the path to the target file | It’s a required argument. |
mode |
A string that specifies the mode in which you want to open the file | It defaults to "r" , which is the reading mode. You’ll learn about the available modes in a moment. |
buffering |
An integer that sets the buffering policy | You can pass 0 to switch buffering off, which is only possible in binary mode. You can use 1 to select line buffering, which is only usable in text mode. Finally, you can use an integer greater than 1 to indicate the size in bytes of a fixed-size chunk buffer. |
encoding |
The name of the encoding used to decode or encode the file | You can only use this argument in text mode. |
errors |
A string that specifies how encoding and decoding errors are to be handled | You can only use this argument in text mode. It can take one of the following values: "strict" , "ignore" , "replace" , "surrogateescape" , "xmlcharrefreplace" , "backslashreplace" , or "namereplace" . These values have similar meanings to those you learned in the section about the ord() and chr() functions. |
newline |
A string that determines how to parse newline characters from the stream | It can be None , "" , "\n" , "\r" , or "\r\n" . |
closefd |
A Boolean value that defines whether you want to close a file descriptor | It can be False when you provide a file descriptor instead of a filename and want the descriptor to remain open when the file is closed. Otherwise, it must be True . |
opener |
A callable that you use as a custom opener for the target file | The opener must return an open file descriptor. |
Trong hướng dẫn này, bạn sẽ không đề cập đến tất cả những lập luận này. Thay vào đó, bạn sẽ tìm hiểu về hai đối số được sử dụng phổ biến nhất là mode
và encoding
.
Dưới đây là danh sách các giá trị mode
được phép:
"r"
Mở tệp để đọc và là giá trị mặc định
"w"
Mở file để ghi, cắt bớt file trước
"x"
Mở tệp để tạo độc quyền, không thành công nếu tệp đã tồn tại
"a"
Mở file để ghi, thêm dữ liệu mới vào cuối file nếu đã tồn tại
"b"
Mở tệp ở chế độ nhị phân
"t"
Mở tệp ở chế độ văn bản, đây là chế độ mặc định
"+"
Mở file để cập nhật, cho phép thao tác đọc và ghi
Trong bảng này, các giá trị "b"
và "t"
xác định hai chế độ chung tương ứng cho tệp nhị phân và tệp văn bản. Bạn có thể kết hợp hai chế độ này với các chế độ khác. Ví dụ: chế độ "wb"
cho phép bạn ghi dữ liệu nhị phân vào một tệp, chế độ "rt"
cho phép bạn đọc dữ liệu dựa trên văn bản từ một tệp và sớm.
Lưu ý rằng "t"
là chế độ mặc định. Vì vậy, nếu bạn đặt chế độ thành "w"
, Python sẽ giả định rằng bạn muốn ghi văn bản vào tệp đích.
Đây là đoạn mã ghi một số văn bản vào một tệp trong thư mục làm việc của bạn:
>>> with open("hello.txt", "w") as file:
... file.write("Hello, World!")
...
13
Trong ví dụ này, bạn mở một tệp có tên hello.txt
để bạn có thể viết văn bản vào đó. Trong khối mã của câu lệnh with
, bạn gọi phương thức .write()
trên đối tượng tệp để viết một số văn bản. Lưu ý rằng phương thức trả về số byte đã ghi. Đó là lý do tại sao bạn nhận được 13
trên màn hình.
Sau khi chạy mã, bạn sẽ có tệp hello.txt
trong thư mục làm việc của mình. Hãy tiếp tục và mở nó để kiểm tra nội dung của nó.
Bạn có thể thử nghiệm các chế độ khác và biết cách chúng hoạt động để có thể sử dụng chúng một cách an toàn trong mã của mình. Hãy coi đây là một bài tập thực tế!
Sử dụng đối số encoding
là một yêu cầu điển hình khác khi bạn làm việc với tệp văn bản. Trong tình huống này, cách tốt nhất là nêu rõ cách mã hóa văn bản mà bạn đang sử dụng trong mã của mình. Mã hóa UTF-8 là ví dụ phổ biến về giá trị mà bạn chuyển sang encoding
:
>>> with open("hello.txt", "w", encoding="utf-8") as file:
... file.write("Hello, Pythonista!")
...
13
>>> with open("hello.txt", "r", encoding="utf-8") as file:
... print(file.read())
...
Hello, Pythonista!
Trong ví dụ này, bạn sử dụng mã hóa UTF-8 để ghi và đọc từ tệp văn bản. Lưu ý rằng bạn cần sử dụng rõ ràng tên của đối số để cung cấp giá trị mã hóa. Điều này là do đối số sau trong danh sách là đệm
chứ không phải mã hóa
và nếu bạn không sử dụng tên rõ ràng thì bạn sẽ nhận được TypeError ngoại lệ.
In văn bản ra màn hình hoặc đầu ra khác: print()
Một yêu cầu phổ biến khác nảy sinh khi bạn tạo ứng dụng CLI hoặc TUI là hiển thị thông tin trên màn hình để thông báo cho người dùng về trạng thái của ứng dụng. Trong trường hợp này, bạn có thể sử dụng hàm print()
tích hợp sẵn, đây là một công cụ cơ bản trong lập trình Python.
Lưu ý: Để tìm hiểu sâu hơn về cách sử dụng hàm print()
, hãy xem hướng dẫn Hướng dẫn sử dụng hàm print()
của Python.
Hàm print()
có chữ ký sau:
print(*objects, sep=" ", end="\n", file=None, flush=False)
Việc gọi print()
sẽ in các đối tượng đầu vào ra màn hình theo mặc định. Bạn có thể sử dụng các đối số còn lại để điều chỉnh cách hoạt động của hàm. Dưới đây là bản tóm tắt các lập luận và ý nghĩa của chúng:
*objects
Một số lượng đối tượng Python tùy ý
sep
Chuỗi bạn muốn sử dụng để phân tách các đối tượng đầu vào với nhau
end
Chuỗi sử dụng sau đối tượng đầu vào cuối cùng
file
Đối tượng tệp đang mở nơi bạn muốn ghi các đối tượng đầu vào
flush
Giá trị Boolean xác định xem bạn có muốn xóa bộ đệm đầu ra hay không
Khi bạn gọi print()
, nó lấy đối tượng
đầu vào, chuyển đổi chúng thành chuỗi, nối chúng bằng sep
và nối thêm end
. Nếu bạn gọi print()
mà không có đối số thì nó sẽ in ra end
. Lưu ý rằng các đối số sep
, end
, file
và flush
là các đối số từ khóa.
Dưới đây là một số ví dụ về cách sử dụng hàm print()
:
>>> print()
>>> print("Hello")
Hello
>>> print("Hello", "Pythonista!")
Hello Pythonista!
>>> print("Hello", "Pythonista!", sep="\t")
Hello Pythonista!
>>> print("Hello", "Pythonista!", sep="\t", end=" 👋\n")
Hello Pythonista! 👋
Khi bạn gọi print()
không có đối số thì end
sẽ được in trên màn hình. Đối số này mặc định là ký tự dòng mới, vì vậy đó là những gì bạn nhận được. Với một đối tượng làm đối số, đối tượng sẽ được in, theo sau là ký tự dòng mới. Với một số đối tượng làm đối số, các đối số được nối bằng sep
và một dòng mới được thêm vào cuối.
Bạn cũng có thể điều chỉnh giá trị của end
và làm cho Python in nội dung khác ở cuối kết quả đầu ra.
Đối số file
mặc định là đầu ra tiêu chuẩn, đó là màn hình của bạn. Luồng sys.stdout
cung cấp giá trị mặc định này. Tuy nhiên, bạn có thể chuyển hướng đầu ra đến một đối tượng tệp theo sở thích của mình:
>>> with open("hello.txt", mode="w") as text_file:
... print("Hello, World!", file=text_file)
...
Đoạn mã này sẽ ghi đè tệp hello.txt
hiện có của bạn từ phần trước, viết cụm từ "Hello, World!"
vào đó.
Cuối cùng, bạn có đối số flush
liên quan đến việc lưu vào bộ đệm dữ liệu. Theo mặc định, Python đệm các cuộc gọi đến print()
trong bộ đệm dữ liệu RAM. Điều này cho phép Python thực hiện ít lệnh gọi hệ thống hơn cho các thao tác ghi bằng cách gộp các ký tự trong bộ đệm và ghi tất cả chúng cùng một lúc chỉ bằng một lệnh gọi hệ thống.
Bạn có thể đặt đối số flush
thành True
nếu bạn muốn đầu ra mã của mình hiển thị theo thời gian thực. Nếu bạn giữ flush
ở giá trị mặc định là False
thì Python sẽ đệm đầu ra và đầu ra đó sẽ chỉ hiển thị khi bộ đệm dữ liệu đầy hoặc khi chương trình của bạn kết thúc thi hành.
Lưu ý: Để tìm hiểu sâu hơn về việc xóa đầu ra của print()
, hãy xem hướng dẫn Cách xóa đầu ra của hàm in Python.
Một ví dụ thú vị về việc sử dụng flush
là khi bạn cần tạo thanh tiến trình cho ứng dụng CLI. Hãy xem xét chức năng sau:
def progress(percent=0, width=30):
end = "" if percent < 100 else "\n"
left = width * percent // 100
right = width - left
print(
"\r[",
"#" * left,
" " * right,
"]",
f" {percent:.0f}%",
sep="",
end=end,
flush=True,
)
Hàm này tạo ra một thanh tiến trình theo chiều ngang bằng cách tận dụng đối số flush
. Đây là cách bạn có thể sử dụng nó trong mã của mình:
>>> from time import sleep
>>> from progress import progress
>>> for percent in range(101):
... sleep(0.2)
... progress(percent)
...
[########### ] 38%
Vòng lặp này gọi progress()
với các giá trị tiến trình giả định liên tiếp. Đầu ra của mỗi cuộc gọi được xóa và thanh tiến trình được hiển thị trên cùng một dòng.
Chuỗi định dạng: format()
Python có một số công cụ tiện dụng để nội suy và định dạng chuỗi, bao gồm chuỗi f và phương thức str.format()
. Các công cụ này tận dụng ngôn ngữ nhỏ định dạng chuỗi của Python, cho phép bạn định dạng chuỗi của mình một cách độc đáo bằng cú pháp chuyên dụng.
Hàm format()
tích hợp sẵn là một công cụ khác mà bạn có thể sử dụng để định dạng các giá trị. Hàm này có chữ ký sau:
format(value, format_spec="")
Hàm chuyển đổi giá trị
thành biểu diễn được định dạng. Để xác định định dạng mong muốn, bạn có thể sử dụng đối số format_spec
, chấp nhận một chuỗi tuân theo cú pháp được xác định trong ngôn ngữ mini định dạng chuỗi. Đối số format_spec
mặc định là một chuỗi trống, điều này khiến hàm trả về giá trị được truyền vào.
Hãy xem xét các ví dụ sau về cách sử dụng hàm format()
:
>>> import math
>>> from datetime import datetime
>>> format(math.pi, ".4f") # Four decimal places
'3.1416'
>>> format(math.pi, "e") # In scientific notation
'3.141593e+00'
>>> format(1000000, ",.2f") # Thousand separators
'1,000,000.00'
>>> format("Header", "=^30") # Centered and filled
'============Header============'
>>> format(datetime.now(), "%a %b %d, %Y") # Date
'Mon Jul 1, 2024'
Trong những ví dụ này, bạn đã sử dụng một số công cụ xác định định dạng khác nhau. Trình xác định ".4f"
định dạng giá trị đầu vào dưới dạng số dấu phẩy động có bốn chữ số thập phân. Trình xác định "e"
cho phép bạn định dạng giá trị đầu vào bằng ký hiệu khoa học.
Lưu ý: Để tìm hiểu thêm về công cụ xác định định dạng, hãy xem hướng dẫn Định dạng ngôn ngữ nhỏ cho chuỗi gọn gàng của Python.
Với công cụ xác định định dạng ",.2f"
, bạn có thể định dạng một số bằng dấu phẩy làm dấu phân cách hàng nghìn và có hai chữ số thập phân, đây là định dạng thích hợp cho các giá trị tiền tệ. Sau đó, bạn sử dụng công cụ xác định "=^30"
để định dạng chuỗi "Header"
được căn giữa theo chiều rộng 30
ký tự bằng dấu bằng như một nhân vật phụ. Cuối cùng, bạn sử dụng "%a %b %d, %Y"
để định dạng ngày.
Làm việc với các lớp, đối tượng và thuộc tính
Python hỗ trợ lập trình hướng đối tượng (OOP) với các lớp, kiểu, kế thừa và nhiều tính năng liên quan khác. Trong Python, mọi thứ đều là đối tượng. Vì vậy, mô hình OOP là cốt lõi của chính ngôn ngữ.
Bạn sẽ có một số hàm dựng sẵn giúp bạn thực hiện các tác vụ khác nhau liên quan đến lớp, kiểu, thuộc tính, phương thức, tính kế thừa và các khái niệm khác liên quan đến OOP.
Dưới đây là bản tóm tắt các hàm tích hợp liên quan đến OOP của Python:
property()
Trả về giá trị thuộc tính của một lớp
classmethod()
Trả về một phương thức lớp
staticmethod()
Trả về một phương thức tĩnh
getattr()
Trả về giá trị của thuộc tính được đặt tên của một đối tượng
setattr()
Đặt giá trị của thuộc tính được đặt tên của một đối tượng
delattr()
Xóa một thuộc tính khỏi một đối tượng
hasattr()
Trả về
True
nếu một đối tượng có thuộc tính nhất địnhtype()
Trả về loại đối tượng hoặc cho phép tạo các lớp mới một cách linh hoạt
isinstance()
Xác định xem một đối tượng có phải là một thể hiện của một lớp nhất định hay không
issubclass()
Xác định xem một lớp có phải là lớp con của một lớp nhất định hay không
callable()
Trả về
True
nếu một đối tượng có thể gọi đượcsuper()
Trả về một đối tượng proxy ủy quyền các lệnh gọi phương thức cho lớp cha hoặc lớp anh chị em
object()
Tạo một đối tượng không đặc trưng mới
Trong các phần sau, bạn sẽ tìm hiểu về tất cả các hàm này và cách sử dụng chúng trong mã Python hướng đối tượng của mình.
Thuộc tính tòa nhà: property()
Hàm thuộc tính()
tích hợp sẵn của Python cho phép bạn tạo thuộc tính được quản lý trong các lớp của mình. Thuộc tính được quản lý, còn được gọi là thuộc tính, có giá trị liên quan và cách triển khai nội bộ hoặc hành vi giống như chức năng.
Để minh họa điều này bằng một ví dụ, giả sử bạn muốn tạo một lớp Điểm
. Trong Python, bạn sẽ bắt đầu với những thứ như sau:
>>> class Point:
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> point = Point(42, 21)
>>> point.x
42
>>> point.y
21
>>> point.x = 0
>>> point.x
0
Trong lớp này, bạn xác định hai thuộc tính, .x
và .y
, để biểu thị tọa độ của điểm. Bạn có thể truy cập và cập nhật các thuộc tính trực tiếp bằng ký hiệu dấu chấm. Vì vậy, kể từ bây giờ, cả hai thuộc tính đều là một phần của API công khai của lớp bạn.
Bây giờ, giả sử bạn cần thêm một số logic xác thực lên trên .x
và .y
. Ví dụ: bạn có thể cần xác thực giá trị đầu vào cho cả hai thuộc tính. Bạn sẽ làm điều đó như thế nào? Trong các ngôn ngữ lập trình như Java hoặc C++, bạn sẽ sử dụng các phương thức getter và setter, được dịch sang Python có thể trông giống như thế này:
class Point:
def __init__(self, x, y):
self.set_x(x)
self.set_y(y)
def get_x(self):
return self._x
def set_x(self, x):
self._x = self.validate(x)
def get_y(self):
return self._y
def set_y(self, y):
self._y = self.validate(y)
def validate(self, value):
if not isinstance(value, int | float):
raise ValueError("coordinates must be numbers")
return value
Trong cách triển khai Điểm
mới này, bạn đã biến .x
và .y
thành các thuộc tính không công khai bằng cách thêm dấu gạch dưới vào trước tên của chúng. bây giờ là ._x
và ._y
. Sau đó, bạn xác định các phương thức getter và setter cho cả hai thuộc tính. Trong các phương thức setter, .set_x()
và .set_y()
, bạn chèn logic xác thực được xác định trong phương thức .validate()
.
Bây giờ, bạn phải sử dụng lớp như trong đoạn mã sau:
>>> from point_v1 import Point
>>> point = Point(42, 21)
>>> point.get_x()
42
>>> point.get_y()
21
>>> point.set_x(0)
>>> point.get_x()
0
>>> point.set_y("7")
Traceback (most recent call last):
...
ValueError: coordinates must be numbers
Lớp của bạn hoạt động khác sau khi cập nhật. Thay vì truy cập trực tiếp vào các thuộc tính .x
và .y
, bạn phải sử dụng các phương thức getter và setter. Logic xác thực hoạt động, điều này thật tuyệt vời, nhưng bạn đã làm hỏng API của lớp mình. Người dùng của bạn sẽ không thể làm những việc như sau:
>>> point.x
Traceback (most recent call last):
...
AttributeError: 'Point' object has no attribute 'x'
Những người dùng cũ trong lớp của bạn sẽ ngạc nhiên khi biết mã của họ bị hỏng sau khi cập nhật lên phiên bản Point
mới của bạn. Vì vậy, làm thế nào bạn có thể tránh được loại vấn đề này? Cách tiếp cận Pythonic là chuyển đổi các thuộc tính công khai thành thuộc tính thay vì sử dụng các phương thức getter và setter.
Bạn có thể sử dụng hàm property()
tích hợp sẵn để thực hiện chuyển đổi này. Đây là cách bạn có thể giữ nguyên API của lớp Point
của mình:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = self.validate(value)
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = self.validate(value)
def validate(self, value):
if not isinstance(value, int | float):
raise ValueError("coordinates must be numbers")
return value
Điểm
bây giờ trông hơi khác một chút. Nó không có phương thức getter và setter chính thức. Thay vào đó, nó có một số phương thức được trang trí bằng @property
. Có, hàm property()
tích hợp chủ yếu được sử dụng làm công cụ trang trí.
Các phương thức mà bạn trang trí bằng @property
tương đương với các phương thức getter. Trong khi đó, các phương thức mà bạn trang trí bằng tên của getter cộng với .setter()
tương đương với các phương thức setter. Điều thú vị về thuộc tính là bạn vẫn có thể sử dụng thuộc tính như thuộc tính thông thường:
>>> from point_v2 import Point
>>> point = Point(42, 21)
>>> point.x
42
>>> point.y
21
>>> point.x = 0
>>> point.x
0
>>> point.x = "7"
Traceback (most recent call last):
...
ValueError: coordinates must be numbers
Bằng cách biến các thuộc tính thông thường thành thuộc tính, bạn có thể thêm hành vi giống chức năng cho chúng mà không mất khả năng sử dụng chúng làm thuộc tính thông thường. Thuộc tính giúp bạn không phải đưa ra các thay đổi có thể gây lỗi đối với API công khai của mã của mình, do đó, bạn không làm hỏng mã của người dùng.
Tạo lớp và phương thức tĩnh: classmethod()
và staticmethod()
Các lớp cho phép bạn xác định các đoạn mã có thể sử dụng lại để gói gọn dữ liệu và hành vi trong một thực thể duy nhất. Thông thường, bạn lưu trữ dữ liệu trong các thuộc tính, là các biến được xác định bên trong các lớp. Khi nói đến hành vi, bạn sẽ sử dụng các phương thức là các hàm được xác định trong các lớp.
Trong Python, bạn có ba loại phương thức khác nhau:
- Các phương thức thực thể, lấy đối tượng hiện tại làm đối số đầu tiên
- Các phương thức lớp, lấy lớp hiện tại làm đối số đầu tiên
- Các phương thức tĩnh, không lấy phiên bản hiện tại cũng như lớp hiện tại làm đối số
Các phương thức instance cần lấy instance hiện tại làm đối số. Theo quy ước, đối số này được gọi là self
trong Python.
Để tạo một phương thức lớp, bạn cần trang trí phương thức đó bằng trình trang trí @classmethod
. Tương tự, để tạo một phương thức tĩnh, bạn cần trang trí phương thức đó bằng trình trang trí @staticmethod
. Cả hai trình trang trí đều là một phần của các hàm dựng sẵn của Python.
Trường hợp sử dụng phổ biến của các phương thức lớp là cung cấp nhiều hàm tạo cho một lớp. Để minh họa cách viết một phương thức lớp, giả sử bạn muốn một lớp Điểm
mà bạn có thể xây dựng bằng cách sử dụng tọa độ Descartes hoặc tọa độ cực. Trong tình huống này, bạn có thể làm một số việc như sau:
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_polar(cls, distance, angle):
return cls(
x=distance * math.cos(math.radians(angle)),
y=distance * math.sin(math.radians(angle)),
)
Trong ví dụ này, phương thức .from_pole()
là một phương thức lớp. Nó lấy lớp hiện tại làm đối số đầu tiên, mà bạn thường gọi là cls
theo quy ước. Phương thức này trả về một thể hiện mới của lớp bằng cách tính tọa độ Descartes từ tọa độ cực.
Đây là cách bạn có thể sử dụng phương pháp này trong thực tế:
>>> from point import Point
>>> point = Point.from_polar(20, 15)
>>> point.y
5.176380902050415
>>> point.x
19.318516525781366
Trong đoạn mã này, bạn tạo một phiên bản Point
mới bằng cách sử dụng phương thức lớp .from_pole()
. Trong ví dụ này, bạn gọi phương thức trên lớp chứ không phải trên một thể hiện để báo hiệu rằng đây là một phương thức lớp. Bạn cũng có thể gọi một phương thức lớp trên một thể hiện của lớp chứa nó.
Loại phương thức thứ ba là phương thức tĩnh. Một phương thức tĩnh không lấy phiên bản hoặc lớp hiện tại làm đối số. Các phương thức này giống như các hàm thông thường mà bạn quyết định đưa vào một lớp nhất định để thuận tiện. Về mặt chức năng, chúng cũng có thể được định nghĩa là các hàm thông thường trong một mô-đun.
Ví dụ: hãy xem xét lớp Formatter
sau:
class Formatter:
@staticmethod
def as_currency(value):
return f"${value:,.2f}"
@staticmethod
def as_percent(value):
return f"{value:.2%}"
Lớp này định nghĩa hai phương thức tĩnh. Phương thức đầu tiên lấy một giá trị số và định dạng nó dưới dạng giá trị tiền tệ. Phương thức thứ hai lấy một giá trị số và biểu thị nó dưới dạng phần trăm. Bạn có thể đã định nghĩa các phương thức này như các hàm thông thường ở cấp độ mô-đun. Tuy nhiên, bạn đã định nghĩa chúng trong một lớp như một cách để nhóm chúng một cách thuận tiện theo cách chúng sẽ được sử dụng.
Bạn có thể sử dụng lớp này như trong các ví dụ sau:
>>> from formatting import Formatter
>>> Formatter.as_currency(1000)
'$1,000.00'
>>> Formatter.as_percent(0.75)
'75.00%'
>>> formatter = Formatter()
>>> formatter.as_currency(1000)
'$1,000.00'
>>> formatter.as_percent(0.8)
'80.00%'
Bạn có thể sử dụng các phương thức tĩnh bằng cách gọi chúng trên lớp hoặc một trong các thể hiện của nó. Trong ví dụ này, lớp Formatter
hoạt động như một không gian tên nơi bạn xác định các phương thức liên quan để thuận tiện. Tuy nhiên, bạn có thể nhận được kết quả tương tự bằng cách xác định các phương thức dưới dạng hàm cấp mô-đun.
Quản lý thuộc tính: getattr()
, setattr()
và delattr()
Đôi khi, bạn có thể cần truy cập, đặt hoặc xóa các thuộc tính khỏi đối tượng của mình bằng Python. Trong hầu hết các trường hợp, bạn có thể thực hiện trực tiếp các thao tác này bằng cách sử dụng ký hiệu dấu chấm, toán tử gán và câu lệnh del
.
Trong các trường hợp khác, bạn chỉ biết tên của thuộc tính khi chạy, do đó bạn không thể truy cập chúng bằng cú pháp thông thường. Trong những trường hợp này, bạn có thể sử dụng các hàm getattr()
, setattr()
và delattr()
tích hợp sẵn. Các chức năng này có chữ ký sau:
getattr(object, name)
getattr(object, name, default)
setattr(object, name, value)
delattr(object, name)
Trong mọi trường hợp, đối số object
phải lấy một phiên bản của một lớp hiện có. Tương tự, name
phải là tên của thuộc tính hoặc phương thức dưới dạng chuỗi.
Trong chữ ký thứ hai của getattr()
, đối số default
là giá trị tùy chọn mà bạn sẽ nhận được nếu thuộc tính mong muốn không tồn tại trong đối tượng đích.
Trong chữ ký của setattr()
, đối số value
phải giữ giá trị mới mà bạn muốn gán cho một đối số nhất định.
Để minh họa cách các hàm này hoạt động, hãy xem xét lớp sau:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Lớp này có hai thuộc tính phiên bản là .name
và .age
. Đây là cách bạn có thể truy cập, đặt hoặc xóa các thuộc tính bằng cách sử dụng tên của chúng dưới dạng chuỗi:
>>> from person import Person
>>> jane = Person("Jane", 25)
>>> getattr(jane, "name")
'Jane'
>>> getattr(jane, "age")
25
Trong những ví dụ này, bạn sử dụng getattr()
để truy xuất các giá trị được lưu trữ trong .name
và .age
. Đối số đầu tiên của hàm này là đối tượng bạn cần truy xuất thuộc tính. Đối số thứ hai là tên của thuộc tính dưới dạng chuỗi.
Bây giờ hãy nói rằng bạn muốn cập nhật tuổi của Jane. Bạn có thể thực hiện việc này bằng hàm setattr()
:
>>> setattr(jane, "age", 26)
>>> jane.age
26
Sau đó, bạn sử dụng hàm setattr()
để gán giá trị mới cho thuộc tính .age
. Hàm này nhận ba đối số: đối tượng, tên thuộc tính và giá trị mới.
Cuối cùng, bạn có thể sử dụng hàm delattr()
tích hợp sẵn để xóa một thuộc tính khỏi một đối tượng nhất định:
>>> delattr(jane, "age")
>>> jane.age
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'age'
Hàm delattr()
lấy đối tượng làm đối số đầu tiên và tên thuộc tính làm đối số thứ hai. Sau khi gọi hàm, việc cố gắng truy cập .age
sẽ đưa ra một ngoại lệ AttributionError
.
Lưu ý: Để tìm hiểu thêm về cách xóa đối tượng trong Python, hãy xem hướng dẫn del
của Python: Xóa tham chiếu khỏi phạm vi và vùng chứa.
Trong thực tế, các hàm getattr()
, setattr()
và delattr()
tích hợp sẵn sẽ rất hữu ích khi bạn cần thao tác với các thuộc tính sử dụng tên của họ làm chuỗi. Ví dụ: giả sử bạn muốn tạo một lớp FileProcessor
để đọc và ghi các tệp CSV và JSON. Trong tình huống này, bạn có thể có các lớp chuyên dụng để xử lý từng loại tệp:
import csv
import json
class CSVProcessor:
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, encoding="utf-8", newline="") as file:
return list(csv.DictReader(file))
def write(self, data):
with open(
self.filename, mode="w", encoding="utf-8", newline=""
) as file:
writer = csv.DictWriter(file, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
class JSONProcessor:
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, encoding="utf-8") as file:
return json.load(file)
def write(self, data):
with open(self.filename, mode="w", encoding="utf-8") as file:
json.dump(data, file, indent=2)
Trong tệp processors.py
này, bạn xác định hai lớp có thể xử lý tệp CSV và JSON tương ứng. Cả hai lớp đều có các phương thức .read()
và .write()
. Các lớp này có vẻ ổn nhưng bây giờ bạn cần làm cho chúng có thể sử dụng được từ lớp FileProcessor
của mình.
Để viết lớp FileProcessor
, bạn có thể sử dụng một kỹ thuật được gọi là ủy quyền, bao gồm việc đánh giá thuộc tính hoặc phương thức của một đối tượng trong ngữ cảnh của một đối tượng khác. Đây là cách bạn có thể làm điều này bằng Python:
# ...
class FileProcessor:
def __init__(self, filename, processor):
self.filename = filename
self.processor = processor(filename)
def __getattr__(self, attr):
return getattr(self.processor, attr)
Trong lớp này, bạn xác định phương thức đặc biệt .__getattr__()
. Phương thức này hỗ trợ các hoạt động truy cập thuộc tính trong các lớp Python. Trong định nghĩa phương thức, bạn sử dụng hàm getattr()
để truy cập các thuộc tính và phương thức từ đối tượng bộ xử lý được cung cấp.
Trong thực tế, bạn sử dụng kết hợp phương thức .__getattr__()
và hàm getattr()
để triển khai ủy quyền. Lớp FileProcessor
ủy quyền việc xử lý tệp cho lớp bộ xử lý cụ thể mà bạn chuyển vào trong quá trình khởi tạo.
Đây là cách bạn có thể sử dụng lớp FileProcessor
trong mã của mình:
>>> from processors import FileProcessor
>>> file_proc = FileProcessor("products.csv", CSVProcessor)
>>> file_proc.read()
[
{'product': 'Laptop', 'price': '1200', 'sold_units': '30'},
{'product': 'Phone', 'price': '700', 'sold_units': '50'},
{'product': 'Tablet', 'price': '450', 'sold_units': '100'},
{'product': 'Desktop', 'price': '1000', 'sold_units': '20'},
{'product': 'Monitor', 'price': '300', 'sold_units': '50'}
]
Trong mã này, bạn tạo một phiên bản FileProcessor
để xử lý tệp CSV bằng CSVProcessor
. Mặc dù phiên bản không có phương thức .read()
nhưng bạn có thể gọi phương thức này nhờ kỹ thuật ủy quyền dựa trên hàm getattr()
.
Kiểm tra thuộc tính: hasattr()
Một hàm tích hợp khác có liên quan chặt chẽ đến các thuộc tính và phương thức là hàm hasattr()
. Hàm này cho phép bạn kiểm tra xem một đối tượng nhất định có thuộc tính hoặc phương thức nhất định hay không. Hàm này có chữ ký sau:
hasattr(object, name)
Trong chữ ký này, đối số object
có thể nhận bất kỳ đối tượng Python nào, trong khi đối số name
phải giữ tên của một thuộc tính dưới dạng chuỗi. Hàm này là một vị từ trả về True
nếu đối tượng có thuộc tính có tên được cung cấp và False
nếu không.
Trong thực tế, bạn có thể sử dụng hàm này để kiểm tra xem một đối tượng có thuộc tính hoặc phương thức nhất định hay không trước khi bạn thử sử dụng nó. Ví dụ: giả sử bạn có các lớp sau:
class Duck:
def fly(self):
print("The duck is flying")
def swim(self):
print("The duck is swimming")
class Pigeon:
def fly(self):
print("The pigeon is flying")
Những lớp này đại diện cho hai loài chim khác nhau. Cả hai loài chim đều có khả năng bay nhưng chỉ có con vịt mới có khả năng bơi. Bây giờ, giả sử bạn muốn sử dụng chúng trong một vòng lặp như sau:
>>> from birds import Duck, Pigeon
>>> birds = [Duck(), Pigeon()]
>>> for bird in birds:
... bird.fly()
... bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
Traceback (most recent call last):
...
AttributeError: 'Pigeon' object has no attribute 'swim'
Vòng lặp này hoạt động với phiên bản Duck
. Tuy nhiên, nó sẽ tạo ra một ngoại lệ AttributionError
khi bạn gọi .swim()
trên phiên bản Pigeon
vì lớp không có phương thức này. Để tránh lỗi này, bạn có thể sử dụng hàm hasattr()
để kiểm tra xem phương thức có tồn tại hay không trước khi gọi nó:
>>> for bird in birds:
... bird.fly()
... if hasattr(bird, "swim"):
... bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
Mã của bạn bây giờ không bị lỗi vì bạn đã sử dụng hàm hasattr()
để đảm bảo rằng con chim hiện tại có phương thức .swim()
trước khi gọi nó.
Tạo và kiểm tra các loại: type()
, isinstance()
và issubclass()
Python là ngôn ngữ được gõ động, có nghĩa là Python chỉ kiểm tra các loại khi mã chạy và loại biến có thể thay đổi trong suốt thời gian tồn tại của nó. Do tính năng ngôn ngữ này, bạn có thể cần phải kiểm tra rõ ràng loại đối tượng trước khi sử dụng để mã của bạn không bị lỗi.
Lưu ý: Vì Python là ngôn ngữ được gõ động nên kiểu gõ vịt được ưa chuộng hơn so với việc kiểm tra kiểu rõ ràng. Để tìm hiểu về cách gõ vịt, hãy xem hướng dẫn Nhập vịt bằng Python: Viết mã linh hoạt và tách rời.
Để biết loại của một đối tượng nhất định, bạn có thể sử dụng hàm type()
tích hợp sẵn:
>>> type(42)
<class 'int'>
>>> type(2.75)
<class 'float'>
>>> type("Hello")
<class 'str'>
Khi bạn gọi type()
với bất kỳ lớp Python nào làm đối số, thì bạn sẽ nhận được loại của đối tượng, loại này bạn cũng có thể gọi là lớp của đối tượng. Trong ví dụ này, bạn gọi type()
với một số nguyên làm đối số và nhận lớp int
làm phản hồi. Sau đó, bạn sử dụng type()
với số dấu phẩy động và nhận lớp float
, v.v.
Nếu bạn muốn kiểm tra loại đối tượng bằng type()
, thì bạn có thể thực hiện như sau:
>>> type(42) == int
True
>>> type(42) == float
False
Cách sử dụng type()
này có tác dụng. Tuy nhiên, đó không phải là cách tiếp cận được khuyến nghị. Bạn sẽ tìm hiểu thêm về việc kiểm tra loại trong giây lát. Hiện tại, bạn sẽ tiếp tục tìm hiểu kiến thức cơ bản về type()
. Để bắt đầu, đây là chữ ký của hàm:
type(object)
type(name, bases, dict, **kwds)
Bạn đã sử dụng chữ ký đầu tiên. Trong chữ ký này, đối số object
đại diện cho bất kỳ đối tượng Python nào.
Chữ ký thứ hai có liên quan nhiều hơn một chút. Bạn sẽ sử dụng chữ ký này để tạo các lớp mới một cách linh hoạt thay vì xác định loại đối tượng. Dưới đây là bản tóm tắt các lập luận và ý nghĩa của chúng:
name
Tên lớp
base
Một tuple chứa các lớp cơ sở
dict
Từ điển các thuộc tính và phương thức được định nghĩa trong nội dung lớp
**kwds
Đối số từ khóa bổ sung được chuyển đến hàm tạo siêu dữ liệu
Khi bạn sử dụng type()
với các đối số này, bạn có thể xây dựng các lớp một cách linh hoạt. Theo cách này, type()
là dạng động của câu lệnh class
. Hãy xem xét ví dụ về đồ chơi sau:
>>> def greet(self):
... print("Hello, World!")
...
>>> DemoClass = type("DemoClass", (), {"value": 42, "greet": greet})
>>> DemoClass.value
42
>>> instance = DemoClass()
>>> instance.value
42
>>> instance.greet()
Hello, World!
>>> dir(instance)
[
'__class__',
'__delattr__',
'__dict__',
...
'greet',
'value'
]
Trong ví dụ nhanh này, bạn sử dụng type()
để tạo một lớp demo tự động kế thừa từ object
vì bộ base
trống. Lớp mới sẽ có một phương thức gọi là .greet()
mà bạn đã xác định trước. Nó cũng có một thuộc tính lớp tên là .value
mà bạn đặt thành 42
.
Đối với thuộc tính, bạn nên cung cấp tên thuộc tính dưới dạng chuỗi và giá trị của thuộc tính. Đối với các phương thức, bạn nên đặt tên của phương thức dưới dạng một chuỗi và một đối tượng phương thức, đây là một phương thức không có dấu ngoặc đơn gọi. Lưu ý rằng các phương thức phiên bản như .greet()
phải lấy đối tượng hiện tại làm đối số mà bạn thường gọi là self
.
Để có một ví dụ thực tế hơn, giả sử bạn muốn viết một hàm cho phép bạn xây dựng các lớp một cách linh hoạt từ các lược đồ dữ liệu khác nhau. Trong tình huống này, bạn có thể làm một số việc như sau:
def create_class(name, custom_members):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
return f"{name}({self.__dict__})"
class_members = {
"__init__": __init__,
"__repr__": __repr__,
}
class_members.update(custom_members)
return type(name, (), class_members)
Trong mã này, bạn tạo một hàm có hai đối số. Đối số đầu tiên, name
, phải là một chuỗi cung cấp tên lớp hợp lệ. Đối số thứ hai, custom_members
, phải là từ điển gồm các thuộc tính và phương thức.
Sau đó, bạn xác định một hàm bên trong có tên là .__init__()
mà bạn sẽ sử dụng làm hàm khởi tạo lớp. Hàm .__repr__()
sẽ cho phép bạn cung cấp biểu diễn chuỗi cho các đối tượng trong lớp của bạn.
Tiếp theo, bạn tạo một từ điển để bao gồm các hàm làm phương thức cho lớp của mình và cập nhật từ điển với nội dung class_members
, nội dung này sẽ đến từ người dùng.
Cuối cùng, bạn sử dụng hàm type()
để tạo lớp với tên được cung cấp và từ điển của các thành viên. Dưới đây là một vài ví dụ về cách sử dụng chức năng này:
>>> from factory import create_class
>>> User = create_class("User", {"name": "", "age": 0, "email": ""})
>>> Product = create_class(
... "Product", {"name": "", "price": 0.0, "units": 0}
... )
>>> john = User(name="John", age=30, email="john@example.com")
>>> table = Product(name="Table", price=200.0, units=5)
>>> john.name
'John'
>>> john.age
30
>>> john.email
'john@example.com'
>>> table.name
'Table'
>>> table.price
200.0
>>> table.units
5
Trong đoạn mã này, trước tiên bạn tạo hai lớp bằng cách sử dụng create_class()
. Lớp đầu tiên đại diện cho người dùng và lớp thứ hai đại diện cho sản phẩm. Cả hai đều có bộ thuộc tính cá thể khác nhau.
Sau đó, bạn tạo các phiên bản cụ thể của từng lớp với các giá trị phù hợp cho thuộc tính. Cuối cùng, bạn truy cập các thuộc tính bằng ký hiệu dấu chấm. Điều đó thật tuyệt! Lớp học của bạn hoạt động như mong đợi.
Hàm type()
là một công cụ tuyệt vời để tạo các lớp một cách linh hoạt. Mặc dù bạn cũng có thể sử dụng hàm này để kiểm tra loại đối tượng, công cụ được khuyên dùng để kiểm tra loại rõ ràng là hàm isinstance()
tích hợp sẵn vì nó tính đến các lớp con.
Chữ ký cho isinstance()
giống như sau:
isinstance(object, classinfo)
Trong chữ ký này, object
đại diện cho bất kỳ đối tượng Python nào mà bạn quan tâm. Đối số classinfo
là lớp hoặc các lớp mà bạn muốn kiểm tra. Đối số này có thể là một đối tượng lớp đơn, một bộ đối tượng lớp hoặc một kiểu kết hợp.
Hãy xem xét các ví dụ sau khi bạn sử dụng isinstance()
để kiểm tra các giá trị số:
>>> isinstance(42, int)
True
>>> isinstance(42.0, (int, float))
True
>>> isinstance(42.0, int | float)
True
Trong ví dụ đầu tiên, bạn sử dụng isinstance()
để kiểm tra xem 42
có phải là phiên bản của lớp int
hay không. Trong ví dụ thứ hai, bạn sử dụng isinstance()
để kiểm tra xem 42.0
có phải là phiên bản của int
hay float
. Trong ví dụ này, bạn sử dụng một bộ lớp để cung cấp đối số classinfo
.
Cuối cùng, trong ví dụ thứ ba, bạn thực hiện kiểm tra tương tự như trong ví dụ thứ hai. Lần này, bạn sử dụng ký tự ống dẫn (|
) để tạo kiểu kết hợp với int
và float
. Lưu ý rằng isinstance()
là hàm vị ngữ trả về True
nếu đối tượng đầu vào là một phiên bản của một trong các lớp được cung cấp.
Hàm isinstance()
cũng xem xét các lớp con. Ví dụ: lớp bool
là lớp con của int
vì vậy nếu bạn so sánh một phiên bản của bool
với int
, thì kết quả là bạn sẽ nhận được True
:
>>> isinstance(False, int)
True
>>> type(False) == int
False
Vì bool
là lớp con của int
nên hàm isinstance()
trả về True
khi bạn kiểm tra giá trị Boolean với lớp int
. Lưu ý rằng nếu bạn cố gắng thực hiện kiểm tra tương tự với type()
thì bạn sẽ nhận được False
vì type()
không xem xét các lớp con.
Có một chức năng tích hợp khác có thể hữu ích cho việc kiểm tra kiểu. Hàm này được gọi là issubclass()
và nó kiểm tra xem một lớp đã cho có phải là lớp con của lớp khác hay không:
>>> issubclass(int, object)
True
>>> issubclass(bool, int)
True
>>> issubclass(int, float)
False
Trong ví dụ đầu tiên, bạn kiểm tra xem lớp int
có phải là lớp con của object
hay không. Trong trường hợp này, bạn nhận được True
vì tất cả các lớp Python đều xuất phát từ đối tượng
. Sau đó, bạn kiểm tra xem bool
có phải là lớp con của int
hay không, điều này cũng đúng như bạn đã biết.
Trong ví dụ cuối cùng, bạn sử dụng issubclass()
để kiểm tra xem int
có phải là lớp con của float
hay không, là False
.
Chữ ký của issubclass()
như sau:
issubclass(class, classinfo)
Trong trường hợp này, đối số class
là lớp mà bạn muốn kiểm tra, trong khi đối số classinfo
hoạt động giống như trong isinstance()
.
Kiểm tra các đối tượng có thể gọi được: callable()
Một đối tượng có thể gọi được trong Python là bất kỳ đối tượng nào bạn có thể gọi bằng cách sử dụng một cặp dấu ngoặc đơn và một loạt đối số nếu cần. Trong Python, các đối tượng có thể gọi được bao gồm các hàm, lớp, phương thức, phiên bản của các lớp có phương thức .__call__()
, các bao đóng và các hàm tạo.
Đôi khi, bạn có thể cần biết liệu một đối tượng có thể gọi được hay không trước khi gọi nó trong mã của mình. Để thực hiện việc này, bạn có thể sử dụng hàm callable()
tích hợp sẵn, hàm này lấy một đối tượng làm đối số và trả về True
nếu đối tượng có vẻ như có thể gọi được. Nếu không, nó sẽ trả về False
.
Dưới đây là một số ví dụ về cách sử dụng callable()
với một số đối tượng tích hợp sẵn:
>>> callable(abs)
True
>>> callable(int)
True
>>> callable(list)
True
>>> callable(True)
False
>>> callable(None)
False
Trong ba ví dụ đầu tiên, các đối số của callable()
đều là các hàm, do đó kết quả là bạn nhận được True
. Trong hai ví dụ cuối cùng, bạn sử dụng các đối tượng True
và None
làm đối số. Những đối tượng này không thể gọi được nên kết quả là bạn nhận được False
.
Ví dụ thực tế, giả sử bạn cần xây dựng một ứng dụng xử lý các lệnh. Mọi lệnh phải có thể gọi được, nếu không nó sẽ không hợp lệ. Để kiểm tra tình trạng này, bạn có thể sử dụng callable()
. Đây là cách triển khai đồ chơi:
class CommandProcessor:
def __init__(self):
self.commands = {}
def register_command(self, command):
if not callable(command):
raise ValueError("command is not callable")
self.commands[command.__name__] = command
def execute_command(self, name, *args, **kwargs):
if (command := self.commands.get(name)) is None:
raise ValueError(f"command '{name}' not found")
return command(*args, **kwargs)
Trong lớp này, phương thức .register_command()
sử dụng callable()
để kiểm tra xem lệnh đầu vào có phải là đối tượng có thể gọi được hay không. Nếu đúng như vậy thì bạn đăng ký lệnh là hợp lệ. Tiếp theo, bạn có phương thức .execute_command()
để chạy lệnh dưới dạng lệnh có thể gọi được.
Đây là một ví dụ về cách sử dụng lớp này:
>>> from commands import CommandProcessor
>>> command_processor = CommandProcessor()
>>> def add(a, b):
... return a + b
...
>>> command_processor.register_command(add)
>>> command_processor.execute_command("add", 1, 2)
3
>>> subtract = 3 - 2
>>> command_processor.register_command(subtract)
Traceback (most recent call last):
...
ValueError: command is not callable
Trong ví dụ này, bạn tạo một phiên bản CommandProcessor
để xử lý các lệnh. Sau đó, bạn viết add()
để sử dụng nó làm lệnh. Vì add()
là một đối tượng có thể gọi được nên bạn có thể đăng ký nó như một lệnh hợp lệ và chạy nó bằng phương thức .execute_command()
.
Cuối cùng, bạn xác định biến trừ
để giữ kết quả của phép trừ. Biến này không thể gọi được. Do đó, bạn nhận được ngoại lệ ValueError
khi đăng ký nó dưới dạng lệnh.
Truy cập Thành viên của Phụ huynh: super()
Khi làm việc với tính kế thừa trong các lớp Python, bạn thường cần truy cập vào các thuộc tính hoặc phương thức của lớp cha trong một lớp con. Cách Pythonic để thực hiện việc này là sử dụng hàm super()
tích hợp sẵn.
Lưu ý: Để tìm hiểu thêm về super()
, hãy xem hướng dẫn Tăng cường lớp học của bạn bằng Python super()
.
Trường hợp sử dụng phổ biến của super()
là khi bạn cần tạo một lớp con của một lớp hiện có và bạn cần một cách thích hợp để khởi tạo các thuộc tính của lớp cha. Hãy xem xét các lớp sau đại diện cho hình chữ nhật và hình vuông:
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
Trong mã này, bạn định nghĩa một lớp Rectangle
với hai thuộc tính, .length
và .width
. Nó cũng có hai phương pháp tính diện tích và chu vi của hình chữ nhật. Tiếp theo, bạn định nghĩa lớp Square
. Vì hình vuông là một loại hình chữ nhật có các cạnh bằng nhau nên bạn nên tạo Hình vuông
làm lớp con của Hình chữ nhật
và sử dụng lại chức năng mà bạn đã triển khai.
Trong hàm tạo Square()
, bạn chỉ cần một đối số duy nhất để biểu thị độ dài cạnh. Bạn có thể sử dụng đối số này để khởi tạo lớp cha bằng cách sử dụng super()
như bạn đã làm trong dòng được đánh dấu. Hàm super() cho phép bạn truy cập vào lớp cha Rectangle
. Khi đã truy cập vào lớp, bạn có thể gọi phương thức .__init__()
của nó để khởi tạo thuộc tính .length
và .width
với giá trị của bên
.
Xây dựng các đối tượng chung: object()
Trong Python, mỗi lớp đơn lẻ đều kế thừa ngầm từ lớp object
, lớp này được tích hợp sẵn trong ngôn ngữ. Nói cách khác, lớp object
là lớp cơ sở cho mọi lớp trong Python:
>>> issubclass(int, object)
True
>>> issubclass(float, object)
True
>>> issubclass(bool, object)
True
>>> issubclass(dict, object)
True
>>> class DemoClass:
... pass
...
>>> issubclass(DemoClass, object)
True
Bất kể lớp bạn đang xem xét là lớp tích hợp hay lớp tùy chỉnh, nó đều kế thừa từ đối tượng
.
Trong một số trường hợp, bạn có thể muốn tạo các phiên bản của lớp object
. Để thực hiện việc này, bạn có thể sử dụng hàm object()
tích hợp sẵn, đây thực sự là một hàm tạo lớp chứ không phải là một hàm, nhưng tài liệu Python liệt kê nó trong số các hàm dựng sẵn của nó.
Hàm object()
không nhận đối số và trả về một đối tượng không có đặc điểm mới, có các phương thức chung cho tất cả các đối tượng Python. Không giống như các đối tượng thông thường, đối tượng mà bạn nhận được khi gọi object()
không có thuộc tính .__dict__
, do đó bạn không thể thêm thuộc tính động vào loại đối tượng này :
>>> obj = object()
>>> dir(obj)
[
'__class__',
'__delattr__',
'__dir__',
...
'__str__',
'__subclasshook__'
]
>>> obj.attr = "Some value"
Traceback (most recent call last):
...
AttributeError: 'object' object has no attribute 'attr'
Trong ví dụ này, bạn tạo một đối tượng không có tính năng mới bằng cách gọi object()
. Với hàm dir()
tích hợp sẵn, bạn có thể liệt kê tất cả các phương thức và thuộc tính mà đối tượng này cung cấp. Cuối cùng, nếu bạn cố gắng thêm một thuộc tính vào đối tượng không có đặc điểm của mình một cách linh hoạt thì bạn sẽ nhận được một ngoại lệ AttributionError
.
Trong thực tế, bạn có thể sử dụng hàm object()
khi muốn tạo các giá trị trọng điểm duy nhất. Giá trị trọng điểm là điểm đánh dấu duy nhất mà bạn có thể sử dụng để biểu thị sự vắng mặt của giá trị. Bạn cũng có thể sử dụng nó như một điều kiện để dừng các thuật toán lặp hoặc đệ quy.
Để minh họa cách sử dụng object()
để tạo một giá trị canh gác, hãy xem xét lớp Circle
sau:
from time import sleep
SENTINEL = object()
class Circle:
def __init__(self, radius):
self.radius = radius
self._diameter = SENTINEL
@property
def diameter(self):
if self._diameter is SENTINEL:
sleep(0.5) # Simulate a costly computation
self._diameter = self.radius * 2
return self._diameter
Trong lớp này, bạn có thuộc tính .radius
nhận giá trị tại thời điểm khởi tạo. Sau đó, bạn có một thuộc tính không công khai tên là ._diameter
mà bạn khởi tạo bằng hằng số SENTINEL
. Để tạo hằng số này, bạn sử dụng hàm object()
.
Cuối cùng, bạn có thuộc tính .diameter
để tính đường kính từ bán kính được cung cấp. Trong ví dụ này, bạn sử dụng sleep()
từ mô-đun time
để mô phỏng việc tìm đường kính là một thao tác tốn kém. Do chi phí tính toán, bạn quyết định lưu trữ đường kính để nó được tính toán một lần trong suốt vòng đời của đối tượng.
Để kiểm tra xem đường kính đã được tính toán chưa, bạn so sánh giá trị hiện tại của nó với hằng số SENTINEL
. Trong ví dụ này, bạn cũng có thể sử dụng None
làm giá trị trọng điểm vì đường kính của hình tròn khó có thể nhận giá trị null. Tuy nhiên, khi None
có thể là giá trị hợp lệ cho thuộc tính hiện có thì object()
có thể là cách tốt nhất.
Làm việc với phạm vi Python
Python, giống như nhiều ngôn ngữ lập trình khác, quản lý khái niệm phạm vi. Phạm vi quy định cách tra cứu các biến và tên trong mã của bạn. Nó xác định mức độ hiển thị của một biến hoặc tên trong mã.
Phạm vi phụ thuộc vào nơi bạn tạo biến đó. Phạm vi của Python tuân theo một quy tắc được gọi là quy tắc LEGB. Các chữ cái trong từ viết tắt này là viết tắt của các phạm vi cục bộ, bao quanh, toàn cầu và tích hợp và quy tắc này tóm tắt bốn phạm vi mà bạn tìm thấy trong Python.
Lưu ý: Để tìm hiểu sâu hơn về phạm vi Python, hãy xem hướng dẫn Phạm vi Python & Quy tắc LEGB: Giải quyết tên trong mã của bạn.
Bạn sẽ tìm thấy hai hàm dựng sẵn có liên quan chặt chẽ đến phạm vi trong Python. Các chức năng này được liệt kê trong bảng dưới đây:
locals()
Cập nhật và trả về một từ điển đại diện cho bảng ký hiệu cục bộ hiện tại
globals()
Trả về một từ điển đại diện cho bảng ký hiệu chung hiện tại
Trong các phần sau, bạn sẽ tìm hiểu những kiến thức cơ bản về các hàm này và cách sử dụng chúng trong mã Python để quản lý một số khía cạnh trong phạm vi tên của bạn.
Kiểm tra và cập nhật phạm vi cục bộ: locals()
Phạm vi cục bộ là phạm vi hàm vì nó bao gồm phần thân của hàm. Mỗi khi bạn gọi một hàm, Python sẽ tạo một phạm vi cục bộ mới cho hàm đó. Theo mặc định, các đối số và tên mà bạn gán trong phần thân hàm chỉ tồn tại trong phạm vi cục bộ mà Python tạo khi bạn gọi hàm. Khi hàm trả về, phạm vi cục bộ biến mất và tên bị quên.
Nếu bạn cần kiểm tra trạng thái của phạm vi cục bộ hiện tại của mình thì bạn có thể sử dụng hàm locals()
tích hợp sẵn:
>>> def add(a, b):
... result = a + b
... print(locals())
... return result
...
>>> add(2, 5)
{'a': 2, 'b': 5, 'result': 7}
7
Trong hàm này, bạn lấy hai đối số, a
và b
. Các đối số này là cục bộ của add()
, nghĩa là bạn chỉ có thể truy cập và sử dụng chúng bên trong hàm. Sau đó, bạn tạo một biến cục bộ có tên là result
mà bạn sử dụng làm biến tạm thời để lưu trữ kết quả tính toán. Lệnh gọi locals()
trả về một từ điển chứa tên và giá trị của tất cả các biến này.
Lưu ý rằng locals()
chỉ lấy thông tin phạm vi ở điểm mà bạn gọi nó:
>>> def add(a, b):
... print(locals())
... result = a + b
... return result
...
>>> add(2, 5)
{'a': 2, 'b': 5}
7
Trong biến thể add()
này, bạn gọi locals()
ở đầu hàm. Vì vậy, bạn chỉ nhận được các đối số trong từ điển kết quả. Điều này là do khi bạn gọi locals()
, biến result
vẫn chưa được xác định.
Kiểm tra và cập nhật phạm vi toàn cầu: globals()
Phạm vi toàn cầu là một phạm vi quan trọng khác trong Python. Đó là phạm vi cấp mô-đun và cho phép bạn xác định tên hoặc biến toàn cục. Bạn có thể truy cập và sửa đổi tên chung từ bất kỳ đâu trong mã của mình.
Để kiểm tra và cập nhật các biến và tên nằm trong phạm vi toàn cục hiện tại của bạn, bạn có thể sử dụng hàm globals()
tích hợp sẵn. Ví dụ: khi bạn bắt đầu một phiên REPL mới và gọi globals()
thì bạn sẽ nhận được kết quả như bên dưới:
>>> globals()
{
'__name__': '__main__',
'__doc__': None,
...
'__builtins__': <module 'builtins' (built-in)>
}
Theo mặc định, khi bạn bắt đầu phiên REPL, trình thông dịch sẽ tải một số tên và đối tượng vào phạm vi toàn cục của bạn. Ví dụ: đối tượng __name__
giữ tên của mô-đun hiện tại, là "__main__"
khi bạn đang ở trong một mô-đun thực thi. Nếu bạn đang sử dụng một mô-đun đã nhập thì biến này sẽ chứa tên của mô-đun.
Sau đó, bạn có tên __doc__
, tên này sẽ chứa chuỗi tài liệu của mô-đun nếu được cung cấp. Bạn cũng sẽ có một số tên khác. Cuối cùng, bạn có tên __buildins__
, chứa vùng tên nơi xác định các tên dựng sẵn. Đây là một mô-đun đặc biệt bao gồm tất cả các hàm dựng sẵn được đề cập trong hướng dẫn này và một số đối tượng dựng sẵn khác, như các ngoại lệ.
Nếu bạn bắt đầu xác định các biến và hàm trong phiên REPL của mình thì những tên này sẽ được thêm vào từ điển mà globals()
trả về:
>>> language = "Python"
>>> number = 42
>>> def greet():
... print("Hello, World!")
...
>>> class Demo:
... pass
...
>>> globals()
{
...
'__builtins__': <module 'builtins' (built-in)>,
'language': 'Python',
'number': 42,
'greet': <function greet at 0x100984040>,
'Demo': <class '__main__.Demo'>
}
Trong ví dụ này, bạn định nghĩa hai biến, một hàm và một lớp. Khi bạn gọi globals()
, bạn sẽ nhận được tên của tất cả các đối tượng đó ở cuối từ điển kết quả.
Từ điển mà globals()
trả về là từ điển có thể ghi. Bạn có thể tận dụng tính năng này khi cần sửa đổi hoặc cập nhật nội dung của phạm vi toàn cầu theo cách thủ công. Trường hợp sử dụng phổ biến của tính năng này là khi bạn cần tải các tham số cấu hình từ một tệp.
Ví dụ: giả sử bạn có tệp JSON sau đây với một số giá trị cấu hình cho kết nối cơ sở dữ liệu của bạn:
{
"DATABASE_URL": "postgres://user:pass@localhost/dbname",
"DEBUG_MODE": true,
"MAX_CONNECTIONS": 10
}
Bạn cần viết một hàm tải tệp này và thêm các tham số cấu hình được cung cấp vào phạm vi toàn cầu hiện tại của bạn. Đây là một cách triển khai có thể có của chức năng này:
import json
def load_config(config_file):
with open(config_file) as file:
config = json.load(file)
globals().update(config)
Trong chức năng này, trước tiên bạn mở tệp cấu hình và tải nội dung của nó vào từ điển có tên config
. Sau đó, bạn cập nhật từ điển mà globals()
trả về với nội dung của config
.
Đây là cách hoạt động của hàm trên:
>>> from config import load_config
>>> load_config("config.json")
>>> globals()
{
...
'DATABASE_URL': 'postgres://user:pass@localhost/dbname',
'DEBUG_MODE': True,
'MAX_CONNECTIONS': 10
}
>>> MAX_CONNECTIONS
10
Sau khi gọi load_config()
với tệp config.json
làm đối số, bạn sẽ tải các tham số cấu hình dưới dạng hằng số vào phạm vi toàn cục của mình. Bây giờ, bạn có thể sử dụng trực tiếp các hằng số này trong mã của mình.
Đối tượng hướng nội
Trong lập trình, nội quan kiểu là khả năng chương trình kiểm tra loại và thuộc tính của đối tượng trong thời gian chạy. Mọi thứ trong Python đều là một đối tượng, vì vậy khả năng kiểm tra các loại và thuộc tính trong thời gian chạy là một tài sản quý giá.
Dưới đây là một số hàm dựng sẵn cho phép bạn thực hiện một số kiểu xem xét nội tâm trong Python:
id()
Trả về danh tính của một đối tượng
dir()
Trả về danh sách tên trong phạm vi cục bộ hiện tại hoặc danh sách thuộc tính đối tượng
vars()
Trả về thuộc tính
__dict__
cho mô-đun, lớp hoặc đối tượng
Trong các phần sau, bạn sẽ tìm hiểu cách các hàm này hoạt động và cách bạn có thể sử dụng chúng trong mã để thực hiện việc xem xét nội tâm kiểu. Để bắt đầu, bạn sẽ bắt đầu với hàm id()
.
Biết danh tính của đối tượng: id()
Trong Python, mọi đối tượng riêng lẻ đều có một danh tính liên quan. Danh tính này là một số nguyên duy nhất và không đổi để xác định đối tượng trong suốt thời gian tồn tại của nó. Hai đối tượng có vòng đời không trùng nhau có thể có cùng danh tính.
Nếu bạn cần biết danh tính của một đối tượng nhất định thì bạn có thể sử dụng hàm id()
với đối tượng đó làm đối số:
>>> id(42)
4315605776
>>> id("Python")
4315120464
>>> def greet():
... print("Hello, World!")
...
>>> id(greet)
4307259040
>>> class Demo:
... pass
...
>>> id(Demo)
4892672720
Khi bạn gọi id()
với bất kỳ đối tượng Python nào làm đối số, bạn sẽ nhận được một số là danh tính của đối tượng. Trong quá trình triển khai CPython của Python, danh tính của một đối tượng cũng là địa chỉ bộ nhớ nơi đối tượng đó tồn tại.
Biết danh tính của một đối tượng có thể giúp ích rất nhiều khi bạn gỡ lỗi mã của mình. Ví dụ: giả sử bạn muốn viết mã tính toán các giá trị riêng lẻ từ một loại chuỗi Fibonacci. Bạn có thể làm điều này bằng nhiều cách. Tuy nhiên, bạn nghĩ đến việc sử dụng một lớp với các thể hiện có thể gọi được và thuộc tính thể hiện cho phép bạn lưu vào bộ đệm các giá trị đã được tính toán.
Đây là một triển khai có thể có của lớp này:
class Fibonaccish:
def __init__(self, initial_value=1):
self._cache = [0, initial_value]
def __call__(self, index):
if index < len(self._cache):
fib_number = self._cache[index]
print(f"{index} {fib_number} id = {id(fib_number)}")
else:
fib_number = self(index - 1) + self(index - 2)
self._cache.append(fib_number)
return fib_number
Trong phương thức khởi tạo của Fibonaccish
, bạn xác định một danh sách để lưu giữ bộ nhớ đệm của các giá trị được tính toán. Sau đó, bạn xác định phương thức đặc biệt .__call__()
, cho phép các phiên bản của lớp của bạn có thể gọi được giống như các hàm.
Lưu ý: Chuỗi Fibonacci thông thường bắt đầu bằng 0 và 1. Trong chuỗi giống Fibonacci của bạn, số thứ hai có thể là bất kỳ số nào nhưng bạn tuân theo quy tắc rằng một số là tổng của hai số trước đó trong sự liên tiếp.
Trong phương pháp này, bạn xác định xem giá trị Fibonacci cho chỉ mục đích đã được tính toán và lưu trữ trong bộ nhớ đệm hay chưa. Sau đó, bạn thêm lệnh gọi đến print()
để giúp bạn gỡ lỗi mã bằng cách sử dụng id()
để đảm bảo sử dụng các giá trị được lưu trong bộ nhớ đệm.
Đây là cách lớp này hoạt động trong thực tế:
>>> from fibonacci import Fibonaccish
>>> fibonacci_333 = Fibonacci(333)
>>> fibonacci_333(2)
0 0 id = 94800819952840
1 333 id = 140276932935312
333
>>> fibonacci_333(4)
2 333 id = 140276932934960
1 333 id = 140276932935312
2 333 id = 140276932934960
999
Trong ví dụ về mã này, bạn tạo một phiên bản của Fibonaccish
với giá trị ban đầu là 333. Các giá trị đầu tiên của chuỗi này sẽ là 0, 333, 333, 666, 999 và 1665.
Phiên bản bạn tạo có thể gọi được nên bạn có thể sử dụng nó như một hàm thông thường. Sau đó, bạn gọi phiên bản có 2
làm đối số. Cuộc gọi in danh tính của các giá trị 0
và 333
tại chỉ mục 0
và 1
tương ứng. Tiếp theo, bạn gọi fibonacci_333()
với 4
làm đối số. Trong trường hợp này, bạn nhận được danh tính của 333
ba lần, cả ở chỉ mục 1 và 2.
Khi xem xét các danh tính, bạn nhận ra rằng hàm của bạn sử dụng cùng một đối tượng cho cùng một chỉ mục, trong khi đó lại khác nhau đối với hai phiên bản khác nhau của 333
. Bằng cách này, bạn có thể xác nhận rằng hàm này sử dụng bộ nhớ đệm như mong đợi.
Nếu bạn lặp lại ví dụ với dãy Fibonacci thông thường, Fibonacci(1)
, bạn sẽ thấy kết quả hơi khác một chút. Trong trường hợp này, Python sẽ thực hiện 1
bên trong để cùng một đối tượng được sử dụng ở cả chỉ mục 1 và 2 trong bộ đệm của bạn.
Kiểm tra tên và thuộc tính: dir()
và vars()
Đôi khi, bạn cần biết các thuộc tính hoặc phương thức được xác định trong một đối tượng hoặc phạm vi nhất định. Đối với loại yêu cầu này, Python có hai hàm dựng sẵn khác nhau, dir()
và vars()
.
Hàm dir()
không có đối số sẽ trả về danh sách các tên trong phạm vi hiện tại. Vì vậy, kết quả sẽ phụ thuộc vào vị trí bạn gọi hàm. Với bất kỳ đối tượng Python nào làm đối số, dir()
sẽ cố gắng trả về danh sách các thuộc tính cho đối tượng đó.
Ví dụ: nếu bạn gọi dir()
mà không có đối số trong phiên REPL mới thì bạn sẽ nhận được kết quả như sau:
>>> dir()
[
'__annotations__',
'__builtins__',
'__doc__',
'__loader__',
'__name__',
'__package__',
'__spec__'
]
Như bạn có thể thấy, dir()
trả về danh sách các tên được xác định trong phạm vi hiện tại của bạn, đó là phạm vi toàn cục trong ví dụ này.
Nếu bạn gọi dir()
với đối tượng Python làm đối số thì bạn sẽ nhận được danh sách các thuộc tính và phương thức của đối tượng. Nếu đối tượng đầu vào là một lớp thì bạn sẽ nhận được danh sách các phương thức và thuộc tính của lớp. Nếu đối tượng là một thể hiện của một lớp hiện có thì bạn sẽ nhận được danh sách các phương thức, thuộc tính lớp và thuộc tính thể hiện.
Hãy xem xét ví dụ sau sử dụng lại lớp Rectangle
của bạn từ phần trên hàm super()
:
>>> class Rectangle:
... def __init__(self, length, width):
... self.length = length
... self.width = width
... def area(self):
... return self.length * self.width
... def perimeter(self):
... return 2 * (self.length + self.width)
...
>>> dir(Rectangle)
[
'__class__',
'__delattr__',
'__dict__',
...
'area',
'perimeter'
]
>>> rectangle = Rectangle(2, 4)
>>> dir(rectangle)
[
'__class__',
'__delattr__',
'__dict__',
...
'area',
'length',
'perimeter',
'width'
]
Trong lệnh gọi đầu tiên tới dir()
, bạn nhận được các thuộc tính và phương thức lớp của lớp Rectangle
. Trong trường hợp này, bạn sử dụng đối tượng lớp làm đối số. Trong lệnh gọi thứ hai tới dir()
, bạn sử dụng một phiên bản của Rectangle
làm đối số và nhận tất cả các phương thức, thuộc tính lớp và thuộc tính phiên bản.
Hành vi dir()
mặc định là khác nhau đối với các loại đối tượng khác nhau. Dưới đây là tóm tắt về những khác biệt này:
- A module object
Trả về danh sách tên được xác định trong mô-đun.
- A type or class object
Trả về danh sách tên của các thuộc tính và phương thức của lớp cũng như của các lớp cơ sở.
- Other objects
Trả về danh sách các thuộc tính và phương thức, bao gồm thuộc tính lớp và thuộc tính của lớp cơ sở.
Bạn cũng có thể tùy chỉnh hành vi mặc định của dir()
bằng cách cung cấp phương thức đặc biệt .__dir__()
trong các lớp tùy chỉnh của mình. Tuy nhiên, chủ đề này nằm ngoài phạm vi của hướng dẫn này.
Hàm vars()
trả về thuộc tính .__dict__
cho mô-đun, lớp, phiên bản hoặc bất kỳ đối tượng nào khác có thuộc tính .__dict__
:
>>> vars(Rectangle)
mappingproxy(
{
'__module__': '__main__',
'__init__': <function Rectangle.__init__ at 0x10352d080>,
'area': <function Rectangle.area at 0x10352d120>,
'perimeter': <function Rectangle.perimeter at 0x10352d1c0>,
'__dict__': <attribute '__dict__' of 'Rectangle' objects>,
'__weakref__': <attribute '__weakref__' of 'Rectangle' objects>,
'__doc__': None
})
>>> vars(rectangle)
{'length': 2, 'width': 4}
Trong ví dụ này, bạn gọi vars()
với lớp Rectangle
làm đối số. Bạn nhận được thuộc tính .__dict__
của lớp, chứa các phương thức và thuộc tính lớp. Lưu ý rằng nó cũng chứa thuộc tính .__dict__
chứa các thuộc tính của các thể hiện của lớp. .__dict__
đó là những gì bạn nhận được khi gọi vars()
với một phiên bản của lớp.
Thuộc tính .__dict__
là một từ điển hoạt động như một không gian tên ánh xạ tên tới các đối tượng. Ví dụ: nó có thể ánh xạ tên phương thức tới một đối tượng phương thức hoặc tên thuộc tính tới một giá trị hoặc đối tượng cụ thể.
Chạy mã Python từ chuỗi
Trong một số trường hợp hiếm hoi, việc đánh giá các biểu thức hoặc chạy mã dưới dạng đối tượng chuỗi có thể hữu ích. Cách làm này không phổ biến trong mã trong thế giới thực vì nó có thể không an toàn, đặc biệt khi mã đích đến từ một nguồn không đáng tin cậy, chẳng hạn như thông tin đầu vào của người dùng.
Bất kể các vấn đề bảo mật liên quan, Python đều có ba hàm tích hợp cho phép bạn đánh giá các biểu thức hoặc chạy mã dưới dạng chuỗi. Dưới đây là tóm tắt về các chức năng này:
eval()
Đánh giá các biểu thức Python tùy ý từ một chuỗi hoặc mã đầu vào được biên dịch
exec()
Thực thi mã Python tùy ý từ một chuỗi hoặc đầu vào mã được biên dịch
compile()
Tạo một đối tượng mã được biên dịch từ một chuỗi
Trong các phần sau, bạn sẽ tìm hiểu những kiến thức cơ bản về các hàm này. Để bắt đầu, bạn sẽ bắt đầu bằng cách sử dụng hàm eval()
để đánh giá các biểu thức Python.
Thực thi biểu thức từ chuỗi: eval()
Trong Python, biểu thức là sự kết hợp giữa các đối tượng và toán tử trả về một giá trị. Bạn sẽ tìm thấy một số loại biểu thức, bao gồm toán học, Boolean, so sánh, biểu thức bitwise, v.v. Khi làm việc với các biểu thức, bạn có thể chạy chúng như mã Python thông thường. Tuy nhiên, nếu bạn cần đánh giá các biểu thức được xác định dưới dạng chuỗi thì sao?
Ví dụ: hãy nghĩ về cách bạn đánh giá những điều sau:
"sum([2, 3, 4, 5]) / 4 + 100"
Nếu bạn muốn đánh giá chuỗi này dưới dạng một biểu thức thì bạn sẽ phải phân tích chuỗi đó và tìm ra cách trích xuất các toán hạng và toán tử. Sau đó, bạn có thể xây dựng lại biểu thức và chạy nó trong trình thông dịch Python. Quá trình này nghe có vẻ như là một việc phải làm nhanh chóng. Tuy nhiên, nó có thể quá sức trong thực tế, đặc biệt nếu bạn xem xét vô số biểu thức khác nhau mà bạn có thể cần phải đánh giá trong mã thực.
May mắn thay, Python có hàm eval()
tích hợp sẵn giúp bạn đánh giá các biểu thức dưới dạng chuỗi.
Lưu ý: Để tìm hiểu thêm về eval()
, hãy xem hướng dẫn Python eval()
: Đánh giá biểu thức động.
Nếu bạn có một chuỗi chứa biểu thức Python hợp lệ thì bạn có thể gọi eval()
với chuỗi đó làm đối số. Hàm sẽ phân tích chuỗi, biên dịch nó thành mã byte và cuối cùng đánh giá nó như một biểu thức bình thường:
>>> eval("sum([2, 3, 4, 5]) / 4 + 100")
103.5
Ồ! Thật nhanh chóng và suôn sẻ! Bạn vừa chuyển chuỗi của mình tới eval()
, chạy mã và nhận được kết quả của biểu thức.
Chữ ký của eval()
trông giống như sau:
eval(expression[, globals[, locals]])
Đối số đầu tiên, biểu thức
, chứa biểu thức mà bạn cần đánh giá. Các đối số còn lại là tùy chọn. Đó là lý do tại sao chúng được đặt trong dấu ngoặc vuông. Dưới đây là bản tóm tắt những lập luận này và ý nghĩa của chúng:
expression
Một chuỗi chứa biểu thức Python hợp lệ
globals
Một từ điển chứa một không gian tên chung để sử dụng trong lệnh gọi
eval()
locals
Một từ điển chứa một không gian tên cục bộ để sử dụng trong lệnh gọi
eval()
Bạn đã xem ví dụ về cách sử dụng đối số biểu thức
, vì vậy bây giờ bạn có thể tập trung vào hai đối số còn lại. Trong mỗi ví dụ, bạn sẽ cần cung cấp một biểu thức.
Dưới đây là ví dụ về cách sử dụng đối số globals
:
>>> numbers = [2, 3, 4, 5]
>>> n = len(numbers)
>>> eval("sum(numbers) / n + 100")
103.5
>>> eval("sum(numbers) / n + 100", {})
Traceback (most recent call last):
...
NameError: name 'numbers' is not defined
>>> eval("sum(numbers) / n + 100", {"numbers": numbers, "n": n})
103.5
Theo mặc định, eval()
có quyền truy cập vào phạm vi toàn cục, do đó bạn có thể sử dụng tất cả các tên được xác định trong phạm vi này trong biểu thức bạn chuyển cho hàm. Nếu bạn đặt globals
thành một từ điển trống thì bạn hạn chế quyền truy cập vào phạm vi toàn cục và chức năng này không thành công.
Cuối cùng, bạn có thể sử dụng một từ điển rõ ràng, như bạn đã làm trong ví dụ cuối cùng, để cung cấp các biến toàn cục mà bạn muốn sử dụng khi đánh giá biểu thức đích.
Đối số locals
hoạt động tương tự. Phải mất một từ điển tên địa phương:
>>> def evaluator(expression):
... numbers = [2, 3, 4, 5]
... n = len(numbers)
... return eval(expression, {}, {"numbers": numbers, "n": n})
...
>>> evaluator("sum(numbers) / n + 100")
103.5
Bên trong hàm evaluator()
, bạn xác định numbers
và n
làm biến cục bộ. Trong lệnh gọi eval()
, bạn sử dụng một từ điển trống cho globals
và một từ điển chứa các biến cục bộ cho locals
.
Mặc dù hàm eval()
có vẻ giống như một công cụ tuyệt vời nhưng bạn phải cẩn thận khi sử dụng nó trong mã của mình. Trong thực tế, bạn sẽ an toàn hơn nếu không sử dụng công cụ này trong mã thực tế. Tại sao?
Hàm eval()
có hàm ý bảo mật khó có thể vượt qua. Ví dụ: nếu bạn sử dụng hàm này để đánh giá các biểu thức do người dùng bên ngoài cung cấp thì bạn sẽ khiến hệ thống của mình phải thực thi mã Python tùy ý.
Để biết thêm thông tin chi tiết về cách giảm thiểu rủi ro bảo mật liên quan đến eval()
, hãy xem phần Giảm thiểu các vấn đề bảo mật của eval()
trong Python eval()
: Đánh giá biểu thức một cách linh hoạt hướng dẫn.
Chạy mã từ chuỗi: exec()
và compile()
Hàm eval()
là một công cụ mạnh mẽ trong Python. Tuy nhiên, nó được thiết kế để đánh giá các biểu thức. Đôi khi, bạn có thể muốn chạy những đoạn mã phức tạp hơn dưới dạng chuỗi. Ví dụ: bạn có thể muốn chạy các vòng lặp, câu lệnh điều kiện, câu lệnh ghép và thậm chí toàn bộ tập lệnh. Trong trường hợp này, bạn có thể sử dụng các hàm exec()
và compile()
tích hợp sẵn.
Lưu ý: Để tìm hiểu thêm về exec()
, hãy xem hướng dẫn exec()
của Python: Thực thi mã được tạo động.
Đây là chữ ký của hàm exec()
:
exec(code [, globals [, locals]])
Đối số code
có thể là một chuỗi chứa mã Python hợp lệ. Nó cũng có thể là một đối tượng mã được biên dịch mà bạn có thể tạo bằng hàm compile()
. Bạn sẽ tìm hiểu về compile()
sau. Hiện tại, bạn sẽ sử dụng một chuỗi để cung cấp đối số code
.
Nếu code
xuất hiện dưới dạng một chuỗi thì exec()
sẽ phân tích cú pháp dưới dạng một chuỗi các câu lệnh Python. Sau đó, nó biên dịch mã thành mã byte và cuối cùng, nó thực thi mã trừ khi xảy ra lỗi cú pháp trong bước phân tích cú pháp hoặc biên dịch.
Hãy xem xét ví dụ sau:
>>> functions = [
... "def add(a, b): return a + b",
... "def subtract(a, b): return a - b",
... "def multiply(a, b): return a * b",
... "def divide(a, b): return a / b",
... ]
>>> for function in functions:
... exec(function)
...
Trong ví dụ này, bạn xác định danh sách các chuỗi. Mỗi chuỗi chứa các hàm Python cho phép toán số học cơ bản. Sau đó, bạn bắt đầu lặp lại danh sách. Với exec()
, bạn thực thi các chuỗi xác định hàm. Bước này đưa mọi chức năng vào phạm vi toàn cầu hiện tại của bạn. Bây giờ, bạn có thể sử dụng chúng như một chức năng thông thường:
>>> add(1, 2)
3
>>> subtract(3, 2)
1
>>> multiply(2, 3)
6
>>> divide(6, 3)
2.0
Các hàm số học hiện có sẵn trong phạm vi toàn cầu của bạn, vì vậy bạn có thể sử dụng chúng để chạy các phép tính của mình.
Giống như eval()
, exec()
nhận các đối số globals
và locals
, các đối số này cũng là tùy chọn. Các đối số này có ý nghĩa tương tự nhau trong cả hai hàm, vì vậy bạn có thể thử chúng như một bài tập.
Lưu ý: Hàm exec()
cũng có ý nghĩa bảo mật. Để tìm hiểu thêm về chúng, hãy xem phần Khám phá và giảm thiểu rủi ro bảo mật đằng sau exec()
trong exec()
của Python: Thực thi mã được tạo động hướng dẫn.
Khi có một chuỗi chứa mã mà bạn sẽ sử dụng lại nhiều lần, bạn có thể sử dụng hàm compile()
để biên dịch mã một lần và sử dụng mã đó ở mọi nơi. Cách thực hành này sẽ giúp mã của bạn hiệu quả và nhanh hơn vì bước biên dịch chỉ chạy một lần.
Chữ ký của compile()
trông giống như sau:
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
Chữ ký này hơi phức tạp một chút vì nó có một số lập luận mà bạn cần hiểu. Dưới đây là bản tóm tắt các lập luận và ý nghĩa của chúng:
source
Giữ mã mà bạn cần biên dịch thành mã byte
filename
Giữ tập tin mà mã được đọc
mode
Chỉ định loại mã nào phải được biên dịch
flags
anddont_inherit
Kiểm soát những tùy chọn trình biên dịch nào sẽ được kích hoạt và những tính năng nào trong tương lai sẽ được cho phép
optimize
Chỉ định mức độ tối ưu hóa của quá trình biên dịch
Để đọc từ một đối tượng chuỗi, bạn sẽ phải đặt filename
thành giá trị "<string>"
. Đối số mode
có thể nhận một trong các giá trị sau:
"eval"
khisource
bao gồm một biểu thức"exec"
khisource
là một chuỗi các câu lệnh"single"
khisource
là một câu lệnh tương tác duy nhất
Tùy thuộc vào mã nguồn và chức năng bạn định sử dụng để chạy mã đó, bạn sẽ chọn giá trị đầu tiên hoặc giá trị thứ hai. Đối số single
rất hữu ích khi bạn muốn chạy một câu lệnh như print("Hello, World!")
mà bạn thường chạy trong một phiên tương tác.
Để minh họa cách sử dụng compile()
, hãy xem xét ví dụ về đồ chơi sau:
>>> code = """
... result = sum(number for number in iterable if not number % 2)
... """
>>> compiled_code = compile(code, "<string>", "exec")
>>> context = {"iterable": [1, 2, 3, 4]}
>>> exec(compiled_code, context)
>>> context["result"]
6
>>> context = {"iterable": [10, 40, 50, 20]}
>>> exec(compiled_code, context)
>>> context["result"]
120
Trong ví dụ này, bạn có một đoạn mã trong một chuỗi. Mã này bao gồm lệnh gọi sum()
bao bọc một biểu thức trình tạo có thể lặp lại các số và trả về các số chẵn. Tiếp theo, bạn sử dụng hàm compile()
để biên dịch chuỗi thành đối tượng mã sẵn sàng để thực thi. Từ điển context
chứa các số có thể lặp lại.
Bạn gọi exec()
với mã được biên dịch và từ điển ngữ cảnh làm đối số. Lưu ý rằng bạn sử dụng ngữ cảnh
để cung cấp đối số globals
. Lệnh gọi exec()
sẽ cập nhật từ điển này với bất kỳ tên nào mà bạn xác định trong mã được biên dịch. Trong ví dụ cụ thể này, context
cuối cùng giữ biến result
với tổng các số chẵn trong iterable.
Để truy cập giá trị được tính toán, bạn sử dụng từ điển context
với khóa "result"
. Trong ví dụ cuối cùng, bạn sử dụng lại mã đã biên dịch để thực hiện phép tính tương tự với danh sách giá trị khác.
Sử dụng các hàm khác
Python có một số hàm dựng sẵn khác bao gồm nhiều chủ đề khác nhau. Dưới đây là tóm tắt về các chức năng này:
help()
Gọi hệ thống trợ giúp tích hợp
hash()
Tính giá trị băm của một đối tượng
__import__()
Được gọi bằng câu lệnh
import
memoryview()
Trả về một đối tượng xem bộ nhớ
Trong các phần sau, bạn sẽ tìm hiểu những kiến thức cơ bản về các hàm này và cách sử dụng chúng trong mã Python hoặc trong phiên tương tác của ngôn ngữ. Để bắt đầu, bạn sẽ bắt đầu với hàm help()
được tích hợp sẵn.
Truy cập Hệ thống trợ giúp tích hợp: help()
Hàm help()
tích hợp sẵn sẽ rất hữu ích khi bạn làm việc trong phiên REPL của Python. Chức năng này cho phép bạn truy cập vào hệ thống trợ giúp tương tác tích hợp sẵn. Hãy tiếp tục và mở một phiên Python tương tác trong terminal của bạn. Sau đó, gọi help()
mà không có đối số. Bạn sẽ được cung cấp hệ thống trợ giúp:
>>> help()
Welcome to Python 3.x's help utility!
If this is your first time using Python, you should definitely check out
the tutorial on the internet at https://docs.python.org/3.x/tutorial/.
Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules. To quit this help utility and
return to the interpreter, just type "quit".
To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics". Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".
help>
Kết quả đầu ra mang đến cho bạn sự chào đón nồng nhiệt đối với tiện ích trợ giúp Python. Sau đó, nó gợi ý bạn nên tham gia hướng dẫn Python chính thức nếu bạn chưa quen với ngôn ngữ này. Trong đoạn thứ ba và thứ tư, bạn sẽ được hướng dẫn cách sử dụng hệ thống trợ giúp.
Ở cuối trang, bạn sẽ thấy lời nhắc help>
đang chờ bạn nhập thông tin. Hãy tiếp tục và nhập tên str
rồi nhấn Enter. Bạn sẽ thấy một cái gì đó như sau:
Help on class str in module builtins:
class str(object)
| str(object='') -> str
| str(bytes_or_buffer[, encoding[, errors]]) -> str
|
| Create a new string object from the given object. If encoding or
| errors is specified, then the object must expose a data buffer
| that will be decoded using the given encoding and error handler.
| Otherwise, returns the result of object.__str__() (if defined)
| or repr(object).
| encoding defaults to sys.getdefaultencoding().
| errors defaults to 'strict'.
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
...
Đây là trang trợ giúp cho lớp str
. Trên trang này, bạn sẽ tìm thấy thông tin chi tiết về lớp học và mục tiêu của lớp học. Để rời khỏi trang và quay lại hệ thống trợ giúp, hãy tiếp tục và nhấn Q.
Khi ở trong hệ thống trợ giúp, bạn có thể tham khảo trợ giúp cho nhiều đối tượng Python, bao gồm các mô-đun, hàm, từ khóa tích hợp sẵn, v.v. Hãy tiếp tục và thử xem. Bạn có thể tìm thấy một số thông tin hữu ích!
Có cách thứ hai để bạn có thể sử dụng hàm help()
. Đầu tiên, nhập Q tại dấu nhắc help>
rồi nhấn Enter để quay lại đến phiên tương tác của bạn. Khi đã ở đó, bạn có thể gọi help()
với bất kỳ đối tượng Python nào làm đối số. Ví dụ: nếu bạn gọi hàm với lớp str
làm đối số thì nó sẽ đưa bạn đến cùng một trang bạn đã thấy trước đó:
>>> help(str)
Cách gọi help()
này cho phép bạn truy cập nhanh vào trang trợ giúp của một đối tượng nhất định. Điều quan trọng cần lưu ý là bạn có thể sử dụng đối tượng trực tiếp làm đối số cho help()
hoặc tên của đối tượng dưới dạng một chuỗi như trong help("str")
. Tuy nhiên, trong hầu hết các trường hợp, sử dụng tên của đối tượng dưới dạng chuỗi sẽ an toàn hơn:
>>> help(sys)
Traceback (most recent call last):
...
NameError: name 'sys' is not defined
Trong ví dụ này, bạn cố gắng truy cập trang trợ giúp cho mô-đun sys
. Bạn sử dụng tên của mô-đun làm đối số và nhận được ngoại lệ NameError
vì mô-đun này không có trong phạm vi hiện tại của bạn. Lệnh gọi help()
sẽ hoạt động an toàn nếu bạn sử dụng tên mô-đun dưới dạng chuỗi, như trong "sys"
. Hãy tiếp tục và thử xem!
Tạo mã băm: hash()
Nếu bạn làm việc trong các lĩnh vực như tính toàn vẹn dữ liệu, bảo mật hoặc mật mã thì có thể bạn đã quen với mã băm. Mã băm là một số có thể hoạt động như dấu vân tay kỹ thuật số cho một phần dữ liệu nhất định. Nó thường nhỏ hơn nhiều so với dữ liệu gốc và cho phép bạn xác minh tính toàn vẹn của nó.
Để tạo mã băm cho một đối tượng nhất định, bạn cần có hàm băm. Python có chức năng tích hợp sẵn để tạo mã băm. Hàm này được gọi thuận tiện là hash()
.
Lưu ý: Để tìm hiểu thêm về bảng băm, hãy xem hướng dẫn Xây dựng bảng băm bằng Python bằng TDD.
Chữ ký của hash()
giống như sau:
hash(object)
Nó lấy một đối tượng làm đối số và trả về giá trị băm của đối tượng đầu vào. Giá trị băm phải là số nguyên.
Dưới đây là một số ví dụ về cách sử dụng hàm hash()
:
>>> hash(42)
42
>>> hash(2.7)
1614090106449586178
>>> hash("Hello")
-6239611042439236057
>>> hash(int)
270201092
>>> class DemoClass: pass
...
>>> hash(DemoClass)
346520709
>>> demo_instance = DemoClass()
>>> hash(demo_instance)
271491289
Trong các ví dụ này, bạn đã sử dụng hàm hash()
với các đối tượng khác nhau, bao gồm các giá trị số, chuỗi, đối tượng hàm và các lớp tùy chỉnh. Trong mọi trường hợp, bạn nhận được một mã băm duy nhất.
Trong thực tế, có những đối tượng không có giá trị băm:
>>> hash([1, 2, 3])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
>>> hash({"one": 1, "two": 2})
Traceback (most recent call last):
...
TypeError: unhashable type: 'dict'
>>> hash({"red", "green", "bleu"})
Traceback (most recent call last):
...
TypeError: unhashable type: 'set'
Bạn sẽ lưu ý rằng các đối tượng có thể thay đổi không thể băm được trong Python vì bạn có thể thay đổi giá trị của đối tượng có thể thay đổi trong suốt thời gian tồn tại của nó.
Nhập đối tượng từ tên chuỗi: __import__()
Hàm __import__()
tích hợp sẵn của Python là một công cụ nâng cao không phổ biến trong lập trình hàng ngày. Hàm này được gọi nội bộ bằng câu lệnh import
. Việc sử dụng trực tiếp __import__()
không được khuyến khích mà thay vào đó là importlib.import_module()
. Tuy nhiên, đây là một chức năng tích hợp sẵn nên bạn sẽ tìm hiểu một chút về nó trong phần này.
Lưu ý: Để tìm hiểu sâu hơn về hệ thống nhập, hãy xem hướng dẫn import
Python: Kỹ thuật và mẹo nâng cao.
Chữ ký của __import__()
trông giống như sau:
__import__(name, globals=None, locals=None, fromlist=(), level=0)
Với chức năng này, bạn có thể nhập mô-đun theo tên
của nó. tên
này phải là một chuỗi. Dưới đây là bản tóm tắt các đối số của hàm và ý nghĩa của chúng:
name
Tên của mô-đun dưới dạng một chuỗi
globals
Một từ điển đại diện cho không gian tên toàn cầu
locals
Một từ điển đại diện cho không gian tên địa phương
fromlist
Danh sách các đối tượng hoặc mô-đun con cần được nhập từ mô-đun
level
Giá trị dương cho biết số lượng thư mục mẹ cần tìm kiếm liên quan đến thư mục của mô-đun đang gọi
__import__()
Để thực hiện điều gì đó tương đương với import sys
bằng hàm __import__()
, bạn có thể thực hiện như sau:
>>> sys = __import__("sys")
>>> sys
<module 'sys' (built-in)>
Trong ví dụ này, bạn tạo một biến sys
và gán cho nó kết quả của việc gọi __import__()
với chuỗi "sys"
làm đối số. Lệnh gọi __import__()
này sẽ nhập mô-đun sys
vào phạm vi toàn cầu hiện tại của bạn.
Thao tác dữ liệu nhị phân hiệu quả: memoryview()
Hàm memoryview()
tích hợp cho phép bạn truy cập dữ liệu nội bộ của một đối tượng hỗ trợ giao thức bộ đệm, chẳng hạn như array.array
, bytes
các đối tượng và bytearray
. Bạn có thể sử dụng chức năng này để thao tác các tập dữ liệu lớn hoặc giao tiếp với dữ liệu nhị phân.
Ví dụ: nếu bạn có một tập dữ liệu lớn và muốn sử dụng một phần của tập dữ liệu đó thì việc tạo một bản sao sẽ không hiệu quả. Thay vào đó, bạn có thể tạo một đối tượng memoryview
để truy cập dữ liệu mà không cần sao chép nó. Điều này cho phép bạn sử dụng ít bộ nhớ hơn và tăng tốc độ thực thi.
Ví dụ: giả sử bạn có dữ liệu pixel của một hình ảnh được biểu thị dưới dạng bytearray
và bạn muốn đảo ngược các giá trị pixel. Để thực hiện thao tác này một cách hiệu quả, bạn có thể sử dụng hàm memoryview()
:
>>> image = bytearray([0, 127, 255, 64, 128, 192, 32, 96, 160])
>>> mv = memoryview(image)
>>> for i in range(len(mv)):
... mv[i] = 255 - mv[i]
...
>>> list(mv)
[255, 128, 0, 191, 127, 63, 223, 159, 95]
>>> list(image)
[255, 128, 0, 191, 127, 63, 223, 159, 95]
Trong ví dụ này, bạn tạo một đối tượng memoryview
để truy cập dữ liệu đại diện cho hình ảnh của bạn. Trong vòng lặp for
, bạn lặp lại dữ liệu và đảo ngược các giá trị pixel. Sự chuyển đổi phản ánh trên dữ liệu gốc.
Nói tóm lại, hàm memoryview()
là một công cụ mạnh mẽ để làm việc với các đối tượng hỗ trợ giao thức bộ đệm mà không cần sao chép dữ liệu, giúp mã của bạn hoạt động hiệu quả và nhanh hơn.
Phần kết luận
Bạn đã học được kiến thức cơ bản về các hàm dựng sẵn của Python. Đây là các hàm mà bạn có thể sử dụng trực tiếp mà không cần nhập bất cứ thứ gì vì chúng có sẵn trong phạm vi hoặc không gian tên tích hợp sẵn.
Các hàm tích hợp giải quyết nhiều vấn đề lập trình phổ biến như thực hiện các phép toán, làm việc với các kiểu dữ liệu phổ biến, xử lý dữ liệu lặp lại, xử lý đầu vào và đầu ra, làm việc với phạm vi, v.v.
Trong hướng dẫn này, bạn đã học được:
- Thông tin cơ bản về các hàm tích hợp của Python
- Giới thiệu về các trường hợp sử dụng phổ biến của các hàm tích hợp trong Python
- Cách sử dụng các hàm này để giải các bài toán thực tế trong Python
Với kiến thức này, bạn đã có các kỹ năng Python cơ bản để giúp bạn viết mã Pythonic mạnh mẽ. Quan trọng hơn, giờ đây bạn đã biết về tất cả các chức năng đáng kinh ngạc này và có thể sử dụng chúng trong mã của mình để giải quyết các tác vụ thông thường một cách hiệu quả mà không cần phải phát minh lại bánh xe.