1. 程式人生 > >資料結構與演算法(Python)-Python快速入門篇3

資料結構與演算法(Python)-Python快速入門篇3

寫在前面

對於簡單的任務,我們可以利用一些函式,按照任務處理的流程書寫程式碼來完成需求,這種方式稱之為程序式程式設計(procedural programming);但是對於複雜的系統,如何有條理的將每個模組的程式碼內聚起來,如何清晰和簡潔地表達各個模組之間的互動,就需要一種新的指導思想,面向物件程式設計(object-oriented programming)。OOP強調的就是為獨立模組構造物件,物件之間通過訊息通訊來完成複雜的功能。OOP主題是複雜的,本節只對有限主題進行學習,知識結構如下圖所示:

入門3

面向物件程式設計(OOP)

1)類的定義

在Python每個變數實際上都是指向物件的一個引用,這在前面已經說明過了。我們使用class關鍵字定義類,就像使用def關鍵字定義函式一樣。在Python中,支援

兩種定義類的寫法:

#python2.x
class MyClass: # old-style 
class MyClass(object): # new-style class
#python3.x
class MyClass(object): # new-style class
class MyClass: # new-style class (implicitly inherits from object)

上面的兩種定義方式是受到歷史原因引起的,在此我們不去深究。一種合理的方式是,在python2.x中總是顯式使用:

class MyClass(object):
    pass

這種方式;在python3.x中如果為了相容2.x程式碼則也是用這種方式,否則沒有區別。

2)類的成員變數和方法

OOP與面向過程程式設計一個很大的區別在於,物件可以附加屬性(包含哪些資料)和方法(支援哪些操作,也是一種函式),通過將資料和函式內聚到物件身上,我們使物件的概念更加清晰,與系統其它部分的互動更加明確。例如下面定義一個圓形類:

class Circle():
    pi = 3.141592
    def __init__(self, radius=1): #初始化函式 類似C++建構函式
        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())

在上面的例子中,我們定義了一個簡單的圓形類,這個圓形類包含一個屬性即半徑,用radius儲存,這稱之為類的成員變數(member variables);同時包含3個方法分別用來計算圓的面積,設定和獲取圓形的半徑,這些稱之為類的成員方法(member methods)。定義一個類之後,我們一般無法直接操作類,而是例項化類的一個具體物件(instance object)來操作,就好比你說開車,應該是開的某一輛具體車型的汽車。

在上面的例子中,我們也看到了一個self關鍵字,這個關鍵字用來表示物件本身的引用,類似於c++之中的this指標。當我們呼叫方法:

print(c.area())   # 通過物件呼叫成員方法

的時候,實際上物件c將會作為引數傳入類成員函式area,此時self就繫結到了這個具體物件,那麼函式實際操作的資料就是這個物件c的資料了。實際上也可以這樣呼叫函式:

print(Circle.area(c))  # 通過顯式傳入物件 呼叫類的方法

這裡我們實際上將c顯式的傳入了,而不是由直譯器替我們傳入。如果這樣呼叫:

print(Circle.area())  # TypeError 缺少例項物件

將會產生:

TypeError: unbound method area() must be called with Circle instance as first argument (got nothing instead)

的錯誤,原因在於,沒能正確傳入一個例項變數作為第一個引數呼叫函式。

在Python中主要包括三種類型的成員變數和函式,他們之間都有區別,列出如下:

2.1)例項級別的成員 (instance -level members)

例項級別的成員就是屬於每個物件自身的資料和函式,例如上例中的radius和另外三個成員函式。例項級別的成員變數,一般在_init_函式中進行初始化。例如:

class Foo(object):
    def hello(self):   # 注意這裡需要傳入例項物件給self
        print("hello from %s" % self.__class__.__name__)

呼叫方式:

obj= Foo()
obj.hello() # 方式一
#>>  "hello from Foo"
Foo.hello(obj)  # 方式二
#>>  "hello from Foo"

2.2)類級別的成員(class-level members)

類級別的成員,不屬於某個具體的物件,而是由類來保持的資料或者函式。例如某個類需要保持物件建立數量的計數,這個計數就定義為類變數。上面例子中的pi就是類成員變數,類成員變數定義在init函式之外。

class Foo(object):
    @classmethod
    def hello(cls):  # 注意這裡傳入的為類物件 而不是某個例項物件
        print("hello from %s" % cls.__name__)
# 呼叫方式
Foo.hello()    # 對於類成員函式 使用類名字呼叫更清晰
#>>  "hello from Foo"
Foo().hello()        
#>> "hello from Foo"

注意上面程式碼中使用了”@classmethod“這種標記,在Python中稱之為Decorators,感興趣地可以瞭解

2.3)靜態成員(static members)

靜態成員是一種類裡面為了某些操作的方便而包含在類裡面的函式,這些函式在使用時不需要任何類或者例項的資訊,實際上主要是為了便於管理,將它包含在類定義裡面,實際應用得比較少。

class Foo(object):
    @staticmethod
    def hello():  # 不需要傳入例項或者類物件作為引數
        print("hello from FOO static")
 Foo.hello()
 #>> hello from FOO static

在類的成員函式中,有一類特殊函式,這類函式以雙下劃線開始和結尾,用來表示特定行為,例如str,函式用來將物件轉換為字串,這個字串將在print等函式中用來輸出物件的表示。


class Book(object):

    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:{0} , author:{1}, pages:{2} ".format(
            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

#>> A book is created
#>>Title:Inside Steve's Brain , author:Leander Kahney,pages:304
#>>304
#>> A book is destroyed

這種機制類似於C++的操作符過載,Python中可以過載的函式還有add() ,sub() 等。

3)面向物件三要素

面向物件程式設計包含三大要素,分別是封裝(encapsulation )、繼承(inheritance)、多型(Polymorphism)。下面從這三個角度看下Python如何處理的,因為面向物件程式設計本身是個複雜主題,這裡只提供一個基本思路,詳細可以參考其他資料。

3.1 封裝性

封裝涉及的任務是如何設計類的介面供使用者使用,重點是類的訪問控制(access-control),避免使用者有意或者無意破壞資料。需要注意的是,Python設計上不支援private變數和方法,通用的做法是用兩個連續下劃線表明這個成員應當被視為私有,不應該在類外使用。下面這個例子來自SO How to do encapsulation in Python?

class C(object):
    def __init__(self):
        self.a = 123    # OK to access directly
        self._a = 123   # should be considered private
        self.__a = 123  # considered private, name mangled
>>> c = C()
>>> c.a
123
>>> c._a
123
>>> c.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute '__a'
>>> c._C__a
123

在上面例子中,__a 方法被重寫了(name mangling )為 _C__a,但實際上也能訪問,這只是Python的一種提示方式,Python設計上不支援private。

3.2 繼承性

繼承是一個類對另一個類共同行為的複用機制,被繼承的類稱之為父類或者基類(base classes),從父類繼承而來的類稱之為子類或者派生類(derived classes)。子類不僅具有父類的成員變數和函式,同時也能修改父類的成員函式(overriding)和新增新的成員函式。

定義一個普通的銀行賬戶,再定義一個最低額度的銀行賬戶,如下:

class BankAccount(object):
    def __init__(self):
        self.balance = 0

    def withdraw(self, amount):
        self.balance -= amount
        return self.balance

    def deposit(self, amount):
        self.balance += amount
        return self.balance


class MinimumBalanceAccount(BankAccount):
    def __init__(self, minimum_balance):
        BankAccount.__init__(self)    # call super class init method
        self.minimum_balance = minimum_balance

    def withdraw(self, amount):   # overriding
        if self.balance - amount < self.minimum_balance:
            print 'Sorry, minimum balance must be maintained.'
        else:
            return BankAccount.withdraw(self, amount)

a = BankAccount()
b = MinimumBalanceAccount(100)
a.deposit(100)
b.deposit(100)
print(a.withdraw(10)) # >> 90
print(b.withdraw(10)) # >> Sorry, minimum balance must be maintained.None

在上面的例子中,MinimumBalanceAccount的定義中,括號裡書寫了
父類類名字BankAccount,從父類繼承了balance 成員和其他函式,同時子類MinimumBalanceAccount添加了一個最低額度的minimum_balance成員,同時重寫了withdraw方法。

需要注意的是子類呼叫父類的方法有兩種:

super(SubClass, instance).method(args)
SuperClass.method(instance, args)

上面的例子中,我們使用:

BankAccount.withdraw(self, amount)

可以替換為:

super(MinimumBalanceAccount,self).withdraw(amount)

3.3 多型性

多型性是指利用父類的引用型別可以指向父類和子類任意物件,在執行時根據實際指向的物件型別來動態決定如何操作的行為。例如:

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

這裡的索引操作符,實際就是一種多型行為,對於字串、元組、列表進行同種操作,但實際行為由物件本身決定。下面的例子利用繼承定義了多個類,展示了多型行為:

class Animal(object):

   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!")

animals = [Cat("Missy"), Dog("Rocky")]
for a in animals:
    a.talk()
# >> Meow!
# >> Woof!

這裡對a物件呼叫talk,實際執行時由物件的實際型別決定了哪種操作,這種動態執行的效果就是多型性,多型特效能很大程度上簡化程式碼。

4 抽象類

Python在面向物件程式設計方面,沒有提供定義介面的方法,但是支援抽象基類的。要定義抽象基類,需要使用python的abc庫。下面給出一個python2.x版本的例子(來自Abstract Classes in Python):

from abc import ABCMeta, abstractmethod

class Animal(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def say_something(self): pass # 抽象方法 非抽象子類需實現

class Cat(Animal):
    def say_something(self):
        return "Miauuu!"
a = Animal()
a.say_something()

如果試圖例項化抽象基類,則會提示錯誤:

Traceback (most recent call last):
  File "abstactDemo.py", line 13, in <module>
    a = Animal()
TypeError: Can't instantiate abstract class Animal with abstract methods say_something

例項化實現了全部抽象方法的子類,則是允許的:

from abc import ABCMeta, abstractmethod

class Animal(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def say_something(self):
          return "I'm an animal!"

class Cat(Animal):
    def say_something(self):
        s = super(Cat, self).say_something()
        return "%s - %s" % (s, "Miauuu")

a = Cat()
print(a.say_something())
# >> I'm an animal! - Miauuu

參考資料