Tóm tắt buổi trước và nội dung kỳ này
Buổi hôm trước chúng ta đã học qua những Data Structure quan trọng như: list dictionary tuple set. Ngoài ra ta còn được học biểu thức comprehensions giúp tạo một sequence nhanh chóng và linh hoạt.
Do sắp xếp Livestream chưa tối ưu lắm nên phần nội dung kiến thức này rơi vào cuối buổi thứ 6 và toàn bộ thời lượng ngày thứ 7, các bạn chịu khó xem lại xíu nhen:
🔴 Buổi live ngày 7
Trong kỳ này ta đi tiếp về các kiến thức liên quan tới Hàm. Khi trước ở phần Language Semantics (Ngày 4) ta có học qua về cách gọi hàm, nhưng đó chỉ là bề nổi, bữa nay ta sẽ đi vào chi tiết hơn về các kiến thức liên quan như: Namespace là gì? Variable scope là gì? Lambda function hay Anonymous function là gì? Generators là gì? Làm sao để kiểm soát lỗi bằng Exception?
🔴 Buổi live ngày 8:
Functions – hàm (phương thức)
🔥 syntax
▶ Một hàm được khai báo bằng từ khóa def và nó sẽ chứa 1 block of code (khối mã) indentate vào 1 tab (4 spaces). Hàm có thể trả về kết quả return hoặc chỉ dùng để thực thi tác vụ (trong trường hợp này sẽ trả về None)
In [177]: def function_without_return(x):
.....: print(x)
In [178]: result = function_without_return("hello!")
hello!
In [179]: print(result)
NoneIn [173]: def my_function(x, y):
.....: return x + y▶ Khi biên dịch tới return, giá trị hay biểu thức sau return sẽ được gửi tới ngữ cảnh nơi mà hàm được gọi.
In [174]: my_function(1, 2)
Out[174]: 3
In [175]: result = my_function(1, 2)
In [176]: result
Out[176]: 3▶ Một hàm có thể có nhiều biểu thức return và thậm chí 1 biểu thức return nhưng trả về nhiều giá trị (cơ mà thật ra nó sẽ gộp về 1 tuple).
Ví dụ 1: Có 2 biểu thức return trong cùng 1 hàm
def return_many_2(x, y, z=1.5):
if z > 1:
return z * (x + y)
else:
return z / (x + y)
# -------------------------
def return_many_1():
def inner():
return 1, 2, 3
return inner()
print(return_many_1())
Ví dụ 2: Kết quả trả về tạo thành 1 tuple
def return_results(a, b):
sum = a + b
product = a * b
return sum, product
print(return_results(1, 2))
🔥 positional argms and keyword argms
Với keyword argms ta thường dùng để chỉ định giá trị default hoặc tham số optional.
Còn positional argms là tên gọi chung của các tham số còn lại cần truyền vô
def my_function2(x, y, z=1.5):
if z > 1:
return z * (x + y)
else:
return z / (x + y)x và y là positional còn z là optional
In [181]: my_function2(5, 6, z=0.7)
Out[181]: 0.06363636363636363
In [182]: my_function2(3.14, 7, 3.5)
Out[182]: 35.49
In [183]: my_function2(10, 20) # my_function2(y =20, x=10)
Out[183]: 45.0Và như tập trước có nói thì ta có thể để tên dúng khi gọi như vậy không cần nhớ chính xác vị trí
🍀Namespaces, Scope, Local functions
Thật ra cách sử dụng hàm hay phương thức thì mình đã có đề cập nhiều lần ở các phần trước rồi. Cơ mà phần này ta sẽ đi sâu nhiều hơn về bản chất, đặc biệt là ảnh hưởng của scope (phạm vi) điều mà ta dễ bị “ngộ nhận” nhất và lỗi khiến dữ liệu ta bị thay đổi mà không hề hay biết.
🔥 Namespaces – Chỉ là “tập hợp các tên”?
Có hơi trừu tượng khi nói về phần này, nhưng mình tin sẽ đáng giá vì hiểu rõ namespace thì qua bên scope càng củng cố kiến thức của chúng ta. Vậy nên chịu khó đọc dài dài tí nhé
🎯 Bạn có tự hỏi?
Trong Python, ta có thể tưởng tượng namespace là một mapping (hiểu là dict cũng được) của tất cả tên định danh với những objects tương ứng (Vì cơ bản mọi thứ chương trình Python khởi tạo hay xử lý/thực thi đều là obj nên ta không thể liệt kê hết được)
Ví dụ: x = "foo" thì ta có một tên “tượng trưng” (symbolic name) x và tham chiếu tới obj str có giá trị foo
Như vậy với một phần mềm phức tạp, có khi tới hàng trăm nghìn cái tên được chỉ tới một đối tượng cụ thể nào đó. Như vậy Bạn có tự hỏi làm sao Python quản lý được hết chúng để tụi nó không “bem” lẫn nhau?
Đây là lúc namespace ra đời, tới đây thì các câu hỏi được đặt ra là:
- Cách Python quản lý các namespaces?
- Khi nào Python tạo một namespace mới?
- Cách implement namespace
- Cách
variable scopexác định tên định danh có visibility (hiện hữu) ?
🎍 Các loại namespaces

Các loại namespaces đều có vòng đời riêng. Khi Python chạy chương trình, nó tạo ra tụi nó lúc cần và xóa đi khi không dùng tới.
Ngoài ra có nhiều namespaces luôn có sẵn bất cứ lúc nào (chỉ khi chương trình vẫn đang chạy – nói cách khác là từ khi start Python interpreter và miễn là interpreter vẫn còn chạy) – đây chính là
built-in namespacevàglobal namespace
Built-in Namepsace
dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError',
'BaseException','BlockingIOError', 'BrokenPipeError', 'BufferError',
'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError',
'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
'Exception', 'False', 'FileExistsError', 'FileNotFoundError',
'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError',
'ImportError', 'ImportWarning', 'IndentationError', 'IndexError',
'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt',
'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None',
'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError',
'OverflowError', 'PendingDeprecationWarning', 'PermissionError',
'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning',
'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration',
'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
'TimeoutError', 'True', 'TypeError', 'UnboundLocalError',
'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError',
'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__',
'__doc__', '__import__', '__loader__', '__name__', '__package__',
'__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray',
'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex',
'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate',
'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input',
'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list',
'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct',
'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr',
'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']Như mình đề cập thì rõ ràng các hàm như print() hay id() có thể gọi ở bất cứ phần nào của chương trình.
Global Namespace
Có hai đối tượng gắn liền với global namespace là
- Hàm/chương trình
main
- Bất kỳ
modulenào được đưa vào chương trình bằng hàm import
Như vậy rõ ràng các module khác nhau có namespace khác nhau thì cho dù hàm hay thuộc tính có tên giống nhau thì vẫn tách biết khi chạy
Local namespace hay enclosing namespace
Bất cứ khi nào interpreter thực thi một hàm ta khai báo nào thì nó sẽ tạo ra một local namespace tương ứng. Dễ hiểu là do ta tạo hàm chỉ xài ở cục bộ thôi mà! Nên namespace này sẽ xóa khi hàm ngưng chạy.
>>> def f():
... print('Start f()')
...
... def g():
... print('Start g()')
... print('End g()')
... return
...
... g()
...
... print('End f()')
... return
...
>>> f()
Start f()
Start g()
End g()
End f()Trong ví dụ này, g() được định nghĩa trong thân của f():
- Dòng 1 đến 12: Định nghĩa f(), the enclosing function.
- Dòng 4 đến 7: Định nghĩa g(), the enclosed function.
- Dòng 15: Chương trình chính gọi f().
- Dòng 9: f() gọi g().
Khi chương trình chính gọi f(), Python tạo một namespace mới cho f(). Tương tự, khi f() gọi g(), g() có namespace riêng của mình. Namespace được tạo cho g() là namespace local, và namespace được tạo cho f() là namespace enclosing.
Mỗi namespace này tồn tại cho đến khi hàm tương ứng của nó kết thúc. Python có thể không thu hồi ngay lập tức bộ nhớ được phân bổ cho các namespace đó khi hàm của chúng kết thúc, nhưng tất cả các tham chiếu đến các đối tượng mà chúng chứa sẽ không còn hợp lệ.
🔥 Scope – Phạm vi hiệu lực
Cho ví dụ hàm sau
def func():
a = []
for i in range(5):
a.append(i)Ở ví dụ này sau khi đã tìm hiểu về local namespace ở trên thì ta sẽ biết a sẽ bị xóa sau khi hàm func() kết thúc. Nhưng nếu
In [184]: a = []
In [185]: def func():
.....: for i in range(5):
.....: a.append(i)Lúc này a vẫn còn hiệu lực vì nó không nằm trong scope của hàm func()
In [186]: func()
In [187]: a
Out[187]: [0, 1, 2, 3, 4]
In [188]: func()
In [189]: a
Out[189]: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]Nếu ta muốn gán biến bên ngoài function’s scope thì ta dùng từ khóa global hoặc nonlocal
In [190]: a = None
In [191]: def bind_a_variable():
.....: global a
.....: a = []
.....: bind_a_variable()
.....:
In [192]: print(a)
[]nonlocal cho phép sửa đổi các biến khai báo ở scope cao hơn – Nhưng nó không phải biến gobalnhen. Thường dùng trong các case hàm lồng hàm để sửa đổi các biến trong hàm cha. Ví dụ:
def nonlocal_test():
x = 1
def inner():
nonlocal x
x += 1
print(id(x))
return x
return inner()
print(nonlocal_test())
Trong ví dụ trên nếu bạn quên sử dụng nonlocal để truy cập biến x trong hàm cha của nó, Python sẽ giả định rằng biến đó là cục bộ và cho ra lỗi UnboundLocalError: local variable 'x' referenced before assignment
def nonlocal_test():
x = 1
def inner():
x += 1
return x
return inner()
print(nonlocal_test())Mặc dù
globaltrông thì khá tiện khi có thể linh hoạt tương tác qua lại giữa các biến nhưng mình không khuyến khích dùng nó vì hệ thống lúc này phải lưu trữ các trạng thái khác nhau (vốn ta không thấy được và phàm cái gì mắt không thấy thì sẽ làm sai).Nếu thật sự muốn thực hiện việc chỉnh sửa “vượt cấp” như này thì hãy nghĩ tới chuyện ứng dụng OOP (sử dụng các classes)
🔥 Return mutiples values
Như đã đề cập ở trên thì ta có thể trả về cùng lúc nhiều giá trị dưới dạng tuple, nhưng hoàn toàn tách các giá trị đó ra nhờ vào thuộc tính của tuple
def f():
a = 5
b = 6
c = 7
return a, b, c
a, b, c = f()Trong các chương trình phân tích hoặc khoa học dữ liệu thì sẽ không hiếm việc ta viết 1 hàm nhưng trả về nhiều giá trị khác nhau. Ngoài để dạng tuple ta còn có thể chuyển nó về các dạng khác tùy ý như dictionary:
def f():
a = 5
b = 6
c = 7
return {"a" : a, "b" : b, "c" : c}🔥 Functions are objects
Như chúng ta biết thì để trở thành một object thì nó phải có các đặc điểm như:
- Có thể gắn cho một biến
- Có thể truyền làm tham số cho hàm
Thú vị là trong Python bản thân một hàm là đối tượng nên nhiều cấu trúc chương trình có thể dễ dàng biểu diễn mà các ngôn ngữ lập trình khác không làm được.
Ví dụ ta cần làm sách và biến đổi danh sách strings sau:
In [193]: states = [" Alabama ", "Georgia!", "Georgia", "georgia", "FlOrIda",
.....: "south carolina##", "West virginia?"]Bất cứ ai đã từng làm việc với dữ liệu khảo sát do người dùng gửi có thể đã thấy những kết quả lộn xộn như thế này. Cần làm nhiều việc để làm cho danh sách các chuỗi này thống nhất và sẵn sàng cho việc phân tích: loại bỏ khoảng trắng, loại bỏ các dấu chấm câu và chuẩn hóa việc viết hoa thích hợp.
🎍 re
Một cách để làm điều này là sử dụng các phương thức tích hợp với thư viện có sẵn re viết tắc của regular expressions mà có lần mình đã đề cập trong tập trước
import re
def clean_strings(strings):
result = []
for value in strings:
value = value.strip()
value = re.sub("[!#?]", "", value)
value = value.title()
result.append(value)
return resultKết quả cho ra
In [195]: clean_strings(states)
Out[195]:
['Alabama',
'Georgia',
'Georgia',
'Georgia',
'Florida',
'South Carolina',
'West Virginia']🎍 lst functions
Nhưng vì hàm cũng là objs nên ta có thể có một giải pháp thay thế hữu hiệu sau, đó là đưa hết những hàm xử lý vào một danh sách và áp nó vô bộ strings cần biến đổi
def remove_punctuation(value):
return re.sub("[!#?]", "", value)
clean_ops = [str.strip, remove_punctuation, str.title]
def clean_strings(strings, ops):
result = []
for value in strings:
for func in ops:
value = func(value)
result.append(value)
return resultTa sẽ có kết quả sau:
In [197]: clean_strings(states, clean_ops)
Out[197]:
['Alabama',
'Georgia',
'Georgia',
'Georgia',
'Florida',
'South Carolina',
'West Virginia']Như vậy với cách này ta có thể sửa đổi strings ở high-level. Nói cách khác clean_strings có thể tái sử dụng và generic (đa dụng – chập nhận bất kỳ kiểu dữ liệu nào như hàm sort())
🎍 map()
Một cách khác để dùng hàm trở thành tham số của hàm khác là dùng hàm có sẵn map() giúp áp một hàm xác định vào một chuỗi trình tự thuộc một loại nào đó (như chuỗi loại strings ở trên)
In [198]: for x in map(remove_punctuation, states):
.....: print(x)
Alabama
Georgia
Georgia
georgia
FlOrIda
south carolina
West virginia
map()thay thếlst comprehensionnhưng bỏ đi chức năng lọc
🔥Anonymous (Lambda) Functions
Python hỗ trợ các hàm đặc biệt có tên là anonymous (vô danh) hay lambda, đây là một cách viết các hàm bao gồm một câu lệnh duy nhất, kết quả của nó là giá trị trả về.
Chúng được định nghĩa với từ khóa lambda, từ khóa này không có nghĩa gì khác ngoài “chúng tôi đang khai báo một hàm vô danh”:
In [199]: def short_function(x):
.....: return x * 2
In [200]: equiv_anon = lambda x: x * 2Mặc dù hơi trù tượng nhưng đây là cách khai báo hàm dùng rất phổ biến với những ai lập trình Python và đặc biệt với dân Data. Có rất nhiều trường hợp ta phải truyền các hàm khác nhau làm tham số của một hàm transformation. Và việc phải khai báo đầy đủ cấu trúc sẽ khiến việc khai báo hàm transform trở nên dài dòng gây rối.
In [201]: def apply_to_list(some_list, f):
.....: return [f(x) for x in some_list]
In [202]: ints = [4, 0, 1, 5, 6]
In [203]: apply_to_list(ints, lambda x: x * 2)
Out[203]: [8, 0, 2, 10, 12]Hoặc trong một ví dụ khác ta cần sắp xếp tập hợp các chuỗi ký tự bằng số độ dài của từng chữ:
In [204]: strings = ["foo", "card", "bar", "aaaa", "abab"]
In [205]: strings.sort(key=lambda x: len(set(x)))
In [206]: strings
Out[206]: ['aaaa', 'foo', 'abab', 'bar', 'card']🔥 Generator
Generator là một hàm hoặc biểu thức đặc biệt tạo một iterable object. Hay để tạo một iterator ta có nhiều cách trong đó ở nội dung bài này gồm:
- Default behavior khi dùng
forloop
- Dùng hàm
iter() - Tạo hàm generator bằng
yieldkeyword - Generator expression
- Thư viện
itertools
🎍 iterable object là gì?
Dừng lại khoảng chừng 5s, iterable object à … Chẳng phải cái từ iterable này mình đã nhắc tới nhiều khi học về list hay các cấu trúc dữ liệu sequence mà nhỉ ❓
Thật ra ngoài các sequence object vốn chắc chắn có đặc điểm iterable thì nhiều đối tượng khác đều hỗ trợ tính năng lặp lại (iteration) chẳng hạn chính các dòng lệnh trong 1 file code.
Giao thức
Fun factiterator protocolgiúp các đối tượng có khả năngiterable
✔ How to create iterator | object support iteration
In [207]: some_dict = {"a": 1, "b": 2, "c": 3}
In [208]: for key in some_dict:
.....: print(key)
a
b
cTrong mệnh đề for key in some_dict:, trình biên dịch Python ngầm định sẽ tạo một iterator từ biến some_dict và thực chất nó đang duyệt ở trên iterator
🎍 Phân biệt iterator và sequence object
some_dict là một dict nhưng iterator tạo từ nó là sao ❓
▶ Thật ra chúng độc lập với nhau:
- Iterator là một đối tượng cho phép bạn lặp lại các phần tử của nó một cách tuần tự. Nó được tạo ra bởi các đối tượng có thuộc tính
iterablenhưdictlststr, … trong các ngữ cảnh nhưforloop ở trên chẳng hạn. - Dictionary hay bất kỳ một đối tượng chuỗi trình tự nào khác đóng vai trò là đối tượng lưu trữ dữ liệu.
✔ Hàm iter()
Tới đây thì dễ rồi, chỉ cần dùng hàm iter() để vừa tạo ra iterator vừa kiểm tra đối tượng có iterable không:
In [209]: dict_iterator = iter(some_dict)
In [210]: dict_iterator
Out[210]: <dict_keyiterator at 0x17d60e020>
In [211]:iter(1)
TypeError: 'int' object is not iterableỞ chiều ngược các phương thức nhận
listhoặclist-like objectsử dụng được cho cáciterable object.Ví dụ
summaxminhay các constructorslisttuple:In [211]: list(dict_iterator) Out[211]: ['a', 'b', 'c']
✔ yield keyword – Tạo hàm generator() bằng yield
Trong khi các hàm thông thường thực thi và trả về một kết quả duy nhất tại một thời điểm, generator có thể trả về một chuỗi nhiều giá trị bằng cách tạm dừng và tiếp tục thực thi mỗi khi generator được sử dụng (1).
Để tạo một generator, bạn sử dụng từ khóa yield thay vì return trong một hàm.
def squares(n=10):
print(f"Generating squares from 1 to {n ** 2}")
for i in range(1, n + 1):
yield i ** 2Ở (1) phía trên là sao nhỉ, ta thử tiếp tục ví dụ là sẽ hiểu
In [213]: gen = squares()
In [214]: gen
Out[214]: <generator object squares at 0x17d5fea40>Đáng lý ra với một hàm bình thường thì ta sẽ được trả output là 1 4 9 16 25 36 49 64 81 100 Nhưng ở đây nó lại trả về địa chỉ của một generator object squares at ___
Đúng như vậy, chỉ khi ta yêu cầu các phần tử của generator thì nó mới bắt đầu thực thi code:
In [215]: for x in gen:
.....: print(x, end=" ")
Generating squares from 1 to 100
1 4 9 16 25 36 49 64 81 100Vì generator tạo ra đầu ra một phần tử tại một thời điểm thay vì toàn bộ danh sách cùng một lúc, nên nó có thể giúp chương trình của bạn sử dụng ít bộ nhớ hơn.
Ví dụ:
- Nếu bạn có một danh sách lớn các số nguyên và bạn muốn tính tổng của tất cả các số nguyên đó, bạn có thể sử dụng vòng lặp
forthông thường để lặp qua danh sách và cộng tất cả các số nguyên lại với nhau. Tuy nhiên, điều này sẽ yêu cầu bạn lưu trữ toàn bộ danh sách trong bộ nhớ cùng một lúc.
- Thay vào đó bạn sử dụng generator để tạo ra một chuỗi các số nguyên, bạn có thể tính tổng của tất cả các số nguyên mà không cần lưu trữ toàn bộ chuỗi trong bộ nhớ cùng một lúc. Generator sẽ chỉ tạo ra một số nguyên tại một thời điểm, và bạn có thể sử dụng vòng lặp
forđể lặp qua chuỗi và cộng tất cả các số nguyên lại với nhau.
✔ Generator expressions – Biểu thức Generator
Một cách khác để tạo ra một generator là bằng cách sử dụng generator expression hay biểu thức generator.
Cú pháp tạo generator expression giống với list comprehension nhưng thay vì ngoặc vuông [] thì thay bằng ngoặc tròn ()
In [216]: gen = (x ** 2 for x in range(100))
In [217]: gen
Out[217]: <generator object <genexpr> at 0x17d5feff0>Đoạn code trên tương tự cách dùng generator cũ dài dòng sau
def _make_gen():
for x in range(100):
yield x ** 2
gen = _make_gen()Một ứng dụng ta hay bắt gặp là ta sẽ sử dụng generator expressions để thay thế list comprehensions trở thành tham số như ví dụ sau:
In [218]: sum(x ** 2 for x in range(100))
Out[218]: 328350
In [219]: dict((i, i ** 2) for i in range(5))
Out[219]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}Nếu so về perfomance giữa comprehension expression và generator expression thì chỉ khi số lượng phần tử (elements) đủ lớn thì ta mới thấy sự khác biệt được.
🎍 SPEED TEST
Nếu bạn nào kiên nhẫn đợi được thì có thể chạy thử để xem kết quả so sánh nhé 👀 Tạm thời mình skip qua
%timeit sum(x ** 2 for x in range(1_000_000))
%timeit sum([x ** 2 for x in range(1_000_000_000)])✔ The standard bibrary itertools module
itertools là một thư viện mô-đun có sẵn cho phép tạo ra iterator bằng các generators – hay các hàm của nó (một số hàm thực thi các thuật toán dữ liệu phổ biến)
▶ Ví dụ: groupby() sau đây nhận một sequence và một hàm (trả về các key là các ký tự đầu) sau đó nhóm các elements cùng key.
In [220]: import itertools
In [221]: def first_letter(x):
.....: return x[0]
In [222]: names = ["Alan", "Adam", "Wes", "Will", "Albert", "Steven"]
In [223]: for letter, names in itertools.groupby(names, first_letter):
.....: print(letter, list(names)) # names is a generator
A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']Một số hàm thông dụng thường dùng mình liệt kê sau đây nhưng bạn hoàn toàn có thể truy cập vào the official Python document để tìm đọc thêm nhé

itertools functionschain: Kết hợp hai chuỗi
import itertools
numbers = [1, 2, 3, 4, 5]
letters = ["a", "b", "c", "d", "e"]
combined_list = itertools.chain(numbers, letters)
print(list(combined_list))
combination : Phép phân phối tạo ra tuples
import itertools
numbers = [1, 2, 3, 4, 5]
combinations = itertools.combinations(numbers, 2)
for combination in combinations:
print(combination)
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(2, 3)
(2, 4)
(2, 5)
(3, 4)
(3, 5)
(4, 5)permutation: Phép hoán vị
import itertools
numbers = [1, 2, 3]
permutations = itertools.permutations(numbers)
for permutation in permutations:
print(permutation)product: Tạo tất cả các tích số có thể có
import itertools
numbers = [1, 2, 3]
products = itertools.product(numbers)
for product in products:
print(product)
(1, 1, 1)
(1, 1, 2)
(1, 1, 3)
(1, 2, 1)
---
(3, 3, 3)🔥 Errors and Exception Handling
Trong các ứng dụng phân tích dữ liệu, nhiều hàm chỉ hoạt động trên một số loại đầu vào nhất định. Ví dụ: hàm float của Python có khả năng chuyển đổi chuỗi thành số thực, nhưng nó sẽ báo lỗi ValueError khi đầu vào không hợp lệ:
In [224]: float("1.2345")
Out[224]: 1.2345
In [225]: float("something")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-225-5ccfe07933f4> in <module>
----> 1 float("something")
ValueError: could not convert string to float: 'something'Trường hợp ta mong muốn chủ động xử lý và kiểm soát lỗi thay vì phó mặt cho hệ thống thì ta có thể viết lại hàm trên bằng try và except như sau:
def attempt_float(x):
try:
return float(x)
except:
return xNhư vậy mỗi khi có lỗi (exception) xảy ra thì chương trình sẽ nhảy vào phần except: để thực thi
In [227]: attempt_float("1.2345")
Out[227]: 1.2345
In [228]: attempt_float("something")
Out[228]: 'something'Thật ra có rất nhiều kiểu lỗi nên hệ thống cũng có thể trả về rất nhiều loại lỗi, lấy ví dụ như hàm float ở trên thử:
In [229]: float((1, 2))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-229-82f777b0e564> in <module>
----> 1 float((1, 2))
TypeError: float() argument must be a string or a real number, not 'tuple'Như ta thấy thì ở đây nó trả về lỗi TypeError chứ không phải ValueError nữa rồi, lỗi ở đây ý chỉ rằng có 1 con bug không hợp lệ trong chương trình.
Vậy làm sao để kiểm soát đúng cái lỗi mình muốn theo dõi thôi? Vậy thì ta chỉ cần sử dụng đúng cái Exception type ngay sau except thôi:
def attempt_float(x):
try:
return float(x)
except ValueError:
return xCó điểm khác xíu là lúc này với những lỗi chưa catch hay except lúc này sẽ báo lỗi ở ngay tại dòng return của try: thay vì lỗi lúc gọi hàm. Như vậy thì rõ ràng là cụ thể hơn đúng không
In [231]: attempt_float((1, 2))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-231-8b0026e9e6b7> in <module>
----> 1 attempt_float((1, 2))
<ipython-input-230-6209ddecd2b5> in attempt_float(x)
1 def attempt_float(x):
2 try:
----> 3 return float(x)
4 except ValueError:
5 return x
TypeError: float() argument must be a string or a real number, not 'tuple'Ngoài ra ta còn có thể bắt nhiều loại exception cùng lúc bằng cách dùng 1 tuple để chứa các loại lỗi:
def attempt_float(x):
try:
return float(x)
except (TypeError, ValueError):
return xTrong một số trường hợp, bạn muốn thực thi một đoạn chương trình nhất định bất kể try có thành công hay không thì hãy sử dụng finally:
f = open(path, mode="w")
try:
write_to_file(f)
finally:
f.close()Như ở trên thì f lúc nào cũng closed. Tương tự nếu bạn muốn xử lý việc try: có thành công thì làm gì đó, hãy dùng else
f = open(path, mode="w")
try:
write_to_file(f)
except:
print("Failed")
else:
print("Succeeded")
finally:
f.close()🎍 Exceptions in IPython
Nếu exception xuất hiện khi chạy %run đoạn script hay biểu thức, IPython mặc định sẽ in ra toàn bộ call stack tree (traceback) với vài dòng ngữ cảnh quanh vị trí được chỉ định trong stack:
In [10]: %run examples/ipython_bug.py
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
/home/wesm/code/pydata-book/examples/ipython_bug.py in <module>()
13 throws_an_exception()
14
---> 15 calling_things()
/home/wesm/code/pydata-book/examples/ipython_bug.py in calling_things()
11 def calling_things():
12 works_fine()
---> 13 throws_an_exception()
14
15 calling_things()
/home/wesm/code/pydata-book/examples/ipython_bug.py in throws_an_exception()
7 a = 5
8 b = 6
----> 9 assert(a + b == 10)
10
11 def calling_things():
AssertionError:Có thêm ngữ cảnh tự nó là một lợi thế lớn so với trình thông dịch Python tiêu chuẩn (không cung cấp bất kỳ ngữ cảnh bổ sung nào). Bạn có thể kiểm soát lượng ngữ cảnh được hiển thị bằng cách sử dụng magic method %xmode, từ Plain (giống như trình thông dịch Python tiêu chuẩn) đến Verbose (hiển thị trực tiếp giá trị tham số hàm và nhiều hơn thế nữa).
Như bạn sẽ thấy sau này trong Phụ lục B: Chi tiết hơn về Hệ thống IPython, bạn có thể biết thêm về stack (sử dụng %debug hoặc %pdb) sau khi xảy ra lỗi để gỡ lỗi.
Lời kết
Buổi hôm nay có các kiến thức khá là trừu tượng nhưng ảnh hưởng và giúp ích rất nhiều cho quá trình debug cũng nhưng đọc tài liệu vậy nên mình mới cần tường tận giải thích.
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”
