Để thực hành viết code theo tài liệu này cũng như để làm các bài tập, sử dụng Google Colab notebook sau đây:
https://colab.research.google.com/drive/1lM0sB_kU0YPbEZTz4mAD0M-TaNNvah93?usp=sharing
Trong Python, khi ta muốn chạy code khi một điều kiện nào đó được thoả mãn thì ta có thể sử dụng lệnh điều kiện if. Lệnh này có các cách sử dụng như sau:
Chỉ có một lệnh if:
if…else…:
if condition:
# thực hiện một điều gì đó nếu điều kiện if được thoả mãn
else:
# thực hiện một điều gì đó nếu điều kiện if không được thoả mãnif…elif…else…:
if condition:
# thực hiện một điều gì đó nếu điều kiện if được thoả mãn
elif another_condition:
# thực hiện một điều gì đó nếu điều kiện if không được thoả mãn
# và điều kiện another_condition được thoả mãn
else:
# thực hiện một điều gì đó nếu không có điều kiện nào bên trên được thoả mãnCấu trúc if…elif…else…giúp bạn triển khai các điều kiện phức tạp một cách rõ ràng hơn thay vì phải viết nhiều lệnh if lồng vào nhau (nested if). Nếu bạn nào đã từng viết lệnh if trong Microsoft Excel và phải lồng nhiều lệnh if vào nhau thì sẽ hiểu là cách làm kiểu nested if này rối rắm và dễ gây sai sót đến thế nào.
Câu hỏi để học viên tự trả lời: Vậy sử dụng cấu trúc if…elif… thì khác gì so với sử dụng 2 câu lệnh if?
Cấu trúc if…else… dạng rút gọn:
Nếu ta chỉ có một lệnh để thực hiện cho điều kiện if và một lệnh cho điều kiện else thì ta có thể sử dụng cấu trúc rút gọn như dưới đây.
## larger than 5
Match case là cấu trúc được giới thiệu kể từ Python 3.10. Với cấu trúc này, ta truyền vào 1 tham số rồi kiểm tra xem tham số đó khớp với trường hợp (case) nào. Nếu tìm được một trường hợp khớp, một đoạn code sẽ được chạy. Nếu không có trường hợp nào khớp thì một hành đọng mặc định sẽ được thực hiện.
Cú pháp:
Câu lệnh match case trong Python được bắt đầu với từ khoá match và sau đó là tham số cần so sánh. Sau đó ta dùng từ khoá case để khai báo một loạt các trường hợp khác nhau với những pattern cần kiểm tra để khớp với tham số. Ký tự gạch dưới “_” là trường hợp khi mà tất cả các case đều không khớp với tham số.
match parameter:
case pattern1:
# code for pattern 1
case pattern2:
# code for pattern 2
.
.
case patterN:
# code for pattern N
case _:
# default code blockĐến đây thì bạn có thể tự hỏi là ồ vậy sao không dùng lệnh if cũng được mà, có gì khác đâu? Đúng vậy, trong nhiều trường hợp dùng if … else sẽ đơn giản và dễ hiểu hơn. Tuy nhiên trong một số trường hợp nhất định, như các ví dụ dưới đây chẳng hạn, thì nếu dùng if…else ta sẽ phải viết code khá lằng nhằng. Dùng match…case… sẽ giúp code của ta rõ ràng, dễ hiểu hơn nhiều.
Ví dụ 1: Ta là một data engineer và đơn vị của ta có một hệ thống tiếp nhận yêu cầu từ các đơn vị khác trong công ty (yêu cầu cung cấp dữ liệu, yêu cầu cấp quyền truy cập, yêu cầu tra soát báo cáo, v.v.). Ta cần tạo 1 data pipeline đế dựa trên trạng thái của các yêu cầu này và dựa theo một số quy tắc nhất định mà lấy thông tin và tạo 1 báo cáo cập nhật tình hình xử lý các yêu cầu của đơn vị mình cho lãnh đạo đơn vị. Mỗi khi một yêu cầu bất kỳ trên hệ thống được cập nhật thông tin (có người comment vào yêu cầu, yêu cầu được chuyển trạng thái, nội dung mô tả của yêu cầu được thay đổi, người phụ trách thực hiện yêu cầu được thay đổi, v.v.) thì ta sẽ được một payload dạng JSON file (khi load vào python thì JSON có dạng giống như python dict, gồm các key và value) trong đó chứa khá nhiều thông tin về yêu cầu đó.
Để cho bài toán trở nên đơn giản và tập trung vào ví dụ sử dụng match…case… thì ta tạm thời không quan tâm đến các khía cạnh như tần suất chạy pipeline, công nghệ được sử dụng để nhận và xử lý data dạng JSON, … và ta chỉ tập trung vào một số quy tắc đơn giản sau đây cho data pipeline này, mục đích là gửi email thông báo cập nhật tình hình ngay lập tức cho lãnh đạo đơn vị đối với một số loại yêu cầu có tính chất quan trọng:
Nếu loại yêu cầu là “DATA REQUEST” hoặc “REPORT REQUEST” và có mức độ quan trọng là “HIGH” hoặc có người gửi yêu cầu là văn phòng tổng giám đốc (CEO office) thì gửi email thông báo cho lãnh đạo về thông tin cập nhật mới nhất của yêu cầu và gửi kèm đường link dẫn tới yêu cầu đó
Nếu loại yêu cầu là “BUG REPORT” thì gửi email thông báo cho lãnh đạo và kèm đường link dẫn tới yêu cầu
Nếu loại yêu cầu là “BUG REPORT” và đến từ Khối Vận hành thì khi gửi email cần cc thêm email của phòng kiểm soát rủi ro Vận hành, download tài liệu thông qua link tài liệu ở yêu cầu rồi gửi tài liệu đó qua email thông báo.
Trong data pipeline, ta có thể triển khai logic như dưới đây để kiểm tra cấu trúc của JSON payload và chỉ xử lý những dữ liệu thoả mãn các yêu cầu trên:
sample_payload_1: dict = {
"ticket_id": 111,
"ticket_link": "data_department/servicedesk/ticket/111",
"ticket_type": "REPORT REQUEST",
"priority": "HIGH",
"requester": {
"email": "tuan_anh@company.com",
"dept": "COMMERCIAL"
},
"assignee": "thanh_long@company.com",
"comment": "Cảm ơn Long. Anh đã nhận được báo cáo.",
"status": "DONE",
"previous_status": "UAT",
"document_link": None
}
sample_payload_2: dict = {
"ticket_id": 222,
"ticket_link": "data_department/servicedesk/ticket/222",
"ticket_type": "DATA REQUEST",
"priority": "NORMAL",
"requester": {
"email": "ceo_office@company.com",
"dept": "CEO OFFICE"
},
"assignee": "thanh_long@company.com",
"comment": None,
"status": "OPEN",
"previous_status": None,
"document_link": None
}
sample_payload_3: dict = {
"ticket_id": 333,
"ticket_link": "data_department/servicedesk/ticket/333",
"ticket_type": "BUG REPORT",
"priority": "URGENT",
"requester": {
"email": "thu_phuong@company.com",
"dept": "OPS"
},
"assignee": "duy_anh@company.com",
"comment": "Em gửi anh ảnh chụp màn hình lỗi ạ",
"status": "IN PROGRESS",
"previous_status": "OPEN",
"document_link": "sharepoint/user/user_id/abcdxyz"
}
sample_payload_4: dict = {
"ticket_id": 444,
"ticket_link": "data_department/servicedesk/ticket/444",
"ticket_type": "BUG REPORT",
"priority": "NORMAL",
"requester": {
"email": "van_anh@company.com",
"dept": "FINANCE"
},
"assignee": "duy_anh@company.com",
"comment": "Em gửi anh file tài liệu liên quan đến lỗi",
"status": "COMPLETED",
"previous_status": "UAT",
"document_link": "sharepoint/user/user_id/12bacdyy"
}
# Email của các lãnh đạo đơn vị
data_leader_emails = ["data_head@company.com", "data_leaders@company.com"]
# Email của Phòng kiểm soát rủi ro vận hành
ops_risk_emails = ["ops_risk@company.com"]
# Functions để xử lý dữ liệu và gửi email thông báo
def send_noti_email(
email: list,
ticket_link: str,
document_link: str=None
) -> None:
print(f"Gửi thông báo tới {email}. Link dẫn tới yêu cầu: {ticket_link}")
if document_link:
print(f"Download tài liệu từ {document_link} và đính kèm tài liệu vào email")
else:
pass
# Kiểm tra payload và xử lý theo các rule đã đề ra:
def process_payload(payload: dict) -> None:
receive_emails = []
match payload:
# Nếu loại yêu cầu là "DATA REQUEST" hoặc "REPORT REQUEST" và có mức độ quan trọng là "HIGH"
# hoặc có người gửi yêu cầu là văn phòng tổng giám đốc (CEO office)
# thì gửi email thông báo cho lãnh đạo về thông tin cập nhật mới nhất của yêu cầu và gửi kèm đường link dẫn tới yêu cầu đó
case {
"ticket_type": ticket_type,
"priority": priority,
"requester": {
"dept": requester_dept
}
} if (ticket_type in ("DATA REQUEST", "REPORT REQUEST")) and ((priority == "HIGH") or requester_dept == "CEO OFFICE"):
receive_emails.extend(data_leader_emails)
send_noti_email(
email=receive_emails,
ticket_link=payload["ticket_link"]
)
# Nếu loại yêu cầu là "BUG REPORT" và đến từ Khối Vận hành thì khi gửi email cần cc thêm email của phòng kiểm soát rủi ro Vận hành,
# download tài liệu thông qua link tài liệu ở yêu cầu rồi gửi tài liệu đó qua email thông báo.
case {
"ticket_type": ticket_type,
"requester": {
"dept": requester_dept
}
} if (ticket_type == "BUG REPORT") and (requester_dept == "OPS"):
receive_emails.extend(data_leader_emails + ops_risk_emails)
send_noti_email(
email=receive_emails,
ticket_link=payload["ticket_link"],
document_link=payload["document_link"]
)
# Nếu loại yêu cầu là "BUG REPORT" thì gửi email thông báo cho lãnh đạo và kèm đường link dẫn tới yêu cầu
case {
"ticket_type": ticket_type
} if ticket_type == "BUG REPORT":
receive_emails.extend(data_leader_emails)
send_noti_email(
email=receive_emails,
ticket_link=payload["ticket_link"]
)
process_payload(sample_payload_1)## Gửi thông báo tới ['data_head@company.com', 'data_leaders@company.com']. Link dẫn tới yêu cầu: data_department/servicedesk/ticket/111
## Gửi thông báo tới ['data_head@company.com', 'data_leaders@company.com']. Link dẫn tới yêu cầu: data_department/servicedesk/ticket/222
## Gửi thông báo tới ['data_head@company.com', 'data_leaders@company.com', 'ops_risk@company.com']. Link dẫn tới yêu cầu: data_department/servicedesk/ticket/333
## Download tài liệu từ sharepoint/user/user_id/abcdxyz và đính kèm tài liệu vào email
## Gửi thông báo tới ['data_head@company.com', 'data_leaders@company.com']. Link dẫn tới yêu cầu: data_department/servicedesk/ticket/444
Sử dụng vòng lặp là một cách cơ bản và thường dùng trong lập trình để viết code ngắn gọn hơn, tự động hoá các tác vụ mà lặp đi lặp lại nhiều lần (ví dụ như load lần lượt các file trong 1 folder, lần lượt invoke API endpoint nhiều lần hoặc nhiều endpoint, …). Trong Python ta có 2 loại vòng lặp sau:
Vòng lặp for:
## 0
## 1
## 2
## 3
## 4
Vòng lặp while:
## 5
## 4
## 3
## 2
## 1
Ví dụ: ta dùng vòng lặp để tạo và thực hiện câu lệnh xoá một loạt các bảng trong danh sách có sẵn
tables = ["users", "orders", "products"]
for table in tables:
query = f"DROP TABLE {table};"
print(f"Executing: {query}")## Executing: DROP TABLE users;
## Executing: DROP TABLE orders;
## Executing: DROP TABLE products;
Giả sử ta có bộ điều kiện sau để kiểm tra xem khách hàng nào được mua vé xem phim:
Nếu user dưới 18 tuổi thì không cho phép mua vé xem phim (vì bộ phim này có mã R)
Nếu user trên hoặc bằng 18 tuổi thì cho phép mua vé xem phim
Nếu user trong độ tuổi từ 18 - 25 và có thẻ sinh viên thì được mua vé giá ưu đãi giảm 20%
Nếu user trong độ tuổi nghỉ hưu (nữ trên 55 tuổi và nam trên 60 tuổi) thì được mua vé giá ưu đãi giảm 15%
So sánh 2 cách triển khai bộ quy tắc trên thành code như dưới đây. Bạn thấy cách nào dễ hiểu hơn?
Bộ quy tắc ở đây không quá phức tạp, vì vậy cả 2 cách đều sẽ không gây quá nhiều khó khăn cho người đọc code, tuy nhiên với cách 1, nếu không comment giải thích đầy đủ thì có thể người đọc sẽ hơi thấy khó hiểu 1 chút. Cách 2 với việc sử dụng các biến is_adult, is_student, is_retired thì tự tên các biến này đã phần nào giải thích cho logic của bộ quy tắc rồi, nên kể cả khi ta không comment giải thích thì code cũng vẫn sẽ dễ hiểu với người đọc.
age = 19
student_id_card = "abc_123_xyz"
gender = "F"
# Cách 1:
if age < 18:
print("Not allowed to buy tickets")
elif age >= 18 and age <= 25 and student_id_card:
# lưu ý rằng trong Python, một "empty string" là một "Falsy value"
# nên trong lệnh điều kiện if, biến "student_id_card" nếu trống thì sẽ trả về False, nếu có giá trị sẽ trả về True
# cách sử dụng biến student_id_card như này giúp code ngắn gọn hơn.
# đọc thêm tại đây: https://www.freecodecamp.org/news/truthy-and-falsy-values-in-python/
print("Allowed to buy tickets at 20% discount")
elif (gender == "F" and age > 55) or (gender == "M" and age > 60):
print("Allowed to buy tickets at 15% discount")
else:
print("Allowed to buy tickets at normal price")## Allowed to buy tickets at 20% discount
# Cách 2
is_adult = (age >= 18)
is_student = (age >= 18 and age <= 25 and len(student_id_card) != 0)
is_retired = (gender == "F" and age > 55) or (gender == "M" and age > 60)
if not is_adult:
print("Not allowed to buy tickets")
elif is_student:
print("Allowed to buy tickets at 20% discount")
elif is_retired:
print("Allowed to buy tickets at 15% discount")
else:
print("Allowed to buy tickets at normal price")## Allowed to buy tickets at 20% discount