Lộ trình học Python từ con số 0 (Phần 2)

Trong phần trước, tôi đã giới thiệu những vấn đề cơ bản trong Python. Ở phần này, tối sẽ giúp bạn tìm hiểu sâu hơn.

Classes & Objects

Đầu tiên, ta tìm hiểu sơ qua về lý thuyết.

Objects (đối tượng) là các thực thể trong thực tế ta muốn nhắc tới như xe, chó, gà, …. Các object có 2 đặc trưng cơ bản: dữ liệu và hoạt động.

Ví dụ, xe sẽ có các dữ liệu về số bánh xe, số của sổ, số chỗ ngồi,… Chúng là tổ hợp để thực hiện các hoạt động của xe như đi, dừng, nâng lên, hay hiển thị số nhiên liệu còn lại.

Chúng nhận diện các dữ liệu như các thuộc tính và hành động thì như các hàm thực thi trong lập trình hướng đối tượng.

Mỗi Class là một chương trình, trong đó có chứa các object đơn lẻ. Trên thực tế, ta thường tạo ra những object cùng loại với nhau. Ví dụ, xe hơi sẽ có những yếu tố: động cơ, bánh xe, cửa… và mỗi loại xe sẽ được xây dựng trên cùng một bộ chương trình, cùng các thành phần.

Lập trình hướng đối tượng trong Python

Có 02 phần chính trong mỗi chương trình lập trình hướng đối tượng trong Python: class & object.

Mỗi class là một chương trình, một mô hình với các object trong class đó.

Nói chung, class là một mô hình hoặc cách để biểu diễn các thuộc tính và các hàm thực thi. Ví dụ, ta xây dựng một class là Vehicle (phương tiện di chuyển). Với mỗi bộ thuộc tính riêng, ta sẽ xác định được loại xe riêng. Số lượng các bánh xe, loại bồn chứa, sức chứa chỗ ngồi và vận tốc maximum đều là các thuộc tính của 1 phương tiện di chuyển.

Từ đây, ta xây dựng cấu trúc chương trình trên Python một Class Vehicle với các thành phần đơn giản như sau:

class Vehicle:
    pass

Objects là các instances (trường hợp) của 1 class. Chúng ta tạo 1 instance bằng cách đặt tên cho class.

car = Vehicle()
print(car) # <__main__.Vehicle instance at 0x7fb1de6c2638>

Ở đây, car là 1 object (hoặc instance) của class Vehicle.

Lưu ý rằng, class Vehicle có 4 thuộc tính: số lượng bánh xe, loại bồn chứa, sức chứa chỗ ngồi và vận tốc tối đa. Chúng ta sử dụng tất cả những thuộc tính này khi xây dựng một object. Vậy ta sẽ xây dựng hàm xác định cho class như sau:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

Phương thức init được gọi là một constructor. Ở đây khi tạo một object mới, ta sẽ xác định được các thuộc tính của object đó. Hãy tưởng tượng rằng bạn rất yêu thích loại Tesla Model S, và muốn tạo loại object này. Nó sẽ có 4 bánh xe, chạy năng lượng điện, gồm 5 ghế và vận tốc tối đa là 250km/giờ(155 mph). Tạo object như sau:

tesla_model_s = Vehicle(4, 'electric', 5, 250)

Bốn bánh + “loại bồn chứa” bằng điện + 5 ghế + vận tốc tối đa 250km/ giờ.

Tất cả những thuộc tính đều được đưa vào như vậy. Nhưng làm thế nào ta có thể truy cập vào giá trị những thuộc tính này? Ta sẽ xây dựng một hàm để gọi giá trị ra.

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

    def number_of_wheels(self):
        return self.number_of_wheels

    def set_number_of_wheels(self, number):
        self.number_of_wheels = number

Chúng ta tạo 02 hàm, đó là number_of_wheels  và set_number_of_wheels. Chúng ta gọi nó là getter & setter. getter lấy giá trị thuộc tính và setter thiết lập 1 giá trị mới cho thuộc tính đó.

Trong Python, thì sử dụng @property (decorators) để xác định getters và  setters.

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity
 
    @property
    def number_of_wheels(self):
        return self.number_of_wheels
 
    @number_of_wheels.setter
    def number_of_wheels(self, number):
        self.number_of_wheels = number

Và sử dụng các hàm này như những thuộc tính:

tesla_model_s = Vehicle(4, 'electric', 5, 250)
print(tesla_model_s.number_of_wheels) # 4
tesla_model_s.number_of_wheels = 2 # setting number of wheels to 2
print(tesla_model_s.number_of_wheels) # 2

Điều này hơi khác với hàm xác định. Các hàm hoạt động như một thuộc tính. Ví dụ, khi set số bánh xe mới, chúng ta không áp số 2 vào thành 1 tham số mà set giá trị 2 vào number_of_wheels. Đây là 1 cách để viết code pythonic getter và setter.

Ngoài ra, ta còn có thể sử dụng hàm cho những mục đích khác. Chẳng hạn, hàm make_noise:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

    def make_noise(self):
        print('VRUUUUUUUM')

Khi chúng ta gọi hàm ra, biểu thức kết quả sẽ hiển thị: VRRRRUUUUM.

tesla_model_s = Vehicle(4, 'electric', 5, 250)
tesla_model_s.make_noise() # VRUUUUUUUM

Encapsulation: Ẩn thông tin

Encapsulation (đóng gói) là một cơ chế nhằm hạn chế quyền truy cập vào trạng thái bên trong đối tượng.Nhưng đồng thời, nó hỗ trợ thực hiện trên data đó (cả object và hàm thực hiện).

“Encapsulation can be used to hide data members and members function. Under this definition, encapsulation means that the internal representation of an object is generally hidden from view outside of the object’s definition.” — Wikipedia  

Điều này ngăn việc dữ liệu bị sửa đổi trực tiếp. Tất cả hiển thị nội bộ của object sẽ được ẩn đi. Chỉ có object đó mới tương tác được với data nội bộ của nó.

Đầu tiên, ta cùng tìm hiểu value và method của 2 trường hợp public và non-public.

Value trong trường hợp Public

Với một Class trong Python, chúng ta có thể tạo ra một public instance variable bằng một constructor.

  class Person:
    def __init__(self, first_name):
        self.first_name = first_name

Ở đây, áp dụng value first_name như 1 đối số đến public instance variable.

tk = Person('TK')
print(tk.first_name) # => TK

Nhưng với Class:

class Person:
    first_name = 'TK'

Ta không cần áp dụng first_name như 1 đối số, và tất cả các objects sẽ có 1  class attribute được khởi tạo với TK.

tk = Person()
print(tk.first_name) # => TK

Hiện chúng ta đã biết sử dụng public instance variables và thuộc tính class . Điều thú vị khác nữa về phần public là có thể quản lý giá trị của biến, nghĩa là object có thể quản lý giá trị biến số của nó: các giá trị biến số Get và Set.

Nhắc đến Person, nếu muốn set giá trị khác cho biến first_name:

tk = Person('TK')
tk.first_name = 'Kaio'
print(tk.first_name) # => Kaio

Chúng ta vừa gắn giá trị mới cho trường first_name và cập nhật lại giá trị. Vì đây là Public nên ta có thể làm được điều đó.

Value trong trường hợp Non-Public

We don’t use the term “private” here, since no attribute is really private in Python (without a generally unnecessary amount of work). — PEP 8

Giống như trường hợp trên: public instance variable , chúng ta có thể xác địnhnon-public instance variable trong hàm constructor hoặc trong class. Điểm khác biệt trong cú pháp chính là: đối với non-public instance variables, sẽ sử dụng gạch dưới (_) trước tên variable.

“‘Private’ instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member)” — Python Software Foundation

Ví dụ:

class Person:
    def __init__(self, first_name, email):
        self.first_name = first_name
        self._email = email

Bạn có thấy biến email không? Đây là cách chúng ta xác định 1 non-public variable

tk = Person('TK', 'tk@mail.com')
print(tk._email) # tk@mail.com

“Chúng ta có thể access & cập nhật nó. Non-public variables chỉ là 1 Quy ước và nên được xem như phần non-public của API”

Vì vậy, sử dụng một hàm cho phép xác định trong class. Ta cùng thực hiện hai hàm (email và update_email) để hiểu rõ hơn:

class Person:
    def __init__(self, first_name, email):
        self.first_name = first_name
        self._email = email
 
    def update_email(self, new_email):
        self._email = new_email
 
    def email(self):
        return self._email

Bây giờ chúng ta có thể cập nhập & truy cập non-public variables bằng những methods đó. Cùng xem:

tk = Person('TK', 'tk@mail.com')
print(tk.email()) # => tk@mail.com
tk._email = 'new_tk@mail.com'
print(tk.email()) # => tk@mail.com
tk.update_email('new_tk@mail.com')
print(tk.email()) # => new_tk@mail.com
  1. Khởi tạo 1 object mới bằng first_name TK và email tk@mail.com
  2. Printt email bằng cách tiếp cận non-public variable với 1 method
  3. Cố gắng set email mới ngoài class
  4. Cần phải xem non-public variable như phần non-public của API
  5. Cập nhật non-public variable bằng method instance
  6. Chúng ta có thể cập nhật nó trong class bằng method helper

Public Method

Với public methods, chúng ta cũng có thể sử dụng ngoài class:

class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age
 
    def show_age(self):
        return self._age

Kiểm tra lại:

tk = Person('TK', 25)
print(tk.show_age()) # => 25

Thật tuyệt, vậy là không có vấn đề gì.

Non-public Method

Nhưng với non-public methods thì không làm được điều này. Hãy biểu diễn cùng class Person nhưng bây giờ với 1 show_age non-public method bằng 1 dấu gạch dưới (_).

class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age
 
    def _show_age(self):
        return self._age

Giờ chúng ta sẽ thử gọi non-public method này với object của mình:

tk = Person('TK', 25)
print(tk._show_age()) # => 25

“Chúng ta có thể access & Update nó. Non-public methods chỉ là 1 quy ước & nên được xem như 1 phần non-public của API”

Dưới đây là ví dụ về cách sử dụng non-public methods:

 class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age
 
    def show_age(self):
        return self._get_age()
 
    def _get_age(self):
        return self._age
 
tk = Person('TK', 25)
print(tk.show_age()) # => 25

Chúng ta có 1 _get_agenon-public method và 1 show_agepublic method. Object của chúng ta có thể sử dụng  show_age (bên ngoài class) và _get_age chỉ được sử dụng bên trong class definition (trong method show_age). Nhưng 1 lần nữa, đây chỉ là vấn đề liên quan đến quy ước.

Kết Encapsulation 

Với encapsulation, có thể đảm bảo rằng hiển thị nội bộ của object được ẩn.

Inheritance: các hành vi và các đặc tính

Một vài objects nào đó sẽ sở hữu vài điểm chung: behavior & characterists của chúng.

Trong lập trình hướng đối tượng, các class có thể thừa hưởng những đặc tính (data) và hành vi (methods) tương tự từ class khác.

Cùng xem 1 ví dụ khác và implement nó bằng Python.

Hãy tưởng tượng 1 chiếc xe hơi. Số lượng bánh xe, sức chứa chỗ ngồi và vận tốc tối đa là tất cả các attributes của 1 chiếc xe. Có thể nói rằng 1 classElectricCar thừa hưởng cùng các attributes từ class Car thông dụng.

Class Car đã implement như sau:

class Car:
    def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

Một khi đã khởi tạo, chúng ta có thể sử dụng tất cả instance variables đã được tạo ra. Tốt.

Trong Python, hãy áp dụng parent class vào child class như 1 tham số. Một class ElectricCar có thể kế thừa từ class Car.

class ElectricCar(Car):
    def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
        Car.__init__(self, number_of_wheels, seating_capacity, maximum_velocity)

Đơn giản vậy thôi, chúng ta không cần phải implement bất kì method nào khác, vì class nà đã có rồi (được thừa hưởng từ class Car).

my_electric_car = ElectricCar(4, 5, 250)
print(my_electric_car.number_of_wheels) # => 4
print(my_electric_car.seating_capacity) # => 5
print(my_electric_car.maximum_velocity) # => 250

Tổng quan

Như vậy, bài viết này đã giúp chúng ta nắm được những kiến thức Python cơ bản:

  • Cách hoạt động của các biến Python
  • Cách hoạt động của conditional statements Python
  • Cách looping Python (while & for) hoạt động
  • Cách sử dụng Lists: Collection | Array
  • Dictionary Key-Value Collection
  • Cách lặp thông qua data structures
  • Objects & Classes
  • Các attibutes như data của objects
  • Methods như hành vi của các objects
  • Sử dụng getters và setters của Python & property decorator
  • Encapsulation: ẩn thông tin
  • Inheritance: behaviors (hành vi) và characteristics (đặc tính)

Nếu bạn muốn có thêm thông tin, bạn có thể tham khảo One Month Python Bootcamp.

Hi vọng bài viết có thể giúp ích cho bạn.

Hãy theo dõi https://trituenhantao.io/ để có thêm nhiều thông tin mới nhé. Ngoài ra, các bạn có thể đăng ký nhận ebook về python miễn phí tại đây.

Bạn muốn trích dẫn bài này:
-----
"Lộ trình học Python từ con số 0 (Phần 2)," Trí tuệ nhân tạo, Ngày xuất bản: 14/05/2019, URL: https://trituenhantao.io/lap-trinh/lo-trinh-hoc-python-tu-con-so-0-phan-2/, Ngày truy cập: 18/04/2024.