1. 程式人生 > 實用技巧 >第三模組第24章 面向物件

第三模組第24章 面向物件

book.luffycity.com/python-book/index.html

https://www.cnblogs.com/linhaifeng/articles/6182264.html

1.3 程式設計正規化

# 程式設計正規化:
# 按照某種語法風格+資料結構+演算法來編寫程式

1.4 面向過程程式設計

面向過程: 核心是過程二字, 過程指的是解決問題的步驟, 設計一條流水線, 機械式的思維.

優點: 複雜的問題流程化, 進而簡單化

缺點: 可擴充套件性差

1.5 面向物件程式設計介紹

面向物件: 核心是物件二字, 物件是特徵與技能的結合體

優點: 可擴充套件性強

缺點: 程式設計複雜度高

應用場景: 使用者需求經常變化, 如網際網路應用, 遊戲, 企業內部應用

1.6 定義類與例項化出物件

類是一系列物件相似特徵與技能的結合體

強調: 站在不同的角度, 得到的分類是不一樣的

在現實世界中, 先有物件, 後有類

在程式中, 先定義類, 後呼叫類來產生物件

# 先定義類
class LuffyStudent:
    school = 'luffycity'
    def learn(self):
        print('is learning')
    def eat(self):
        print('is eating')
    def sleep(self):
        print('is sleeping')
# 後例項化出物件
stu1 = LuffyStudent() stu2 = LuffyStudent() print(stu1) # <__main__.LuffyStudent object at 0x000001EB53893D68> print(stu2) # <__main__.LuffyStudent object at 0x000001EB538C6FD0>

1.7 如何使用類

'''
類在定義階段就會執行程式碼
類內定義的變數稱為資料屬性, 定義的函式稱為函式屬性
'''
class LuffyStudent:
    school = 'luffycity'  # 資料屬性
    def
learn(self): # 函式屬性 print('is learning') def eat(self): print('is eating') def sleep(self): print('is sleeping') # 在類的定義階段程式碼就會執行 print(LuffyStudent.__dict__) # 檢視類的名稱空間 ''' 結果: {'__module__': '__main__',
'school': 'luffycity', 'learn': <function LuffyStudent.learn at 0x0000019A2BC41488>, 'eat': <function LuffyStudent.eat at 0x0000019A2BC41A60>, 'sleep': <function LuffyStudent.sleep at 0x0000019A2BC41B70>, '__dict__': <attribute '__dict__' of 'LuffyStudent' objects>, '__weakref__': <attribute '__weakref__' of 'LuffyStudent' objects>, '__doc__': None}
''' # 1. 檢視類的屬性 print(LuffyStudent.school) # luffycity print(LuffyStudent.learn) # <function LuffyStudent.learn at 0x0000019A2BC41488> # 2. 增加屬性 LuffyStudent.country = 'china' # 3. 刪除屬性 del LuffyStudent.country # 4. 修改屬性 LuffyStudent.school = 'Luffycity'

1.8 如何使用物件

# __init__方法用來為物件定製物件獨有的特徵
class LuffyStudent:
    school = 'luffycity'  # 物件共有的特徵
    def __init__(self, name, gender, age):  # 例項化物件時自動呼叫本方法
        self.name = name
        self.gender = gender
        self.age = age
    def learn(self):
        print('is learning')
    def eat(self):
        print('is eating')
    def sleep(self):
        print('is sleeping')
stu1 = LuffyStudent('egon','male',18)

# 加上__init__後, 例項化的步驟
# 1. 先產生一個空物件
# 2. 觸發Luffycity.__init__, 將空物件傳給self, 並將後面的引數傳進來

print(stu1.__dict__)  # 檢視物件stu1的名稱空間
# 結果: {'name': 'egon', 'gender': 'male', 'age': 18}

# 1.檢視物件的屬性
print(stu1.name)

# 2. 修改物件的屬性
stu1.name = 'alex'

# 3. 刪除
del stu1.name

# 4. 增加
stu1.name = 'egon'

1.9 屬性查詢與繫結方法

class LuffyStudent:
    school = 'luffycity'
    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age
    def learn(self, x):
        print('%s is learning %s'%(self.name,x))
    def eat(self):
        print('%s is eating'%self.name)
    def sleep(self):
        print('%s is sleeping'%self.name)
stu1 = LuffyStudent('egon','male',18)
stu2 = LuffyStudent('alex','male',28)

# 類中的資料屬性: 是所有物件共有的
print(LuffyStudent.school, id(LuffyStudent.school))  # luffycity 2210760573872, 類可以訪問
print(stu1.school, id(stu1.school))  # luffycity 2210760573872, 物件可以訪問

# 類中的函式屬性: 是繫結給物件使用的, 繫結到不同的物件是不同的繫結方法, 物件呼叫繫結方法時會將物件本身當成第一個引數傳入
print(LuffyStudent.learn)  # <function LuffyStudent.learn at 0x00000214BB8E1B70>
print(stu1.learn)  # <bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x00000214BB8DF978>>
print(stu2.learn)  # <bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x00000214BB8DF9B0>>

LuffyStudent.learn(stu1, '英語')  # 類名呼叫函式, 需將物件傳進去, egon is learning 英語
stu1.learn('數學')  # 物件呼叫方法, 無需傳物件, 只需傳self後的引數, egon is learning 數學
stu2.learn('語文')  # alex is learning 語文

# 類中定義的函式屬性, 在未經處理的情況下, 不是給類用的, 是繫結給物件使用的.
# 如果物件中和類中的屬性存在重名, 則優先查詢物件自己中的, 沒有則去類中找, 自己的類中沒有則去父類中去找, 都找不到會報錯, 不會去全域性中找.

1.10python中一切皆物件

  1. 站在不同的角度, 定義出來的類是截然不同的

  2. 現實中的類不完全等於程式中的類

  3. 有時為了變成需要, 程式中可能會出現現實中不存在的類

# python中一切皆物件, python3中統一了類與型別的概念, 型別就是類.
print(type([1,2]))  # <class 'list'>
l = [1, 2, 3]  # l = list([1, 2, 3]), 類list例項化物件
l.append(5)  #l物件呼叫繫結方法append

1.11 面向物件可擴充套件性總結

1.12 小練習1

'''
練習1:
編寫一個學生類, 產生一堆學生物件
要求:
有一個計數器(屬性), 統計總共例項了多少物件
'''
class Student:
    school = 'luffycity'
    count = 0
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
        # self.count += 1  會為物件建立私有屬性count
        Student.count+=1
    def learn(self):
        print('%s is learning' %self.name)
stu1 = Student('egon', 'male', 18)
stu2 = Student('alex', 'female', 28)
print(Student.count)  # 2
print(stu1.count)  # 2
print(stu2.count)  # 2

1.13 小練習2

'''
練習2:
模仿王者榮耀定義兩個英雄類
要求:
英雄需要有暱稱, 攻擊力, 生命值等屬性
例項化出兩個英雄物件
英雄之間可以互毆, 被毆打的一方掉血, 血量小於0則判定為死亡.
'''
class Garen:
    camp = 'Demacia'
    def __init__(self, nickname, life_value, aggresivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggresivity = aggresivity
    def attack(self, enemy):
        enemy.life_value -= self.aggresivity
class Riven:
    camp = 'Noxus'
    def __init__(self, nickname, life_value, aggresivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggresivity = aggresivity
    def attack(self, enemy):
        enemy.life_value -= self.aggresivity
g1 = Garen('草叢倫', 100, 30)
r1 = Riven('瑞文', 80, 50)
g1.attack(r1)
print(r1.life_value)  # 50

1.14 繼承與重用性

'''
什麼是繼承?
繼承指的是類與類之間的關係, 是一種'什麼是什麼的關係', 繼承的功能之一就是用來解決程式碼重用問題.
繼承是一種建立新類的方式, 在python中, 新建的類可以繼承一個或多個父類, 父類又可以稱為基類或超類, 新建的類稱為派生類或子類.
'''
class ParentClass1:
    pass
class ParentClass2:
    pass
class SubClass1(ParentClass1):
    pass
class SubClass2(ParentClass1, ParentClass2):
    pass
print(SubClass1.__bases__)  # 檢視父類, (<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__)  # (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)


class Hero:
    def __init__(self, nickname, life_value, aggresivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggresivity = aggresivity
    def attack(self, enemy):
        enemy.life_value -= self.aggresivity
class Garen(Hero):
    camp = 'Demacia'
class Riven(Hero):
    camp = 'Noxus'
g1 = Garen('草叢倫', 100, 30)
r1 = Riven('瑞文', 80, 50)
g1.attack(r1)
print(g1.nickname)  # 從物件自己這找, 如果沒有則去類中找, 如果沒有則去父類中找, 否則報錯, 不會去全域性找.
print(r1.life_value)  # 50

# 屬性查詢小練習
class Foo:
    def f1(self):
        print('from Foo.f1')
    def f2(self):
        print('from FOO.f2')
        self.f1()  # b.f1()
class Bar(Foo):
    def f1(self):
        print('from Bar.f1')
b = Bar()
b.f2()
'''
結果:
from FOO.f2
from Bar.f1
'''

1.15 派生

# 如果子類中派生出新的屬性, 則以新的屬性為主
class Hero:
    def __init__(self, nickname, life_value, aggresivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggresivity = aggresivity
    def attack(self, enemy):
        enemy.life_value -= self.aggresivity
class Garen(Hero):
    camp = 'Demacia'
    def attack(self, enemy):
        print('from Garen class')
class Riven(Hero):
    camp = 'Noxus'
g1 = Garen('草叢倫', 100, 30)
r1 = Riven('瑞文', 80, 50)
g1.attack(r1)
print(g1.nickname)  # 從物件自己這找, 如果沒有則去類中找, 如果沒有則去父類中找, 否則報錯, 不會去全域性找.
print(r1.life_value)  # 80

1.16 繼承的實現原理

python到底是如何實現繼承的? 對於定義的每一個類, python會計算出一個方法解析順序(MRO)列表, 該列表是一個簡單的所有基類的線性排序列表, 例如:

>>> F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, 
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

為了實現繼承, python會在MRO列表上從左到右開始查詢基類, 查詢到第一個匹配這個屬性的類為止, 而這個MRO列表的構造是通過一個C3線性演算法來實現的. 它實際上就是合併所有父類的MRO列表並遵循如下三條準則:

  1. 子類會先於父類被檢查

  2. 多個父類會根據它們在列表中的順序被檢查

  3. 如果對下一個類存在兩個合法的選擇, 選擇第一個父類.

在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那麼屬性的查詢方式有兩種,分別是:深度優先和廣度優先

python中類分為兩種: 新式類和經典類, python2中有這兩種分法, python3中只有新式類.

python2中的經典類: 沒有繼承object的類, 以及它的子類都稱為經典類.

python2中的新式類: 繼承了object的類, 以及它的子類都稱為新式類.

python3中預設繼承object類, 都是新式類

class Foo:
    pass
print(Foo.__bases__)  # (<class 'object'>,)

class Foo:
    pass
print(Foo.__bases__)  # (<class 'object'>,)

# 驗證多繼承情況下的屬性查詢
class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以檢視線性列表,經典類沒有這個屬性
'''
結果:
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>,
 <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, 
 <class 'object'>)
'''

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

1.17 在子類中重用父類的方法或屬性

# 在子類派生出的新的方法中重用父類的方法, 有兩種實現方式:
# 方式一: 指名道姓(不依賴繼承)
# 方式二: super()(依賴繼承), super會在MRO列表中尋找關係
class Hero:
    def __init__(self, nickname, life_value, aggresivity):
        self.nickname = nickname
        self.life_value = life_value
        self.aggresivity = aggresivity
    def attack(self, enemy):
        enemy.life_value -= self.aggresivity
class Garen(Hero):
    camp = 'Demacia'
    def __init__(self, nickname, life_value, aggresivity, weapon):
        # Hero.__init__(self, nickname, life_value, aggresivity)   # 指明道姓的方式
        super(Garen, self).__init__(nickname, life_value, aggresivity)
        # super(Garen, self)建立了一個物件, python2中需要這麼寫, 但是在python3中可以簡寫成以下方式:
        # super().__init__(nickname, life_value, aggresivity)
        self.weapon = weapon
    def attack(self, enemy):
        # Hero.attack(self, enemy)  # 指明道姓的方式, 不依賴於繼承關係
        super(Garen, self).attack(enemy)
        print('from Garen class')
class Riven(Hero):
    camp = 'Noxus'
g1 = Garen('草叢倫', 100, 30, '金箍棒')
r1 = Riven('瑞文', 80, 50)
g1.attack(r1)
print(r1.life_value)  # 50

class A:
    def f1(self):
        print('from A')
        super().f1()
class B:
    def f1(self):
        print('from B')
class C(A, B):
    pass
print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
c = C()
c.f1()
'''
結果:
from A
from B
'''
# 說明, 在查詢時基於C的MRO列表進行查詢.

1.18 組合

# 組合: 解決誰有誰的類之間的關係.
class Person:
    school = 'luffycity'
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
class Teacher(Person):
    school = 'luffycity'
    def __init__(self, name, age, gender, level, salary):
        super().__init__(name, age, gender)
        self.level = level
        self.salary = salary
    def teach(self):
        print('%s is teaching' %self.name)
class Student(Person):
    school = 'luffycity'
    def __init__(self, name, age, gender, clas):
        super().__init__(name, age, gender)
        self.clas = clas
    def teach(self):
        print('%s is teaching' %self.name)
class Course:
    def __init__(self, cname, price, period):
        self.cname = cname
        self.price = price
        self.period = period
    def tell_info(self):
        print('課程名<%s>'%self.cname)

t1 = Teacher('alex', 18, 'female', 10, 3000)
s1 = Student('egon', 28, 'female', '一班')
c1 = Course('語文', 100, 20)
c2 = Course('數學', 200, 50)
t1.course1 = c1  # t1的屬性指向了一個物件
print(t1.course1.cname)
t1.course1.tell_info()

t1.course2 = c2  # t1的屬性指向了一個物件
print(t1.course2.cname)
t1.course2.tell_info()

s1.courses = []
s1.courses.append(c1)
s1.courses.append(c2)

1.19 抽象類與歸一化

import abc
class Animal(metaclass=abc.ABCMeta):  # 抽象類, 只能被繼承, 不能被例項化. 其作用是規範子類.
    all_type = 'animal'
    @abc.abstractmethod
    def run(self):
        pass
    @abc.abstractmethod
    def eat(self):
        pass
class People(Animal):
    def run(self):  # 必須包含父類中的run和eat方法.
        print('People is running')
    def eat(self):
        print('People is eating')
class Pig(Animal):
    def run(self):
        print('Pig is running')
    def eat(self):
        print('Pig is eating')
peop1 = People()
pig1 = Pig()
peop1.run()  # People is running
pig1.run()  # Pig is running

1.20 多型與多型性

# 多型:  同一類事物的多種形態
import abc
class Animal(metaclass=abc.ABCMeta):
    all_type = 'animal'
    @abc.abstractmethod
    def run(self):
        pass
class People(Animal):
    def run(self):
        print('People is running')
class Pig(Animal):
    def run(self):
        print('Pig is running')
# 多型性: 指的是可以在不考慮物件型別的情況下直接使用物件
# peop1, pig1都是動物, 只要是動物肯定有run方法, 於是我們可以不用考慮它們具體是什麼型別, 可以直接使用
peop1 = People()
pig1 = Pig()
peop1.run()  # People is running
pig1.run()  # Pig is running
# 以上稱為動態多型性
# 靜態多型性: 如任何型別都可以使用運算子+進行運算
# 更進一步, 我們可以定義一個統一的介面進行使用
def func(animal):
    animal.run()
func(peop1)
func(pig1)
# 以上示例中繼承了同一父類, 子類受父類的約束
'''
多型性的好處:
1. 增加了程式的靈活性
    以不變應萬變, 不論物件千變萬化, 使用者都是同一形式去呼叫
2. 增加了程式的可擴充套件性
'''

class File:
    def read(self):
        print('file read')
    def write(self):
        print('file write')
class Disk:
    def read(self):
        print('disk read')
    def write(self):
        print('disk write')
class Text:
    def read(self):
        print('text read')
    def write(self):
        print('text write')
f = File()
d = Disk()
t = Text()
def f1(x):
    x.read()
    x.write()
f1(f)
f1(d)
f1(t)
# 以上示例沒有繼承同一父類, 但是彼此存在相似之處.

'''
python崇尚鴨子型別, 只要長得像鴨子, 就是鴨子.
類與類之間不繼承同一父類, 只要有相似之處, 就可以了.
'''

1.21 封裝之如何隱藏屬性

class A:
    __x = 1  # 變形隱藏  _A__x = 1
    def __init__(self, name):
        self.__name = name
    def __foo(self):  # 定義階段, 檢測語法時轉化: def _A__foo(self):
        print('run foo')
    def bar(self):
        self.__foo()  # self._A__foo()
        print('from bar')
# print(A.__x)  # AttributeError: type object 'A' has no attribute '__x', 說明已經隱藏了
a = A('egon')
print(A.__dict__)
print(a.__dict__)  # {'_A__name': 'egon'}
a.bar()  # run foo  from bar
a._A__foo()  # run foo

'''
這種變形的特點:
    1. 在類外部無法直接通過obj.__AttriName訪問
    2. 在類內部可以直接通過obj.__AttriName訪問
    3. 子類無法覆蓋父類__開頭的屬性
'''

class Foo:
    def __func(self):  # _Foo__func
        print('from foo')
class Bar(Foo):
    def __func(self):  # _Bar__func
        print('from bar')
# 加__會在類的定義階段進行變形

'''
總結這種變形需要注意的問題:
1. 這種機制並沒有真正意義上限制我們從外部直接訪問屬性, 知道了類名和屬性名就可以拼出名字:
_類名__屬性名,然後就可以訪問了.
2. 變形的過程只在類的定義階段發生一次, 在定義後的賦值操作, 不會變形.
3. 在繼承中, 父類如果不想讓子類覆蓋自己的方法, 可以將方法定義為私有
'''
# 驗證問題2
class B:
    __x = 1
    def __init__(self, name):
        self.__name = name
B.__y = 2
print(B.__dict__) # {..., '__y': 2}
# 驗證問題3
class C:
    def foo(self):
        print('C.foo')
    def bar(self):
        print('C.bar')
        self.foo()
class D(C):
    def foo(self):
        print('D.foo')
d = D()
d.bar()  #C.bar  D.foo

class C:
    def __foo(self):  # _C__foo
        print('C.foo')
    def bar(self):
        print('C.bar')
        self.__foo()  # self._C__foo
class D(C):
    def __foo(self):  # _D__foo
        print('D.foo')
d = D()
d.bar()  #  C.bar  C.foo

1.22 封裝的意義

# 一, 封裝資料屬性的目的: 明確地區分內外, 控制外部對隱藏屬性的操作行為.
class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def tell_info(self):
        print('Name:<%s>, Age:<%s>' %(self.__name, self.__age))
    def set_info(self, name, age):
        if not isinstance(name,str):
            print('名字必須是字串型別')
            return
        if not isinstance(age, int):
            print('年齡必須是數字型別')
            return
        self.__name = name
        self.__age = age
p = People('egon', 18)
p.tell_info()
# 加了__後則無法直接訪問或修改屬性, 必須通過特定的介面(如tell_info, set_info)來間接進行訪問和修改.
# 介面上附加一些邏輯, 來控制外部對隱藏屬性的訪問和修改.

# 二, 封裝方法的目的: 隔離複雜度
# 不讓直接接觸到零散的方法, 如__card, __auth, 可以直接接觸到封裝好的方法, 如withdraw.
class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('使用者認證')
    def __input(self):
        print('輸入取款金額')
    def __print(self):
        print('列印賬單')
    def __take_money(self):
        print('取款')
    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print()
        self.__take_money()
a = ATM()
a.withdraw()

1.23 封裝與可擴充套件性

class Room:
    def __init__(self, name, owner, height, weight, length):
        self.name = name
        self.owner = owner
        self.__height = height
        self.__weight = weight
        self.__length = length
    def tell_area(self):
        return self.__weight * self.__length
r=Room('衛生間','alex',10,10,10)
print(r.tell_area())
# 使用者只需要記住介面即可, 無需關注裡面的原理.

1.24 property的使用

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height
    @property  # 將bmi的呼叫方式由bmi()轉為bmi, 使使用者感覺呼叫的不是一個功能, 而是一個屬性.
    def bmi(self):
        return self.weight / (self.height ** 2)
p = People('egon', 75, 1.81)
print(p.bmi)
# 注意: bmi本質是一個方法, 不能對其進行賦值.

class People:
    def __init__(self, name):
        self.__name = name
    @property
    def name(self):
        return self.__name
# 注意: name本質是一個方法, 不能對其進行賦值.
# 不過, 通過以下方法可以實現賦值.
    @name.setter
    def name(self, val):
        if not isinstance(val, str):
            print('名字必須是字串型別')
        self.__name = val
    @name.deleter
    def name(self):
        print('不允許刪除')
p = People('egon')
p.name = 'EGON'
print(p.name)  # EGON
del p.name  # 不允許刪除

1.25 繫結方法與非繫結方法介紹

'''
在類內部定義的函式, 分為兩大類:
    一: 繫結方法: 繫結給誰, 就應該由誰來呼叫, 誰來呼叫就自動把誰當作第一個引數傳入.
        繫結到物件的方法: 在類內定義的沒有被任何裝飾器修飾的方法
        繫結到類的方法: 在類內定義的被裝飾器classmethod修飾的方法
    二: 非繫結方法: 沒有自動傳值這麼一說, 就是在類中定義的一個普通工具, 物件和類都可以使用.
        非繫結方法: 不與類或者物件繫結
'''
class Foo:
    def __init__(self, name):
        self.name = name
    def tell(self):  # 繫結到物件的方法
        print('名字是%s' %self.name)
    @classmethod
    def func(cls):  # 繫結到類的方法
        print(cls)
    @staticmethod
    def func1(x, y):  #括號裡面可以寫引數, 也可以不寫引數. 沒有自動傳遞的引數.
        print(x+y)

f = Foo('egon')
print(Foo.tell)  # <function Foo.tell at 0x0000026860001A60>
Foo.tell(f) # 此處, 必須對self進行傳值, self不能自動傳值.
print(f.tell)  # <bound method Foo.tell of <__main__.Foo object at 0x000002B04F8B3CC0>>
f.tell()

print(Foo.func)  # <bound method Foo.func of <class '__main__.Foo'>>
Foo.func()  # <class '__main__.Foo'>

print(Foo.func1)  # <function Foo.func1 at 0x000002BF6F551BF8>
print(f.func1)  # <function Foo.func1 at 0x000002BF6F551BF8>
Foo.func1(1,2)  # 3
f.func1(1,3)  # 4

1.26 繫結方法與非繫結方法的使用

import settings
import hashlib
import time
class People:
    def __init__(self, name, age, gender):
        self.id = self.create_id()
        self.name = name
        self.age = age
        self.gender = gender
    def tell_info(self):  # 繫結到物件的方法
        print('Name: %s, Age:%s, Gender:%s'%(self.name, self.age, self.gender))
    @classmethod
    def from_conf(cls):
        obj = cls(settings.name, settings.age, settings.gender)
        return obj
    @staticmethod
    def create_id():
        time.sleep(0.01)
        m = hashlib.md5(str(time.time()).encode('utf-8'))
        return m.hexdigest()

p1 = People('egon', 18, 'male')
# 繫結給物件, 就應該由物件來呼叫, 自動將物件本身當作第一個引數傳入
p1.tell_info()  # Name: egon, Age:18, Gender:male

# 繫結給類, 就應該由類來呼叫, 自動將類本身當作第一個引數傳入
p2 = People.from_conf()
p2.tell_info()  # Name: alex, Age:10, Gender:male

# 非繫結方法, 不與類或者物件繫結, 誰都可以來呼叫, 沒有自動傳值一說.
p3 = People('aaa', 12, 'female')
p4 = People('bbb', 22, 'male')
p5 = People('ccc', 18, 'female')
print(p3.id)  # e4487e9a7fed738946cd85e9d38bd795
print(p4.id)  # b02113fa5ebd6a9e1c50b25c2e7c48f3
print(p5.id)  # 8f157e0fd5d76047055fc5f77fc90b98
# settings.py
name = 'alex'
age = 10
gender = 'male'

1.27 反射

# 反射: 通過字串對映到物件的屬性
class People:
    country = 'china'
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def talk(self):
        print('%s is talking' %self.name)
obj = People('egon', 18)

# 1. hasattr
print(hasattr(obj,'name'))  # True, 判斷obj下是否有name屬性
print(hasattr(obj,'talk'))  # True, 判斷obj下是否有talk
# 2. getattr
print(getattr(obj, 'name'))  # egon  有則返回結果, 沒有則報錯, 如果不想讓其報錯, 可以使用default
print(getattr(obj, 'namex', None))  # None,  沒有返回None
# 3. setattr
setattr(obj, 'age', 50)
print(obj.age)  # 50
setattr(obj, 'gender', 'male')
print(obj.__dict__)  # {'name': 'egon', 'age': 50, 'gender': 'male'}
print(obj.gender)  # male
# 4. delattr
delattr(obj, 'gender')
print(obj.__dict__)  # {'name': 'egon', 'age': 50}
# 以上方法也適用於類
print(getattr(People, 'country'))  # china

# 反射的應用:
class Service:
    def run(self):
        while True:
            inp = input('>>>:').strip()
            cmds = inp.split()
            if hasattr(self, cmds[0]):
                func=getattr(self, cmds[0])
                func(cmds)
    def get(self, cmds):
        print('get...', cmds)
    def put(self, cmds):
        print('put...', cmds)
obj = Service()
# 接收使用者輸入並觸發後面的方法
obj.run()
'''
結果:
>>>:get
get... ['get']
>>>:get a.txt
get... ['get', 'a.txt']
'''

1.28 內建方法介紹

https://www.cnblogs.com/linhaifeng/articles/6204014.html

一: isinstance(obj,cls)和issubclass(sub,super)

isinstance(obj,cls)檢查是否obj是否是類 cls 的物件

1 class Foo(object):
2     pass
3  
4 obj = Foo()
5  
6 isinstance(obj, Foo)

issubclass(sub, super)檢查sub類是否是 super 類的派生類

1 class Foo(object):
2     pass
3  
4 class Bar(Foo):
5     pass
6  
7 issubclass(Bar, Foo)

二: item系列

# item系列
# 將物件模擬地像字典
class Foo:
    def __init__(self, name):
        self.name = name
    def __getitem__(self, item):
        return self.__dict__.get(item)
    def __setitem__(self, key, value):
        self.__dict__[key] = value
    def __delitem__(self, key):
        # del self.__dict__[key]
        self.__dict__.pop(key)
obj = Foo('egon')
# 檢視屬性
print(obj['name'])
# 設定屬性
obj['gender'] = 'male'
# 刪除屬性
del obj['name']
# __str__方法:
class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return '<name:%s, age:%s>'%(self.name, self.age)
obj = People('egon', 18)
print(obj)  # 列印物件時觸發物件下的__str__方法, 並且要求返回的一定是字串型別的資料
# __del__方法
# 在python自動回收物件之前觸發__del__, 回收跟物件相關聯的某些資源.
class Open:
    def __init__(self, filename):
        print('open file...')
        self.filename = filename
    def __del__(self):
        print('回收作業系統等資源: f.close()')
f = Open('settings.py')
print('--------main---------')
# del f  會手動觸發__del__, 不使用這種方法則自動呼叫__del__
'''
結果:
open file...
--------main---------
del...
'''

1.29 元類

https://www.cnblogs.com/linhaifeng/articles/8029564.html

# 元類介紹
'''
儲備知識exec命令的使用, 有三個引數
引數1: 字串形式的命令
引數2: 全域性作用局(字典形式), 如果不指定就預設使用globals()
引數3: 區域性作用局(字典形式), 如果不指定就預設使用locals()
'''
g = {
    'x':1,
    'y':2
}
l = {}
exec('''
global x,m
x = 10
m = 100
z = 3
''', g, l)
print(g)
# 可以將exec當作函式看待

'''
python中一切皆物件, 物件可以怎麼用?
1. 都可以被引用, x = obj
2. 都可以當作函式的引數傳入
3. 都可以當作函式的返回值
4. 都可以當作容器型別的元素
'''
# 類也是物件, 物件是經過例項化類得來的, 那麼Foo也應該是由類例項化得到的.
class Foo:  # Foo = type(...)
    pass
obj = Foo()
print(type(obj))  # <class '__main__.Foo'>
print(type(Foo))  # <class 'type'>

#產生類的類稱之為元類, 預設所有用class定義的類, 它們的元類是type.
# 定義類的兩種方式:
# 方式一: class
class Chinese:  # Chinese  = type(...)
    country = 'china'
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def talk(self):
        print('%s is talking' %self.name)
# 方式二:
# 定義類的三要素: 類名, 類的基類們, 類的名稱空間
class_name = 'Chinese'
class_bases = (object,)
class_body = '''
country = 'china'
def __init__(self, name, age):
    self.name = name
    self.age = age
def talk(self):
    print('%s is talking' %self.name)
'''
class_dic = {}
exec(class_body,globals(),class_dic)
print(class_dic)  # {'country': 'china', '__init__': <function __init__ at 0x0000022563A21EA0>, 'talk': <function talk at 0x0000022563A380D0>}
Chinese1 = type(class_name,class_bases,class_dic)
print(Chinese1)  # <class '__main__.Chinese'>
obj1 = Chinese1('alex', 20)
print(obj1, obj1.name, obj1.age)  # <__main__.Chinese object at 0x0000018894F4FF98> alex 20
# 定義元類控制類的建立
class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('類名的首字母必須大寫')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必須有註釋, 且註釋不能為空')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

class Chinese(object,metaclass=Mymeta):  # Chinese = Mymeta(class_name, class_bases, class_dic)
    ''''''
    country = 'china'
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def talk(self):
        print('%s is talking' %self.name)
# 自定義元類控制類的例項化行為
# 知識儲備__call__方法
class Foo:
    def __call__(self, *args, **kwargs):
        print(args, kwargs)
obj = Foo()
obj(1, 2, x = 'aa')  # (1, 2) {'x': 'aa'}, 在呼叫物件時會自動觸發執行__call__方法
# 元類內部也應該有一個__call__方法, 會在呼叫Foo時觸發執行.

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('類名的首字母必須大寫')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必須有註釋, 且註釋不能為空')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
    def __call__(self, *args, **kwargs):
        # 第一件事: 先造出一個空物件obj
        obj = object.__new__(self)
        # 第二件事: 初始化obj
        self.__init__(obj,*args,**kwargs)
        # 第三件事: 返回obj
        return obj

class Chinese(object,metaclass=Mymeta):  # Chinese = Mymeta(class_name, class_bases, class_dic)
    '''
    xxx
    '''
    country = 'china'
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def talk(self):
        print('%s is talking' %self.name)
obj = Chinese('egon', age=18)
print(obj.__dict__)
# 自定義元類控制類的例項化行為的應用
# 單例模式
# 實現方式一:
class MySQL:
    __instance = None
    def __init__(self):
        self.host = '127.0.0.1'
        self.port = 3306
    @classmethod
    def singleton(cls):
        if not cls.__instance:
            obj = cls()
            cls.__instance=obj
        return cls.__instance
obj1 = MySQL.singleton()
obj2 = MySQL.singleton()

# 實現方式二: 元類的方式
class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('類名的首字母必須大寫')
        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必須有註釋, 且註釋不能為空')
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if not self.__instance:
            obj = object.__new__(self)
            self.__init__(obj)
            self.__instance = obj
        return self.__instance
class Mysql(object,metaclass=Mymeta):
    '''
    xxx
    '''
    def __init__(self):
        self.host = '127.0.0.1'
        self.port = 3306
obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()
print(obj1 is obj2 is obj3)  # True

面向物件實戰:

https://www.cnblogs.com/linhaifeng/articles/6182264.html#_label15