Daily Archives: 27/01/2016

Python – Module

Trong phần này chúng ta sẽ tìm hiểu về module trong Python.

Module trong Python rất đơn giản, đó là các file chứa code python có đuôi mở rộng là .py. Dùng module là một cách tuyệt vời để quản lý code Python, ngoài module ra thì chúng ta còn có các cách sau:

  • Dùng Hàm
  • Dùng Lớp
  • Dùng Module
  • Dùng Package

Trong đó quy mô nhỏ nhất là hàm và lớn nhất là Package. Bạn có thể hình dung chúng ta viết hàm để chia nhỏ các công việc ra, sau đó gộp các hàm liên quan với nhau vào một lớp cụ thể, các lớp có liên quan với nhau lại được gộp vào một file làm một module, cứ thế chương trình lớn dần và chúng ta lại gộp các module thành một package. Chúng ta sẽ tìm hiểu về package ở bài sau.

Đặt tên module

Tên module là tên file có phần mở rộng là .py. Ví dụ như chúng ta có file empty.py thì empty là tên module. Mặc nhiên module này có một biến tên là __name__, biến này lưu trữ tên của module đang được sử dụng. Khi chúng ta viết một file Python rồi chạy thì biến thì tên module của file này được gọi là __main__.

Trong ví dụ dưới đây chúng ta có 2 file: empty.py và modulename.py. Khi chúng ta chạy từ file modulename.py thì file này được gọi là module main. Module main sử dụng module của file empty.py. Để sử dụng một module thì chúng ta dùng từ khóa import. 

"""
An empty module
"""

import empty
import sys

print (__name__)
print (empty.__name__)
print (sys.__name__)

Trong đoạn code trên chúng ta sử dụng 2 module, thứ nhất là module sys có sẵn trong Python và thứ hai là module empty do chúng ta tự tạo ra. Chúng ta in tên của các module này ra màn hình

__main__
empty
sys

Tên của module main sẽ luôn luôn là __main__. Các module khác sẽ là tên file.

Thư mục chứa module

Khi chúng ta import một module nào đó. Trình thông dịch sẽ tìm các module có sẵn trong Python. Nếu không thấy, nó sẽ tìm trong biến sys.path, biến này lưu trữ một list các chuỗi đường dẫn bao gồm đường dẫn đến thư mục hiện tại của file và đường dẫn đến thư mục cài đặt Python cùng với một số thư mục con bên trong. Nếu không tìm thấy module thì báo lỗi ImportError.


import sys
import textwrap

sp = sorted(sys.path)
dnames = ', '.join(sp)

print (textwrap.fill(dnames))

Đoạn code trên in ra tất cả các thư mục trong biến sys.path.

import textwrap

Module textwrap dùng để định dạng kiểu hiển thị.

sp = sorted(sys.path)

Dòng code trên lấy danh sách các đường dẫn thư mục và sắp xếp chúng.

dnames = ', '.join(sp)

Sau đó chúng ta nối chúng lại thành một chuỗi, ngăn cách bởi dấu phẩy.

C:\Users\PhoCode\, C:\Python, C:\Python\DLLs, C:\Python\lib,
C:\Python\lib\site-packages, C:\Python\python35.zip

Từ khóa import

Như chúng ta đã biết từ khóa này được dùng để tích hợp một module vào chương trình.

from module import *

Cú pháp trên có ý nghĩa là tích hợp tất cả những gì có trong module vào chương trình của chúng ta ngoại trừ các đối tượng có tên bắt đầu bằng dấu gạch dưới _. Những đối tượng này được sử dụng với tên của module chứ không thể được gọi riêng như những đối tượng khác.


from math import *

print (cos(3))
print (pi)

Đoạn code trên tích hợp tất cả các đối tượng có thể sử dụng trong module math vào chương trình. Chúng ta có thể gọi các hàm trong module này một cách trực tiếp chứ không cần phải thông qua tên module.

-0.9899924966
3.14159265359

Cách import kiểu này có thể làm rối loạn chương trình của chúng ta vì có thể có rất nhiều module trùng tên với nhau, do đó chúng sẽ bị ghi đè.


from math import  *

pi = 3.14

print (cos(3))
print (pi)

Đoạn code trên sẽ in ra con số 3.14. Vì trình thông dịch nghĩ rằng chúng ta muốn sử dụng biến pi do chúng ta tự gán chứ không phải biến pi có sẵn trong module math.

Ví dụ dưới đây kiểm nghiệm các hàm không được tích hợp vào chương trình bằng cách sử dụng kiểu import trên.

"""
names is a test module
"""

_version = 1.0

names = ["Paul", "Frank", "Jessica", "Thomas", "Katherine"]

def show_names():
   for i in names:
      print (i)

def _show_version():
   print (_version)
from names import *

print (locals())

show_names()

Biến _version và hàm _show_version() sẽ không được tích hợp vào module private vì tên của chúng có dấu gạch dưới ở đầu. Chúng ta dùng hàm locals() để in ra các module đã được tích hợp.

{'__builtins__': <module '__builtin__' (built-in)>, '__file__': './private.py', 
'show_names': <function show_names at 0xb7dd233c>, 
'names': ['Paul', 'Frank', 'Jessica', 'Thomas', 'Katherine'],
 '__name__': '__main__', '__doc__': None}
Paul
Frank
Jessica
Thomas
Katherine
from module import fun, var

Kiểu cú pháp trên chỉ tích hợp một số đối tượng được chỉ định vào module của chúng ta.

from math import sin, pi

print (sin(3))
print (pi)

Trong ví dụ trên chúng ta tích hợp 2 đối tượng sin và pi từ module math.


from names import _version, _show_version

print (_version)
_show_version()

Với kiểu cú pháp này chúng ta có thể tích hợp các module có tên có dấu gạch dưới ở đầu.

1.0
1.0
import module

Cú pháp cuối cùng này là cú pháp được sử dụng nhiều nhất. Bạn có thể dùng tất cả các đối tượng có trong module và để sử dụng thì bạn phải ghi tên module cùng với dấu chấm rồi mới đến tên đối tượng. Nhờ đó mà chương trình của bạn không bị rối tung lên.

import math

pi = 3.14

print (math.cos(3))
print (math.pi)
print (math.sin(3))
print (pi)

Trong ví dụ trên chúng ta import module math. Chúng ta có thể truy xuất được cả biến pi của chúng ta và biến pi trong module math.

-0.9899924966
3.14159265359
0.14112000806
3.14

import math as m

print (m.pi)
print (m.cos(3))

Ngoài ra khi import một module nào đó, bạn có thể muốn dùng một cái tên khác ngắn gọn hơn bằng cách thêm từ khóa as cùng với tên mà bạn muốn dùng. Tên này chỉ có hiệu lực trong file mà bạn đang viết.

3.14159265359
-0.9899924966

Nếu bạn import sai tên module thì lỗi ImportError sẽ nảy sinh.


try:
    import empty2
except ImportError, e:
    print ('Failed to import:', e)

Trong ví dụ trên, chúng ta không có module nào tên là empty2 do đó trình thông dịch báo lỗi.

Failed to import: No module named empty2

Thực thi module

Để sử dụng module thì chúng ta import nó từ chương trình của chúng ta nhưng chúng ta cũng có thể chạy chính nó. Ngoài các module có sẵn trong Python còn có rất nhiều lập trình viên viết các module khác cho Python. Thường thì sau khi viết xong họ hay viết những đoạn code để kiểm tra module của mình ở cuối file.

Chúng ta sẽ kiểm chứng điều này bằng cách viết module in dãy số fibonacci. Dãy số fibbonacci là dãy số mà số thứ i bằng tổng của số thứ i-1i-2, ngoại trừ hai số đầu tiên có giá trị là 1.


"""
A module containing the fibonacci
function. 
"""

def fib(n):
    a, b = 0, 1
    while b < n:
        print (b,)
        (a, b) = (b, a + b)


# testing

if __name__ == '__main__':
   fib(500)

Chúng ta sẽ chạy module này từ command prompt.

1 1 2 3 5 8 13 21 34 55 89 144 233 377

Trong đoạn code trên chúng ta có câu lệnh if để kiểm tra, nếu module được chạy từ trình biên dịch thì nó sẽ tự thực thi câu lệnh fib(500). Còn nếu được import từ module khác thì không.

>>> import fibonacci as fib
>>> fib.fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

Python – Hướng đối tượng trong Python

Trong phần này chúng ta sẽ tìm hiểu về lập trình hướng đối tượng trong Python.

Lập trình hướng đối tượng (gọi tắt là OOP, từ chữ Anh ngữ object-oriented programming), là kĩ thuật lập trình hỗ trợ công nghệ đối tượng. OOP được xem là giúp tăng năng suất, đơn giản hóa độ phức tạp khi bảo trì cũng như mở rộng phần mềm bằng cách cho phép lập trình viên tập trung vào các đối tượng phần mềm ở bậc cao hơn. Ngoài ra, nhiều người còn cho rằng OOP dễ tiếp thu hơn cho những người mới học về lập trình hơn là các phương pháp trước đó (theo Wikipedia).

Trong OOP có các khái niệm cơ bản sau đây:

  • Trừu tượng (Abstraction)
  • Đa hình (Polymorphism)
  • Đóng gói (Encapsulation)
  • Kế thừa (Inheritance)

Các khái niệm trên giải thích theo wikipedia hay lấy trong sách rất khó hiểu nên mình sẽ làm ví dụ luôn và tới đoạn nào mình sẽ giải thích đoạn đó cho dễ hiểu.

Khái niệm đối tượng

Đối tượng là các thực thể của một lớp nào đó. Lớp là khuôn mẫu của các đối tượng. Ví dụ như list, tuple, dictionary, string, int… là các lớp. Khi chúng ta khai báo biến thuộc các lớp này thì chúng là các đối tượng. Tất cả mọi thứ trong Python đều là đối tượng.


import sys


def function(): pass


print (type(1))
print (type(""))
print (type([]))
print (type({}))
print (type(()))
print (type(object))
print (type(function))
print (type(sys))

Trong ví dụ trên nhờ vào hàm type() mà chúng ta biết được thực chất tất cả các kiểu dữ liệu và các module mà chúng ta đã học thực chất đều là các đối tượng.

<class 'int'>
<class 'str'>
<class 'list'>
<class 'dict'>
<class 'tuple'>
<class 'type'>
<class 'function'>
<class 'module'>

Từ khóa class

Trong ví dụ trên, các lớp mà chúng ta kiểm tra bằng hàm type là các lớp có sẵn trong Python. Khi chúng ta muốn định nghĩa một lớp cho riêng mình thì chúng ta dùng từ khóa class. Một lớp có thể hiểu là một bản thiết kế để tạo ra một thực thể (mà chúng ta gọi là đối tượng). Một đối tượng là một thực thể được xây dựng từ một bản thiết kế đó (mà chúng ta gọi là lớp). Ví dụ chúng ta định nghĩa lớp Dog, thì chúng ta có các đối tượng là Huck, Lulu…. 😀

class First:
   pass

fr = First()

print (type(fr))
print (type(First))

Trong đoạn code trên chúng ta định nghĩa một lớp. Phần thân lớp tạm thời được để trống.

fr = First()

Ở dòng trên chúng ta xây dựng nên thực thể fr từ bản thiết kế First. Hay nói cách khác là chúng ta đã tạo ra đối tượng fr từ lớp First.

<class '__main__.First'>
<class 'type'>

Bên trong một lớp, chúng ta có thể định nghĩa các biến và hàm, biến và hàm bên trong một lớp được gọi là thuộc tínhphương thức. Chẳng hạn chúng ta định nghĩa lớp Employee (nhân viên), bên trong lớp này sẽ có thuộc tính Salary (lương) và phương thức calculateSalary (tính tiền lương).

Thuộc tính

Thuộc tính là biến nằm trong một lớp. Thuộc tính mô tả các đặc tính của một đối tượng. Trong Python có một phương thức đặc biệt gọi là  __init__() dùng để khởi tạo giá trị cho các thuộc tính của một đối tượng.

class Cat:
   def __init__(self, name):
      self.name = name


missy = Cat('Missy')
lucky = Cat('Lucky')

print (missy.name)
print (lucky.name)

Trong đoạn code trên, chúng ta định nghĩa lớp Cat. Bên trong lớp này, chúng ta định nghĩa phương thức __init__(). Phương thức __init__() là phương thức khởi tạo của tất cả các lớp, mỗi khi tạo một đối tượng phương thức này sẽ tự động được gọi.

def __init__(self, name):

Bất cứ phương thức nào của Python cũng đều phải có tham số đầu tiên là self rồi mới đến các tham số khác. self thực ra chỉ là biến đối tượng đã gọi phương thức này mà thôi. Chẳng hạn trong ví dụ trên khi chúng ta gọi missy = Cat("Missy") thì self chính là missy. 

self.name = name

Ở dòng trên chúng ta khởi tạo thuộc tính name và gán giá trị cho nó.

missy = Cat('Missy')
lucky = Cat('Lucky')

Trong hai dòng trên chúng ta dùng hàm Cat() để tạo ra hai đối tượng lớp thuộc lớp Cat. Chúng ta không trực tiếp đưa tham số self vào mà chỉ đưa các tham số thứ 2 trở đi thôi, self được tự động đưa vào bởi trình thông dịch. Trong ví dụ trên “Missy” và “Lucky” sẽ được gán cho thuộc tính name trong lớp Cat.

print (missy.name)
print (lucky.name)

Trong hai dòng code trên chúng ta in ra giá trị của thuộc tính name trong hai đối tượng Cat. Để truy xuất đến thuộc tính của một đối tượng thì chúng ta ghi tên đối tượng, dấu chấm và tên thuộc tính.

Missy
Lucky

Ngoài ra bạn có thể gán giá trị cho các thuộc tính ở bất cứ đâu sau phần định nghĩa lớp chứ không chỉ riêng bên trong phương thức khởi tạo.

class Dynamic:
   pass


d = Dynamic()
d.name = "Dynamic"
print (d.name)

Trong ví dụ trên chúng ta định nghĩa lớp Dynamic.

d.name = "Dynamic"

Ở dòng này chúng ta khai báo và gán giá trị cho thuộc tính name.

Dynamic

Một đặc điểm nữa trong Python bạn có thể định nghĩa các thuộc tính chung cho mọi đối tượng.


class Cat:
   species = 'mammal'

   def __init__(self, name, age):
      self.name = name
      self.age = age


missy = Cat('Missy', 3)
lucky = Cat('Lucky', 5)

print (missy.name, missy.age)
print (lucky.name, lucky.age)

print (Cat.species)
print (missy.__class__.species)
print (lucky.species)

Trong ví dụ trên, chúng ta tạo ra hai đối tượng Cat với thuộc tính name và age, hai thuộc tính này được khai báo bên trong phương thức __init__() và được gọi là thuộc tính đối tượng. Ngoài ra hai đối tượng này còn có một thuộc tính chung là thuộc tính species, thuộc tính này được khai báo ở ngoài phương thức __init__()  được gọi là thuộc tính lớp. Thuộc tính lớp được chia sẻ chung cho mọi đối tượng của lớp đó, trong khi thuộc tính đối tượng chỉ dành riêng cho đối tượng. Nghĩa là khi bạn thay đổi giá trị của thuộc tính chia sẻ trong một đối tượng thì tất cả các thuộc tính chia sẻ đó trong các đối tượng khác cũng thay đổi theo.

print (Cat.species)
print (missy.__class__.species)

Có hai cách để truy xuất thuộc tính lớp, thứ nhất là thông qua tên lớp, cách thứ hai là thông qua một thuộc tính đặc biệt nữa là thuộc tính __class__.

Missy 3
Lucky 5
mammal
mammal
mammal

Phương thức

Phương thức chẳng qua là các hàm, chỉ khác hàm bình thường ở chỗ chúng được định nghĩa bên trong một lớp. Các phương thức được sử dụng để thực hiện các công việc cụ thể. Phương thức là một thành phần quan trọng trong khái niệm Encapsulation (đóng gói). Chẳng hạn như bạn có một lớp tên là Network, lớp này có phương thức connect() dùng để kết nối đến các máy tính trong mạng LAN, thì bạn chỉ quan tâm rằng phương thức này làm công việc kết nối mạng chứ không quan tâm đến việc nó kết nối như thế nào.

class Circle:
   pi = 3.141592

   def __init__(self, radius=1):
      self.radius = radius 

   def area(self):
      return self.radius * self.radius * Circle.pi

   def setRadius(self, radius):
      self.radius = radius

   def getRadius(self):
      return self.radius


c = Circle()

c.setRadius(5)
print (c.getRadius())
print (c.area())

Trong ví dụ trên, chúng ta định nghĩa lớp Circle (hình tròn) và 3 phương thức mới.

 def area(self):
    return self.radius * self.radius * Circle.pi

Phương thức area() trả về diện tích hình tròn.

def setRadius(self, radius):
   self.radius = radius

Phương thức setRadius() thiết lập giá trị cho thuộc tính radius (bán kính).

 def getRadius(self):
    return self.radius

Phương thức getRadius() trả về lấy giá trị bán kính.

 c.setRadius(5)

Chúng ta gọi các phương thức thông qua biến đối tượng.

5
78.5398

Kế thừa

Kế thừa là định nghĩa một lớp dựa trên một lớp đã được định nghĩa trước đó. Lớp kế thừa từ lớp khác được gọi là lớp dẫn xuất, lớp được các lớp khác kế thừa mình thì gọi là lớp cơ sở. Kế thừa trong lập trình hướng đối tượng cho phép chúng ta sử dụng lại mã nguồn và giảm độ phức tạp của chương trình. Lớp dẫn xuất có thể kế thừa hoặc mở rộng các tính năng của lớp cơ sở.

class Animal:
   def __init__(self):
      print ("Animal created")

   def whoAmI(self):
      print ("Animal")

   def eat(self):
      print ("Eating")


class Dog(Animal):
   def __init__(self):
      Animal.__init__(self)
      print ("Dog created")

   def whoAmI(self):
      print ("Dog")

   def bark(self):
      print ("Woof!")


d = Dog()
d.whoAmI()
d.eat()
d.bark()

Trong ví dụ trên chúng ta định nghĩa hai lớp là là Animal và lớp Dog. Lớp Dog kế thừa từ lớp Animal, thừa hưởng một số phương thức của lớp Animal và có các phương thức của riêng nó. Ở trên lớp Dog kế thừa phương thức eat(), kế thừa và thay đổi phương thức whoAmI(), ngoài ra lớp Dog còn có phương thức của riêng nó là phương thức bark().

class Dog(Animal):
    def __init__(self):
        Animal.__init__(self)
        print ("Dog created")

Để kế thừa một lớp thì chúng ta đặt tên lớp đó bên trong cặp dấu ngoặc tròn () ngay phía sau phần định nghĩa tên lớp. Nếu bên trong lớp cơ sở đã định nghĩa phương thức __init__(), chúng ta phải gọi lại phương thức __init__() từ lớp cơ sở.

Animal created
Dog created
Dog
Eating
Woof!

Đa hình

Đa hình là bạn có thể sử dụng các toán tử hay các hàm trên nhiều kiểu dữ liệu khác nhau.


a = "alfa"
b = (1, 2, 3, 4)
c = ['o', 'm', 'e', 'g', 'a']

print (a[2])
print (b[1])
print (c[3])

Bản thân các hàm có sẵn trong Python cũng có tính chất đa hình. Chẳng hạn như hàm print() mà chúng ta hay dùng, mỗi lần gọi hàm này bạn có thể đưa vào hầu hết các kiểu dữ liệu khác nhau.

f
2
g

Trong hướng đối tượng thì tính đa hình càng được phát huy tác dụng.

class Animal:
   def __init__(self, name=''):
      self.name = name

   def talk(self):
      pass


class Cat(Animal):
   def talk(self):
      print ("Meow!")


class Dog(Animal):
   def talk(self):
      print ("Woof!")


a = Animal()
a.talk()

c = Cat("Missy")
c.talk()

d = Dog("Rocky")
d.talk()

Trong ví dụ trên, chúng ta định nghĩa hai lớp chó (Dog) và mèo (Cat) kế thừa từ lớp Animal. Do đó cả hai lớp này đều kế thừa phương thức talk() của lớp Animal, nhưng mỗi lớp lại in ra hai dòng text khác nhau.

Meow!
Woof!

Các phương thức đặc biệt

Tất cả các lớp dù là có sẵn hay do chúng ta định nghĩa đều kế thừa từ một lớp gốc trong Python có tên là object. Lớp này có sẵn một số phương thức và đương nhiên là các lớp do chúng ta định nghĩa đều kế thừa các phương thức này, ví dụ như phương thức __init__()… Trong Python khi chúng ta gọi đến các hàm hay toán tử được xây dựng sẵn như print(), del… chúng sẽ gọi đến các phương thức gốc của lớp object. Chính vì các lớp do chúng ta định nghĩa đều được kế thừa từ lớp object nên chúng ta cũng có thể dùng các hàm hay toán tử có sẵn trong Python với các lớp của chúng ta. Trong các ngôn ngữ như C++ thì tính chất này được gọi là quá tải toán tử (operator overloading).

class Book:
   def __init__(self, title, author, pages):
      print ("A book is created")
      self.title = title
      self.author = author
      self.pages = pages

   def __str__(self):
      return "Title:%s , author:%s, pages:%s " % \
              (self.title, self.author, self.pages)

   def __len__(self):
      return self.pages

   def __del__(self):
      print ("A book is destroyed")


book = Book("Inside Steve's Brain", "Leander Kahney", 304)

print (book)
print (len(book))
del book

Trong ví dụ trên, chúng ta định nghĩa lớp Book. Bên trong lớp này chúng ta định nghĩa 4 phương thức được kế thừa từ lớp object là __init__(),__str__(), __len__() và __del__().

print (book)

Khi sử dụng hàm print(), hàm này sẽ gọi đến phương thức __str__(), do đó khi chúng ta kế thừa lại phương thức này, hàm print() sẽ gọi đến phương thức __str__() trong lớp Book của chúng ta.

print (len(book))

Tương tự, hàm len() gọi đến phương thức __len__()

del book

Từ khóa del gọi đến phương thức __del__() và trên thực tế là phương thức này làm công việc hủy bỏ một đối tượng ra khỏi bộ nhớ. Nhưng ở đây chúng ta kế thừa phương thức này chỉ làm công việc là in ra một đoạn text.

Trong ví dụ dưới đây, chúng ta sẽ kế thừa các toán tử cộng và trừ.


class Vector:

  def __init__(self, data):
    self.data = data

  def __str__(self):
    return repr(self.data)

  def __add__(self, other):
    data = []
    for j in range(len(self.data)):
      data.append(self.data[j] + other.data[j])
    return Vector(data)

  def __sub__(self, other):
    data = []
    for j in range(len(self.data)):
      data.append(self.data[j] - other.data[j])
    return Vector(data)


x = Vector([1, 2, 3])
y = Vector([3, 0, 2])
print (x + y)
print (y - x)
def __add__(self, other):
  data = []
  for j in range(len(self.data)):
    data.append(self.data[j] + other.data[j])
  return Vector(data)

Chúng ta định nghĩa lớp Vector, lớp vector này kế thừa hai phương thức __add__()__sub__(). phương thức __add__() sẽ được gọi khi sử dụng toán tử + và phương thức __sub__() sẽ được gọi khi sử dụng toán tử -.

[4, 2, 5]
[2, -2, -1]