Function (hàm) trong Python

    1

    Việc sử dụng hàm trong Python là rất phổ biến. Trong tập bài giảng này bạn đã gặp và sử dụng nhiều hàm khác nhau. Python cho phép bạn tự xây dựng hàm riêng và sau đó có thể sử dụng chúng giống như các hàm xây dựng sẵn. Bài học này sẽ hướng dẫn chi tiết các vấn đề liên quan đến xây dựng và sử dụng hàm trong Python.

    Khái niệm hàm trong Python

    Trong các ngôn ngữ lập trình đều cung cấp khả năng nhóm các đoạn code lại thành một đơn vị và đặt tên. Một nhóm code có đặt tên như vậy cung cấp khả năng tái sử dụng ở nhiều vị trí, thay vì phải viết lặp lại các đoạn code. Loại đơn vị code như vậy trong các ngôn ngữ lập trình thường được gọi là hàm (function), thủ tục (procedure), chương trình con (sub routine), v.v.

    Tương tự, Python cũng cung cấp khả năng xây dựng các khối code được đặt tên. Trong python người ta gọi một đơn vị như vậy là hàm (function).

    Việc sử dụng hàm trong Python là rất phổ biến. Trong tập bài giảng này bạn đã gặp và sử dụng nhiều hàm khác nhau. Để xuất/nhập dữ liệu với giao diện console, bạn đã sử dụng hàm print() và input(). Để tìm số phần tử của danh sách/tuple hoặc độ dài xâu, bạn dùng hàm len(). Các hàm trên được gọi chung là hàm xây dựng sẵn (built-in function).

    Nhìn chung bạn có thể để ý thấy việc sử dụng các hàm xây dựng sẵn có một số điểm sau:

    • Hàm có thể trả về kết quả hoặc không: ví dụ, print() không trả về giá trị nào, trong khi len() trả lại thông tin về độ dài chuỗi.
    • Hàm có thể cần thông tin đầu vào hoặc không: ví dụ, len() cần đầu vào là một chuỗi/list/tuple, print() có thể nhận thông tin đầu vào (là bất kỳ dữ liệu gì) hoặc không cần thông tin gì (khi đó nó in ra một dòng trống).
    • Thông tin đầu vào có thể phải đáp ứng yêu cầu về kiểu: ví dụ, để dùng len() thì đầu vào phải là 1 kiểu tập hợp (như list, tuple, str) chứ không thể, ví dụ, là một số. Hàm print() lại có thể chấp nhận bất kỳ thông tin gì.

    Python cho phép bạn tự xây dựng hàm riêng và sau đó có thể sử dụng chúng giống như các hàm xây dựng sẵn. Các hàm do bạn tự xây dựng về bản chất không có gì khác so với các hàm xây dựng sẵn.

    Xây dựng và sử dụng hàm trong Python

    Hãy xem ví dụ sau đây:

    def fact(n):
    	'''Calculate the factorial of an input number
    	Params: 
    	n (int) : positive number
    	Returns: 
    	int: value n!
    	'''
    	p = 1
    	for i in range(1, n+1):
    		p *= i
    	return p
    print('3! = ', fact(3))
    print('4! = ', fact(4))
    print('5! = ', fact(5))

    Trong ví dụ trên chúng ta đã định nghĩa hàm fact() (tính giai thừa của một số nguyên) và sử dụng hàm này để tính 3!, 4! và 5!. Toàn bộ khối def fact(n): cho đến hết return p là phần định nghĩa của hàm fact(). fact(3), fact(4), fact(5) là những lời gọi hàm.

    Hàm được định nghĩa bằng từ khóa def. Lệnh def cũng là một lệnh phức hợp với 1 clause với cấu trúc như sau:

    Theo sau từ khóa def là tên hàm và cặp dấu (). Những gì đặt ở giữa cặp dấu () được gọi là tham số (hình thức) của hàm. Nếu có nhiều tham số thì chúng viết tách nhau bởi dấu phẩy. Trong hàm fact() chỉ có 1 tham số n.

    Tên hàm trong Python phải tuân theo quy tắc đặt định danh. Ngoài ra tên hàm thường đặt theo quy ước “underscore notation” giống như tên biến: tên viết chữ thường, nếu có nhiều từ thì viết tách nhau bởi dấu gạch chân.

    Lưu ý tham số trong Python không cần chỉ định kiểu dữ liệu.

    Từ khóa def, tên hàm và danh sách tham số tạo thành header của lệnh.

    Ngay sau header là một/một số dòng văn bản gọi là docstring. Docstring không bắt buộc và có tác dụng cung cấp tài liệu hỗ trợ cho việc sử dụng hàm. Chúng ta sẽ quay lại với docstring ở phần sau của bài học.

    Sau docstring là code của thân hàm. Trong code thân hàm bạn có thể sử dụng biến từ tham số (biến n trong ví dụ trên).

    Nếu hàm trả lại kết quả cho nơi gọi, trong hàm phải có lệnh return <giá trị>. Nếu hàm không trả lại kết quả, bạn không cần dùng return.

    Phần định nghĩa của hàm sẽ không có giá trị gì nếu chúng ta không sử dụng hàm đó trong code. Việc sử dụng hàm (gọi hàm) tự xây dựng không có gì khác biệt với các hàm xây dựng sẵn:

    print('3! = ', fact(3))
    print('4! = ', fact(4))
    print('5! = ', fact(5))

    Để sử dụng hàm, chúng ta viết tên hàm và viết các giá trị tham số trong ngoặc ().

    Một trong những vấn đề quan trọng hàng đầu khi xây dựng và sử dụng hàm là tham số. Tham số cho hàm trong Python bao gồm tham số bắt buộc, tham số mặc định và tham số biến động.

    Docstring cho hàm

    Docstring là khái niệm riêng trong Python. Docstring là chuỗi ký tự nằm ngay sau header của hàm và đóng vai trò tài liệu hướng dẫn cho hàm.

    Docstring được sử dụng cho hàm, class, module và package.

    Hãy xem lại ví dụ về hàm tính giai thừa:

    def fact(n):
    	'''	Calculate the factorial of an input number
    	Params: 
    	n (int) : positive number
    	Returns: 
    	int: value n!
    	'''
    	p = 1
    	for i in range(1, n+1):
    		p *= i
    	return p

    Một số trình biên tập code hoặc IDE có khả năng đọc docstring để cung cấp hỗ trợ. Ví dụ trong Pycharm:

    Visual Studio cũng có khả năng tương tự.

    Nếu hàm (và các đơn vị code khác như class, module, package) được viết docstring đầy đủ, người khác sử dụng code của bạn sẽ dễ dàng hơn. Bản thân bạn cũng dễ dàng hơn khi sử dụng hàm do mình viết.

    Nếu người khác sử dụng hàm của bạn viết ở chế độ tương tác có thể sử dụng hàm help như sau:

    >>> help(fact)
    Help on function fact in module __main__:
    fact(n)
        Calculate the factorial of an input number
        
        Params: 
        n (int) : positive number
        
        Returns: 
        int: value n!
    >>> 

    Từ đây người dùng có thể dễ dàng hiểu được hàm bạn viết.

    Ngoài ra docstring cũng có thể được sử dụng qua thuộc tính __doc__ mà bất kỳ hàm/class nào đều có:

    >>> print(fact.__doc__)
    	Calculate the factorial of an input number
    	Params: 
    	n (int) : positive number
    	Returns: 
    	int: value n!
    	
    >>>

    Mặc dù không có quy định nào về cách viết docstring, đa số sử dụng quy ước định dạng như sau:

    def some_function(argument1):
        """Summary or Description of the Function
        Parameters:
        argument1 (int): Description of arg1
        Returns:
        int:Returning value
       """

    Biến cục bộ và phạm vi của biến

    Trong một file mã nguồn bạn thường viết nhiều hàm. Trong file code bạn cũng đồng thời có những mã không nằm trong thân hàm nào.

    Ngoài thân hàm bạn có thể khai báo và sử dụng biến như đã học. Trong khối code thân của hàm (còn gọi là suite) bạn có thể khai báo các biến mới để sử dụng.

    Từ đây phát sinh vấn đề:

    • Một biến được khai báo ngoài hàm thì trong thân hàm có thể sử dụng lại biến đó không?
    • Một biến khai báo trong thân hàm thì ngoài hàm có thể sử dụng được biến đó không?
    • Điều gì xảy ra nếu cả trong thân hàm và ngoài thân hàm khai báo cùng một biến?
    • Một biến khai báo trong thân hàm này có thể sử dụng trong thân hàm khác không (các hàm nằm trong cùng một file code)?

    Các vấn đề trên liên quan đến phạm vi tác dụng (scope) của biến trong file code. Phạm vi tác dụng của biến là những nơi (trong file code) bạn có thể sử dụng được biến đó.

    Trước hết hãy cùng xem ví dụ sau:

    msg = 'Hello world!'
    name = 'Donald'
    def func1():
    	print('--- func 1 ---')
    	name = 'Obama'
    	print(msg, name)
    	greeting = 'Welcome to hell!'
    	print(greeting)
    	
    def func2():
    	print('--- func 2 ---')
    	global msg
    	print(msg, name)
    	msg = 'Hello again!'
    	print(msg)
    	greeting = 'Welcome to heaven!'	
    	print(greeting)	
    def func3():
    	print('--- func 3 ---')
    	print(msg, name)
    func1()
    func2()
    func3()

    Biến trong Python thuộc về một trong hai phạm vi cơ bản: biến cục bộ và biến toàn cục. Biến toàn cục được khai báo bên ngoài hàm, trực tiếp trong file code. Biến cục bộ được khai báo bên trong hàm.

    Trong ví dụ trên, msgname khai báo đầu tiên là hai biến toàn cục. Trong hàm func1 có biến cục bộ greeting, trong func2 cũng khai báo một biến cục bộ cùng tên greeting.

    Biến cục bộ khai báo trong hàm nào chỉ có thể sử dụng bên trong hàm đó. Hàm khác không nhìn thấy nó. Ngoài phạm vi của hàm, biến cục bộ không còn tồn tại. Biến greeting trong func2 và func1 là hoàn toàn khác biệt, dù cùng tên. Trong func3 không biết gì về greeting.

    Biến toàn cục có thể được sử dụng ở bất kỳ chỗ nào trong file code sau vị trí nó được khai báo, kể cả trong hàm. Như trong hàm func3 sử dụng biến toàn cục msgname, trong func1 sử dụng biến toàn cục msg, trong func2 sử dụng biến toàn cục msgname.

    Trong func1 có điểm đặc biệt: lệnh name = 'Obama' không hề tác động lên biến name toàn cục. Nó là một lệnh khai báo biến cục bộ name. Khi gặp phép gán trong thân hàm, Python sẽ coi đây là lệnh tạo biến cục bộ, ngay cả khi có một biến toàn cục trùng tên.

    Khi có lệnh tạo biến cục bộ trùng tên với biến toàn cục thì biến cục bộ sẽ che biến toàn cục. Tức là trong hàm đó Python sẽ chỉ sử dụng biến cục bộ. Như vậy trong func1, biến cục bộ name sẽ che đi biến toàn cục cùng tên. Biến msg vẫn là biến cục bộ (giá trị ‘Hello world’). Như vậy kết quả in ra sẽ là ‘Hello world! Obama’ chứ không phải ‘Hello world! Donald’.

    Trong func2 bạn gặp cách dùng đặc biệt: global msg. Lệnh này báo hiệu rằng sẽ sử dụng biến msg toàn cục. Khi đó, lệnh gán msg = 'Hello again!' không tạo ra biến cục bộ msg mới. Thay vào đó lệnh này tác động lên biến msg toàn cục. Như vậy, lệnh gán msg = 'Hello again!' tác động lên biến msg toàn cục.

    Do func2 tác động lên biến toàn cục msg nên trong lời gọi func3 sẽ in ra ‘Hello again! Donald’ (biến name toàn cục vẫn giữ nguyên giá trị từ đầu). Lệnh gán name = 'Obama' trong func1 sinh ra một biến cục bộ cùng tên name chứ không tác động lên biến toàn cục name.

    Đệ quy trong Python

    Đệ quy là hiện tượng một hàm gọi lại chính nó. Python cũng cho phép tình trạng này.

    Hãy xem lại ví dụ về tính giai thừa. Giai thừa của số nguyên dương n được định nghĩa như sau:

    • Nếu n = 1 thì n! = 1;
    • Nếu n > 1 thì n! = n * (n-1)!

    Đây là loại định nghĩa đệ quy. Định nghĩa đệ quy là loại định nghĩa qua chính nó. Như trên người ta định nghĩa n! thông qua (n-1)!. Đặc thù của định nghĩa đệ quy là phải có một điều kiện dừng (n = 1 thì n! = 1).

    Với định nghĩa giai thừa như trên chúng ta có thể viết lại hàm fact theo cách khác như sau:

    def fact(n):
    	if n == 1: return 1
    	else: return n * fact(n - 1)

    Hàm fact giờ được viết lại theo đúng định nghĩa giai thừa kiểu đệ quy: trong thân hàm fact có lời gọi đến chính hàm fact với tham số n – 1.

    Mặc dù cách viết đệ quy thường ngắn gọn, việc thực thi chương trình dạng đệ quy tốn bộ nhớ hơn.

    Kết luận

    Trong bài học này bạn đã học chi tiết về cách xây dựng và sử dụng hàm trong Python.

    Có thể thấy, việc xây dựng hàm trong Python tương đối đơn giản so với các ngôn ngữ khác. Python hỗ trợ nhiều cách khác nhau để truyền và sử dụng tham số. Python cũng có đặc điểm khác biệt về docstring để tạo documentation.

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Theo dõi
    Thông báo của
    guest

    1 Thảo luận
    Cũ nhất
    Mới nhất
    Phản hồi nội tuyến
    Xem tất cả bình luận
    Tuấn

    Chỗ “Biến msg vẫn là biến cục bộ (giá trị ‘Hello world’) ” phải là biến toàn cục chứ bạn?