Python là một ngôn ngữ thông dịch được sử dụng rộng rãi trên thế giới. Nó cung cấp các cấu trúc dữ liệu bậc cao hiệu quả, hỗ trợ lập trình hướng đối tượng đơn giản, và cũng có thể dùng trong thi đấu thuật toán.
Ưu điểm của Python
Python là ngôn ngữ thông dịch: không cần biên dịch và liên kết, giảm thao tác.
Python là ngôn ngữ tương tác: trình thông dịch hỗ trợ tương tác, có thể nhập lệnh trực tiếp trong terminal.
Python dễ học, dễ dùng: cung cấp nhiều cấu trúc dữ liệu, hỗ trợ phát triển chương trình lớn.
Python tính thực tiễn cao: từ nhập/xuất đơn giản đến tính toán khoa học hay WEB ứng dụng lớn đều có thể viết.
Python mã ngắn, dễ đọc: thường ngắn hơn các ngôn ngữ khác cho cùng chức năng.
Python hỗ trợ mở rộng: CPython viết bằng C cho phép liên kết ứng dụng C, dùng Python mở rộng/điều khiển ứng dụng đó.
Lưu ý khi học Python
Hiện chủ yếu dùng Python 3.7+; Python 2 và Python 3.6 trở xuống đã không còn hỗ trợ, nhưng một số hệ thống cũ vẫn dùng. Bài này giới thiệu Python bản mới. Nếu gặp mã Python 2 có thể dùng 2to3.
Triết lý thiết kế và cú pháp khác nhiều so với các ngôn ngữ khác, ẩn nhiều chi tiết thấp, nên phong cách thực dụng và thanh lịch.
Python là ngôn ngữ thông dịch động, nên chạy chậm hơn, đặc biệt với for loop. Nên dùng filter, map hoặc list comprehension để tăng hiệu năng.
Windows: có thể lấy Python miễn phí nhanh qua Microsoft Store.
macOS/Linux: nhiều bản phân phối Linux có sẵn Python; nếu chỉ học cú pháp, không cần cài thêm.
Lưu ý
Trên một số hệ thống cài Python mặc định (như Unix), hãy chạy python3 để mở Python 3. 1
Ngoài ra có thể dùng venv, conda, Nix... để quản lý toolchain và package, tạo môi trường ảo cách ly.
Python là ngôn ngữ thông dịch nên cách chạy khác C++. Khi dùng IDE có thể không thấy rõ, nên cần nhấn mạnh cách chạy.
Khi gõ python3 hoặc mở IDLE, bạn vào môi trường tương tác (REPL) — “Read-Eval-Print Loop”. Có thể nhập lệnh và thấy kết quả ngay, rất tiện để kiểm chứng cú pháp; phần sau sẽ dùng nhiều dạng này.
Nhưng nếu viết chương trình hoàn chỉnh, nên tạo file .py và chạy python3 filename.py.
Một số phiên bản Python theo nền tảng
Hệ thống/phiên bản
Phiên bản python
Noi Linux 2.0
3.8.0, gồm requests
Luogu
3.11.5, NumPy 1.25.2
OJ dựa trên Hydro
3.8.0+ gồm NumPy
Ubuntu 22.04 (sẵn)
3.10.4
Microsoft Store
Bản ổn định mới nhất
Lưu ý
Bảng có hiệu lực tại thời điểm viết (2025/01/15). Nên kiểm tra lại trên nền tảng.
Python cú pháp ngắn gọn; có nhiều tài liệu. Ở đây chỉ giới thiệu tính năng hữu ích cho OIer. Xem thêm Python Docs và Python Wiki.
Chú thích
Chú thích không ảnh hưởng chạy, nhưng giúp dễ hiểu.
1234567
# Dòng bắt đầu bằng # là chú thích một dòng"""Chuỗi nhiều dòng dùng ba dấu nháy(ba nháy đơn hoặc ba nháy kép)thường cũng dùng làm chú thích"""
Khuyến khích thêm chú thích để dễ đọc.
Kiểu dữ liệu cơ bản
Mọi thứ đều là đối tượng
Trong Python không cần khai báo kiểu trước, gán trực tiếp:
1 2 3 4 5 6 7 8 910111213
>>> x=-3# Không cần dấu ; ở cuối>>> x-3>>> f=3.1415926535897932384626;f# Muốn có dấu ; cũng được, đỡ một dòng3.141592653589793>>> s1="O">>> s1# Nháy đơn và nháy kép như nhau'O'>>> b='A'==65# 'A' và 65 khác kiểu nên không bằng nhau>>> b# True/False viết hoa chữ cái đầuFalse>>> True+1==2andnotFalse!=0# Dùng từ khóa nhưng cũng hỗ trợ ký hiệuTrue
Nhưng điều đó không có nghĩa Python không có kiểu: interpreter tự suy luận. Dùng type() để xem:
12345678
>>> type(x)<class 'int'>>>> type(f)<class 'float'>>>> type(s1)# Đừng đặt tên biến là str, sẽ che mất str<class 'str'>>>> type(b)<class 'bool'>
Trong C/C++ các hàm nằm rải ở nhiều header. Python tích hợp nhiều hàm tiện dụng; bạn dùng trực tiếp mà không cần biết chúng ở đâu. Nhược điểm là tên trùng từ thông dụng, nên tránh đặt biến trùng tên built-in.
Python có int, float, str, bool tương tự int, float, string, bool trong C++. Nhưng khác ở chỗ: không có char, không có double (float thực tế là double của C). Nếu cần chính xác hơn dùng decimal; nếu cần số phức có complex (đừng đặt tên biến là complex).
Các kiểu đều là class; khác biệt lớn với C++ là mọi dữ liệu đều là đối tượng, hàm là đối tượng, kiểu cũng là đối tượng:
123456
>>> type(int)<class 'type'>>>> type(pow)# Hàm built-in, sẽ nói sau<class 'builtin_function_or_method'>>>> type(type)# type() cũng là built-in nhưng đặc biệt<class 'type'>
Khái niệm này có thể khó lúc đầu; về sau bạn sẽ thấy Python ưu tiên thao tác theo đối tượng, khiến mã ngắn gọn rõ ràng.
Tính toán số
Python có thể coi như máy tính đa năng.
Trong REPL, nhập biểu thức sau >>> và dùng + - * / % như C++ và () để nhóm. Dưới đây là khác biệt chính:
1 2 3 4 5 6 7 8 9101112131415161718
>>> 5.0*6# Kết quả phép nhân có float nếu có float30.0>>> 15/3# Khác C/C++: chia luôn trả float5.0>>> 5/100000# Số lớn hiển thị dạng khoa học5e-05>>> 5//3# Chia nguyên (floor), trả int1>>> -5//3# Làm tròn xuống, khác C/C++-2>>> 5%3# Lấy dư2>>> -5%3# Dư luôn không âm, khác C/C++, nhưng thỏa (a//b)*b+(a%b)==a 1>>> x=abs(-1e4)# Hàm trị tuyệt đối>>> x+=1# Không có ++/-->>> x10001.0
/ luôn trả float; muốn chia nguyên dùng //. % lấy dư. Dạng khoa học giống C++.
Python dùng ** để lũy thừa, và có pow(a, b, mod) cho lũy thừa nhanh.
123456789
>>> 3**4# Lũy thừa81>>> 2**51213407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096>>> pow(2,512,int(1e4))# 2**512 % 10000 nhanh, 1e4 là float nên cần int4096>>> 2048**2048# Thử số lớn trong IDLE?>>> 0.1+0.1+0.1-0.3==0.# Như C/C++, không so sánh float trực tiếpFalse
Kiểm tra kiểu
Dùng type(obj) để lấy kiểu, như type(8) và type('a').
I/O chủ yếu qua input() và print(). print() rất trực quan:
123456789
>>> a=[1,2,3];print(a[-1])# Mặc định xuống dòng3>>> print(ans[0],ans[1])# In nhiều biến, ngăn cách bằng khoảng trắng1 2>>> print(a[0],a[1],end='')# end='' để không xuống dòng1 2>>>>>> print(a[0],a[1],sep=', ')# sep=', ' đổi cách ngăn1, 2>>> print(str(a[0])+', '+str(a[1]))# Nối chuỗi thủ công
input() giống getline() của C++: đọc cả dòng thành chuỗi, không có newline cuối.
123
>>> s=input('请输入一串数字: ');s# Tự debug có thể truyền prompt请输入一串数字: 1 2 3 4 5 6'1 2 3 4 5 6'
Chuỗi
Python 3 có chuỗi Unicode mạnh mẽ, giống string C++, hỗ trợ nối +, truy cập index, nhân *, và in.
1 2 3 4 5 6 7 8 91011121314151617181920
>>> s1="O"# Nháy đơn/đôi đều được>>> s1+='I-Wiki'# Khuyến nghị dùng nháy đôi để đồng bộ C++>>> 'OI'ins1# Kiểm tra substringTrue>>> len(s1)# Tương tự s.length() trong C++7>>> s2=""" 感谢你的阅读... 欢迎参与贡献!""" # Chuỗi ba nháy có thể nhiều dòng>>> s1 + s2 'OI-Wiki 感谢你的阅读\n欢迎参与贡献!'>>> print(s1 + s2) # In chuỗiOI-Wiki 感谢你的阅读欢迎参与贡献!>>> s2[2] * 2 + s2[3] + s2[-1] # Index âm từ phải, tương đương modulo'谢谢你!'>>> s1[0] = 'o' # str là bất biến, không sửa trực tiếpTraceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: 'str' object does not support item assignment
Python có nhiều kiểu phức hợp; phổ biến nhất là list. [1,2,3] và ['a','b','c'] đều là list.
List là mutable, khác string là immutable. Có thể dùng list comprehension để chuyển đổi:
1 2 3 4 5 6 7 8 910111213
>>> # Tạo mảng số nguyên [65, 70), range là kiểu dãy, mặc định bước 1>>> nums=list(range(65,70))[65, 66, 67, 68, 69]>>> lst=[chr(x)forxinnums]>>> lst['A', 'B', 'C', 'D', 'E']>>> s=''.join(lst);s'ABCDE'>> list(s)['A', 'B', 'C', 'D', 'E']>>> # Nếu không biết s.lower() có thể viết kiểu này>>> ''.join([chr(ord(ch)-65+97)forchinsifch>='A'andch<='Z'])'abcde'
>>> vis=[[0]*3]*3# Tạo mảng 3x3 toàn 0>>> vis[[0, 0, 0], [0, 0, 0], [0, 0, 0]]>>> vis[0][0]=1;vis# Vì sao cả các hàng khác bị đổi?[[1, 0, 0], [1, 0, 0], [1, 0, 0]]>>> # Xem gán list 1D>>> a1=[0,0,0];a2=a1;a3=a1[:]>>> a1[0]=1;a1[1, 0, 0]>>> a2[1, 0, 0]>>> a3[0, 0, 0]>>> id(a1)==id(a2)andid(a1)!=id(a3)True>>> vis2=vis[:]>>> vis[0][1]=2;vis>>> [[1,2,0],[1,2,0],[1,2,0]]>>> vis2>>> [[1,2,0],[1,2,0],[1,2,0]]>>> id(vis)!=id(vis2)True>>> [id(vis[i])==id(vis2[i])foriinrange(3)][True, True, True]>>> [id(x)forxinvis][139760373248192, 139760373248192, 139760373248192]
Trong Python, gán chỉ truyền tham chiếu. Với kiểu immutable như số/chuỗi, gán tạo đối tượng mới; với list (mutable) thì nhiều biến có thể trỏ cùng đối tượng. Vì vậy [[0]*3]*3 lặp lại cùng một list. Khi copy bằng slice cũng chỉ “shallow copy”, cần deepcopy nếu muốn sâu. Cách tạo 2D đúng là dùng list comprehension:
123456789
>>> vis1=[[0]*3for_inrange(3)]# Dùng _ làm biến đếm>>> # Trong REPL, _ mặc định là kết quả trước, có thể dùng __>>> vis1[[0, 0, 0], [0, 0, 0], [0, 0, 0]]>>> [id(x)forxinvis1][139685508981248, 139685508981568, 139685508981184]>>> vis1[0][0]=1[[1, 0, 0], [0, 0, 0], [0, 0, 0]]>>> a2[0][0]=10# Truy cập/gán mảng 2D
Chúng ta chưa học vòng lặp nhưng đã dùng comprehension vì Python chạy chậm với vòng lặp for. Nếu cần hiệu năng, ưu tiên comprehension hoặc filter, map, nhưng tùy bài.
Dùng NumPy
NumPy là gì
NumPy là thư viện tính toán khoa học, hỗ trợ mảng/matrix hiệu năng cao. Có thể dùng để thử nghiệm thuật toán. Kiểu dữ liệu lõi là ndarray, mảng n chiều, lưu liên tục, độ dài cố định. NumPy viết bằng C nên nhanh. Không phải chuẩn, cần pip install numpy, và OI không đảm bảo có (xem phiên bản Python).
Python dùng input() và print(). Dưới đây là nâng cao.
In định dạng
Thi đấu thường chỉ in số/chuỗi, print() là đủ, trừ khi cần định dạng float. Có 3 cách: % kiểu printf, format, và f-string (Python 3.6+). Xem hướng dẫn. Ở đây chỉ minh họa kiểu %:
1234
>>> pi=3.1415926;print('%.4f'%pi)# %[flags][width][.precision]type3.1416>>> '%.4f - %8f = %d'%(pi,0.1416,3)# Nhiều tham số dùng ()'3.1416 - 0.141600 = 3'
Hàm split()
input() đọc cả dòng; trong OI thường một dòng nhiều số nên cần split() và list comprehension:
Nhập N dòng mà không dùng loop bằng thao tác sequence:
1 2 3 4 5 6 7 8 910111213
>>> N=4;mat=[[int(x)forxininput().split()]foriinrange(N)]1 3 3 1 4 1 2 3 4 3 4 1 >>> mat[[1, 3, 3], [1, 4, 1], [2, 3, 4], [3, 4, 1]]>>> u,v,w=map(list,zip(*mat))# * giải nén mat thành các list bên trong# zip() gom các phần tử cùng vị trí thành tuple, trả iterator# map(list, iterable) chuyển tuple thành list>>> print(u,v,w)[1, 1, 2, 3] [3, 4, 3, 4] [3, 1, 4, 1]
Thực chất là chuyển ma trận N×3 thành 3×N. zip() ghép phần tử theo vị trí; map() áp hàm lên phần tử. Trong Python 3, zip() và map() trả iterator.
a=[]withopen("in.txt")asf:N=int(f.readline())# Đọc dòng đầu Na[len(a):]=[[int(x)forxinf.readline().split()]foriinrange(N)]withopen("out.txt","w")asf:f.write("1\n")
Có nhiều hàm I/O khác; OI chưa hỗ trợ Python nên lược.
Python dùng thụt lề thay {}; sai thụt lề sẽ lỗi, tab và space trộn cũng lỗi; dòng bắt đầu khối cần :. Điều này tăng tính đọc, nhưng copy/paste mất thụt lề sẽ khó chịu.
Vòng lặp
List comprehension nhanh, nhưng nhiều trường hợp vẫn cần loop. Ví dụ đọc nhiều dòng:
12345678
# Từ đây không dùng REPL, hãy tự chạy fileu,v,w=([]foriinrange(3))# Gán nhiều biếnforiinrange(4):# Giả sử 4 dòng_u,_v,_w=[int(x)forxininput().split()]u.append(_u),v.append(_v),w.append(_w)# Không thể dùng cin >> u[i] >> v[i] >> w[i] vì list chưa đủ độ dài# Có thể pre-allocate MAXN nhưng phải nhớ độ dài thực và cắt phần thừaprint(u,v,w)
for trong Python giống range-based loop của C++11. Muốn duyệt chỉ số cần range(len(lst)).
Dùng while để đọc không biết số dòng:
1 2 3 4 5 6 7 8 910
u,v,w=[],[],[]s=input()# Python không cho gán trong điều kiệnwhiles:# Không thể while(!scanf())# Dùng slice nối tránh append(), list comp lồng listu[len(u):],v[len(v):],w[len(w):]=[[int(x)]forxins.split()]s=input()# Python 3.8 có walrus, nhưng OJ có thể không hỗ trợwhiles:=input():u[len(u):],v[len(v):],w[len(w):]=[[int(x)]forxins.split()]print(u,v,w)
Rẽ nhánh
Gần giống C/C++ nhưng không có gán trong điều kiện (trừ :=), và không có switch.
1 2 3 4 5 6 7 8 9101112
# Điều kiện không cần ngoặcif4>=3>2and3!=5==5!=7:print("Quan hệ có thể viết liên tiếp")x=Noneor[]or-2print("&& || !","và hoặc không","and or not",sep="\n")print("Dùng and/or giúp giảm dòng")ifnotx:print("Số âm cũng là True, không in dòng này")elifx&1:print("Dùng elif, không phải else if\n""Toán tử bit giống C, chẵn&1 = 0")else:print("Cũng có toán tử ba ngôi")ifxelseprint("Chú ý cấu trúc")
Xử lý ngoại lệ
C++ có try/catch nhưng ít dùng trong thi; Python hay dùng EAFP. Ví dụ:
1 2 3 4 5 6 7 8 910111213
s="OI-wiki"pat="NOIP"x=s.find(pat)# find() không thấy trả -1try:y=s.index(pat)# index() không thấy thì ném lỗiprint(y)# Bị bỏ quaexceptValueError:print("Không tìm thấy")try:print(y)# y chưa định nghĩa, sẽ lỗiexceptNameErrorase:print("Không thể in y")print("Lý do:",e)
Container built-in
Python có nhiều container mạnh, ngoài list còn có tuple, dict, set.
tuple là list bất biến; nếu phần tử là mutable thì vẫn sửa được phần tử đó. tuple nhỏ nhẹ và hashable, hữu ích cho dict/set.
12345678
tup=tuple([[1,2],4])# Từ list tạo tuple# Tương đương tup = ([1,2], 4)tup[0].append(3)print(tup)a,b=0,"I-Wiki"# Gán nhiều biến là unpack tupleprint(id(a),id(b))b,a=a,bprint(id(a),id(b))# id đổi chỗ, cho thấy biến chỉ là “tên”
dict giống map trong C++ (khác map() built-in). Khóa là đối tượng hashable. Tính chất dict thay đổi qua các phiên bản, tự tìm hiểu.
set giống set trong C++: không trùng phần tử, giống dict chỉ có key. {} tạo dict rỗng chứ không phải set.
Viết hàm
Python không cần khai báo kiểu tham số/return, giúp code ngắn.
1 2 3 4 5 6 7 8 91011121314151617181920
defadd(a,b):returna+b# Lợi thế kiểu động, a/b có thể là chuỗidefadd_no_swap(a,b):print("in func #1:",id(a),id(b))a+=bb,a=a,bprint("in func #2:",id(a),id(b))# a, b đã đổireturna,b# Trả nhiều giá trị là tuplelst1=[1,2]lst2=[3,4]print("outside func #1:",id(lst1),id(lst2))add_no_swap(lst1,lst2)# Bên ngoài không đổi chỗprint("outside func #2:",id(lst1),id(lst2))# Nhưng giá trị đã đổiprint(lst1,lst2)
Tham số mặc định
Tham số mặc định linh hoạt nhưng dễ bẫy:
1 2 3 4 5 6 7 8 91011
defappend_to(element,to=[]):to.append(element)returntolst1=append_to(12)lst2=append_to(42)print(lst1,lst2)# Bạn nghĩ [12] [42]# Nhưng thực tế [12, 42] [12, 42]
Do mặc định chỉ khởi tạo một lần, các lần gọi dùng chung list. Cách đúng: dùng None.
1 2 3 4 5 6 7 8 9101112
defappend_to(element,to=None):iftoisNone:to=[]to.append(element)returntolst1=append_to(12)lst2=append_to(42)print(lst1,lst2)# Kết quả [12] [42]
Ghi chú kiểu
Python là dynamic, lỗi kiểu có thể lộ khi chạy:
123456789
>>> ifFalse:... 1+"two"# Dòng này không chạy... else:... 1+2...3>>> 1+"two"TypeError: unsupported operand type(s) for +: 'int' and 'str'
Python 3.5+ hỗ trợ type hints, chỉ là gợi ý, cần tool tĩnh (PyCharm, Mypy). Ví dụ:
Cho \(n(1 \leq n \leq 10^5)\) đỉnh, \(m(1 \leq m \leq 2\times 10^5)\) cung có trọng số không âm, tính khoảng cách từ \(s\) đến mọi đỉnh. Đảm bảo từ \(s\) đến mọi đỉnh.
classqxx:# Lớp forward-star (struct)def__init__(self):self.nex=0self.t=0self.v=0e=[qxx()foriinrange(M)]# Danh sáchh=[0foriinrange(N)]cnt=0dist=[INFforiinrange(N)]q=pq.PriorityQueue()# Hàng đợi ưu tiên, min-heap theo phần tử đầudefadd_path(f,t,v):# Thêm cạnh vào forward-star# Nếu muốn sửa biến toàn cục, dùng globalglobalcnt,e,h# Dòng debug, nhiều biến dùng tuple# print("add_path(%d,%d,%d)" % (f,t,v))cnt+=1e[cnt].nex=h[f]e[cnt].t=te[cnt].v=vh[f]=cnt
defnextedgeid(u):# Generator, dùng trong fori=h[u]whilei:yieldii=e[i].nexdefdijkstra(s):dist[s]=0q.put((0,s))whilenotq.empty():u=q.get()# get() đồng thời xóa phần tửifdist[u[1]]<u[0]:continueforiinnextedgeid(u[1]):v=e[i].tw=e[i].vifdist[v]<=dist[u[1]]+w:continuedist[v]=dist[u[1]]+wq.put((dist[v],v))
if__name__=="__main__":# Đọc nhiều số trên một dòngn,m,s=map(int,input().split())foriinrange(m):u,v,w=map(int,input().split())add_path(u,v,w)dijkstra(s)foriinrange(1,n+1):print(dist[i],end=" ")print()
try:# Nhập module hàng đợi ưu tiênimportQueueaspq# python version < 3.0exceptImportError:importqueueaspq# python3.*N=int(1e5+5)M=int(2e5+5)INF=0x3F3F3F3Fclassqxx:# Lớp forward-star (struct)def__init__(self):self.nex=0self.t=0self.v=0e=[qxx()foriinrange(M)]# Danh sáchh=[0foriinrange(N)]cnt=0dist=[INFforiinrange(N)]q=pq.PriorityQueue()# Hàng đợi ưu tiên, min-heap theo phần tử đầudefadd_path(f,t,v):# Thêm cạnh vào forward-star# Nếu muốn sửa biến toàn cục, dùng globalglobalcnt,e,h# Dòng debug, nhiều biến dùng tuple# print("add_path(%d,%d,%d)" % (f,t,v))cnt+=1e[cnt].nex=h[f]e[cnt].t=te[cnt].v=vh[f]=cntdefnextedgeid(u):# Generator, dùng trong fori=h[u]whilei:yieldii=e[i].nexdefdijkstra(s):dist[s]=0q.put((0,s))whilenotq.empty():u=q.get()ifdist[u[1]]<u[0]:continueforiinnextedgeid(u[1]):v=e[i].tw=e[i].vifdist[v]<=dist[u[1]]+w:continuedist[v]=dist[u[1]]+wq.put((dist[v],v))# Nếu chạy trực tiếp thì vào đâyif__name__=="__main__":# Đọc nhiều số trên một dòngn,m,s=map(int,input().split())foriinrange(m):u,v,w=map(int,input().split())add_path(u,v,w)dijkstra(s)foriinrange(1,n+1):# Hai cách in đều đượcprint("{}".format(dist[i]),end=" ")# print("%d" % dist[i],end=' ')print()# Xuống dòng
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:cmpute, Henry-ZHR, ranwen, abc1763613206, billchenchina, chinggg, ChungZH, CoelacanthusHex, countercurrent-time, Dong Tsing-hsuen, Early0v0, Enter-tainer, F1shAndCat, Great-designer, hensier, HeRaNO, Hszzzx, imba-tjd, Ir1d, ksyx, lingxier, LovelyBuggies, Marcythm, mgt, Mooos-MoSheng, NachtgeistW, ouuan, Rottenwooood, shawlleyw, shuzhouliu, sshwy, SukkaW, Suyun514, Tiphereth-A, tLLWtG, wineee, wxh06, Xeonacid, yusancky, zyouxam, zzjjbb, jiangmuran, CuriosityQiu All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply