Bài 11: Functions - Hàm (Phần 1)

Mục Tiêu Bài Học

Sau khi hoàn thành bài này, bạn sẽ:

  • ✅ Hiểu function là gì và tại sao cần dùng
  • ✅ Định nghĩa functions với def
  • ✅ Làm việc với parameters và arguments
  • ✅ Sử dụng return values
  • ✅ Áp dụng default và keyword arguments

Function Là Gì?

Function là khối code có thể tái sử dụng, thực hiện một nhiệm vụ cụ thể.

Tại sao dùng functions?

  • Reusability - Viết một lần, dùng nhiều lần
  • Organization - Chia code thành các phần nhỏ
  • Maintainability - Dễ sửa và update
  • Testing - Dễ test từng phần
  • Readability - Code dễ đọc hơn
# Không dùng function - lặp lại codeprint("=" * 40)print("Welcome")print("=" * 40) # Code khác... print("=" * 40)print("Goodbye")print("=" * 40) # Dùng function - reusabledef print_header(text):    print("=" * 40)    print(text)    print("=" * 40) print_header("Welcome")# Code khác...print_header("Goodbye")

Định Nghĩa Function

Cú Pháp Cơ Bản

# Syntax:# def function_name(parameters):#     """docstring"""#     code block#     return value # Simple function (no parameters, no return)def greet():    print("Hello, World!") # Call functiongreet()  # Hello, World! # Function với parameterdef greet_person(name):    print(f"Hello, {name}!") greet_person("Alice")  # Hello, Alice!greet_person("Bob")    # Hello, Bob! # Function với returndef add(a, b):    result = a + b    return result sum_result = add(5, 3)print(sum_result)  # 8

Naming Convention

# ✅ Good - snake_casedef calculate_total():    pass def get_user_name():    pass def is_valid():    pass # ❌ Bad - không theo conventiondef CalculateTotal():  # PascalCase for classes    pass def getUserName():     # camelCase for JavaScript    pass

Parameters và Arguments

Parameter là biến trong function definition.
Argument là giá trị truyền vào khi call function.

def greet(name):  # 'name' là parameter    print(f"Hello, {name}!") greet("Alice")  # "Alice" là argument

Positional Arguments

Positional arguments phải theo đúng thứ tự.

def describe_person(name, age, city):    print(f"{name} is {age} years old and lives in {city}") # Đúng thứ tựdescribe_person("Alice", 25, "Hanoi")# Alice is 25 years old and lives in Hanoi # Sai thứ tự - lỗi logicdescribe_person(25, "Alice", "Hanoi")# 25 is Alice years old and lives in Hanoi (Sai!)

Keyword Arguments

Keyword arguments chỉ định tên parameter.

def describe_person(name, age, city):    print(f"{name} is {age} years old and lives in {city}") # Keyword arguments - không cần đúng thứ tựdescribe_person(age=25, name="Alice", city="Hanoi")describe_person(city="HCMC", name="Bob", age=30) # Mix positional và keyworddescribe_person("Charlie", age=35, city="Danang") # ❌ Keyword trước positional - SyntaxError# describe_person(name="Alice", 25, "Hanoi")

Default Arguments

Default arguments có giá trị mặc định.

def greet(name, greeting="Hello"):    print(f"{greeting}, {name}!") # Dùng defaultgreet("Alice")  # Hello, Alice! # Override defaultgreet("Bob", "Hi")  # Hi, Bob!greet("Charlie", greeting="Hey")  # Hey, Charlie! # Multiple defaultsdef create_user(username, email, role="user", active=True):    return {        "username": username,        "email": email,        "role": role,        "active": active    } user1 = create_user("alice", "[email protected]")print(user1)  # {..., "role": "user", "active": True} user2 = create_user("bob", "[email protected]", role="admin")print(user2)  # {..., "role": "admin", "active": True}

⚠️ Mutable Default Arguments:

# ❌ NGUY HIỂM - Mutable defaultdef add_item(item, items=[]):  # BAD!    items.append(item)    return items list1 = add_item("apple")list2 = add_item("banana")  # Cùng list với list1!print(list1)  # ['apple', 'banana']print(list2)  # ['apple', 'banana'] - Unexpected! # ✅ ĐÚNG - Dùng Nonedef add_item(item, items=None):    if items is None:        items = []    items.append(item)    return items list1 = add_item("apple")list2 = add_item("banana")print(list1)  # ['apple']print(list2)  # ['banana'] - Correct!

Return Values

return trả về giá trị từ function.

Return Cơ Bản

# Return một valuedef square(x):    return x ** 2 result = square(5)print(result)  # 25 # Return nhiều values (tuple)def get_user():    name = "Alice"    age = 25    city = "Hanoi"    return name, age, city name, age, city = get_user()print(name, age, city)  # Alice 25 Hanoi # Không có return - trả về Nonedef greet(name):    print(f"Hello, {name}!") result = greet("Alice")  # Hello, Alice!print(result)  # None

Early Return

# Early return với guard clausedef divide(a, b):    if b == 0:        return None  # Early return    return a / b print(divide(10, 2))  # 5.0print(divide(10, 0))  # None # Validate inputdef process_age(age):    if age < 0:        return "Invalid age"    if age < 18:        return "Minor"    if age < 60:        return "Adult"    return "Senior" print(process_age(-5))   # Invalid ageprint(process_age(15))   # Minorprint(process_age(30))   # Adultprint(process_age(65))   # Senior

Return Complex Objects

# Return listdef get_evens(numbers):    return [n for n in numbers if n % 2 == 0] evens = get_evens([1, 2, 3, 4, 5, 6])print(evens)  # [2, 4, 6] # Return dictdef create_person(name, age, city):    return {        "name": name,        "age": age,        "city": city,        "id": hash(name)    } person = create_person("Alice", 25, "Hanoi")print(person) # Return function (advanced)def multiplier(factor):    def multiply(x):        return x * factor    return multiply double = multiplier(2)triple = multiplier(3) print(double(5))  # 10print(triple(5))  # 15

Docstrings - Function Documentation

Docstring là documentation cho function.

# Single-line docstringdef greet(name):    """Greet a person by name."""    print(f"Hello, {name}!") # Multi-line docstringdef calculate_area(width, height):    """    Calculate the area of a rectangle.        Parameters:        width (float): The width of the rectangle        height (float): The height of the rectangle        Returns:        float: The area of the rectangle    """    return width * height # Access docstringprint(greet.__doc__)print(calculate_area.__doc__) # help() functionhelp(calculate_area)

Google Style Docstring:

def process_data(data, threshold=0.5):    """    Process data with threshold filtering.        Args:        data (list): List of numbers to process        threshold (float, optional): Minimum value threshold. Defaults to 0.5.        Returns:        list: Filtered and processed data        Raises:        ValueError: If data is empty        Example:        >>> process_data([0.3, 0.7, 0.9], threshold=0.5)        [0.7, 0.9]    """    if not data:        raise ValueError("Data cannot be empty")    return [x for x in data if x >= threshold]

Ví Dụ Thực Tế

1. Calculator Functions

def add(a, b):    """Add two numbers."""    return a + b def subtract(a, b):    """Subtract b from a."""    return a - b def multiply(a, b):    """Multiply two numbers."""    return a * b def divide(a, b):    """    Divide a by b.        Returns None if b is zero.    """    if b == 0:        return None    return a / b # Use functionsprint(f"10 + 5 = {add(10, 5)}")print(f"10 - 5 = {subtract(10, 5)}")print(f"10 * 5 = {multiply(10, 5)}")print(f"10 / 5 = {divide(10, 5)}")print(f"10 / 0 = {divide(10, 0)}")

2. Temperature Converter

def celsius_to_fahrenheit(celsius):    """Convert Celsius to Fahrenheit."""    return (celsius * 9/5) + 32 def fahrenheit_to_celsius(fahrenheit):    """Convert Fahrenheit to Celsius."""    return (fahrenheit - 32) * 5/9 def celsius_to_kelvin(celsius):    """Convert Celsius to Kelvin."""    return celsius + 273.15 def kelvin_to_celsius(kelvin):    """Convert Kelvin to Celsius."""    return kelvin - 273.15 # Testtemp_c = 25print(f"{temp_c}°C = {celsius_to_fahrenheit(temp_c):.1f}°F")print(f"{temp_c}°C = {celsius_to_kelvin(temp_c):.1f}K") temp_f = 77print(f"{temp_f}°F = {fahrenheit_to_celsius(temp_f):.1f}°C")

3. String Utilities

def is_palindrome(text):    """    Check if text is a palindrome.        Args:        text (str): Text to check        Returns:        bool: True if palindrome, False otherwise    """    text = text.lower().replace(" ", "")    return text == text[::-1] def count_words(text):    """Count words in text."""    return len(text.split()) def capitalize_words(text):    """Capitalize first letter of each word."""    return " ".join(word.capitalize() for word in text.split()) def reverse_words(text):    """Reverse order of words."""    return " ".join(reversed(text.split())) # Testprint(is_palindrome("radar"))  # Trueprint(is_palindrome("hello"))  # Falseprint(count_words("Hello world from Python"))  # 4print(capitalize_words("hello world"))  # Hello Worldprint(reverse_words("Hello World"))  # World Hello

4. Validation Functions

def is_valid_email(email):    """    Basic email validation.        Args:        email (str): Email to validate        Returns:        bool: True if valid format    """    if "@" not in email:        return False    if email.count("@") != 1:        return False    if "." not in email.split("@")[1]:        return False    return True def is_valid_phone(phone):    """    Validate phone number (10 digits).        Args:        phone (str): Phone number        Returns:        bool: True if valid    """    phone = phone.replace("-", "").replace(" ", "")    return phone.isdigit() and len(phone) == 10 def is_strong_password(password):    """    Check password strength.        Requirements:    - At least 8 characters    - Contains uppercase    - Contains lowercase    - Contains digit        Args:        password (str): Password to check        Returns:        tuple: (bool, str) - (is_valid, message)    """    if len(password) < 8:        return False, "Too short (min 8 chars)"        has_upper = any(c.isupper() for c in password)    has_lower = any(c.islower() for c in password)    has_digit = any(c.isdigit() for c in password)        if not has_upper:        return False, "Need uppercase"    if not has_lower:        return False, "Need lowercase"    if not has_digit:        return False, "Need digit"        return True, "Strong password" # Testprint(is_valid_email("[email protected]"))  # Trueprint(is_valid_email("invalid.email"))     # False print(is_valid_phone("0123456789"))   # Trueprint(is_valid_phone("012-345-6789")) # True valid, msg = is_strong_password("Weak1")print(f"{valid}: {msg}")valid, msg = is_strong_password("StrongPass123")print(f"{valid}: {msg}")

5. List Operations

def find_max(numbers):    """Find maximum value in list."""    if not numbers:        return None        max_val = numbers[0]    for num in numbers:        if num > max_val:            max_val = num    return max_val def find_min(numbers):    """Find minimum value in list."""    if not numbers:        return None        min_val = numbers[0]    for num in numbers:        if num < min_val:            min_val = num    return min_val def calculate_average(numbers):    """Calculate average of numbers."""    if not numbers:        return None    return sum(numbers) / len(numbers) def remove_duplicates(items):    """Remove duplicates while preserving order."""    seen = set()    result = []    for item in items:        if item not in seen:            seen.add(item)            result.append(item)    return result def chunk_list(items, size):    """    Split list into chunks of given size.        Args:        items (list): List to chunk        size (int): Chunk size        Returns:        list: List of chunks    """    chunks = []    for i in range(0, len(items), size):        chunks.append(items[i:i + size])    return chunks # Testnumbers = [3, 7, 1, 9, 2, 8, 5]print(f"Max: {find_max(numbers)}")print(f"Min: {find_min(numbers)}")print(f"Average: {calculate_average(numbers):.2f}") items = [1, 2, 2, 3, 4, 4, 5]print(f"Unique: {remove_duplicates(items)}") data = [1, 2, 3, 4, 5, 6, 7, 8, 9]print(f"Chunks: {chunk_list(data, 3)}")

Best Practices

# 1. Function nên làm một việc# ❌ Làm quá nhiều việcdef process_user(name, email):    # Validate    if "@" not in email:        return False    # Save to database    save_to_db(name, email)    # Send email    send_welcome_email(email)    # Log activity    log_activity(name) # ✅ Chia thành nhiều functionsdef validate_email(email):    return "@" in email def create_user(name, email):    if not validate_email(email):        return False    save_to_db(name, email)    send_welcome_email(email)    log_activity(name)    return True # 2. Descriptive names# ❌ Uncleardef calc(x, y):    return x * y * 0.2 # ✅ Cleardef calculate_tax(price, quantity):    TAX_RATE = 0.2    return price * quantity * TAX_RATE # 3. Avoid side effects# ❌ Modifies globaltotal = 0def add_to_total(value):    global total    total += value # ✅ Pure functiondef add_to_total(total, value):    return total + value # 4. Keep it short# ✅ Max 20-30 lines per function

Bài Tập Thực Hành

Bài 1: Basic Functions

Viết functions:

  • greet(name, greeting="Hello") - Greet person
  • is_even(n) - Check if even
  • factorial(n) - Calculate factorial

Bài 2: String Functions

Viết functions:

  • count_vowels(text) - Count vowels
  • is_anagram(s1, s2) - Check if anagrams
  • remove_spaces(text) - Remove all spaces

Bài 3: Math Functions

Viết functions:

  • is_prime(n) - Check prime number
  • gcd(a, b) - Greatest common divisor
  • lcm(a, b) - Least common multiple

Bài 4: List Functions

Viết functions:

  • flatten(nested_list) - Flatten nested list
  • rotate_list(lst, k) - Rotate list k positions
  • find_pairs(numbers, target) - Find pairs summing to target

Bài 5: Grade Calculator

Viết function calculate_grade(scores):

  • Input: list of scores
  • Return: dict với average, grade (A-F), status (Pass/Fail)

Tóm Tắt

✅ Function: def name(parameters):
✅ Parameters: variables trong definition
✅ Arguments: values khi call function
✅ Return: trả về value với return
✅ Default arguments: giá trị mặc định
✅ Keyword arguments: chỉ định tên parameter
✅ Docstring: document function với """

Bài Tiếp Theo

Bài 11.2: Functions (Phần 2) - *args, **kwargs, lambda functions, scope, và recursion.


Remember:

  • Function names: snake_case
  • One function, one purpose
  • Use descriptive names
  • Document với docstrings
  • Avoid mutable default arguments
  • Keep functions short và focused!