Kiến trúc lập trình Python - Phần 4

vào lúc 07:09
IV. Lớp trong Python:
1. Cú pháp định nghĩa lớp:

        Python sử dụng một cách thức khá đơn giản để định nghĩa một lớp.Cú pháp như sau:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

        Cũng giống như định nghĩa hàm, một lớp phải được định nghĩa trước khi nó có thể sử dụng.Thực tế, các lệnh viết trong định nghĩa lớp thường sửdụng là các định nghĩa hàm.
        Đối tượng lớp sẽ được tạo ra ngay sau khi định nghĩa lớp kết thúc.
2. Các đối tượng lớp:
        Các đối tượng lớp trong Python hỗ trợ 2 thao tác : tham chiếu thuộc tính và thực thể.
2.1. Tham chiếu thuộc tính
        Cú pháp tham chiếu thuộc tính của các đối tượng lớp cũng như cú pháp chuẩn cho tất cả các tham chiếu thuộc tính trong Python:
        Obj.name
        Giá trị của tên thuộc tính là tất cả các tên mà nằm trong không gian tên của lớp khi đối tượng được tạo ra.
Xét một lớp được định nghĩa như sau:
class MyClass:
    "A simple example class"
    i = 12345
    def f(self):
        return 'hello world'
Thì MyClass.i và MyClass.f là các giá trị tham chiếu thuộc tính, trả về một số nguyên và một đối tượng hàm.
2.2. Thực thể lớp:
        Thực thể lớp sử dụng ký hiệu hàm, mô tả đối tượng lớp như là một hàm không tham số và trả về một thực thể mới của lớp đó. Ví dụ (giả sử đã có lớp MyClass ở trên):
            x = MyClass()
tạo ra một thực thể của lớp này và đăng kí đối tượng tạo ra với biến cục bộ x.
        Việc thao tác một thực thể ( gọi một đối tượng lớp) tạo ra một đối tượng rỗng. Có nhiều lớp mục đích là để tạo ra các đối tượng với các thực thể  thỏa mãn một trạng thái khởi tạo xác định. Vì thế một lớp có thể định nghĩa một phương thức đặc biệt tên là __init__( ), giống như sau:
def __init__(self):
        self.data = []
Khi một lớp định nghĩa một phương thức __init__( ), thực thể lớp tự động gọi đến hàm __init__( ) cho thực thể vừa được tạo ra. Vì thế trong ví dụ trên, một thực thể khởi tạo mới có thể thu được bởi:
x = MyClass()
Tất nhiên, hàm __init__( ) có thể có đối số để linh hoạt hơn. Trong trường hợp đó, các đối số được truyền đến toán tử thực thể lớp  thông qua hàm __init__( ).

Ví dụ:
>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

3. Các đối tượng thực thể:
        Đối tượng thực thể chỉ cho phép thao tác duy nhất là tham chiếu thuộc tính. Có 2 loại thuộc tính là: thuộc tính dữ liệu và phương thức.
        Thuộc tính dữ liệu không cần phải khai báo, giống như biến cục bộ, chúng tự sinh ra ngay lần đầu gọi đến chúng. Ví dụ, nếu x là một thực thể của MyClass tạo ra ở trên, thì đoạn mã dưới đây sẽ in ra giá trị 16

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter

        Loại tham chiếu thuộc tính thứ 2 là phương thức. Một phương thức là một hàm thuộc một đối tượng. Trong ví dụ trên MyClass.f là một hàm, còn x.f là một đối tượng phương thức, không phải một đối tượng hàm.

4. Các đối tượng phương thức:

        Thông thường một đối tượng phương thức được gọi sau khi nó được chấp nhận:
        x.f ( )
        Trong ví dụ MyClass, lời gọi trên sẽ trả về xâu ‘hello world’. Tuy nhiên, không cần thiết phải gọi 1 phương thức theo đúng cách như trên, vì x.f làmột đối tượng phương thức, có thể được lưu trữ lại và gọi ở lần tiếp theo, ví dụ:

            xf = x.f
      while True:
            print xf( )

            Đoạn mã này sẽ tiếp tục in ra dòng chữ ‘hello world’ cho đến khi ngừng chương trình.

5. Tính kế thừa:
5.1. Kế thừa đơn
        Trong Python không có khái niệm Protected như C++ , chỉ có các hàm ,dữ liệu thành phần public hay private , và chúng được phân biệt qua tên , những tên bắt đầu bằng __ và kết thúc có tối đa 1 dấu _ là private
        Ví dụ __x , __x_ , __abc là những biến private , __y__ ,a ,bc , xyz__ , x_ là những biến public . Tương tự như vậy với tên hàm.
        Các biến và phương thức private chỉ có thể truy cập nội bộ trong class đó , các biến và phương thức public có thể truy xuất trong toàn bộ chương trình. Chỉ có 1 loại kế thừa là public.
        Cú pháp khai báo một lớp con kế thừa như sau

    class DerivedClassName(BaseClassName):
<statement-1>
         .
         .
         .
    <statement-N>

        Xét ví dụ sau:

        >>> class A:
    sa='Day la thuoc tinh cua lop A'
    __pri='Day la thuoc tinh rieng cua A'
    def __init__(self):
         print 'Ham khoi tao cua lop A'
    pass

>>> class B(A):
         sb='Day la thuoc tinh cua lop B'
         def __init__(self):
             print 'Ham khoi tao cua lop B'

>>> b=B()
Ham khoi tao cua lop B
>>> b.sa
'Day la thuoc tinh cua lop A'
>>> b.sb
'Day la thuoc tinh cua lop B'
>>> b.__pri

Traceback (most recent call last):
  File "<pyshell#261>", line 1, in <module>
    b.__pri
AttributeError: B instance has no attribute '__pri'
>>> 

Như thấy ở trên lớp A có 1 thuộc tính public là sa và 1 thuộc tính private là __pri mà lớp B kế thừa từ A cũng không truy xuất được. Một điểm lưu ý nữa là hàm khởi tạo của B không tự gọi hàm khởi tạo của A , do đó người lập trình phải gọi hàm khởi tạo của A .

5.2.Đa kế thừa
        Khai báo Đa kế thừa cũng như đơn kế thừa  ,chỉ việc thêm danh sách các lớp cơ sở vào trong cặp dấu () sau tên lớp:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

        Do Python không tự gọi hàm khởi tạo của các lớp cha nên thứ tự kế thừa không quan trọng.
        Ví dụ đa kế thừa:

>>> class A:
    sa='Day la thuoc tinh lop A'
    def __init__(self):
         print 'Ham khoi tao lop A'
    pass

>>> class B:
    sb='Day la thuoc tinh lop B'
    def __init__(self):
         print 'Ham khoi tao lop B'
    pass

>>> class C(A,B):
    sc='Day la thuoc tinh lop C'
    def __init__(self):
         print 'Ham khoi tao lop C'
    pass

>>> c=C()
Ham khoi tao lop C
>>> c.sa
'Day la thuoc tinh lop A'
>>> c.sb
'Day la thuoc tinh lop B'
>>> c.sc
'Day la thuoc tinh lop C'
>>> 
       
6. Các kỹ thuật về chồng hàm, chồng toán tử, hàm và dữ liệu thành phần tĩnh:
6.1. Chồng hàm trong class
        Python không hỗ trợ chồng hàm trong class khi mà không có một khai báo chặt chẽ về kiểu của dữ liệu các tham số truyền cho hàm , kiểu của chúng được xác định tuỳ theo giá trị truyền cho hàm lúc gọi hàm.

6.2 Chồng toán tử
        Chồng toán tử trong Python được thực hiện bằng việc khai báo đè lên một số tên đặc biệt.Có rất nhiều toán tử có thể chồng trong Python.
Ví dụ   phương thức __add__(self,x) sẽ định nghĩa lại phép cộng
                    __sub__(self,x) sẽ định nghĩa lại phép trừ
selfở đây là tham số bắt buộc phải có đối với mọi phương thức , nó giống con trỏthis trong C++
               
Xét lớp sau
        >>> class A:
         x=1
         y=2.0
         def __add__(self,a):
         self.x-=a
         self.y-=a

>>> a
<__main__.A instance at 0x00BBCF58>
>>> a=A()
>>> a.x
1
>>> a.y
2.0
>>> a+1
>>> a.x
0
>>> a.y
1.0
>>> 

6.3 Hàm và dữ liệu thành phần tĩnh
a. Hàm (hay phương thức tĩnh ) được khai báo trong Python như sau

        class C:
    @staticmethod
    def f(arg1, arg2, ...): ...
        Hoặc

        class C:
         @classmethod
         def f(arg1,arg2,…):….
               
        Sự khác nhau giữa @staticmethod và @classmethod như sau:
- Nếu một hàm khai báo là @classmethod    thì khi gọi hàm, hệ thống sẽ ngầm định truyền cho hàm tham số đầu tiên là thực thể của chính class đó , do đó hàm phải luôn sử dụng arg1 như là self (giống con trỏthis trong C++ ).
- Nếu một hàm khai báo là @staticmethod   thì khi gọi hàm, hệ thống sẽ không ngầm định truyền cho hàm tham số đầu tiên là thực thể của chính class đó , do đó hàm này không thể biết được thực thể nào đã gọi mình.

Ví dụ:

>>> class C:
    @staticmethod
    def hello():
         print 'Hello'
        
>>> C.hello()
Hello


class D:
    @classmethod
    def hello():
         print 'Hello'
        
>>> D.hello()

Traceback (most recent call last):
  File "<pyshell#73>", line 1, in <module>
    D.hello()
TypeError: hello() takes no arguments (1 given)
>>> 

Lỗi xảy ra trong lớp D là do hàm hello khi khai báo không nhận 1 tham số nào, nhưng khi được gọi, hệ thống tự thêm tham số đàu tiên là self vào.

b. Dữ liệu thành phần tĩnh
Dữ liệu thành phần tĩnh của lớp được khai báo ngay sau tên lớp

>>> class A:
             counter=0
             def __init__(self):
                 self.__class__.counter+=1
                 print 'Thuoc tinh tinh',A.counter
                 print 'Thuoc tinh cua lop',self.counter


>>> A.counter
0
>>> a=A()
Thuoc tinh tinh 1
Thuoc tinh cua lop 1
>>> b=A()
Thuoc tinh tinh 2
Thuoc tinh cua lop 2
>>> 

Thuộc tính tĩnh được chia sẻ bởi tất cả các thực thể của lớp và bản thân tên lớp đó
7. Kỹ thuật Odds and Ends đối với lớp
Trong Python thông đôi khi kỹ thuật này cũng được sử dụng để tạo ra các kiểu dữ liệu giống record trong Pascal hoặc struct trong C, đồng thời cùng với một vài thành phần dữ liệu được đặt tên. Ta dùng một định nghĩa lớp rỗng để thực hiện hiện điều này:
class Employee:
    pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
8. Các phương thức đặc biệt:

        Có một số các phương thức được dành để sử dụng với các mục đích đặc biệt, nó được kết thúc với 2 dấu gạch dưới. Thông thường các phương thức đó được bắt đầu với một dấu gạch dưới để đánh dấu nó là ‘private’ với phạm vi mà nó được sử dụng trong đó.

Phương thức khởi tạo:
__init__
        Một trong những mục đích của nó là để xây dựng một thực thể lớp, và tên đặc biệt cho phương thức này là ‘__init__’
        Hàm __init__() được gọi trược khi một thực thể được trả về. Ví dụ:

class A:
    def __init__(self):
         print 'A.__init__()'
a = A()
sẽ đưa ra
A.__init__()

__init__()  có thể có đối số, trong trường hợp cần thông qua các đối số để tạo ra một thực thể. Ví dụ:

class Foo:
    def __init__ (self, printme):
         print printme
foo = Foo('Hi!')
sẽ đưa ra:
Hi!

Sau đây là một ví dụ chỉ ra sự khác nhau giữa sử dụng và không sử dụng __init__():

class Foo:
    def __init__ (self, x):
         print x
foo = Foo('Hi!')
class Foo2:
    def setx(self, x):
         print x
f = Foo2()
Foo2.setx(f,'Hi!')

Sẽ in ra

Hi!
Hi!
Phương thức miêu tả:
__str__

    Biến đổi một đối tượng thành một xâu, như là với câu lệnh print hoặc với hàm str(), nó có thể được đưa ra bởi __str__. Thông thường __str__ trảvề một bản đã định dạng của nội dung đối tượng, điều này sẽ không được thường xuyên thực hiện. Ví dụ:

class Bar:
    def __init__ (self, iamthis):
         self.iamthis = iamthis
    def __str__ (self):
         return self.iamthis
bar = Bar('apple')
print bar

sẽ in ra:
apple

__repr__
       
Hàm này cũng giống như __str__().  __repr__ được sử dụng để trả về biểu diển của một đối tượng trong dạng xâu. Thông thường nó có thể trả vềđối tượng gốc. Ví dụ:

class Bar:
    def __init__ (self, iamthis):
         self.iamthis = iamthis
    def __repr__(self):
         return "Bar('%s')" % self.iamthis
bar = Bar('apple')
print bar
sẽ in ra:
Bar('apple')

Các thuộc tính:
__setattr__
       
Đây là hàm quản lí các thuộc tính trong một lớp. Nó được cung cấp với tên và giá trị các biến được đăng kí. Tất nhiên, mỗi lớp có một __setattr__ mặc định đơn giản để thiết lập giá trị của các biến, nhưng cũng có thể gạt bỏ nó:

>>> class Unchangable:
... def __setattr__(self, name, value):
... print "Nice try"
...
>>> u = Unchangable()
>>> u.x = 9
Nice try
>>> u.x
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: Unchangable instance has no attribute 'x'

__getattr___

Cũng giống với  __setattr__, trừ hàm này được gọi khi ta  truy nhập vào một thành viên lớp, và mặc định đơn giản trả về giá trị.

>>> class HiddenMembers:
... def __getattr__(self, name):
... return "You don't get to see " + name
...
>>> h = HiddenMembers()
>>> h.anything
"You don't get to see anything"

__delattr__
Hàm này được gọi để xóa một đối tượng

>>> class Permanent:
... def __delattr__(self, name):
... print name, "cannot be deleted"
...
>>> p = Permanent()
>>> p.x = 9
>>> del p.x
x cannot be deleted
>>> p.x
9
9. Đóng gói lớp:

        Bởi vì tất cả các thành phần của một lớp trong Python là có thể truy nhập bởi các hàm và phương thức bên ngoài lớp đó, không có cách nào để đóng gói các hàm ghi đè __getattr__, __setattr__ và __delattr__. Tuy nhiên, thực tế, để tạo ta một lớp hoặc một module để đơn giản người ta chỉ sử dụngphần giao diện đã dành cho và tránh truy cập đến vùng làm việc của module khác

Hãy like nếu bài viết có ích →
Kết bạn với gisgpsrs trên Facebook để nhận bài viết mới nóng hổi

Không có nhận xét nào:

Đăng nhận xét