Python 程式設計技巧(重點在於面向物件和魔法方法)
一、Python 一切皆物件
Python 的一切皆物件是非常徹底的,不管是函式還是類,都是物件,物件就有一些獨特的特性
1.賦值給一個變數 2.可以新增到集合物件中 3.都能作為函式的引數進行傳遞 4.都能當做函式的返回值
有需要Python學習資料的小夥伴嗎?小編整理【一套Python資料、原始碼和PDF】,感興趣者可以加學習群:548377875或者加小編微信:【mmp9972】反正閒著也是閒著呢,不如學點東西啦~~
1.賦值給一個變數
(1)函式賦值給一個變數
示例程式碼:
def add(name = "K0rz3n"): print name object_test = add object_test()
(2)類賦值給一個變數
class Person():
def __init__(self):
print "K0rz3n"
class_test = Person
class_test()
2.新增到集合物件中
def add(name = "K0rz3n"): print name class Person(): def __init__(self): print "K0rz3n" obj_list = [] obj_list.append(add) obj_list.append(Person) for item in obj_list: print item()
結果:
K0rz3n
None
K0rz3n
<__main__.Person instance at 0x0000000003277E08>
3.可以作為函式的返回值
函式作為返回值其實是 python 裝飾器的精髓
示例程式碼:
def add(name = "K0rz3n"): print name class Person(): def __init__(self): print "K0rz3n" def decorator(): print "dec success" return add test = decorator() test()
結果:
dec success
K0rz3n
二、type 和 object 的關係
1.type
type 這個類例項化了一切,包括 object 和 他自己
示例程式碼:
>>> type(1)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(type)
<class 'type'>
>>> type(object)
<class 'type'>
可以看到,1 是由 int 這個類生成的物件,int 這個類是由type 這個類生成的物件
結論:
type->class->obj
2.object
object 是最頂層的基類,所有的類都繼承了Object,包括type
示例程式碼:
>>> class Student():
... pass
...
>>> Student.__bases__
(<class 'object'>,)
>>> type.__bases__
(<class 'object'>,)
>>> object.__bases__
()
但是 object 是由 type 例項化的
示例程式碼:
>>> type(object)
<class 'type'>
總結:
type 例項化了一切,包括 object 和他自己,object 是所有類的基類(最頂層,包括type)。於是乎 Type 和 object 就形成了一個迴路,這其實也就是python 中類也是物件的原因(不僅繼承了object 還是type 這個類的例項)
三、Python 中的內建型別
1.物件的三個特徵
(1)身份
我們把身份理解成物件在記憶體中的地址
我們可以通過id()這個函式檢視一個物件在記憶體中的地址
示例程式碼:
>>> a= 1
>>> id(a)
1465347104
>>> a = []
>>> id(a)
2406326173512
(2)型別
比如有:
None (全域性唯一)
示例程式碼:
>>> a =None
>>> b = None
>>> id(a)
1464902800
>>> id(b)
1464902800
可以看到兩個變數都是指向了一個地址,說明其是全域性唯一的
數值
Int、float、bool、complex…
迭代型別
可以遍歷的
序列型別
list 、tuple 、str 、array 、range 、bytes…
對映(dict)
相當於字典,有 Key 和 value
集合
set 和 forzenset
上下文管理器
with 語句
其他
模組型別、class和例項、函式型別、方法型別、程式碼型別 、object 物件 、type 型別、ellipsis 型別
Python 有些型別是隱式的,主要是通過物件內建的魔法方法來判斷物件是什麼型別,這其實就是Python魔法方法的精髓,這在我們後面會詳細的講到
(3)值
這個我就不再細說了,相信大家都懂
四、Python 中的魔法函式
1.什麼是魔法函式
1.Python中為我們提供的以雙下劃線開頭和結尾的函式 2.魔法函式屬於全域性,在我們自定義的任意一個類中我們能新增任意一個魔法函式,來讓這個類有著不同的性質(來增強這個類的特性,簡單的說就相當於給這個類安裝了一個外掛)
在我們沒有定義魔法函式之前,我們想遍歷這個類中的員工,我們需要像這樣寫
示例程式碼:
class Company(object):
def __init__(self,employ_list):
self.employ = employ_list
company = Company(["Tom","Alice","Bob"])
employ = company.employ
for item in employ:
print item
結果:
Tom
Alice
Bob
但是我們在定義了一個魔法方法以後事情就變得非常的簡單
示例程式碼:
class Company(object):
def __init__(self,employ_list):
self.employ = employ_list
def __getitem__(self, item):
return self.employ[item]
company = Company(["Tom","Alice","Bob"])
for item in company:
print item
結果:
Tom
Alice
Bob
解釋:
實際上我們定義了 __getitem__
這個魔法方法以後,這個類例項化出來的物件就是一個可迭代的物件
2.__str__
和 __repr__
(1) __str__
Python 在使用 print 的時候會隱式的呼叫 str() 這個函式,而這個函式會隱式的呼叫 __str__
這個魔法方法,比如我們想改變一個物件的預設輸出格式,我們就需要在例項化這個物件的類中重寫 __str__
方法
示例程式碼:
class Company(object):
def __init__(self,employ_list):
self.employ = employ_list
def __str__(self):
return ','.join(self.employ)
company = Company(["Tom","Alice","Bob"])
print company
結果:
Tom,Alice,Bob
`
(2)__repr__
__repr__
是開發者模式下(互動式Python直譯器中在直接寫某個物件的時候會呼叫的函式),我們平時直接把物件寫在互動式解析器中的時候會輸出一些尖括號包裹的類或者物件就是隱式呼叫這個函式的結果,如果我們重寫了這個魔法方法就能改變輸出,這個和 上面的例子是一樣的,我就不再重新寫程式碼
五、Python 深入類和物件
1.鴨子型別和多型
一個物件如果他 走起來 叫起來 看起來 都像一個鴨子那我們就把他看成是一個鴨子,其實意思就是說我們關注的是這個物件內部實現了什麼方法,實現的方法決定了我們將其看成是什麼
1.比如extend(),它裡面並沒有要求傳入某一個具體的型別,而是要求傳入一個可迭代物件,所以很多的型別都是可以的
2.再比如,我們實現多型的時候並不像JAVA一樣要求全部繼承同一個類,python 只要內部實現同一個方法就可以了,到時候直接賦值呼叫就ok
示例程式碼:
class Cat(object):
def say(self):
print "i am a cat\n"
class Dog(object):
def say(self):
print "i am a dog\n"
animal = [Cat,Dog]
for i in animal:
i().say()
結果:
i am a cat
i am a dog
2.Python 中的抽象基類
概念:
可以把 Python 的抽象基類看成是 java 中的介面,java 不能實現多繼承單數能實現多個介面,介面在 java 中是不能例項化的,同樣抽象基類在 Python 中也是不能例項化的(我們可以繼承抽象基類,然後去例項化抽象基類的所有方法)
但是我們不是說過,python 是一種動態語言,動態語言是沒有變數的型別的,Python中變數只是一個符號而已,他可以指向任何型別的物件。
那麼我們為什麼還需要有抽象基類這個概念呢?
1.因為我們在有些時候想判斷某一個物件的型別,而不是通過一個一個的魔法函式的檢驗去檢視物件的屬性如何,這樣的體驗非常不好,我們提供抽象基類實際上就是提供一種型別(屬性)的打包
2.我們在某些時候需要強制某些子類必須實現某些方法
示例程式碼:
class CacheBase():
def get(self,key):
raise NotImplemented
def set(self,key,value):
raise NotImplemented
class cache(CacheBase):
pass
my_cache = cache()
my_cache.get("key")
結果:
TypeError: exceptions must be old-style classes or derived from BaseException, not NotImplementedType
當然我們如果想在例項化的時候就丟擲異常的話就需要用到abc 模組以及裝飾器的操作,這裡不再多介紹
3.isinstance 和 type 的區別
這兩個函式都是檢視物件的歸屬的,但是 isinstacne() 會根據繼承關係不斷的向上遞推,直到最頂層為止,但是 Type() 只能向上遞推一個
另外 == 和 is 不要亂用
== 代表的是返回值是不是相等 is 檢視的是 id() 的結果是不是相等
4.類變數和物件變數
示例程式碼:
class A:
aa = 1
def __init__(self,x,y):
self.x = x
self.y = y
a = A(2,3)
print a.x,a.y,a.aa
print A.aa
結果:
2 3 1
1
這裡 aa 就是類變數,那麼為什麼我們能打印出來呢?因為我們在尋找例項的屬性的時候如果找不到就會自動的向上搜尋
還有一個比較神奇的地方
示例程式碼:
class A:
aa = 1
def __init__(self,x,y):
self.x = x
self.y = y
a = A(2,3)
A.aa = 10
a.aa = 100
print A.aa
print a.aa
結果:
10
100
可以看到我們在例項中修改了 aa 的值,但是我們在類中訪問卻依然沒改變,這是為什麼呢?
解釋:
我們使用 a.aa 訪問或者賦值的時候實際上會在我們的例項中新建一個屬性,我們訪問或者修改的都是這個新的屬性,這個屬性初始值和類的一樣,但是他們有著不用的地址空間,因此修改後互不影響
4.類和例項的屬性查詢順序__mro__
在歷史上 python 設定了很多的查詢演算法,比如 DFS BFS 等,但是依然不能用單純的一個方法解決所有的問題,於是後來出現了 C3 演算法,這個演算法比較複雜,能針對不同的模式調整查詢順序
(1)菱形繼承方式
例項程式碼:
class D(object):
pass
class C(D):
pass
class B(D):
pass
class A(B, C):
pass
print A.__mro__
結果:
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <type 'object'>)
解釋:
可見這種繼承方式使用的是 BFS
(2)錐形繼承方式
示例程式碼:
class E(object):
pass
class D(object):
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
print A.__mro__
結果:
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <type 'object'>)
解釋:
可見這種結構的繼承方式使用的是 DFS
5.類方法、靜態方法和例項方法
(1)例項方法:
示例程式碼:
class Date():
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
def __str__(self):
return "{year}/{month}/{day}".format(year = self.year,month = self.month,day = self.day)
new_day = Date(2018,10,2)
print new_day
結果:
2018/10/2
但是如果我們有這樣的需求,我們輸入 2018-10-2 也能直接解析怎麼辦,這個時候我們就需要使用靜態方法
(2)靜態方法:
示例程式碼:
class Date():
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@staticmethod
def parse_fro_string(data_str):
year,month,day = tuple(data_str.split('-'))
return Date(int(year),int(month),int(day))
def __str__(self):
return "{year}/{month}/{day}".format(year = self.year,month = self.month,day = self.day)
new_day = Date.parse_fro_string("2018-10-2")
print new_day
結果:
2018/10/2
但是,這個還有一個問題,就是靜態方法是硬編碼的,如果我們的類的名字改了,靜態方法也要改,這很不好,於是我們就引出了類方法
(3)類方法:
class Date():
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@staticmethod
def parse_fro_string(data_str):
year,month,day = tuple(data_str.split('-'))
return Date(int(year),int(month),int(day))
@classmethod
def fro_string(cls,data_str):
year, month, day = tuple(data_str.split('-'))
return cls(int(year), int(month), int(day))
def __str__(self):
return "{year}/{month}/{day}".format(year = self.year,month = self.month,day = self.day)
new_day = Date.fro_string("2018-10-2")
print new_day
結果:
2018/10/2
6.資料封裝和私有屬性
python 不像 java 有 Private 或者 protetced 這種,那麼 Python 是怎麼實現私有屬性的
示例程式碼:
class Date():
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@staticmethod
def parse_fro_string(data_str):
year,month,day = tuple(data_str.split('-'))
return Date(int(year),int(month),int(day))
@classmethod
def fro_string(cls,data_str):
year, month, day = tuple(data_str.split('-'))
return cls(int(year), int(month), int(day))
def __str__(self):
return "{year}/{month}/{day}".format(year = self.year,month = self.month,day = self.day)
class User:
def __init__(self,birthday):
self.birthday = birthday
def get_age(self):
return 2018-self.birthday.year
if __name__ == '__main__':
user = User(Date(1990,2,1))
print user.get_age()
print user.birthday
結果:
28
1990/2/1
在這種情況下,我們可以直接訪問到使用者的生日,如果我們不想直接訪問到怎麼辦?我們可以在屬性前面加上雙下劃線來解決
示例程式碼:
class User:
def __init__(self,birthday):
self.__birthday = birthday
def get_age(self):
return 2018-self.__birthday.year
if __name__ == '__main__':
user = User(Date(1990,2,1))
print user.get_age()
print user.__birthday
可以測試一下 get_age 這個函式還是可以使用的但是 直接訪問會出錯
但是實際上這個是可以繞過的,python 只是將其做了一個變形而已,變成了 _User__birthday
示例程式碼:
class User:
def __init__(self,birthday):
self.__birthday = birthday
def get_age(self):
return 2018-self.__birthday.year
if __name__ == '__main__':
user = User(Date(1990,2,1))
print user.get_age()
print user._User__birthday
這樣就又能正常訪問了
7.Python 物件的自省機制
1. 什麼叫做自省?
自省就是通過一定的機制來檢視物件的內部結構(看看自己是什麼東西…自我認識一下)
示例程式碼:
class Person():
name = "user"
class Student(Person):
def __init__(self,school_name):
self.school_name = school_name
if __name__ == '__main__':
user = Student("K0rz3n")
print user.__dict__
print Person.__dict__
結果:
{'school_name': 'K0rz3n'}
{'__module__': '__main__', 'name': 'user', '__doc__': None}
除了使用 __dict__
以外我們還能使用 dir() 這個函式,這個函式功能更加強大,能列出物件的所有屬性
示例程式碼:
class Person():
name = "user"
class Student(Person):
def __init__(self,school_name):
self.school_name = school_name
if __name__ == '__main__':
user = Student("K0rz3n")
print user.__dict__
print Person.__dict__
print dir(user)
print dir(Person)
結果:
{'school_name': 'K0rz3n'}
{'__module__': '__main__', 'name': 'user', '__doc__': None}
['__doc__', '__init__', '__module__', 'name', 'school_name']
['__doc__', '__module__', 'name']
8.super 真的是呼叫父類嗎
我們可以在 B中用super()函式呼叫了父類 A 中的構造方法
示例程式碼:
class A(object):
def __init__(self):
print "a"
class B(A):
def __init__(self):
super(B,self).__init__()
print "b"
if __name__ == '__main__':
b = B()
結果:
a
b
1.為什麼要這樣用
有時候我們可以通過這種方式來重用父類的方法,而不是重寫
2.真的是呼叫父類嗎?
我們來做一個實驗
示例程式碼:
class A(object):
def __init__(self):
print "a"
class B(A):
def __init__(self):
print "b"
super(B,self).__init__()
class C(A):
def __init__(self):
print "c"
super(C,self).__init__()
class D(B,C):
def __init__(self):
print "d"
super(D,self).__init__()
if __name__ == '__main__':
b = D()
結果:
d
b
c
a
解釋:
我們看到當我們呼叫到 B 的時候,super 並沒有去找B 的父類A 而是轉向了C ,這說明我們單純的將 super 理解為尋找父類是不正確的,其實這個順序是我們之前講的 mro
9.Python 中的 with 語句
with 語句是用來使用上下文管理器的,那麼什麼叫做上下文管理器呢?
1.上下文管理器
介紹這個概念就涉及到我們之前說的魔法函式,只要擁有__enter__
和 __exit__
這兩個屬性的類例項化的物件就可以作為上下文管理器
我們可以舉例看一下
示例程式碼:
class Sample(object):
def __enter__(self):
print "enter"
return self # 注意這句話絕對不能少,要不然sample 根本得不到這個物件的例項
def __exit__(self, exc_type, exc_val, exc_tb):
print "exit"
def do_something(self):
print "do_something"
with Sample() as sample:
sample.do_something()
結果:
enter
do_something
exit
解釋:
可以看到,__enter__
和 __exit__
魔法函式會在呼叫和結束的時候自動執行,同時,擁有這兩個魔法函式的類可以用 with 語句進行例項化,成為一個上下文管理器,我們可以在 __enter__
中建立資源,在 __exit__
中釋放
10.使用 contextlib 簡化上下文管理器
例項程式碼:
import contextlib
@contextlib.contextmanager
def file_open(file_name):
print "file_open" # 這裡寫的是 __enter__ 中的程式碼
yield{}
print "file_close" # 這裡寫的是 __exit__ 中的程式碼
with file_open("test.txt") as f:
print "test"
結果:
file_open
test
file_close
五、自定義序列類
1.序列的分類:
(1)容器序列: list 、tuple 、deque
容器序列中可以放置任何的資料型別
(2)扁平序列:str 、bytes 、bytearray 、array.array
該序列中只有一鍾型別
(3) 可變序列: list 、deque 、bytearray 、array
(4) 不可變序列: str 、tuple 、bytes
2.序列中的魔法函式
__len__
有這個方法就能實現計算長度
__iter__
有這個方法就能實現迭代
__container__
有這個方法就能實現使用in判斷元素是否存在
等等
我們如果要自定義序列型別就要去實現這些函式
3.序列中的 + += 和 extend 的區別
1. + 的使用
示例程式碼:
a = [1,2]
c = a +[3,4]
print c
結果:
2.+= 的使用
示例程式碼:
a = [1,2]
a += [3,4]
print a
結果:
[1, 2, 3, 4]
3.但是這兩個實際上是有一些區別的
(1)區別一: += 是直接在 a 上進行操作 這個就不用多解釋了
(2)區別二:+= 可以擴充套件不同型別的可迭代物件
示例程式碼:
a = [1,2]
a += (3,4)
print a
結果:
[1, 2, 3, 4]
我們發現+=的神奇特性,實際上,+=底層呼叫的是 extend()方法,這個方法實際上在呼叫for 迴圈,因此支援for 迴圈的都能新增去
還有一點就是人們經常把 append() 和 extend() 混淆
append() 會將引數直接放進列表中
示例程式碼:
a = [1,2]
a.append([3,4])
print a
結果:
[1, 2, [3, 4]]
4.實現可切片物件
1.先來回顧一下切片操作
模式: [start:end:step]
start 意思是起始位置,預設是0 end 意思是結束的位置,預設是列表的長度 step 預設是1
注意: 1.當step 是負數的時候代表反向切片,這個時候 start 要比 end 來的大 2.切片返回的是一個新的元素而不會改變原來列表的值 3.步長為2表示包括自己在內數兩個,也就是隔一個取一個
例項:
在末尾插入一個元素
a[len(a):] = [1]
在開頭插入一個元素
a[:0] = [1]
在特定位置插入一個元素
a[3:3] = [1]
隔一個修改一個
a[::2] = [0]*3
隔一個刪除一個
del a[::2]
2.自己構造一個可切片物件
示例程式碼:
import numbers
class Group():
def __init__(self,group_name,company_name,staffs):
self.group_name = group_name
self.company_name = company_name
self.staffs = staffs
def __getitem__(self, item):
cls = type(self)
if isinstance(item,slice):
return cls(group_name=self.group_name,company_name=self.company_name,staffs=self.staffs[item])
elif isinstance(item,numbers.Integral):
return cls(group_name=self.group_name,company_name=self.company_name,staffs=[self.staffs[item]])
staffs = ["Alice","Bob","Tom","Kali"]
group = Group(company_name="K0rz3n",group_name="user",staffs=staffs)
sub_group = group[1:2]
sub_group = group[0]
這個程式碼我本地也沒有實現,會報錯,還有待解決
5.bisect 維持已排序的序列
bisect 能維持一個已經排序好的序列(以升序排序)
示例程式碼:
import bisect
inter_list = []
bisect.insort(inter_list,2)
bisect.insort(inter_list,5)
bisect.insort(inter_list,1)
bisect.insort(inter_list,6)
bisect.insort(inter_list,3)
bisect.insort(inter_list,4)
print inter_list
結果:
[1, 2, 3, 4, 5, 6]
我們還能檢視某個元素應該插入什麼位置
示例程式碼:
print bisect.bisect(inter_list,3)
結果:
3
6.什麼時候我們不應該使用列表
array 相當於 C語言的陣列,在資料處理中的效率是非常高的,但是我們要注意 array 中存放的只能是一種資料型別,我們在宣告的時候就要指定
示例程式碼:
import array
my_array = array.array("i")
my_array.append(1)
my_array.append(2)
print my_array
結果:
array('i', [1, 2])
六、Python 中的 set 和 dict
1.dict 中的常用方法
(1)clear 方法:
示例程式碼:
a = {"K0rz3n":{"hello":"world"},
"KKKK":{"hello":"K0rz3n"},
"xxxxx":{"xxx":"sssss"}}
a.clear()
print a
結果:
{}
(2)copy() 方法
注意:
這個拷貝是淺拷貝,也就是說不能迴圈巢狀拷貝
示例程式碼:
a = {"K0rz3n":{"hello":"world"},
"KKKK":{"hello":"K0rz3n"},
"xxxxx":{"xxx":"sssss"}}
new_dict = a.copy()
new_dict["K0rz3n"]["hello"] = "K0rz3n"
print a
結果:
{'K0rz3n': {'hello': 'K0rz3n'}, 'KKKK': {'hello': 'K0rz3n'}, 'xxxxx': {'xxx': 'sssss'}}
我們發現我們修改了拷貝值以後原始值也發生了變化,這其實就是淺拷貝導致的問題,淺拷貝遇到迴圈巢狀的資料時只能拷貝一層,另外的層都是一個指向,也就是指向原始的位置,因此修改了拷貝以後原始值也發生了變化
解決:
使用python 的一個 copy 的庫
示例程式碼:
import copy
a = {"K0rz3n":{"hello":"world"},
"KKKK":{"hello":"K0rz3n"},
"xxxxx":{"xxx":"sssss"}}
new_dict = copy.deepcopy(a)
new_dict["K0rz3n"]["hello"] = "K0rz3n"
print a
結果:
{'K0rz3n': {'hello': 'world'}, 'KKKK': {'hello': 'K0rz3n'}, 'xxxxx': {'xxx': 'sssss'}}
(3)fromkeys 方法
將可迭代的物件轉化成為一個dict
示例程式碼:
new_list = ["K0rz3n","hello","world"]
new_dict = dict.fromkeys(new_list,{"hhh"})
print new_dict
結果:
{'K0rz3n': set(['hhh']), 'world': set(['hhh']), 'hello': set(['hhh'])}
(4)get 方法
為了防止出現 keyerror 的錯誤
示例程式碼:
new_list = ["K0rz3n","hello","world"]
new_dict = dict.fromkeys(new_list,{"hhh"})
value = new_dict.get("hh",{})
print value
結果:
{}
(5)items 方法
Items 方法可以實現元祖的拆包
示例程式碼:
new_list = ["K0rz3n","hello","world"]
new_dict = dict.fromkeys(new_list,{"hhh"})
for key,value in new_dict.items():
print key,value
結果:
K0rz3n set(['hhh'])
world set(['hhh'])
hello set(['hhh'])
(6)setdefault 方法
除了試下 get 一樣的操作以外,還會將這個值設定進對映
示例程式碼:
new_list = ["K0rz3n","hello","world"]
new_dict = dict.fromkeys(new_list,{"hhh"})
new_dict.setdefault("hh","xxx")
print new_dict
結果:
{'K0rz3n': set(['hhh']), 'world': set(['hhh']), 'hh': 'xxx', 'hello': set(['hhh'])}
(7)update 方法
更新dict
示例程式碼:
new_list = ["K0rz3n","hello","world"]
new_dict = dict.fromkeys(new_list,{"hhh"})
new_dict.update(update="hahah")
new_dict.update({"upupup":"xixixi"})
print new_dict
結果:
{'K0rz3n': set(['hhh']), 'world': set(['hhh']), 'upupup': 'xixixi', 'hello': set(['hhh']), 'update': 'hahah'}
2.set 和 frozeset
set 是集合,是無序的,不重複的,frozenset 是不可變集合
set 中放置的是一個可迭代物件
(1)無序
示例程式碼:
a = set('abcdefs')
print a
結果:
set(['a', 'c', 'b', 'e', 'd', 'f', 's'])
可見顯示的順序和我們傳入的順序是不一樣的,也就是說是無序的
(2)不重複
示例程式碼:
a = set('abcdefssss')
print a
結果:
set(['a', 'c', 'b', 'e', 'd', 'f', 's'])
(3)frozenset 是不可變的
示例程式碼:
a = set('abcdefssss')
a.add("x")
print a
b = frozenset('abcdefssss')
b.add("x")
print b
結果:
set(['a', 'c', 'b', 'e', 'd', 'f', 's', 'x'])
AttributeError: 'frozenset' object has no attribute 'add'
(4)set 能使用update 更新
示例程式碼:
a = set('abcdefssss')
a.add("x")
b = set("xxx")
a.update(b)
print a
結果:
set(['a', 'c', 'b', 'e', 'd', 'f', 's', 'x'])
(5)set 使用difference 求差集
示例程式碼:
a = set('abcdefssss')
a.add("x")
b = set("abcde")
new_set = a.difference(b)
print new_set
結果:
set([‘x’, ‘s’, ‘f’])
還有一種方式就是直接使用 - 號,其實底層都是使用魔法函式實現的,當然還有 | &
示例程式碼:
a = set('abcdefssss')
a.add("x")
b = set("abcde")
new_set = a.difference(b)
another_set = a-b
print new_set
print another_set
結果:
set(['x', 's', 'f'])
set(['x', 's', 'f'])
2.dict 背後的實現原理
dict 背後都是通過hash 實現的,背後是一個數組,連續的記憶體空間,通過鍵值的hash到陣列中尋找,因此速度非常的塊。
因此:
- dict 的 key 和 set 的值都必須是可hash 的
- dict 的記憶體花銷大,但是查詢速度快,很多自定義的物件或者是python 內部的物件都是用dict 包裝的
- dict 的儲存順序和存入的順序有關
- 新增新的元素可能會改變已有資料的順序(因為,在陣列空間不足以後可能會新開闢一個新的陣列,在元素進行轉移的時候可能會改變儲存順序)
六、物件引用、可變性和垃圾回收
1.Python 中的變數是什麼?
Python 的變數和 Java 中的變數是不一樣的,Java 中的變數是有大小的(因為有型別),宣告一個變數就會在記憶體中開闢一定大小的空間,存放不同型別的資料,但是Python 不一樣,Python 中的變數是一個指標,大小都是一樣的
示例程式碼:
a = [1,2,3,4]
b = a
b.append(5)
print a
結果:
[1, 2, 3, 4, 5]
我們看到,我們修改b 實際上把a 也修改了,本質上是指向了同一個記憶體空間
2.Python 中的 == 和 is
(1)is 比較的是兩個的 id
示例程式碼:
a = [1,2,3,4]
b = a
print id(a),id(b)
print a is b
結果:
50435592 50435592
True
但是有一個奇怪的情況
示例程式碼:
a = [1,2,3]
b = [1,2,3]
print a is b
a = 1
b = 1
print a is b
結果:
False
True
解釋:
其實python 在對於小整數、小字串的情況下不會另外新建記憶體空間
(2)== 比較的是值
示例程式碼:
a = [1,2,3]
b = [1,2,3]
print a == b
a = 1
b = 1
print a == b
結果:
True
True
3.del 和垃圾回收
Python 中的垃圾回收機制使用的是引用計數的方式
del 並不能直接釋放記憶體,只有在引用計數為0的時候才會釋放
示例程式碼:
a = object()
b = a
del a
print b
print a
結果:
<object object at 0x00000000030F90C0>
NameError: name 'a' is not defined
七、元類程式設計
1.property 動態屬性
使用 @property 裝飾器將取函式的模式改變成取屬性的模式
先看一下原始的使用呼叫函式的方法:
示例程式碼:
from datetime import date,datetime
class User():
def __init__(self,name,birthday):
self.name = name
self.birthday = birthday
def get_age(self):
return datetime.now().year - self.birthday.year
if __name__ == '__main__':
user = User("K0rz3n",date(year = 1999,month = 11,day = 12))
print user.get_age()
結果:
19
我們如果想用呼叫類的屬性的方式訪問呢?
示例程式碼:
from datetime import date,datetime
class User():
def __init__(self,name,birthday):
self.name = name
self.birthday = birthday
@property
def get_age(self):
return datetime.now().year - self.birthday.year
if __name__ == '__main__':
user = User("K0rz3n",date(year = 1999,month = 11,day = 12))
print user.get_age
結果:
19
2.getattr 和 getattribute
(1)__getattr__
這個魔法方法會在找不到屬性的時候呼叫
示例程式碼:
from datetime import date,datetime
class User():
def __init__(self,name,birthday):
self.name = name
self.birthday = birthday
def __getattr__(self, item):
return "can not find the attr"
if __name__ == '__main__':
user = User("K0rz3n",date(year = 1999,month = 11,day = 12))
print user.get_age
結果:
can not find the attr
(2)__getattribute__
這個魔法函式在屬性呼叫的時候會第一個呼叫,不管屬性存不存在,可以把他看成是屬性呼叫的入口,因為地位比較重要因此不建議重寫
示例程式碼:
from datetime import date,datetime
class User(object):
def __init__(self,name,birthday):
self.name = name
self.birthday = birthday
def __getattr__(self, item):
return "can not find the attr"
def __getattribute__(self, item):
return "hello"
if __name__ == '__main__':
user = User("K0rz3n",date(year = 1999,month = 11,day = 12))
print user.get_age
結果:
hello
3.屬性描述符和屬性的查詢過程
(1)屬性描述符:
開發過程中可能會遇到一個問題:們想對使用者傳進來的資料型別進行控制,但是由於我們需要控制的資料過多,我們不能每一個都寫同樣的程式碼,於是這就涉及到了屬性描述符,實際上屬性描述符也只是實現了幾個魔法方法而已(__get__
、__set__
、__delete__
)
下面我們就自己實現一個屬性描述符
示例程式碼:
import numbers
class IntField(object):
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value,numbers.Integral):
raise ValueError("not int")
self.value = value
def __delete__(self, instance):
pass
class User(object):
age = IntField()
if __name__ == '__main__':
user = User()
user.age = "xxx"
print user.age
結果:
ValueError: not int
當然我們能多加幾個條件:
示例程式碼:
import numbers
class IntField(object):
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value,numbers.Integral):
raise ValueError("not int")
if value < 0:
raise ValueError("not > 0")
self.value = value
def __delete__(self, instance):
pass
class User(object):
age = IntField()
if __name__ == '__main__':
user = User()
user.age = -9
print user.age
結果:
ValueError: not > 0
4.__new__
和 __init__
的區別
__new__
可以自定義類的生成過程,而__init__
控制的是類生成的物件,因此 new 的呼叫是在 init 的之前
示例程式碼:
class User(object):
def __new__(cls, *args, **kwargs):
print "new"
def __init__(self,name):
self.name = name
print "init"
if __name__ == '__main__':
user = User("K0rz3n")
結果:
new
這裡其實還發現一個問題,就是如果 new 方法不返回物件,Init 就永遠得不到呼叫
示例程式碼:
class User(object):
def __new__(cls, *args, **kwargs):
print "new"
return super(User,cls).__new__(cls)
def __init__(self,name):
self.name = name
print "init"
if __name__ == '__main__':
user = User("K0rz3n")
結果:
new
init
5.自定義元類
概念:
元類是什麼呢?元類就是建立類的類,比如我們之前講的 type() 就是一個元類,再比如我們在Py3 中建立類的時候在括號中寫 metaclass = 這個就是在指明這個類的元類,MetaClass 也是一個類,他是繼承於type的,我們自定義 MetaClass 來控制類物件生成的過程
通過 type() 動態的建立一個類,語法是 type(類名,繼承關係,屬性或者方法)
示例程式碼:
User = type("User",(),{})
user = User()
print type(user)
結果:
<class '__main__.User'>
我們現在來建立一個帶有屬性的類
示例程式碼:
User = type("User",(),{"name":"K0rz3n"})
user = User()
print user.name
結果:
K0rz3n
我們現在來建立一個帶有方法的類
示例程式碼:
def get_age(self):
print "get_age"
if __name__ == '__main__':
User = type("User",(),{"name":"K0rz3n","get_age":get_age})
user = User()
print user.name
print user.get_age()
結果:
K0rz3n
get_age
None
我們可以建立一個繼承基類的類
示例程式碼:
class BaseClass(object):
def answer(self):
return "i am baseclass"
if __name__ == '__main__':
User = type("User",(BaseClass,),{"name":"K0rz3n","get_age":get_age})
user = User()
print user.answer()
結果:
i am baseclass
八、Python 的迭代器和生成器
1.Python 中的迭代協議:
什麼是迭代器?
迭代器是訪問集合類元素的一種方式,我們能實現for迴圈其實背後就是迭代器,迭代器在Python 中叫做 Iterator ,背後實現的是 __next__
和 __iter__
方法
示例程式碼:
from collections import Iterable,Iterator
a = [1,2,3,4,5]
print isinstance(a,Iterable)
print isinstance(a,Iterator)
結果:
True
False
解釋:
可以看到 a 是一個列表,他是可迭代的,但是他並不是一個迭代器
2.Python 中迭代器和可迭代物件的區別:
1.回顧一下之前的程式碼:
class Company(object):
def __init__(self,employ_list):
self.employ = employ_list
def __getitem__(self, item):
return self.employ[item]
if __name__ == '__main__':
company = Company(["tom","bob","angel"])
for item in company:
print item
為什麼這個能執行迴圈呢?
實際上,我們在呼叫迴圈的時候會自動呼叫,iter() 這個方法,這個方法回去尋找例項化這個物件的類中有沒有__iter__
這個方法,如果沒有他會再退一步尋找__getitem__
這個魔法方法,然後完成呼叫.
2.我們來自己實現一個迭代器:
示例程式碼:
# coding=utf-8
from collections import Iterator
class MyIterator(Iterator):
def __init__(self, employ_list):
self.iter = employ_list
self.index = 0
def next(self): # 真正返回迭代之的邏輯
try:
word = self.iter[self.index]
except IndexError:
raise StopIteration
self.index += 1
return word
class Company(object):
def __init__(self, employ_list):
self.employ = employ_list
def __getitem__(self, item):
return self.employ[item]
def __iter__(self):
return MyIterator(self.employ)
if __name__ == '__main__':
company = Company(["tom", "bob", "angel"])
for i in company:
print i
結果:
tom
bob
angel
3.Python 中的生成器函式:
1.概念:
什麼是生成器函式?
只要函式中有 yield 關鍵字,這個函式就是生成器函式
示例程式碼:
def gen_test():
yield 1
def test():
return 2
if __name__ == '__main__':
gen = gen_test()
res = test()
print gen
print res
結果:
<generator object gen_test at 0x00000000032457E0>
2
解釋:
我們看到,看似同樣是定義一個函式,但是第一個實際上是一個生成器,生成器返回的是一個生成器物件,第二個返回的只是一個乾巴巴的資料。
那麼我們怎麼利用這個生成器的返回值呢?
實際上他底層實現也是一個生成器,既然是生成器我們就能通過迴圈的方式呼叫
示例程式碼:
def gen_test():
yield 1
def test():
return 2
if __name__ == '__main__':
gen = gen_test()
res = test()
for i in gen:
print i
結果:
1
2.看一下生成器的優勢:
原始的生成 斐波那契數列的方法:
示例程式碼:
def fib(index):
fib_list = []
n,a,b = 0,0,1
while n < index:
fib_list.append(b)
a,b = b,a+b
n += 1
return fib_list
fib_list = fib(10)
print fib_list
結果:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
但是這種方法在數字非常大的時候就非常的佔用記憶體空間,他會先把結果放在陣列中再返回給我們,於是我們嘗試使用生成器
示例程式碼:
def fib(index):
n,a,b = 0,0,1
while n < index:
yield b
a,b = b,a+b
n += 1
if __name__ == '__main__':
fib_list = []
for i in fib(10):
fib_list.append(i)
print fib_list
結果:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
生成器方式就能實現在迴圈中不斷生成,避免了一下子生成,(當然我這個程式碼為了方便大家看最後還是放在了列表中)
4.Python 是怎麼實現生成器的:
1.先來講講Python 是怎麼實現函式呼叫的
例項函式:
def foo():
pass
def bar():
pass
首先 python 有一個C語言寫的Python的直譯器,Python 的所有的程式碼都是執行在這個直譯器之上的,然後我們開始呼叫foo 函式,一呼叫函式直譯器就會在堆上分配一個棧幀,然後我們在這個棧幀上執行這個函式,然後發現他呼叫了一個子函式,又會在堆上分配一個新的棧幀,所有的棧幀都分配在堆上,這也就是說,棧幀能脫離於呼叫者存在
而生成器實際上就是對我們的棧幀進行的一次封裝。儲存了當前執行的狀態,也就是說,我們能夠使用其實現迭代
有需要Python學習資料的小夥伴嗎?小編整理【一套Python資料、原始碼和PDF】,感興趣者可以加學習群:548377875或者加小編微信:【mmp9972】反正閒著也是閒著呢,不如學點東西啦~~