目錄
面向對象概述
編程發展至今有面向過程編程、函數式編程、面向對象編程三大流派,未來不知道會不會有面向數據的編程。
Python支持面向對象編程,爲什麼要面向對象呢?面向對象是個啥?我的粗淺理解,就是將一系列相干的數據、方法封裝到一起形成實例,並限制實例的數據、方法調用方式,通過對這個實例的控制達到簡化編程過程,提高代碼可讀性的目的。比如,一個之前學過C++,怎麼實現的呢,數據用struct結構封裝,成員函數就是普通的函數但是入參多了個this指針,在調用函數的時候取struct實例的地址,這樣就實現了成員函數。這就是面向對象的實現方法。Java也不例外。那麼Python呢?
面向對象的一種實現
Python中,屬性數據可以用字典封裝,函數可以作爲對象被封裝在字典裏面,函數可以嵌套從而限制其不能在作用域外被調用。根據這個思想,我們可以定義一個狗函數dog,嵌套函數定義狗的兩個方法bark、wag_tail,再用字典封裝狗的屬性。這樣就實現了面向對象的設計。調用函數的時候,傳入自己作爲參數,實際上就是類似C++裏面的this指針。
def dog(name, type, gender):
def bark(dog):
"""
狗會叫
:return:
"""
print('%s is wang!wang!' % dog['name'])
pass
def wag_tail(dog):
"""
狗會搖尾巴
:return:
"""
print('%s wag tail %s' % (dog['name'], dog['type']))
pass
def init():
"""
初始化狗,名字,品種,公母
會叫,會搖尾巴
:return:
"""
dog_property = {
'name': name,
'type': type,
'gender': gender,
'bark': bark, # 將會叫函數作爲屬性
'wag_tail': wag_tail, # 將會搖尾巴函數作爲屬性
}
return dog_property
pass
ret = init()
return ret
pass
d1 = dog('Snoopy', '哈士奇', 'female')
print(d1)
d1['bark'](d1)
d1['wag_tail'](d1)
# {'name': 'Snoopy', 'type': '哈士奇', 'gender': 'femal', 'bark': <function dog.<locals>.bark at 0x00000000028A50D0>, 'wag_tail': <function dog.<locals>.wag_tail at 0x0000000003BAA620>}
# Snoopy is wang!wang!
# Snoopy wag tail 哈士奇
d2 = dog('旺財', '柴犬', 'male')
print(d2)
d2['bark'](d2)
d2['wag_tail'](d2)
# {'name': '旺財', 'type': '柴犬', 'gender': 'mail', 'bark': <function dog.<locals>.bark at 0x0000000003BAA6A8>, 'wag_tail': <function dog.<locals>.wag_tail at 0x0000000003BAA730>}
# 旺財 is wang!wang!
# 旺財 wag tail 柴犬
類的相關知識
類描述一類事物共有的數據(屬性)和動作(方法)。類本身是沒有具體信息的,是一個抽象的類型。類可以具體化到對象。
class Dog:
"""
這是一個狗類
"""
fur = 'property fur'
def bark():
print('from bark')
pass
def wag_tail(self):
print('from wag_tail')
pass
pass
print(Dog)
# <class '__main__.Dog'>
# 調用類的方法
Dog.bark()
# from bark
Dog.wag_tail('123')
# from wag_tail
# 比較dir函數的輸出和類__dict__的輸出
# 特別是自定義的fur、bark、wag_tail
# dir只列出屬性的名字
# __dict__累出屬性的名字和值
print(dir(Dog))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
# '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
# '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
# '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
# '__str__', '__subclasshook__', '__weakref__', 'bark', 'fur', 'wag_tail']
print(Dog.__dict__)
# {'__module__': '__main__',
# '__doc__': '\n 這是一個狗類\n ',
# 'fur': 'property fur',
# 'bark': <function Dog.bark at 0x0000000003BBA620>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BBA6A8>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# Dog類裏面有fur屬性,可以用類名直接調用
print(Dog.fur)
# Dog的__dict__屬性,查看類的屬性字典
# 上面直接調用Dog.fur實際上也是用屬性字典實現的
print(Dog.__dict__['fur'])
print(Dog.__name__) # 類的名字Dog
print(Dog.__doc__) # 類的說明文檔,也就是類定義後緊跟的說明"""這是一個狗類""""
print(Dog.__bases__) # Python支持多繼承,後面仔細研究,__bases__以元組形式列出了所以父類(<class 'object'>,)
對象的相關知識
我們在面向對象的一種實現那一節用函數實現了面向對象的設計。Python內置了面向對象編程的設計。
我們自己嵌套定義的初始化函數init,Python類裏面有內置的__init__方法來初始化,__init__必須有self入參,自動將實例本身傳給self。自動進行初始化和返回實例,因此__init__不用return。類創建實例的過程和函數調用類似。
對象只保存數據屬性,不保存方法屬性。這樣,每個對象存自己的數據,共享一份方法。
對象調用方法的時候,將對象本身傳給self參數。通過self來訪問對象各自的數據。
class Dog:
"""
這是一個狗類
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品種
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 搖尾巴' % self.name)
pass
pass
d1 = Dog('旺財', '中華田園犬', 'male')
print(d1)
# <__main__.Dog object at 0x00000000026D9BA8>
# __dict__查看對象的屬性字典
# 對象只保存數據屬性,不保存方法
print(d1.__dict__)
# {'name': '旺財', 'type': '中華田園犬', 'gender': 'male'}
# 調用對象的屬性
# 對象有的(__init__)直接調用 name
# 對象沒有的,到外面的作用域類裏面去找 fur
print(d1.name, d1.fur)
# 對象調用方法是調用類的方法,調用時將對象傳給self參數
Dog.bark(d1)
d1.bark()
# 旺財 汪汪!
面向對象屬性的查改增刪操作
Python的面向對象支持屬性的查改增刪。類的屬性、對象的屬性都可以查改增刪。
以前用Java知道可以查、改屬性,學了Python才知道屬性還可以增、刪,而且是數據屬性和方法屬性的查改增刪,真牛逼。
類屬性的查改增刪
我們做查改增刪,對比類的屬性字典__dict__的內容和調用情況。直接看代碼例子……
class Dog:
"""
這是一個狗類
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品種
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 搖尾巴' % self.name)
pass
def eat_food(self, food):
print('%s 吃 %s' % (self.name, food))
pass
pass
d1 = Dog('旺財', '中華田園犬', 'male')
d2 = Dog('123', '中華田園犬', 'male')
# ========== 類屬性的查 ==========
print(Dog.fur)
# property fur
print('查詢', Dog.__dict__)
# 查詢
# {'__module__': '__main__',
# '__doc__': '\n 這是一個狗類\n ',
# 'fur': 'property fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function Dog.eat_food at 0x0000000003BAA7B8>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# ========== 類屬性的改 ==========
Dog.fur = 'modify fur'
print(Dog.fur)
# 改方法屬性
def test(self):
print('%s 是 %s 品種' % (self.name, self.breed))
pass
# 方法屬於類,可以在類上改方法屬性
# 修改後調用eat_food其實調用的是test
Dog.eat_food = test
d1.eat_food()
# 旺財 是 中華田園犬 品種
# 可以看到eat_food的地址已經發生了變化,變成了test函數的地址
print(test)
# <function test at 0x00000000028950D0>
print('修改', Dog.__dict__)
# 修改
# {'__module__': '__main__',
# '__doc__': '\n 這是一個狗類\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function test at 0x00000000028B50D0>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>}
# ========== 類屬性的增 ==========
# 增加數據屬性——項圈
Dog.ring = 'leather'
# 增加方法屬性
def play_ball(self):
print('%s 玩球' % self.name)
pass
# 方法屬於類,可以在類上增加方法屬性play_ball
Dog.play_ball = play_ball
d1.play_ball()
# 旺財 玩球
# 可以看到增加屬性以後__dict__裏面增加了ring屬性、play_ball方法
print('增加', Dog.__dict__)
# 增加
# {'__module__': '__main__',
# '__doc__': '\n 這是一個狗類\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# 'eat_food': <function test at 0x00000000028C50D0>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>,
# 'ring': 'leather',
# 'play_ball': <function play_ball at 0x0000000003BAA7B8>}
# ========== 類屬性的刪 ==========
# 刪除數據屬性
del Dog.ring
# 刪除方法
# 方法屬於類,可以在類上刪除
del Dog.eat_food
# 注意比較刪除前後的屬性字典,刪除ring以後就沒有ring了,刪除以後就沒有eat_food方法了
print('刪除', Dog.__dict__)
# 刪除
# {'__module__': '__main__',
# '__doc__': '\n 這是一個狗類\n ',
# 'fur': 'modify fur',
# '__init__': <function Dog.__init__ at 0x0000000003BAA620>,
# 'bark': <function Dog.bark at 0x0000000003BAA6A8>,
# 'wag_tail': <function Dog.wag_tail at 0x0000000003BAA730>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>,
# 'play_ball': <function play_ball at 0x0000000003BAA7B8>}
對象屬性的查改增刪
我們做查改增刪,對比對象的屬性字典__dict__的內容和調用情況。直接看代碼例子……
class Dog:
"""
這是一個狗類
"""
fur = 'property fur'
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品種
self.gender = gender
pass
def bark(self):
print('%s 汪汪!' % self.name)
pass
def wag_tail(self):
print('%s 搖尾巴' % self.name)
pass
def eat_food(self, food):
print('%s 吃 %s' % (self.name, food))
pass
def play_ball(self, ball):
print('%s 玩 %s' % (self.name, ball))
pass
pass
d1 = Dog('旺財', '中華田園犬', 'male')
d2 = Dog('123', '中華田園犬', 'male')
# ========== 對象屬性的查 ==========
print(d1.name)
print(d2.name)
# 旺財
# 123
d1.play_ball('籃球')
d2.play_ball('籃球')
print(d1.play_ball)
# 旺財 玩 籃球
# 123 玩 籃球
# <bound method Dog.play_ball of <__main__.Dog object at 0x0000000003B9D8D0>>
# 打印d1.play_ball實際訪問的是類Dog.play_ball
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺財', 'breed': '中華田園犬', 'gender': 'male'}
# {'name': '123', 'breed': '中華田園犬', 'gender': 'male'}
# ========== 對象屬性的增 ==========
# 增加數據屬性
d1.age = 10
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺財', 'breed': '中華田園犬', 'gender': 'male', 'age': 10}
# {'name': '123', 'breed': '中華田園犬', 'gender': 'male'}
# 對象可以增加方法屬性嗎?
# 理論和技術上可以這麼做,那麼增加的方法就只屬於這個對象。
# 這違背了對象實例只有數據屬性的原則,不要這麼做!!!
# ========== 對衝屬性的改 ==========
d1.age = 17
print(d1.age)
# 17
# ========== 對象屬性的刪 ==========
del d1.age
del d2.name
print(d1.__dict__)
print(d2.__dict__)
# {'name': '旺財', 'breed': '中華田園犬', 'gender': 'male'}
# {'breed': '中華田園犬', 'gender': 'male'}
關於類、對象屬性容易混淆額或忽略的地方的說明
挺繞的,直接看代碼例子裏的註釋吧……
fur = 'yellow'
class Dog:
"""
這是一個狗類
"""
fur = 'property fur'
li = ['a', 'b']
li2 = ['x', 'y']
def __init__(self, name, breed, gender):
self.name = name
self.breed = breed # 品種
self.gender = gender
print('--->', fur)
pass
pass
# __init__裏面既不是self的也不是作用域內的ring
# 那麼就是全局的ring,和類已經沒有關係了
d1 = Dog('旺財', '中華田園犬', 'male')
d2 = Dog('123', '中華田園犬', 'male')
# ---> yellow
# ---> yellow
# 數據屬性就是屬於各自的
Dog.fur = 'yellow'
d1.fur = 'white'
d2.fur = 'gray'
print('類的毛:', Dog.fur)
print('對象d1的毛:', d1.fur)
print('對象d2的毛:', d2.fur)
# 類的毛: yellow
# 對象d1的毛: white
# 對象d2的毛: gray
# 由於li是類的屬性
# 通過d1調用li的append並沒有給對象新增任何屬性
# 所以修改的就是類的li,因此li都是['a','b','c']
d1.li.append('c')
print(Dog.li)
print(d1.li)
print(d2.li)
# ['a', 'b', 'c']
# ['a', 'b', 'c']
# ['a', 'b', 'c']
# 由於li2也是類的屬性
# 但是,d2.li2=是給d1對象新增了一個屬性,這與上面append不同
# 所以修改的是d2.li2的屬性
d1.li2 = [1, 2, 3]
print(Dog.li2)
print(d1.li2)
print(d2.li2)
# ['x', 'y']
# [1, 2, 3]
# ['x', 'y']
# 此時,再對d1和d2的li2操作,已經有本質的不同了
# d1的li2是d1自己的
# d2的li2是類的
# 這一點可以從對象的__dict__可以看出
d1.li2.append('c')
d2.li2.append('z')
print(Dog.li2)
print(d1.li2)
print(d2.li2)
print(d1.__dict__)
print(d2.__dict__)
# ['x', 'y', 'z']
# [1, 2, 3, 'c']
# ['x', 'y', 'z']
# {'name': '旺財', 'breed': '中華田園犬', 'gender': 'male', 'fur': 'white', 'li2': [1, 2, 3, 'c']}
# {'name': '123', 'breed': '中華田園犬', 'gender': 'male', 'fur': 'gray'}