1. 程式人生 > >python面向物件筆記

python面向物件筆記

一、封裝(屬性/私有方法/公有方法/靜態方法/建構函式...)

# 定義一個類
class Animal:
    # 私有成員(用_開頭的約定為私有成員 - 注:僅僅是君子協定)
    _age = 0

    # 建構函式
    def __init__(self, name):
        # 建議所有私有成員在這裡初始化(不管是已定義的,還是"動態"新增的)
        self.name = name  # 動態新增的私有成員
        self._age = 1  # 已經定義好的私有成員

    # 靜態方法
    @staticmethod
    def eat():
        print("Animal can eat food")

    # 公有方法
    def to_string(self):
        return "name:" + self.name

    # 私有方法(用_開頭的約定為私有方法 - 注:僅僅是君子協定)
    def _some_method(self):
        return "this is a private method of :" + self.name

    # setter示例
    def set_age(self, age):
        if age < 0 or age > 100:
            print("age is invalid!")
        self._age = age

    # getter示例
    def get_age(self):
        return self._age

    def run(self):
        print(self._some_method())
        print("I am running")

    def test(self):
        print("test1")

    # 注:這樣會覆蓋上面的test(self版本)
    def test(self, hello):
        print("test2:" + hello)


animal = Animal("A new animal")
print(animal.to_string())  # 方法呼叫
animal.eat()  # 靜態方法呼叫1(不推薦)
Animal.eat()  # 靜態方法呼叫2
print("animal._age=" + str(animal.get_age()))
animal.set_age(10)  # 呼叫setter
print(animal.get_age())  # 呼叫getter,輸出10
animal.run()  # 公有方法裡呼叫私有方法
animal._age = 30  # 直接修改私有成員(不推薦)
animal._age2 = 40  # 注意:這裡實際上給例項animal"動態"添加了一個_age2屬性
print(animal.get_age())  # 這裡輸出的仍然是10
print("_age2:" + str(animal._age2))  # 這裡輸出的動態新增的_age2屬性值
print(animal.test("hi"))
#print(animal.test())  # 這裡會報錯,因為test(self,hello)這個版本要求hello引數有值
print(animal._some_method())  # 直接呼叫私有方法(不推薦)

輸出:

name:A new animal
Animal can eat food
Animal can eat food
animal._age=1
10
this is a private method of :A new animal
I am running
30
_age2:40
test2:hi
None
this is a private method of :A new animal

幾個要點:

1、約定大於配置:比如建構函式約定為__init__;私有方法/成員約定為"_"開頭(注:只是君子協定,硬要呼叫的話,外部是可以直接呼叫的);例項方法約定第1個引數為self  

2、過載的處理,不象java裡要定義多個方法簽名,python裡就一個版本,但是通過可變引數來實現(後面還要細講)

3、動態語言,例項在執行過程中,可隨時動態新增屬性

另外注意:不要輕易使用__(二個連續的下劃線)做為方法或變數的字首或字尾,"__XXX__"在python裡通常有特定含義,如果使用"__"開頭的變數或寫法,可能會遇到各種奇葩問題。

二、過載

傳統的OOP語言,比如java,過載只能是定義多個不同方法簽名的method,來實現過載,但是python可以做得更簡單,用引數預設值就可以變相實現。

# 定義一個類
class Calculator:
    # 加法
    def add(self, a=0, b=0, c=0, d=0):
        print("args:", a, b, c, d)
        return int(a) + int(b) + int(c) + int(d)


cal = Calculator()
print(cal.add(1, 2))
print(cal.add(1, 2, 3))
print(cal.add(1, "2", 3, "4"))

print("\n")

cal = Calculator #注意這裡,如果漏了(),結果會大不同
print(cal.add(1, 2))

 輸出:

args: 1 2 0 0
3
args: 1 2 3 0
6
args: 1 2 3 4
10


args: 2 0 0 0
2  

注意:16行,如果漏了(),cal = Calculator,實際上只是cal只是相當於類Calculator的別名,這樣呼叫add(1,2)時,類似於靜態方法呼叫,1會認為是第一個引數self,所以輸出就成了2.

如果不確定引數個數,還可以這麼做:

# 定義一個類
class Calculator:
    # 約定:*引數,表示引數個數不限定
    def add(self, *args):
        result = 0
        for arg in args:
            result += int(arg)
        return result


cal = Calculator()
print(cal.add(1, 2, 3))
print(cal.add("1", 2, "3"))

輸出:

6
6

三、繼承

3.1 基本示例

class Fruit:
    def __init__(self, name):
        print("Fruit constructor...")
        self.name = name

    def to_string(self):
        print("Fruit to_string...")
        return "name:" + self.name

    # 抽象方法
    def get_color(self):
        print("Fruit get_color...")
        raise NotImplementedError


class RedApple(Fruit):
    def __init__(self, name):
        print("Apple constructor...")
        # 呼叫父類的建構函式
        Fruit.__init__(self, name)

    def get_color(self):
        return self.name + " is red"


fruit = Fruit("unknown")
print(fruit.to_string())
# print(fruit.get_color())  # 報錯,因為沒實現
print("\n")

redApple = RedApple("red apple")
print(redApple.get_color())
print(redApple.to_string())

print("\n")
print("1、redApple is instance of RedApple ? ", isinstance(redApple, RedApple))
print("2、redApple is instance of Fruit ? ", isinstance(redApple, Fruit))
print("3、fruit is instance of Fruit ? ", isinstance(fruit, Fruit))
print("4、fruit is instance of RedApple ? ", isinstance(fruit, RedApple))
print("5、RedApple is subclass of Fruit ? ", issubclass(RedApple, Fruit))
print("6、Fruit is subclass of Fruit ? ", issubclass(Fruit, Fruit))
print("7、Fruit is subclass of RedApple ? ", issubclass(Fruit, RedApple))

 輸出:

Fruit constructor...
Fruit to_string...
name:unknown


Apple constructor...
Fruit constructor...
red apple is red
Fruit to_string...
name:red apple


1、redApple is instance of RedApple ?  True
2、redApple is instance of Fruit ?  True
3、fruit is instance of Fruit ?  True
4、fruit is instance of RedApple ?  False
5、RedApple is subclass of Fruit ?  True
6、Fruit is subclass of Fruit ?  True
7、Fruit is subclass of RedApple ?  False

注:抽象方法是通過丟擲未實現的異常來實現的。如果想類似java定義抽象類,把__init__方法丟擲未實現異常就行。

3.2 多繼承

python支援多繼承,這點與java有很大區別

class P1:
    def a(self):
        print("P1-a")

    def b(self):
        print("P1-b")

    def x(self):
        print("P1-x")


class P2:
    def a(self):
        print("P2-a")

    def b(self):
        print("P2-b")

    def y(self):
        print("P2-y")


# 多繼承示例
class S1(P1, P2):
    def a(self):
        print("S1-a")


class S2(P2, P1):
    def a(self):
        print("S2-a")


s1 = S1()
s1.a()
s1.b()  # P1-b
s1.x()
s1.y()

print("\n")

s2 = S2()
s2.a()
s2.b()  # P2-b
s2.x()
s2.y()

print("\n")

print("s1 isinstance of P1:", isinstance(s1, P1))
print("s1 isinstance of P2:", isinstance(s1, P2))
print("s1 isinstance of S1:", isinstance(s1, S1))
print("s1 isinstance of S2:", isinstance(s1, S2))

輸出:

S1-a
P1-b
P1-x
P2-y


S2-a
P2-b
P1-x
P2-y


s1 isinstance of P1: True
s1 isinstance of P2: True
s1 isinstance of S1: True
s1 isinstance of S2: False

注意多承繼的順序,如果“爸爸們”之間有重名方法,將會按照繼承順序,誰在前面,就呼叫誰的。eg:def S(A,B) 子類S繼承自A,B,如果A,B中都有方法x,呼叫S.x時,因為A排在B的前面,所以呼叫到的就是A.x方法。從上面的輸出就可以得到印證。

3.3 介面、abc模組、屬性

3.1中抽象類/方法是通過丟擲異常來實現的,有點粗暴,下面介紹一種更優雅的方法,python內建的abc模組

from abc import *


# 介面示例
class IRun(ABC):
    # 抽象方法
    @abstractmethod
    def run(self):
        pass


class Animal(ABC):
    # 抽象屬性
    @property
    @abstractmethod
    def name(self):
        pass


class Dog(Animal, IRun):
    def __init__(self, name):
        self._name = name;

    # 屬性的getter
    @property
    def name(self):
        return self._name

    # 屬性的setter (注:每個property都會生成一個對應的@xxx.setter)
    @name.setter
    def name(self, value):
        self._name = value

    # 介面的方法實現
    def run(self):
        print(self.name + " is running")


dog1 = Dog("a")
dog1.run()
dog2 = Dog("b")
dog2.run()
print(isinstance(dog1, IRun))
print(id(dog1), id(dog2), id(dog1) == id(dog2))  # 判斷2個例項是否相等

  輸出:

a is running
b is running
True
4314753736 4314753792 False