Chuyển đến nội dung chính

Cách sử dụng dấu hoa thị trong Python

Nhiều người dùng Python quen dùng dấu hoa thị cho toán tử nhân và lũy thừa, nhưng trong hướng dẫn này, bạn sẽ khám phá thêm những cách áp dụng khác của dấu hoa thị.
· 9 phút đọc

Khám phá cùng AI

Hầu hết chúng ta dùng dấu hoa thị làm toán tử nhân và lũy thừa, nhưng chúng còn có nhiều cách sử dụng khác khi đóng vai trò toán tử tiền tố trong Python. Sau khi đọc bài viết này, bạn sẽ nắm được toàn bộ cách dùng của dấu hoa thị.

Dấu hoa thị có nhiều trường hợp sử dụng cụ thể trong Python. Nói chung, chúng ta quen với các toán tử nhân và lũy thừa. Nó còn có thể thực hiện một số thao tác khác như mở gói (unpacking), truyền đối số, v.v. trong các tình huống khác nhau. Trước hết, hãy xem cách dùng phổ biến của dấu hoa thị.

Cách dùng chung của * và **

* được dùng làm toán tử nhân, còn ** được dùng làm toán tử lũy thừa.

## declaring variables
a, b = 2,3

## multiplication
print("Multiplication of 2 and 3 is {}".format(a * b))

## as power operator
print("2 power 3 is {}".format(a ** b))
Multiplication of 2 and 3 is 6
2 power 3 is 8

Bây giờ bạn đã thấy cách dùng tổng quát và phổ biến nhất của dấu hoa thị. Tuy nhiên vẫn còn những trường hợp sử dụng dấu hoa thị mà có thể bạn chưa biết. Hãy tiếp tục để khám phá những cách dùng ẩn của dấu hoa thị.

1. Unpacking bằng *

Mở gói (unpack) các phần tử từ một list/tuple/set bằng *. Chúng ta sẽ dùng mở gói trong các đoạn mã sau để triển khai một số tính năng khác của dấu hoa thị. Mở gói là ý tưởng cơ bản đằng sau việc dùng dấu hoa thị như toán tử tiền tố.

Giả sử chúng ta muốn gán phần tử đầu tiên của list/tuple/set cho a và các phần tử còn lại cho b. Thông thường, chúng ta sẽ làm điều này bằng cách cắt (slicing).

## general approach
nums = [i for i in range(1, 6)]
a = nums[0]
b = nums[1:]
print(a, b)

print("--------------------------")

## hard approach
squares = [i ** 2 for i in range(1, 6)]
a = squares[0]
b = []
for i in range(1, len(squares)):
    b.append(squares[i])
print(a, b)
1 [2, 3, 4, 5]
--------------------------
1 [4, 9, 16, 25]

Bây giờ, chúng ta sẽ dùng toán tử * để thực hiện việc này trong một dòng mã.

nums = [i for i in range(1, 6)]

## a sẽ là 1 và b sẽ là một list chứa các phần tử còn lại
a, *b = nums
print(a, b)
1 [2, 3, 4, 5]

Ở đây a có kiểu giống phần tử lấy ra từ list và b là một list. Mở gói cũng hoạt động trên tuple và set giống như với list. Hãy xem vài ví dụ.

## using tuple
## here first element will assign to a and last element to c
a, *b, c = (1, 2, 3, 4, 5)
print(a, b, c)

print("--------------------------")

## using sets
a, *b, c = {1, 4, 9, 16, 25}
print(a, b, c)
1 [2, 3, 4] 5
--------------------------
1 [4, 9, 16] 25

1.1. Kết hợp các iterable khác nhau

Chúng ta có thể thêm hai iterable khác nhau vào list/set/tuple bằng mở gói. Thông thường, ta phải chuyển đổi các iterable và kết hợp chúng thành một. Tuy nhiên không cần thiết nếu dùng *. Hãy xem ví dụ.

## combining a tuple, list, set
nums = [1, 2, 3]
nums2 = (4, 5, 6)
nums3 = {7, 8, 9}

## we convert the combined elements into any iterable we want
## here i am converting into a list
_list = [*nums, *nums2, *nums3]
_tuple = (*nums, *nums2, *nums3)
_set = {*nums, *nums2, *nums3}

print(type(_list))
print(_list)
print("------------------------")
print(type(_tuple))
print(_tuple)
print("------------------------")
print(type(_set))
print(_set)
<class 'list'>
[1, 2, 3, 4, 5, 6, 8, 9, 7]
------------------------
<class 'tuple'>
(1, 2, 3, 4, 5, 6, 8, 9, 7)
------------------------
<class 'set'>
{1, 2, 3, 4, 5, 6, 7, 8, 9}

1.2. Mở gói lồng nhau

Mở gói lồng nhau là lấy ra các phần tử từ cấp đầu tiên của các mục đã trích xuất. Xem ví dụ để hình dung rõ hơn ý tôi muốn nói.

languages = ["Python", "HTML", "CSS", "JS"]

## unpacking
[[first_letter, *remaining], *other] = languages

print(first_letter, remaining, other)
P ['y', 't', 'h', 'o', 'n'] ['HTML', 'CSS', 'JS']

2. Unpacking bằng **

Như đã thấy, ** được dùng để mở gói các phần tử từ một dictionary. Ta phải biết các khóa của dictionary để trích xuất giá trị khi mở gói. Hãy xem ví dụ.

## sample dictionary
person = {"name":"John", "age":19, "year_of_passing":"2021"}
string = "Name:-{name} Year Of Graduation:-{year_of_passing} Age:-{age}".format(**person)
print(string)
Name:-John Year Of Graduation:-2021 Age:-19

Không phổ biến lắm khi dùng ** để trích xuất các phần tử từ một dictionary.

3. Dấu hoa thị trong hàm

Chúng ta có thể truyền và thu nhận đối số bằng dấu hoa thị trong hàm để giảm số dòng mã.

3.1. Unpacking trong hàm

Toán tử * được dùng để gọi hàm bằng cách mở gói một iterable. Giả sử ta có một list và cần truyền từng phần tử của nó riêng lẻ vào print(). Bạn sẽ làm gì? Có lẽ bạn đang nghĩ đến một vòng lặp. Nhưng * giúp việc này trở nên rất dễ.

Chỉ cần truyền iterable dưới dạng *tên_iterable. Nó sẽ mở gói iterable và truyền từng phần tử một cách riêng rẽ.

nums = [i for i in range(1, 6)]
## passsing list using the *
print(*nums)
1 2 3 4 5
nums = (i for i in range(1, 6))
## passsing tuple using the *
print(*nums, sep = ", ")
1, 2, 3, 4, 5

3.2. Đóng gói phần tử

Đóng gói nghĩa là thu nhận nhiều phần tử cùng lúc. Chúng ta dùng tính năng này trong lời gọi hàm.

def average(*nums):
    return sum(nums) / len(nums)

Bây giờ, ta có thể truyền bất kỳ số lượng số nguyên nào để tính trung bình. Hàm average thu nhận tất cả đối số như một list.

## calling the average with some numbers
print(average(1, 2, 3, 4, 5))
3.0

Chúng ta đã thấy cách thu nhận nhiều phần tử vào một list bằng *. Giờ hãy học cách thu nhận nhiều cặp khóa-giá trị bằng **.

def _object(name, **properties):
    print(name, properties)

_object("Car", color="Red", cost=999999, company="Ferrari")
Car {'ceo': 'Louis', 'color': 'Red', 'cost': 999999, 'company': 'Ferrari'}

4. Đối số chỉ-từ-khóa cùng với đối số vị trí

Đối số có tên được gọi là đối số từ khóa (keyword argument). Các đối số khác được gọi là đối số vị trí (positional argument). Trước hết, bạn cần hiểu sự khác nhau giữa đối số từ khóa và vị trí. Hãy xem ví dụ.

## name is a positional argument
## color and cost are keyword arguments
def sample(car, color = None, cost = None):
    print("Car:-{} Color:-{}, Cost:-{}".format(car, color, cost))

Ta có thể dùng tên tham số để truyền đối số khi gọi hàm cho các đối số từ khóa. Nếu không truyền gì cho đối số từ khóa, chúng sẽ nhận giá trị mặc định được cung cấp trong định nghĩa hàm.

Ta có thể chỉ định đối số từ khóa bằng từ khóa hoặc vị trí của đối số.

sample("Ferrari", "Red", 999999)
Car:-Ferrari Color:-Red, Cost:-999999
sample("Ferrari", cost = 999999, color = "Red")
Car:-Ferrari Color:-Red, Cost:-999999

Thứ tự của các đối số từ khóa không quan trọng nếu ta gọi chúng bằng tên đã định nghĩa trong hàm.

sample("Ferrari", color = "Green")
Car:-Ferrari Color:-Green, Cost:-None

Bây giờ bạn đã hiểu về đối số vị trí và từ khóa. Hãy đi vào chủ đề chính.

Đối số chỉ-từ-khóa chỉ được chỉ định bằng từ khóa. Ta không thể đặt chúng theo vị trí như trước. Ta phải khai báo các đối số từ khóa sau đối số * để thu nhận chỉ-từ-khóa.

def keyword_only(*items, _list, default = False):
    print(items)
    print(_list)
    print(default)

Chúng ta phải dùng các từ khóa _list và default khi gọi hàm vì chúng đứng sau *items. Nếu không dùng từ khóa để gọi, ta sẽ gặp lỗi. Hãy xem cả hai trường hợp.

nums = [i ** 2 for i in range(1, 6)]
## calling the function
keyword_only(1, 2, 3, 4, 5, _list = nums, default = True)
(1, 2, 3, 4, 5)
[1, 4, 9, 16, 25]
True

Điều gì xảy ra nếu ta gọi hàm keyword_only bằng các đối số vị trí.

nums = [i ** 2 for i in range(1, 6)]
## calling the function will raise an error
keyword_only(1, 2, 3, 4, 5, nums, False)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-85-bf737d8a8bfc> in <module>()
      1 nums = [i ** 2 for i in range(1, 6)]
----> 2 keyword_only(1, 2, 3, 4, 5, nums, False)


TypeError: keyword_only() missing 1 required keyword-only argument: '_list'

Chúng ta nhận lỗi cho biết thiếu một đối số bắt buộc kiểu chỉ-từ-khóa. Vì vậy, ta phải dùng từ khóa khi gọi các đối số chỉ-từ-khóa. Đối số chỉ-từ-khóa chỉ được định nghĩa sau đối số * trong hàm.

5. Đối số chỉ-từ-khóa không kèm đối số vị trí

Nếu muốn chỉ chấp nhận các đối số chỉ-từ-khóa mà không có đối số vị trí, Python cho phép dùng * trong tham số hàm để đạt được điều này. Hãy xem ví dụ.

def _sample(*, name):
    print(name)

Hàm trên chỉ nhận các đối số chỉ-từ-khóa. Chúng ta phải truyền đối số kèm theo từ khóa. Nếu không, sẽ gặp lỗi. * trong định nghĩa hàm nhằm đảm bảo hàm chỉ nhận đối số thông qua từ khóa. Bất kỳ tham số nào được định nghĩa sau tham số * trong hàm đều phải được chỉ định bằng từ khóa khi gọi hàm.

## calling the function using keyword 'name'
_sample(name = "Datacamp")
Datacamp
## calling the function without using keyword 'name'
## we will get a TypeError
_sample("Datacamp")
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-12-fe0d3718b56c> in <module>()
      1 ## calling the function without using keyword 'name'
      2 ## we will get a TypeError
----> 3 samp("Datacamp")


TypeError: samp() takes 0 positional arguments but 1 was given

Rõ ràng hàm _sample() chỉ nhận các đối số chỉ-từ-khóa*.

Kết luận

Nhà phát triển bổ sung tính năng mới trong mỗi phiên bản Python. Dấu hoa thị là đặc biệt và giúp rút gọn một số phần của mã. Ta không thể chấp nhận nhiều đối số vị trí nếu không có *, và các đối số chỉ-từ-khóa cũng cần * để triển khai tính năng đó. Các thao tác khác của dấu hoa thị có thể đạt được bằng nhiều dòng mã. Hãy luyện tập để hiểu rõ hoàn toàn. Cảm ơn bạn đã đọc!

Nếu bạn muốn tìm hiểu thêm về Python, hãy học khóa Python Data Science Toolbox (Phần 1) của DataCamp.

Chúc bạn viết code vui vẻ :)

Chủ đề

Liên quan