Tóm tắt buổi trước và nội dung kỳ này
Trong buổi trước mình đã đi qua về cụ thể cách cài đặt và một số kiến thức cơ bản về IPython cũng như một vài cú pháp trong Jupyter.
Mặc dù đầu buổi Livestream thứ 4 này mình vẫn phải back lại nói bổ sung thêm mục cuối phần “Nội suy” và cho các bạn biết thêm cách để tạo một đối tượng như thế nào nhưng mình tin vững nền tảng thì mới đi xa được nè
🔴 Buổi live ngày 4:
À ngoài ra mình muốn bổ sung cách cài đặt packages trực tiếp trong Jupyter Notebook
# Install a pip package in the current Jupyter kernel
import sys
!conda install --yes --prefix {sys.prefix} numpy
# !{sys.executable} -m pip install numpy
Language Semantics – ngữ nghĩa (quy tắc)
Python là ngôn ngữ lập trình được thiết kế với trọng tâm là khả năng đọc, đơn giản và rõ ràng. Đơn giản nhất là hãy so sánh một ngôn ngữ bậc cao và nổi tiếng khác như C# bằng một hàm đơn giản là print chữ “Hello World” trong console thì sẽ khác nhau như thế nào?
Đây là code C#
// Hello World! program
using System;
namespace HelloWorld
{
class Hello {
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}Còn đây là Python
print("Hello World")Rõ ràng chẳng cần phải hiểu code viết như thế nào nhưng rõ ràng nhìn vào code Python rõ ràng đỡ … “nản” hơn nhiều rồi.
🔥 indentation – no braces, EOL rules
Điểm khác biệt giữa Python và hầu hết những ngôn ngữ lập trình bậc cao khác là nó sử dụng khoảng trắng (tabs hoặc spaces) để tổ chức code thay vì ngoặc. Ví dụ đoạn code sau:
for x in array:
if x < pivot:
less.append(x)
else:
greater.append(x)Trong ví dụ trên:
Dấu hai chấm : biểu thị sự bắt đầu của một khối mã thụt lề, sau đó tất cả mã phải được thụt lề bằng nhau cho đến khi kết thúc khối mã.
Theo quy ước cộng đồng thì bạn nên thụt vào 4 khoảng trắng làm mặc dịnh, có thể thay thế bằng 1 lần tab.
Giống IPython và Jupyter notebooks thì những IDE hiện đại khác đều có hỗ trợ indentation sẵn
Thêm một quy tắc quan trong nữa là dấu chấm phẩy-semicolon ; bị bắt buộc điền vào cuối mỗi dòng của mỗi câu lệnh để đánh dấu kết thúc câu lệnh đó. Còn trong Python thì nó chỉ được dùng để phân tách các câu lệnh trong cùng 1 dòng như ví dụ sau:
a = 5; b = 6; c = 7Cơ mà lời khuyên của mình đừng làm vậy vì code bạn sẽ khó đọc.
🔥 Everything is an object, isn’t it?
Một đặc điểm quan trọng đó là các tổ chức dữ liệu trong Python theo mô hình đối tượng object models.
Mọi số, chuỗi, cấu trúc dữ liệu, hàm, lớp, mô-đun, v.v. đều tồn tại trong trình thông dịch Python đều là các đối tượng (objects). Mà đối tượng thì phải chứa dữ liệu trên các trường/biến (gọi là thuộc tính) và kèm theo các hàm xử lý (gọi là phương thức).
Việc tạo đối tượng thông qua các lớp (classes) là một khuôn mẫu cho từng đối tượng. Ta định nghĩa thuộc tính và thương thức khi khai báo lớp. Ví dụ:
Thật ra một ngôn ngữ có thể tuân theo quy tắc của nhiều mô hình khác nhau trong đó một mô hình vô cùng phổ biến đó là Mô hình hướng đối tượng (OOP-Object Oriented Programming)
Dưới đây là một số ví dụ về các mô hình trong ngôn ngữ lập trình:
- Mô hình hướng đối tượng: Python, Java, C#
- Mô hình mệnh lệnh (Imperative models): C, C++, Fortran
- Mô hình mệnh đề: Prolog, Lisp, Scheme
🔥 Comments, Docstring – Chú thích
Bất kỳ đoạn hoặc dòng code nào được bắt đầu bằng dấu thăng # thì trình biên dịch Python sẽ skip qua đoạn hay dòng code đó.
Cú pháp này được dÙng để thêm chú thích mong muốn hoặc đôi khi vì ta đơn giản không muốn chạy nhưng lại không muốn xóa chúng đi.
Trong Jupiter ta có thể dùng tổ hợp phí
Phím tắtCtrl+/
results = []
for line in file_handle:
# keep the empty lines for now
# if len(line) == 0:
# continue
results.append(line.replace("foo", "bar"))Một quy tắc của mình là nếu bạn cần chú thích cho hàm, đoạn code hoặc phương thức, lớp khai báo thì sẽ để ở trước đoạn code đó. Còn nếu chỉ để chú thích 1 dòng thì đặt đàng sau dòng code đó là được
print("Reached this line") # Simple status reportNgoài ra một thumb rule nữa là hãy chịu khó document lại những gì mình viết bằng cách sau (người ta gọi là docstring)
def add_numbers(a, b): """ Add two numbers together Returns ------- the_sum : type of arguments """ return a + b
🔥 Function and object method calls
Để gọi hàm thì ta sử dụng dấu ngoặc đơn(parentheses) và thêm tham số vào hàm, nếu có trả về giá trị để dùng lại sau thì nên gán nó vào một biến (variable).
Đôi khi cũng có những hàm không nhận tham số thì ta chỉ cần để trống trong ngoặc:
result = f(x, y, z)
fn_x()Do là mọi thứ trong Python đều là đối tượng nên hầu hết chúng đề có hàm của chúng (bao gồm các hàm hệ thống khởi tạo mặc định) và việc gọi hàm từ một đối tượng để truy cập các nội dung bên trong nó đơn giản bằng cách sau:
obj.some_method(x, y, z)Khi gọi hàm ta có thể truyền tham số theo vị trí (index) của biến hoặc tên của biến đã khai báo trong hàm. Ví dụ:
from math import sqrt
def quadratic(a, b, c):
x1 = -b / (2*a)
x2 = sqrt(b**2 - 4*a*c) / (2*a)
return (x1 + x2), (x1 - x2)Hàm phương trình bậc 2 trên có nhiều cách gọi như:
# Vị trí giá trị truyền vào quan trọng
>>> quadratic(31, 93, 62)
(-1.0, -2.0)
>>> quadratic(62, 93, 31)
(-0.5, -1.0)
# Vị trí không quan trọng
>>> quadratic(a=31, b=93, c=62)
(-1.0, -2.0)
>>> quadratic(c=62, b=93, a=31)
(-1.0, -2.0)
# Và thập chí trộn hầm bà lằng
>>> quadratic(31, 93, c=62)
(-1.0, -2.0)🔥 Variables and argument passing
Khi gán bất cứ thứ gì cho biến thì đồng nghĩa bạn tạo một reference (liên kết – tham chiếu) tới dối tượng ở bên phải dấu bằng gán biến. Ví dụ sau:
In [8]: a = [1, 2, 3]
In [9]: b = a
In [10]: b
Out[10]: [1, 2, 3]Một số ngôn ngữ khác thì khi gán b cho đối tượng của a thì nó sẽ copy phần dịa chỉ bộ nhớ của a ra rồi liên kết địa chỉ dữ liệu trên vùng bộ nhớ mới đó cho b. Nhưng với Python bảng chất a và b đều đang trỏ vào cùng 1 địa chỉ

Cách để kiểm tra:
In [11]: a.append(4)
In [12]: b
Out[12]: [1, 2, 3, 4]DA mình phải nắm được ý nghĩa tham chiếu trong Python – khi, như thế nào và tại sao dữ liệu được sao chép đặc biệt quan trọng khi đang làm việc với các tập dữ liệu lớn hơn trong Python.
Nếu không nắm được chuyện này sẽ dễ dẫn tới các lỗi dirty modifying các tập dữ liệu phân tích khác nhau mà lại không biết.
Gán biến còn được gọi là binding (data) vì chúng ta đang ràng buộc một tên biến với một đối tượng. Tên biến đã được gán đôi khi có thể được gọi là biến bị ràng buộc. (bound variables)
Khi bạn truyền đối tượng vào tham số của một hàm, biến cục bộ mới (local variables) được tạo ra và tham chiếu tới biến gốc ở ngoài hàm. Mọi thay đổi trong hàm đều dẫn tới thay đổi bên ngoài
In [13]: def append_element(some_list, element):
....: some_list.append(element)In [14]: data = [1, 2, 3]
In [15]: append_element(data, 4)
In [16]: data
Out[16]: [1, 2, 3, 4]Nhưng nếu tạo một đối tượng cùng tên với biến gốc ở ngoài hàm và gắn nó vói một đối tượng mới thì nó sẽ không ghi đè qua biến gốc ở parent scope cho dù có thay đổi trong phạm vi hàm
def change_list(list1):
"""Changes the list passed as an argument.
Args:
list1: A list of numbers.
"""
# list1.append(6) #This will modify the parent scope
list1 = [100, 2, 3, 4, 5]
list1.append(101)
# Create a list
list1 = [1, 2, 3, 4, 5]
# Print the list before calling the function
print("Before call function:",list1)
# Call the function
change_list(list1)
# Print the list after calling the function
print("After call function:",list1)🔥 Dynamic references, strong types – Tham chiếu động
Các biến trong Python không tham được ép kiểu theo dạng thừa kế như các ngôn ngữ khác mà đơn giản nó tùy thuộc vào kiểu của đối tượng được gán với nó
In [17]: a = 5
In [18]: type(a)
Out[18]: int
In [19]: a = "foo"
In [20]: type(a)
Out[20]: strChính điều trên nên nhiều người vội kết luận rằng Python không phải là mộttype language
Type-language là ngôn ngữ lập trình trong đó các biến, biểu thức và câu lệnh được gán một kiểu dữ liệu cụ thể. Điều này có nghĩa là các biến chỉ có thể chứa các giá trị của kiểu dữ liệu đã chỉ định.
Điều này hoàn toàn sai, nhìn ví dụ như dươi đây sẽ hiểu
In [21]: "5" + 5
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-21-7fe5aa79f268> in <module>
----> 1 "5" + 5
TypeError: can only concatenate str (not "int") to strMột số ngôn ngữ, ‘5’ có thể được chuyển thành số nguyên, do đó kết quả ra 10.
Lại có các ngôn ngữ khác, số nguyên 5 có thể được chuyển đổi thành ký tự, và vậy nên dòng lệnh trên dùng để nối chuỗi thành ’55’.
Trong Python, việc ép kiểu ngầm như vậy không được phép. Nói cho đúng thì Python là strong type language, có nghĩa là mọi đối tượng đều có một loại (hoặc lớp) cụ thể và các chuyển đổi ngầm sẽ chỉ xảy ra trong một số trường hợp được cho phép nhất định, chẳng hạn như:
In [22]: a = 4.5
In [23]: b = 2
# String formatting, to be visited later
In [24]: print(f"a is {type(a)}, b is {type(b)}")
a is <class 'float'>, b is <class 'int'>
In [25]: a / b
Out[25]: 2.25Như ví dụ trên b là số nguyên nhưng được ép kiểu trở thành số thực trong phép chia.
Việc biết được đối tượng có kiểu dữ liệu nào rất quan trọng vì mỗi kiểu dữ liệu có các xử lý dữ liệu khác nhau và đặc biệt giúp ta kiểm soát đầu vô của các hàm.
Ví dụ có đôi khi
Để kiểm đối tượng có thuộc một kiểu dữ liệu xác định không ta dùng hàm isinstance
In [26]: a = 5
In [27]: isinstance(a, int)
Out[27]: TrueHàm này cũng nhận tuple để kiểm tra kiểu của đối tượng có thuộc một trong những kiểu mình quan tâm (tuple là gì mình sẽ giải thích sau nhen)
In [28]: a = 5; b = 4.5
In [29]: isinstance(a, (int, float))
Out[29]: True
In [30]: isinstance(b, (int, float))
Out[30]: TrueThật ra thì cách làm này mình cũng thấy khá là vô nghĩa vì mình cũng ít dùng 🤔 Bạn nào có biết ý nghĩa của việc này chứ? Bản thân mình thì thường dùng hàm type là chính
🔥 Attributes and methods
Cách truy cập thuộc tính (attributes) hoặc phương thức (methods) giống với việc gọi hàm (function calls) ở trên vậy:
<obj.attribute_name>
Và như mình từng để cập trong phần Magic method thì ta có hàm getattr() cũng được dùng để xác định một hàm nhất định của đối tượng có tồn tại hay không?
In [32]: getattr(a, "split")
Out[32]: <function str.split(sep=None, maxsplit=-1)>Fun fact
getattr()hay các hàm liên quan nhưhasattr()vàsetattr()rất hữu ích trong việc viết các đoạn mã generic dễ tái sử dụng – Bạn sẽ thấy nhiều trong các thư viện
Một ví dụ về generic code cho bạn nào quan tâm
# hàm generic sử dụng getattr() để gọi một phương thức hoặc thuộc tính trên một đối tượng:
def call_sth(obj: Any, method_or_attribute_name: str) -> Any:
"""Gọi một phương thức trên một đối tượng.
Args:
obj: Đối tượng mà phương thức sẽ được gọi trên.
method_name: Tên của phương thức sẽ được gọi.
Returns:
Giá trị trả về của phương thức.
"""
method = getattr(obj, method_or_attribute_name)
return method()
# Ví dụ sử dụng:
class Person:
name = "Alice
def greet(self):
print("Hello!")
person = Person()
# Gọi phương thức `greet()` trên đối tượng `person` bằng cách sử dụng hàm `call_method()`.
call_sth(person, "greet")
call_sth(person, "name")
>>>> Hello!🔥 Duck typing 🦆
“Nếu nó đi như con vịt, kêu như con vịt thì nó là con vịt” Còn sao dùng con vịt mà không dùng con khác thì mình cũng chịu 😂
Đôi khi bạn không quan tâm tới kiểu dữ liệu đối tượng mà chỉ vì các hàm và hành vi (behavior – Ví dụ List thì có hàm len và phần tử đầu tiên của nó được đánh index là 0) của kiểu đó nên bạn mới cần xác định
Một (behavior) thú vị mình muốn đề cập là iterable – lặp lại. Bạn có thể xác định một obj có iterable không chỉ khi nó triển khai (implements) iterator protocol hay chưa.
Fact: iterable obj (hầu hết là các Python collection types) sẽ có magic method __iter__ và để check nó có phải hay không ta dùng hàm iter sau:
In [33]: def isiterable(obj):
....: try:
....: iter(obj)
....: return True
....: except TypeError: # not iterable
....: return FalseIn [34]: isiterable("a string")
Out[34]: True
In [35]: isiterable([1, 2, 3])
Out[35]: True
In [36]: isiterable(5)
Out[36]: False🔥 Imports
Đây là phần bạn thường dùng nhất – module import. Thật ra các module bản chất là các file .py chưa code mà thôi. Ví dụ ta tạo một “thư viện sau” và đặt tên là some_module.py:
# some_module.py
PI = 3.14159
def f(x):
return x + 2
def g(a, b):
return a + bĐể truy cập thư viên đó (phải nằm trên cùng 1 folder) thì ta chỉ dơn giản là:
import some_module
result = some_module.f(5)
pi = some_module.PIHoặc
from some_module import g, PI
result = g(5, PI)Sử dụn keyword alias as để tùy biến thêm cho việc gọi tên thư viện thậm chí là hàm và biến trong thư viện
import some_module as sm
from some_module import PI as pi, g as gf
r1 = sm.f(pi)
r2 = gf(6, pi)🔥 Binary operators and comparisons – Toán tử nhị phân và các phép so sánh
Phần này liên quan mật thiết tới toán rùi nè. Một số toán tử quan trọng mình để trong hình:

Các ví dụ:
1. Cộng trừ nhân chia
In [37]: 5 - 7
Out[37]: -2
In [38]: 12 + 21.5
Out[38]: 33.5
In [39]: 5 <= 2
Out[39]: False2. Kiểm tra xem 2 biến có tham chiếu nhau không
In [40]: a = [1, 2, 3]
In [41]: b = a
In [42]: c = list(a)
In [43]: a is b
Out[43]: True
In [44]: a is not c
Out[44]: TrueLưu ý: Hàm list sẽ tạo ra một bản copy mới cho đối tượng. Toán tử so sánh is tương đồng với dùng ==
In [45]: a == c
Out[45]: TrueDân data mình thường dùng is để check xem biến đó có rỗng không
In [46]: a = None In [47]: a is None Out[47]: TrueCơ mà khi sử dụng Pandas DataFrame thì ta sẽ có hàm sau
import pandas as pd # Create a Pandas DataFrame df = pd.DataFrame([[1, 2, 3], [None, 5, 6]], columns=['A', 'B', 'C']) # Check for None in the DataFrame is_null = df.isnull() # Print the result print(is_null) >>> A B C 0 False False False 1 True False False
Trong Python,
Nonelà một từ khóa đặc biệt được sử dụng để đại diện cho một giá trị rỗng.Nonekhông phải là một kiểu dữ liệu riêng biệt, mà là một đối tượng của lớpNoneType.
Nulllà một thuật ngữ chung được sử dụng trong nhiều ngôn ngữ lập trình để đại diện cho một giá trị rỗng. Trong Python,Nonelà tương đương vớinulltrong các ngôn ngữ khác.
🔥 Mutable and immutable objects – Bất biến?
Nhiều đối tượng trong Python, chẳng hạn như list, dictionary, ndarrayvà hầu hết các kiểu dữ liệu do người dùng định danh (class) , đều là đối tượng có thể thay đổi được. Điều này có nghĩa là đối tượng hoặc giá trị mà chúng chứa có thể được sửa đổi:
In [48]: a_list = ["foo", 2, [4, 5]]
In [49]: a_list[2] = (3, 4)
In [50]: a_list
Out[50]: ['foo', 2, (3, 4)]Nhưng bên cạnh đó có những loại không thể thay đổi dữ liệu bên trong chúng như string, tuples
In [51]: a_tuple = (3, 5, (4, 5))
In [52]: a_tuple[1] = "four"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-52-cd2a018a7529> in <module>
----> 1 a_tuple[1] = "four"
TypeError: 'tuple' object does not support item assignmentHãy nhớ rằng chỉ vì thể thay đổi một đối tượng không có nghĩa là nên làm như vậy. Những hành động như vậy sẽ mang lại hiệu ứng phụ (side effects). Ví dụ, khi viết một hàm, bất kỳ side effects nào cũng nên được thông báo rõ ràng cho người dùng trong tài liệu hoặc chú thích của hàm. Nếu có thể, nên ưu tiên tính bất biến (immutability) để đảm báo tính toàn vẹn dữ liệu.
Lời kết
Thiệt là chương này mình phải ngồi soạn gần 2 ngày rất là phê. Chủ yếu phải vừa soạn vừa test code lại vừa mở rộng vấn dề nên khá mất thời gian nhưng không sao miễn giúp được nhiều bạn là mình thấy vui rồi.
Hẹn gặp các bạn và buổi Livestream kế tiếp nhé (8:00 PM – T3 và CN hằng tuần)
Các kênh cộng đồng của mình
- Facebook: https://www.facebook.com/bocuoilamdata
- Youtube: https://www.youtube.com/@CarrY4U_VN
- Website: https://carry4u.org/
© Bản quyền thuộc về CarrY4U và Bò Cười Làm Data
© Copyright by CarrY4U and bocuoilamdata ☞ Do not Reup”
