Thiết kế và Lịch sử FAQ¶
Tại sao Python sử dụng thụt lề để nhóm các câu lệnh?¶
Guido van Rossum tin rằng việc sử dụng thụt lề để nhóm là cực kỳ tinh tế và đóng góp rất nhiều vào sự rõ ràng của chương trình Python thông thường. Hầu hết mọi người dần yêu thích tính năng này sau một thời gian.
Vì không có dấu ngoặc bắt đầu/kết thúc nên không thể có sự bất đồng giữa việc phân nhóm mà trình phân tích cú pháp và trình đọc của con người cảm nhận được. Thỉnh thoảng các lập trình viên C sẽ gặp một đoạn mã như sau:
nếu (x <= y)
x++;
y--;
z++;
Chỉ câu lệnh x++ được thực thi nếu điều kiện đúng, nhưng việc thụt lề khiến nhiều người tin vào điều ngược lại. Ngay cả những lập trình viên C có kinh nghiệm đôi khi cũng sẽ nhìn chằm chằm vào nó trong một thời gian dài và tự hỏi tại sao y lại bị giảm đi ngay cả đối với x > y.
Vì không có dấu ngoặc bắt đầu/kết thúc nên Python ít xảy ra xung đột về kiểu mã hóa hơn. Trong C có nhiều cách đặt niềng răng khác nhau. Sau khi đã quen với việc đọc và viết mã bằng một phong cách cụ thể, việc cảm thấy hơi khó chịu khi đọc (hoặc bị yêu cầu viết) theo một phong cách khác là điều bình thường.
Nhiều kiểu mã hóa đặt dấu ngoặc bắt đầu/kết thúc trên một dòng. Điều này làm cho các chương trình dài hơn đáng kể và lãng phí không gian màn hình có giá trị, khiến việc có được cái nhìn tổng quan về chương trình trở nên khó khăn hơn. Lý tưởng nhất là một chức năng phải vừa trên một màn hình (ví dụ: 20--30 dòng). 20 dòng Python có thể thực hiện nhiều công việc hơn 20 dòng C. Điều này không chỉ do thiếu dấu ngoặc bắt đầu/kết thúc -- việc thiếu các khai báo và các kiểu dữ liệu cấp cao cũng chịu trách nhiệm -- mà cú pháp dựa trên thụt lề chắc chắn có ích.
Tại sao tôi nhận được kết quả lạ với các phép tính số học đơn giản?¶
Xem câu hỏi tiếp theo.
Tại sao các phép tính dấu phẩy động lại không chính xác đến vậy?¶
Người dùng thường ngạc nhiên trước kết quả như thế này:
>>> 1,2 - 1,0
0.199999999999999996
và cho rằng đó là lỗi trong Python. Không phải vậy. Điều này ít liên quan đến Python mà liên quan nhiều hơn đến cách nền tảng cơ bản xử lý các số dấu phẩy động.
Loại float trong CPython sử dụng C double để lưu trữ. Giá trị của đối tượng float được lưu trữ trong dấu phẩy động nhị phân với độ chính xác cố định (thường là 53 bit) và Python sử dụng các phép toán C, do đó dựa vào việc triển khai phần cứng trong bộ xử lý, để thực hiện các phép toán dấu phẩy động. Điều này có nghĩa là đối với các phép toán dấu phẩy động, Python hoạt động giống như nhiều ngôn ngữ phổ biến bao gồm C và Java.
Nhiều số có thể viết dễ dàng bằng ký hiệu thập phân nhưng không thể biểu diễn chính xác bằng dấu phẩy động nhị phân. Ví dụ: sau:
>>> x = 1,2
giá trị được lưu trữ cho x là giá trị gần đúng (rất tốt) với giá trị thập phân 1.2, nhưng không chính xác bằng giá trị đó. Trên một máy thông thường, giá trị được lưu trữ thực tế là:
1,0011001100110011001100110011001100110011001100110011 (nhị phân)
chính xác là:
1.1999999999999999555910790149937383830547332763671875 (thập phân)
Độ chính xác điển hình là 53 bit cung cấp cho Python độ chính xác 15--16 chữ số thập phân.
Để được giải thích đầy đủ hơn, vui lòng xem chương floating-point arithmetic trong hướng dẫn Python.
Tại sao chuỗi Python không thay đổi được?¶
Có một số lợi thế.
Một là hiệu suất: biết rằng một chuỗi là bất biến có nghĩa là chúng ta có thể phân bổ không gian cho chuỗi đó tại thời điểm tạo và các yêu cầu lưu trữ là cố định và không thay đổi. Đây cũng là một trong những lý do phân biệt giữa bộ và danh sách.
Một ưu điểm khác là các chuỗi trong Python được coi là "cơ bản" như số. Không có hoạt động nào sẽ thay đổi giá trị 8 thành bất kỳ giá trị nào khác và trong Python, không có hoạt động nào sẽ thay đổi chuỗi "tám" thành bất kỳ giá trị nào khác.
Tại sao 'bản thân' phải được sử dụng rõ ràng trong các định nghĩa và lệnh gọi phương thức?¶
Ý tưởng được mượn từ Modula-3. Hóa ra nó rất hữu ích, vì nhiều lý do.
Đầu tiên, rõ ràng hơn là bạn đang sử dụng thuộc tính phương thức hoặc phiên bản thay vì biến cục bộ. Đọc self.x hoặc self.meth() sẽ thấy rõ rằng một biến thể hiện hoặc phương thức được sử dụng ngay cả khi bạn không thuộc lòng định nghĩa lớp. Trong C++, bạn có thể nhận biết bằng cách thiếu khai báo biến cục bộ (giả sử biến toàn cục hiếm hoặc dễ nhận biết) -- nhưng trong Python, không có khai báo biến cục bộ, vì vậy bạn phải tra cứu định nghĩa lớp cho chắc chắn. Một số tiêu chuẩn mã hóa C++ và Java yêu cầu các thuộc tính phiên bản phải có tiền tố m_, vì vậy tính rõ ràng này vẫn hữu ích trong các ngôn ngữ đó.
Thứ hai, điều đó có nghĩa là không cần cú pháp đặc biệt nếu bạn muốn tham chiếu rõ ràng hoặc gọi phương thức từ một lớp cụ thể. Trong C++, nếu bạn muốn sử dụng một phương thức từ lớp cơ sở được ghi đè trong lớp dẫn xuất, bạn phải sử dụng toán tử :: -- trong Python bạn có thể viết baseclass.methodname(self, <argument list>). Điều này đặc biệt hữu ích cho các phương thức __init__() và nói chung trong trường hợp một phương thức lớp dẫn xuất muốn mở rộng phương thức lớp cơ sở có cùng tên và do đó phải gọi phương thức lớp cơ sở bằng cách nào đó.
Cuối cùng, đối với các biến thể hiện, nó giải quyết được vấn đề cú pháp bằng phép gán: vì các biến cục bộ trong Python (theo định nghĩa!) là những biến được gán một giá trị trong thân hàm (và không được khai báo rõ ràng là toàn cục), phải có một cách nào đó để cho trình thông dịch biết rằng một phép gán có nghĩa là gán cho một biến thể hiện thay vì một biến cục bộ và tốt nhất là nó phải là cú pháp (vì lý do hiệu quả). C++ thực hiện điều này thông qua các khai báo, nhưng Python không có các khai báo và sẽ thật đáng tiếc khi phải giới thiệu chúng chỉ vì mục đích này. Sử dụng self.var rõ ràng sẽ giải quyết được vấn đề này. Tương tự, để sử dụng các biến đối tượng, việc phải viết self.var có nghĩa là các tham chiếu đến các tên không đủ tiêu chuẩn bên trong một phương thức không cần phải tìm kiếm trong các thư mục của đối tượng. Nói cách khác, các biến cục bộ và biến thể hiện tồn tại trong hai không gian tên khác nhau và bạn cần cho Python biết nên sử dụng không gian tên nào.
Tại sao tôi không thể sử dụng phép gán trong một biểu thức?¶
Bắt đầu từ Python 3.8, bạn có thể!
Biểu thức gán bằng toán tử hải mã := gán một biến trong biểu thức:
while chunk := fp.read(200):
in (đoạn)
Xem PEP 572 để biết thêm thông tin.
Tại sao Python sử dụng các phương thức cho một số chức năng (ví dụ: list.index()) nhưng lại sử dụng các hàm cho chức năng khác (ví dụ: len(list))?¶
Như Guido đã nói:
(a) For some operations, prefix notation just reads better than postfix -- prefix (and infix!) operations have a long tradition in mathematics which likes notations where the visuals help the mathematician thinking about a problem. Compare the easy with which we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of doing the same thing using a raw OO notation.
(b) When I read code that says len(x) I know that it is asking for the length of something. This tells me two things: the result is an integer, and the argument is some kind of container. To the contrary, when I read x.len(), I have to already know that x is some kind of container implementing an interface or inheriting from a class that has a standard len(). Witness the confusion we occasionally have when a class that is not implementing a mapping has a get() or keys() method, or something that isn't a file has a write() method.
—https://mail.python.org/pipermail/python-3000/2006-November/004643.html
Tại sao join() là phương thức chuỗi thay vì phương thức danh sách hoặc bộ dữ liệu?¶
Chuỗi trở nên giống nhiều loại tiêu chuẩn khác bắt đầu từ Python 1.6, khi các phương thức được thêm vào để cung cấp cùng chức năng luôn có sẵn khi sử dụng các hàm của mô-đun chuỗi. Hầu hết các phương pháp mới này đã được chấp nhận rộng rãi, nhưng một phương pháp có vẻ khiến một số lập trình viên cảm thấy khó chịu là:
", ".join(['1', '2', '4', '8', '16'])
mang lại kết quả:
"1, 2, 4, 8, 16"
Có hai lập luận phổ biến chống lại việc sử dụng này.
Dòng đầu tiên chạy dọc theo dòng: "Trông nó thực sự xấu khi sử dụng một phương thức của một chuỗi ký tự (hằng số chuỗi)", mà câu trả lời là có thể, nhưng một chuỗi ký tự chỉ là một giá trị cố định. Nếu các phương thức được cho phép trên các tên được liên kết với chuỗi thì không có lý do hợp lý nào để làm cho chúng không có sẵn trên các chữ.
Phản đối thứ hai thường được đưa ra như sau: "Tôi thực sự đang yêu cầu một chuỗi nối các thành viên của nó lại với nhau bằng một hằng chuỗi". Đáng tiếc là bạn không như vậy. Vì lý do nào đó, có vẻ ít khó khăn hơn nhiều khi sử dụng split() làm phương thức chuỗi, vì trong trường hợp đó, rất dễ thấy rằng
"1, 2, 4, 8, 16".split(", ")
là một hướng dẫn cho một chuỗi ký tự để trả về các chuỗi con được phân cách bằng dấu phân cách đã cho (hoặc, theo mặc định, các khoảng trắng tùy ý).
join() là một phương thức chuỗi vì khi sử dụng nó, bạn đang yêu cầu chuỗi phân cách lặp lại một chuỗi các chuỗi và chèn chính nó vào giữa các phần tử liền kề. Phương pháp này có thể được sử dụng với bất kỳ đối số nào tuân theo các quy tắc dành cho đối tượng chuỗi, bao gồm bất kỳ lớp mới nào mà bạn có thể tự xác định. Các phương thức tương tự tồn tại cho các đối tượng byte và bytearray.
Ngoại lệ nhanh đến mức nào?¶
Khối try/except cực kỳ hiệu quả nếu không có ngoại lệ nào được đưa ra. Trên thực tế việc bắt một ngoại lệ là rất tốn kém. Trong các phiên bản Python trước 2.0, người ta thường sử dụng thành ngữ này
thử:
giá trị = mydict[key]
ngoại trừ KeyError:
mydict[key] = getvalue(key)
giá trị = mydict[key]
Điều này chỉ có ý nghĩa khi bạn mong đợi lệnh này hầu như luôn có chìa khóa. Nếu không phải như vậy, bạn đã mã hóa nó như thế này
nếu khóa trong mydict:
giá trị = mydict[key]
khác:
value = mydict[key] = getvalue(key)
Đối với trường hợp cụ thể này, bạn cũng có thể sử dụng value = dict.setdefault(key, getvalue(key)), nhưng chỉ khi lệnh gọi getvalue() đủ rẻ vì nó được đánh giá trong mọi trường hợp.
Tại sao không có câu lệnh switch hoặc case trong Python?¶
Nói chung, các câu lệnh switch có cấu trúc thực thi một khối mã khi một biểu thức có một giá trị hoặc tập hợp các giá trị cụ thể. Vì Python 3.10, người ta có thể dễ dàng khớp các giá trị bằng chữ hoặc hằng số trong một không gian tên bằng câu lệnh match ... case. Một giải pháp thay thế cũ hơn là chuỗi if... elif... elif... else.
Đối với những trường hợp bạn cần chọn từ một số lượng rất lớn các khả năng, bạn có thể tạo một từ điển ánh xạ các giá trị trường hợp tới các hàm cần gọi. Ví dụ:
hàm = {'a': hàm_1,
'b': hàm_2,
'c': self.method_1}
func = hàm [giá trị]
func()
Để gọi các phương thức trên đối tượng, bạn có thể đơn giản hóa hơn nữa bằng cách sử dụng getattr() tích hợp để truy xuất các phương thức có tên cụ thể:
lớp MyVisitor:
def thăm_a(tự):
...
công văn def (tự, giá trị):
Method_name = 'visit_' + str(value)
phương thức = getattr(self, Method_name)
phương thức()
Bạn nên sử dụng tiền tố cho tên phương thức, chẳng hạn như visit_ trong ví dụ này. Nếu không có tiền tố như vậy, nếu các giá trị đến từ một nguồn không đáng tin cậy, kẻ tấn công sẽ có thể gọi bất kỳ phương thức nào trên đối tượng của bạn.
Có thể bắt chước chuyển đổi với dự phòng, như với mặc định trường hợp chuyển đổi của C, khó hơn nhiều và ít cần thiết hơn.
Bạn không thể mô phỏng các luồng trong trình thông dịch thay vì dựa vào việc triển khai luồng dành riêng cho hệ điều hành được không?¶
Trả lời 1: Thật không may, trình thông dịch đẩy ít nhất một khung ngăn xếp C cho mỗi khung ngăn xếp Python. Ngoài ra, các tiện ích mở rộng có thể gọi lại Python vào những thời điểm gần như ngẫu nhiên. Do đó, việc triển khai các luồng hoàn chỉnh yêu cầu hỗ trợ luồng cho C.
Trả lời 2: May mắn thay, có Stackless Python, có vòng lặp trình thông dịch được thiết kế lại hoàn toàn để tránh ngăn xếp C.
Tại sao biểu thức lambda không thể chứa câu lệnh?¶
Biểu thức lambda của Python không thể chứa các câu lệnh vì khung cú pháp của Python không thể xử lý các câu lệnh được lồng bên trong các biểu thức. Tuy nhiên, trong Python, đây không phải là vấn đề nghiêm trọng. Không giống như các biểu mẫu lambda trong các ngôn ngữ khác, nơi chúng thêm chức năng, lambda Python chỉ là một ký hiệu tốc ký nếu bạn quá lười xác định hàm.
Các hàm đã là đối tượng hạng nhất trong Python và có thể được khai báo trong phạm vi cục bộ. Do đó, lợi thế duy nhất của việc sử dụng lambda thay vì hàm được xác định cục bộ là bạn không cần đặt tên cho hàm -- nhưng đó chỉ là một biến cục bộ mà đối tượng hàm (chính xác là cùng loại đối tượng mà biểu thức lambda tạo ra) được gán!
Python có thể được biên dịch sang mã máy, C hoặc một số ngôn ngữ khác không?¶
Cython biên dịch phiên bản sửa đổi của Python với các chú thích tùy chọn thành phần mở rộng C. Nuitka là trình biên dịch Python thành mã C++ mới nhất, nhằm hỗ trợ ngôn ngữ Python đầy đủ.
Python quản lý bộ nhớ như thế nào?¶
Chi tiết về quản lý bộ nhớ Python phụ thuộc vào việc triển khai. Việc triển khai tiêu chuẩn của Python, CPython, sử dụng tính năng tham chiếu để phát hiện các đối tượng không thể truy cập và một cơ chế khác để thu thập các chu trình tham chiếu, thực hiện định kỳ thuật toán phát hiện chu trình để tìm kiếm các chu trình không thể truy cập và xóa các đối tượng liên quan. Mô-đun gc cung cấp các chức năng để thực hiện thu thập rác, lấy số liệu thống kê gỡ lỗi và điều chỉnh các tham số của trình thu thập.
Tuy nhiên, các triển khai khác (chẳng hạn như Jython hoặc PyPy) có thể dựa vào một cơ chế khác, chẳng hạn như trình thu gom rác toàn diện. Sự khác biệt này có thể gây ra một số vấn đề khó chuyển đổi nếu mã Python của bạn phụ thuộc vào hoạt động triển khai tính tham chiếu.
Trong một số triển khai Python, đoạn mã sau (phù hợp với CPython) có thể sẽ hết bộ mô tả tệp
cho tệp trong very_long_list_of_files:
f = mở (tập tin)
c = f.read(1)
Thật vậy, bằng cách sử dụng sơ đồ đếm và hàm hủy tham chiếu của CPython, mỗi phép gán mới cho f sẽ đóng tệp trước đó. Tuy nhiên, với GC truyền thống, các đối tượng tệp đó sẽ chỉ được thu thập (và đóng) trong các khoảng thời gian khác nhau và có thể dài.
Nếu bạn muốn viết mã sẽ hoạt động với bất kỳ triển khai Python nào, bạn nên đóng tệp một cách rõ ràng hoặc sử dụng câu lệnh with; điều này sẽ hoạt động bất kể sơ đồ quản lý bộ nhớ
cho tệp trong very_long_list_of_files:
với open(file) là f:
c = f.read(1)
Tại sao CPython không sử dụng sơ đồ thu gom rác truyền thống hơn?¶
Có một điều, đây không phải là tính năng tiêu chuẩn C và do đó nó không thể mang theo được. (Đúng, chúng tôi biết về thư viện Boehm GC. Nó có các đoạn mã biên dịch mã cho các nền tảng chung most, không phải cho tất cả các nền tảng đó và mặc dù nó hầu như trong suốt nhưng không hoàn toàn trong suốt; cần có các bản vá để Python hoạt động với nó.)
GC truyền thống cũng trở thành vấn đề khi Python được nhúng vào các ứng dụng khác. Mặc dù trong Python độc lập, bạn có thể thay thế malloc() và free() tiêu chuẩn bằng các phiên bản do thư viện GC cung cấp, nhưng một ứng dụng nhúng Python có thể muốn có own thay thế cho malloc() và free() và có thể không muốn có Python. Hiện tại, CPython hoạt động với mọi thứ triển khai malloc() và free() đúng cách.
Tại sao tất cả bộ nhớ không được giải phóng khi CPython thoát?¶
Các đối tượng được tham chiếu từ không gian tên chung của các mô-đun Python không phải lúc nào cũng được giải phóng khi Python thoát. Điều này có thể xảy ra nếu có tài liệu tham khảo vòng tròn. Ngoài ra còn có một số bit bộ nhớ nhất định được thư viện C phân bổ không thể giải phóng được (ví dụ: một công cụ như Purify sẽ phàn nàn về những điều này). Tuy nhiên, Python rất tích cực trong việc dọn dẹp bộ nhớ khi thoát và cố gắng tiêu diệt mọi đối tượng.
Nếu bạn muốn buộc Python xóa một số thứ nhất định khi phân bổ, hãy sử dụng mô-đun atexit để chạy một hàm sẽ buộc những thao tác xóa đó.
Tại sao có các kiểu dữ liệu bộ và danh sách riêng biệt?¶
Danh sách và bộ dữ liệu, mặc dù giống nhau ở nhiều khía cạnh, nhưng thường được sử dụng theo những cách cơ bản khác nhau. Các bộ dữ liệu có thể được coi là tương tự như Pascal records hoặc C structs; chúng là những tập hợp nhỏ dữ liệu liên quan có thể thuộc các loại khác nhau được vận hành theo nhóm. Ví dụ: tọa độ Descartes được biểu diễn thích hợp dưới dạng bộ gồm hai hoặc ba số.
Mặt khác, danh sách giống mảng hơn trong các ngôn ngữ khác. Chúng có xu hướng chứa một số lượng đồ vật khác nhau, tất cả đều có cùng loại và được vận hành từng cái một. Ví dụ: os.listdir('.') trả về danh sách các chuỗi đại diện cho các tệp trong thư mục hiện tại. Các chức năng hoạt động trên đầu ra này thường sẽ không bị hỏng nếu bạn thêm một hoặc hai tệp khác vào thư mục.
Các bộ dữ liệu là bất biến, nghĩa là khi một bộ dữ liệu đã được tạo, bạn không thể thay thế bất kỳ phần tử nào của nó bằng một giá trị mới. Danh sách có thể thay đổi, nghĩa là bạn luôn có thể thay đổi các thành phần của danh sách. Chỉ các phần tử bất biến mới có thể được sử dụng làm khóa từ điển và do đó chỉ các bộ dữ liệu chứ không phải danh sách mới có thể được sử dụng làm khóa.
Danh sách được triển khai trong CPython như thế nào?¶
Danh sách của CPython thực sự là các mảng có độ dài thay đổi, không phải danh sách liên kết kiểu Lisp. Việc triển khai sử dụng một mảng tham chiếu liền kề đến các đối tượng khác và giữ một con trỏ tới mảng này cũng như độ dài của mảng trong cấu trúc đầu danh sách.
Điều này làm cho việc lập chỉ mục danh sách a[i] trở thành một thao tác có chi phí không phụ thuộc vào kích thước của danh sách hoặc giá trị của chỉ mục.
Khi các mục được thêm vào hoặc chèn vào, mảng tham chiếu sẽ được thay đổi kích thước. Một số tính năng thông minh được áp dụng để cải thiện hiệu suất của việc thêm các mục liên tục; khi mảng phải được phát triển, một số không gian bổ sung sẽ được phân bổ để những lần tiếp theo không yêu cầu thay đổi kích thước thực tế.
Từ điển được triển khai trong CPython như thế nào?¶
Từ điển của CPython được triển khai dưới dạng bảng băm có thể thay đổi kích thước. So với cây B, điều này mang lại hiệu suất tra cứu tốt hơn (thao tác phổ biến nhất cho đến nay) trong hầu hết các trường hợp và việc triển khai đơn giản hơn.
Từ điển hoạt động bằng cách tính toán mã băm cho mỗi khóa được lưu trữ trong từ điển bằng hàm tích hợp hash(). Mã băm rất khác nhau tùy thuộc vào khóa và hạt giống của mỗi quy trình; ví dụ: 'Python' có thể băm thành -539294296 trong khi 'python', một chuỗi khác nhau một bit, có thể băm thành 1142331976. Mã băm sau đó được sử dụng để tính toán vị trí trong mảng bên trong nơi giá trị sẽ được lưu trữ. Giả sử rằng bạn đang lưu trữ các khóa có tất cả các giá trị băm khác nhau, điều này có nghĩa là các từ điển mất thời gian không đổi -- O(1), theo ký hiệu Big-O -- để truy xuất khóa.
Tại sao khóa từ điển phải bất biến?¶
Việc triển khai bảng băm của từ điển sử dụng giá trị băm được tính từ giá trị khóa để tìm khóa. Nếu khóa là một đối tượng có thể thay đổi thì giá trị của nó có thể thay đổi và do đó hàm băm của nó cũng có thể thay đổi. Nhưng vì bất cứ ai thay đổi đối tượng khóa đều không thể biết rằng nó đang được sử dụng làm khóa từ điển, nên nó không thể di chuyển mục nhập đó trong từ điển. Sau đó, khi bạn cố gắng tra cứu cùng một đối tượng trong từ điển, nó sẽ không được tìm thấy vì giá trị băm của nó khác nhau. Nếu bạn cố tra cứu giá trị cũ thì nó cũng sẽ không được tìm thấy, vì giá trị của đối tượng được tìm thấy trong thùng băm đó sẽ khác.
Nếu bạn muốn một từ điển được lập chỉ mục bằng một danh sách, trước tiên chỉ cần chuyển đổi danh sách thành một bộ dữ liệu; hàm tuple(L) tạo một bộ dữ liệu có cùng mục nhập với danh sách L. Các bộ dữ liệu là bất biến và do đó có thể được sử dụng làm khóa từ điển.
Một số giải pháp không thể chấp nhận được đã được đề xuất:
Danh sách băm theo địa chỉ của chúng (ID đối tượng). Điều này không hiệu quả vì nếu bạn tạo một danh sách mới có cùng giá trị thì nó sẽ không được tìm thấy; ví dụ.:
mydict = {[1, 2]: '12'} print(mydict[[1, 2]])
sẽ đưa ra một ngoại lệ
KeyErrorvì id của[1, 2]được sử dụng ở dòng thứ hai khác với id ở dòng đầu tiên. Nói cách khác, các khóa từ điển nên được so sánh bằng==, không sử dụngis.Tạo một bản sao khi sử dụng danh sách làm khóa. Điều này không hiệu quả vì danh sách, là một đối tượng có thể thay đổi, có thể chứa tham chiếu đến chính nó và khi đó mã sao chép sẽ chạy vào một vòng lặp vô hạn.
Cho phép danh sách làm khóa nhưng yêu cầu người dùng không sửa đổi chúng. Điều này sẽ tạo ra một loại lỗi khó theo dõi trong các chương trình khi bạn vô tình quên hoặc sửa đổi danh sách. Nó cũng vô hiệu hóa một bất biến quan trọng của từ điển: mọi giá trị trong
d.keys()đều có thể được sử dụng làm khóa của từ điển.Đánh dấu danh sách là chỉ đọc khi chúng được sử dụng làm khóa từ điển. Vấn đề là không chỉ đối tượng cấp cao nhất mới có thể thay đổi giá trị của nó; bạn có thể sử dụng một bộ chứa danh sách làm khóa. Nhập bất cứ thứ gì làm khóa vào từ điển sẽ yêu cầu đánh dấu tất cả các đối tượng có thể truy cập từ đó là chỉ đọc -- và một lần nữa, các đối tượng tự tham chiếu có thể gây ra một vòng lặp vô hạn.
Có một mẹo để giải quyết vấn đề này nếu bạn cần, nhưng bạn phải tự chịu rủi ro khi sử dụng nó: Bạn có thể bọc một cấu trúc có thể thay đổi bên trong một thể hiện lớp có cả phương thức __eq__() và __hash__(). Sau đó, bạn phải đảm bảo rằng giá trị băm cho tất cả các đối tượng bao bọc nằm trong từ điển (hoặc cấu trúc dựa trên hàm băm khác), vẫn cố định trong khi đối tượng nằm trong từ điển (hoặc cấu trúc khác).
lớp ListWrapper:
def __init__(self, the_list):
self.the_list = the_list
def __eq__(bản thân, người khác):
trả về self.the_list == other.the_list
chắc chắn __hash__(tự):
l = self.the_list
kết quả = 98767 - len(l)*555
cho i, el trong liệt kê (l):
thử:
kết quả = kết quả + (hash(el) % 9999999) * 1001 + i
ngoại trừ Ngoại lệ:
kết quả = (kết quả% 7777777) + i * 333
kết quả trả về
Lưu ý rằng việc tính toán hàm băm phức tạp do có khả năng một số thành viên trong danh sách có thể không thể băm được và cũng do khả năng tràn số học.
Hơn nữa, phải luôn xảy ra trường hợp nếu o1 == o2 (tức là o1.__eq__(o2) is True) thì hash(o1) == hash(o2) (tức là o1.__hash__() == o2.__hash__()), bất kể đối tượng có trong từ điển hay không. Nếu bạn không đáp ứng được những hạn chế này thì từ điển và các cấu trúc dựa trên hàm băm khác sẽ hoạt động sai.
Trong trường hợp ListWrapper, bất cứ khi nào đối tượng trình bao bọc có trong từ điển, danh sách được bao bọc không được thay đổi để tránh sự bất thường. Đừng làm điều này trừ khi bạn sẵn sàng suy nghĩ kỹ về các yêu cầu và hậu quả của việc không đáp ứng chúng một cách chính xác. Hãy coi như mình đã được cảnh báo.
Tại sao list.sort() không trả về danh sách đã sắp xếp?¶
Trong những tình huống mà hiệu suất là vấn đề quan trọng, việc tạo một bản sao của danh sách chỉ để sắp xếp nó sẽ rất lãng phí. Do đó, list.sort() sắp xếp danh sách tại chỗ. Để nhắc bạn về thực tế đó, nó không trả về danh sách đã sắp xếp. Bằng cách này, bạn sẽ không bị lừa vô tình ghi đè lên danh sách khi bạn cần một bản sao đã được sắp xếp nhưng cũng cần giữ lại phiên bản chưa được sắp xếp.
Nếu bạn muốn trả về một danh sách mới, thay vào đó hãy sử dụng hàm sorted() tích hợp. Hàm này tạo một danh sách mới từ một iterable được cung cấp, sắp xếp nó và trả về nó. Ví dụ: đây là cách lặp lại các khóa của từ điển theo thứ tự được sắp xếp:
cho khóa được sắp xếp (mydict):
... # do sao cũng được với mydict[key]...
Làm cách nào để bạn chỉ định và thực thi thông số giao diện trong Python?¶
Đặc tả giao diện cho một mô-đun được cung cấp bởi các ngôn ngữ như C++ và Java mô tả các nguyên mẫu cho các phương thức và chức năng của mô-đun. Nhiều người cảm thấy rằng việc thực thi các đặc tả giao diện tại thời điểm biên dịch sẽ giúp xây dựng các chương trình lớn.
Python 2.6 bổ sung mô-đun abc cho phép bạn xác định các Lớp cơ sở trừu tượng (ABC). Sau đó, bạn có thể sử dụng isinstance() và issubclass() để kiểm tra xem một phiên bản hoặc một lớp có triển khai một ABC cụ thể hay không. Mô-đun collections.abc xác định một tập hợp các ABC hữu ích như Iterable, Container và MutableMapping.
Đối với Python, nhiều ưu điểm của đặc tả giao diện có thể đạt được bằng một quy tắc kiểm tra thích hợp cho các thành phần.
Một bộ thử nghiệm tốt cho một mô-đun có thể vừa cung cấp thử nghiệm hồi quy, vừa đóng vai trò là đặc tả giao diện mô-đun và một tập hợp các ví dụ. Nhiều mô-đun Python có thể được chạy dưới dạng tập lệnh để cung cấp khả năng "tự kiểm tra" đơn giản. Ngay cả các mô-đun sử dụng giao diện bên ngoài phức tạp cũng thường có thể được kiểm tra riêng biệt bằng cách sử dụng các mô phỏng "sơ khai" tầm thường của giao diện bên ngoài. Các mô-đun doctest và unittest hoặc khung kiểm tra của bên thứ ba có thể được sử dụng để xây dựng các bộ kiểm tra đầy đủ thực hiện mọi dòng mã trong một mô-đun.
Một nguyên tắc kiểm tra thích hợp có thể giúp xây dựng các ứng dụng phức tạp lớn bằng Python cũng như có các thông số kỹ thuật về giao diện. Trên thực tế, nó có thể tốt hơn vì đặc tả giao diện không thể kiểm tra các thuộc tính nhất định của chương trình. Ví dụ: phương thức list.append() dự kiến sẽ thêm các phần tử mới vào cuối danh sách nội bộ nào đó; một đặc tả giao diện không thể kiểm tra xem việc triển khai list.append() của bạn có thực sự thực hiện điều này một cách chính xác hay không, nhưng việc kiểm tra thuộc tính này trong bộ thử nghiệm là điều đơn giản.
Việc viết các bộ thử nghiệm rất hữu ích và bạn có thể muốn thiết kế mã của mình để dễ dàng kiểm tra. Một kỹ thuật ngày càng phổ biến, phát triển dựa trên thử nghiệm, yêu cầu viết các phần của bộ thử nghiệm trước khi bạn viết bất kỳ mã thực tế nào. Tất nhiên Python cho phép bạn cẩu thả và không viết các trường hợp kiểm thử nào cả.
Tại sao không có goto?¶
Vào những năm 1970, mọi người nhận ra rằng goto không hạn chế có thể dẫn đến mã "spaghetti" lộn xộn, khó hiểu và khó sửa đổi. Trong ngôn ngữ cấp cao, điều này cũng không cần thiết miễn là có cách phân nhánh (trong Python, với các câu lệnh if và các biểu thức or, and và if/else) và vòng lặp (với các câu lệnh while và for, có thể chứa continue và break).
Người ta cũng có thể sử dụng các ngoại lệ để cung cấp một "goto có cấu trúc" hoạt động ngay cả trên các lệnh gọi hàm. Nhiều người cảm thấy rằng các ngoại lệ có thể mô phỏng một cách thuận tiện tất cả cách sử dụng hợp lý cấu trúc go hoặc goto của C, Fortran và các ngôn ngữ khác. Ví dụ:
nhãn lớp (Ngoại lệ): chuyển # declare một nhãn
thử:
...
nếu điều kiện: tăng nhãn() nhãn # goto
...
ngoại trừ nhãn: # where tới goto
vượt qua
...
Điều này không cho phép bạn nhảy vào giữa vòng lặp, nhưng dù sao thì điều đó thường bị coi là lạm dụng goto. Sử dụng một cách tiết kiệm.
Tại sao chuỗi thô (chuỗi r) không thể kết thúc bằng dấu gạch chéo ngược?¶
Chính xác hơn, chúng không thể kết thúc bằng số lẻ dấu gạch chéo ngược: dấu gạch chéo ngược không ghép đôi ở cuối thoát khỏi ký tự dấu ngoặc kép đóng, để lại một chuỗi bị kết thúc.
Chuỗi thô được thiết kế để dễ dàng tạo đầu vào cho các bộ xử lý (chủ yếu là các công cụ biểu thức chính quy) muốn thực hiện xử lý thoát dấu gạch chéo ngược của riêng chúng. Những bộ xử lý như vậy dù sao cũng coi dấu gạch chéo ngược ở cuối chưa khớp là một lỗi, vì vậy các chuỗi thô không cho phép điều đó. Đổi lại, chúng cho phép bạn chuyển ký tự trích dẫn chuỗi bằng cách thoát nó bằng dấu gạch chéo ngược. Các quy tắc này hoạt động tốt khi chuỗi r được sử dụng đúng mục đích.
Nếu bạn đang cố gắng tạo tên đường dẫn Windows, hãy lưu ý rằng tất cả các cuộc gọi hệ thống Windows cũng chấp nhận dấu gạch chéo lên:
f = open("/mydir/file.txt") # works ổn thôi!
Nếu bạn đang cố gắng tạo tên đường dẫn cho lệnh DOS, hãy thử ví dụ: một trong
dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"
Tại sao Python không có câu lệnh "with" cho phép gán thuộc tính?¶
Python có câu lệnh with bao bọc việc thực thi một khối, gọi mã ở đầu vào và đầu ra khỏi khối. Một số ngôn ngữ có cấu trúc trông như thế này:
với đối tượng:
a = 1 # equivalent đến obj.a = 1
tổng = tổng + 1 # obj.total = obj.total + 1
Trong Python, cấu trúc như vậy sẽ không rõ ràng.
Các ngôn ngữ khác, chẳng hạn như Object Pascal, Delphi và C++, sử dụng các kiểu tĩnh, do đó, có thể biết một cách rõ ràng thành viên nào đang được chỉ định. Đây là điểm chính của kiểu gõ tĩnh -- trình biên dịch always biết phạm vi của mọi biến tại thời điểm biên dịch.
Python sử dụng các kiểu động. Không thể biết trước thuộc tính nào sẽ được tham chiếu khi chạy. Các thuộc tính thành viên có thể được thêm hoặc xóa khỏi các đối tượng một cách nhanh chóng. Điều này khiến cho không thể biết được, chỉ bằng cách đọc đơn giản, thuộc tính nào đang được tham chiếu: thuộc tính cục bộ, thuộc tính toàn cầu hay thuộc tính thành viên?
Ví dụ: lấy đoạn mã chưa hoàn chỉnh sau:
def foo(a):
với một:
in(x)
Đoạn mã giả định rằng a phải có thuộc tính thành viên được gọi là x. Tuy nhiên, không có gì trong Python cho trình thông dịch biết điều này. Điều gì sẽ xảy ra nếu a là một số nguyên? Nếu có một biến toàn cục có tên x, nó có được sử dụng bên trong khối with không? Như bạn thấy, bản chất năng động của Python khiến cho những lựa chọn như vậy trở nên khó khăn hơn nhiều.
Tuy nhiên, lợi ích chính của with và các tính năng ngôn ngữ tương tự (giảm khối lượng mã) có thể dễ dàng đạt được bằng Python bằng cách gán. Thay vì:
function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63
viết cái này:
ref = function(args).mydict[index][index]
giới thiệu a = 21
giới thiệu b = 42
giới thiệu c = 63
Điều này cũng có tác dụng phụ là tăng tốc độ thực thi vì các ràng buộc tên được giải quyết trong thời gian chạy trong Python và phiên bản thứ hai chỉ cần thực hiện phân giải một lần.
Các đề xuất tương tự nhằm đưa ra cú pháp để giảm khối lượng mã hơn nữa, chẳng hạn như sử dụng 'dấu chấm ở đầu', đã bị từ chối vì tính rõ ràng (xem https://mail.python.org/pipermail/python-ideas/2016-May/040070.html).
Tại sao các trình tạo không hỗ trợ câu lệnh with?¶
Vì lý do kỹ thuật, trình tạo được sử dụng trực tiếp làm trình quản lý bối cảnh sẽ không hoạt động chính xác. Khi, như phổ biến nhất, một trình tạo được sử dụng như một trình vòng lặp chạy cho đến khi hoàn thành thì không cần phải đóng. Khi đó, hãy gói nó thành contextlib.closing(generator) trong câu lệnh with.
Tại sao câu lệnh if/while/def/class lại cần có dấu hai chấm?¶
Dấu hai chấm được yêu cầu chủ yếu để nâng cao khả năng đọc (một trong những kết quả của ngôn ngữ ABC thử nghiệm). Hãy xem xét điều này:
nếu a == b
in (a)
so với
nếu a == b:
in (a)
Lưu ý cách thứ hai dễ đọc hơn một chút. Lưu ý thêm cách dấu hai chấm đặt ra ví dụ trong câu trả lời FAQ này; đó là cách sử dụng tiêu chuẩn trong tiếng Anh.
Một lý do nhỏ khác là dấu hai chấm giúp người soạn thảo dễ dàng đánh dấu cú pháp hơn; họ có thể tìm kiếm dấu hai chấm để quyết định khi nào cần tăng thụt lề thay vì phải thực hiện phân tích văn bản chương trình phức tạp hơn.
Tại sao Python cho phép dấu phẩy ở cuối danh sách và bộ dữ liệu?¶
Python cho phép bạn thêm dấu phẩy ở cuối danh sách, bộ dữ liệu và từ điển
[1, 2, 3,]
('a', 'b', 'c',)
d = {
“A”: [1, 5],
"B": [6, 7], dấu phẩy ở cuối # last là tùy chọn nhưng có kiểu dáng đẹp
}
Có một số lý do để cho phép điều này.
Khi bạn có một giá trị bằng chữ cho một danh sách, bộ dữ liệu hoặc từ điển trải rộng trên nhiều dòng, việc thêm nhiều phần tử sẽ dễ dàng hơn vì bạn không cần phải nhớ thêm dấu phẩy vào dòng trước đó. Các dòng cũng có thể được sắp xếp lại mà không tạo ra lỗi cú pháp.
Việc vô tình bỏ dấu phẩy có thể dẫn đến những lỗi khó chẩn đoán. Ví dụ:
x = [
"phí",
"fie"
"foo",
"fufu"
]
Danh sách này có vẻ như có bốn phần tử nhưng thực tế lại chứa ba phần tử: "fee", "fiefoo" và "fum". Luôn thêm dấu phẩy sẽ tránh được nguồn lỗi này.
Việc cho phép dấu phẩy ở cuối cũng có thể giúp việc tạo mã lập trình dễ dàng hơn.