一、淺拷貝和深拷貝
1.淺拷貝
是對於一個對象的頂層拷貝,通俗的理解是:拷貝了引用,並沒有拷貝內容。相當於把變量里面指向的一個地址給了另一個變量就是淺拷貝,而沒有創建一個新的對象,如a=b。
2.深拷貝
首先要import copy,然后c = copy.deepcopy(a),就表示把a的內容深拷貝到c中,如果發現了a中也存在引用的內容,則遞歸拷貝,也就是把當前的這個引用的對象繼續深拷貝。
3. copy和deepcopy的區別
①copy:淺拷貝,里面如果有可變類型,修改這個可變類型(如list),被拷貝的對象也會相應改變,僅僅拷第一層,如果是不可變類型,就一層都不拷,如果是可變類型就拷一層。
②deepcopy:深拷貝,里面不管是可變類型和不可變類型,被拷貝的對象都不會受到影響,遞歸拷貝。
4.copy和deepcopy拷貝元組的特點
使用copy模塊的copy功能的時候,它會根據當前拷貝的數據類型是可變類型還是不可變類型有不同的處理方式,如元組是不可變類型,拷貝多份沒有用,對copy來說,如果是可變類型就拷一層,如果是不可變類型,就一層都不拷。
二、屬性property
1.屬性property-1
①私有屬性添加getter和setter方法
②使用property升級getter和setter方法
num = property(getNum,setNum)
注意:
Num到底是調用getNum()還是setNum(),要根據實際的場景來判斷,值得注意的是一定要先填getNum后setNum。
如果是給t.num賦值,那么一定調用setNum()。
如果是獲取t.num的值,那么就一定調用getNum()。
property的作用:相當於把方法進行了封裝,開發者在對屬性設置數據的時候更方便。
2.屬性property-2
class Money(object): @property #修飾器 def num(self): print("------getter-----") return self.__num @num.setter #修飾器 def num(self,new_num): print("------setter------") self.__num = new_num t.num = 20 print(t.num)
三、迭代器
1.迭代器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束,迭代器只能往前不會后退。
2.可迭代對象(for 循環遍歷的數據類型)
①一類是集合數據類型,如 list 、 tuple 、 dict 、 set 、 str 等。
②一類是 generator(列表生成式,生成器) ,包括生成器和帶 yield 的generator function。
③這些可以直接作用於for循環的對象統稱為可迭代對象:Iterable。
from collections import Iterable # 如果可以迭代就返回True isinstance([ ], Iterable)
3.判斷是否可以迭代
可以使用isinstance()判斷一個對象是否是Iterable對象:
from collections import Iterable # 如果可以迭代就返回True isinstance([ ], Iterable)
而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。
4.迭代器
①可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator。
②可以使用isinstance()判斷一個對象是否是Iterator對象。
③生成器(i for i in range(10))一定是迭代器,但迭代器不一定是生成器。
④迭代器一定是可迭代對象,但可迭代對象不一定是迭代器。
from collections import Iterator isinstance((x for x in range(10)), Iterator) # 如果是的話就返回True
5.iter( )函數
①生成器都是Iterator(迭代器)對象,但 list、dict、str雖然是Iterable(可迭代),卻不是Iterator(迭代器)。
②把list、dict、str 等 Iterable(可迭代)變成 Iterator(迭代器)可以使用iter()函數,就好比人可以游泳,但不是天生就會,可迭代對象就好比人,迭代器就好比會游泳的人,需要經過iter()訓練一樣。
isinstance(iter([ ]), Iterator) True
四、閉包
1.函數的引用
test1() # 調用函數 ret = test1 # 引用函數 ret() # 通過引用調用函數
2.什么是閉包
def test(number): print("-----1-----") def test_in(number2): print("----2-----") print(number+number2) print("------3------") # 把函數的引用返回了 return test_in # 用來接收test(100),指向了一個函數體,這個100傳給了number ret = test(100) # 這個1傳給了number2 ret(1) # 這個返回101 ret(100) # 這個返回200 ret(200) # 這個返回300
3.閉包再理解
內部函數對外部函數作用域里變量的引用(非全局變量),則稱內部函數為閉包
閉包的實際例子:
def line_conf(a, b): def line(x): return a*x + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5)) print(line2(5))
這個例子中,函數line與變量a,b構成閉包。在創建閉包的時候,我們通過line_conf的參數a,b說明了這兩個變量的取值,這樣,我們就確定了函數的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數a,b,就可以獲得不同的直線表達函數。由此,我們可以看到,閉包也具有提高代碼可復用性的作用
五、裝飾器
1.裝飾器
在有兩個重名的函數中,Python解釋器會調用最后定義的那重名函數,因為在Python里,第一個函數指向的是一片內存,然后又讓這個函數指向另一片內存,就會利用第二片內存來執行,所有函數名應盡量避免相同
寫代碼要遵循開放封閉原則,雖然在這個原則是用的面向對象開發,但是也適用於函數式編程,簡單來說,它規定已經實現的功能代碼不允許被修改,但可以被擴展,即:
①封閉:已實現的功能代碼塊
②開放:對擴展開發
③實例:
def w1(func): def inner(): # 驗證1 # 驗證2 # 驗證3 func() return inner @w1 # 裝飾器 def f1(): print('f1') @w1 # 裝飾器 def f2(): print('f2') ........
對於上述代碼,也是僅僅對基礎平台的代碼進行修改,就可以實現在其他人調用函數 f1、 f2 、f3 、f4 之前都進行【驗證】操作,並且其他業務部門無需做任何操作
2.裝飾器的功能
①引入日志
②函數執行時間統計
③執行函數前預備處理
④執行函數后清理功能
⑤權限校驗等場景
⑤緩存
⑥如果是有多個裝飾器的情況,一般是先裝飾最下面的一個,然后依次往上,@w1類比於f1 = w1(f1)
3.裝飾有參數的函數
在傳遞參數的時候,需要在閉包里面定義一個形參,閉包里面的調用的函數也要定義一個形參,否則會導致兩部分函數調用失敗
4.裝飾不定長的參數的函數
在傳遞參數的時候,需要在閉包里面定義一個*args和**kwargs,閉包里面的調用的函數也要定義一個*args和**kwargs,這樣就可以在調用的時候傳遞任意長度的參數,增加代碼的可復用性
5.裝飾帶返回值的函數
需要在閉包里面進行一個接收,也就是ret = test(),然后再把接收到的ret return出去,這樣在裝飾的test才能返回出當前需要返回的東西,否則只會返回None
6.通用的裝飾
def w1(func): print("-----正在裝飾-----") def inner(*args,**kwargs): print("---正在驗證權限---") print("----記錄日志----") ret = func(*args,**kwargs) return ret return inner
帶有參數的裝飾器:也就是在原來包含一個閉包的函數外面再給他套一個函數,用來傳遞裝飾器的參數
def func_arg(arg): def w1(func): print("---記錄日志---") def inner(*args,**kwargs): func(*args,**kwargs) return inner return w1 @func_arg("heihei") def f1(): print("----f1----") # 1.先執行func_arg("heihei")函數,這個函數return的結果是 # 2.@w1 # 3.使用@w1對f1進行裝飾 # 作用:帶有參數的裝飾器,能夠起到在運行時,有不同的功能
六、python是動態語言
1.Python是動態語言
動態編程語言是高級程序設計語言的一個類別,在計算機科學領域已被廣泛應用。它是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。這種動態語言的應用就好比是在沒有更新app的情況下,它的界面在后台也可以被開發者更改,因為它是動態的,可以把新增的動態程序放置在文本,只要加載一遍即可
2.運行的過程中給對象綁定(添加)屬性
也就是說給對象綁定一個實例屬性(這個屬性是初始化之外的額外屬性),只有這個創建對象的屬性如laozhao.addr = "北京"
3.運行的過程中給類綁定(添加)屬性
如果需要所有的一個類的實例加上一個屬性怎么辦呢? 答案就是直接給這個類綁定屬性,如Person.sex = "male"
4.運行的過程中給類綁定(添加)方法
如果是對這個類綁定一個實例方法,那么就要先import types,然后如對象.方法名 = types.MethodType(函數名, 對象),把run這個方法綁定到P對象上。如果是靜態方法和類方法,就直接用類名.方法名=函數名
5.運行的過程中刪除屬性、方法
①del 對象.屬性名
②delattr(對象, "屬性名")
七、__slots__的作用
1.動態語言
可以在運行的過程中,修改代碼
2.靜態語言
編譯時已經確定好代碼,運行過程中不能修改
3.__slots__的作用
為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class實例能添加的屬性,如__slots__ = ("name","age"),就可以達到限制name和age的屬性,如果發現有添加其他屬性的程序就會發生異常
4.使用__slots__注意
__slots__定義的屬性僅對當前類實例起作用,對繼承的子類是不起作用的
八、生成器
1.什么是生成器
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了
①while的列表推導
list.append(i)
②for的列表推導,range與切片很類似
for i in range(10,78):
③第一個i是元素的值,后面的for是循環的次數,如果第一個i=11,那么所有的元素都是11
a=[i for i in range(1,18)]
④for控制循環的次數,for和if的嵌套
c = [i for i in range(10) if i%2==0]
⑤每執行第一個for循環都要執行第二個for循環的所有次數
d = [i for i in range(3) for j in range(2)]
⑥每執行第一個for循環都要執行第二個for循環的所有次數
d = [(i,j) for i in range(3) for j in range(2)]
例題:找出100以內能被3整除的正整數
aiquot = []
for n in range(1,100)
if n%3 ==0:
aiquot.append(n)
range(3,100,3) # 很簡潔
2.創建生成器方法1
要創建一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[ ]改成( )
如L = [ x*2 for x in range(5)]和G = ( x*2 for x in range(5)),L是一個列表,而G是一個生成器,可以通過next(G)函數獲得生成器的下一個返回值,不斷調用 next()實在是太變態了,正確的方法是使用for循環,因為生成器也是可迭代對象
3.創建生成器方法2
fib函數變成generator,只需要把print(b)改為yield b就可以了,循環過程中不斷調用yield ,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來,當循環到沒有元素的時候,將會生成異常,這時候就要用try和exception來檢測異常,#print自動檢測異常並停止,但是next()就要用try ,在創建生成器的時候需要接收函數的返回值
#1.next(返回函數名) #2.返回函數名.__next__()是一樣的方法來獲取下一個返回值
總結:生成器是這樣一個函數,它記住上一次返回時在函數體中的位置。對生成器函數的第二次或第 n 次調用跳轉至該函數中間,而上次調用的所有局部變量都保持不變,生成器不僅記住了它數據狀態;生成器還記住了它在流控制構造(在命令式編程中,這種構造不只是數據值)中的位置
4.生成器的特點
①節約內存
②迭代到下一次的調用時,所使用的參數都是第一次所保留下的,即是說,在整個所有函數調用的參數都是第一次所調用時保留的,而不是新創建的
5.send用法
①如果在在程序中有個變量等於yield,不是說把yield的值給了這個變量,而是接下來在下一次調用執行一次的時候可以傳一個值,t.send("haha")和t.__next__()都可以讓生成器繼續執行,不同的是send可以傳遞一個值,但是不能在程序剛開始執行就用send傳值,有兩種方法,要么先用__next__調用一次,再send一個值,或者t.send(None)
②生成器:完成多任務,控制多個任務執行的情況
九、元類
1.元類
①類也是對象
②在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段,類同樣也是一種對象
2.動態的創建類
因為類也是對象,你可以在運行時動態的創建它們,就像其他任何對象一樣
def choose_class(name):
if name == 'foo':
class Foo(object):
pass
return Foo # 返回的是類,不是類的實例
else:
class Bar(object):
pass
return Bar
MyClass = choose_class('foo') # 當你使用class關鍵字時,Python解釋器自動創建這個對象
3.使用type創建類
①type還有一種完全不同的功能,動態的創建類,type可以像這樣工作:
②type(類名, 由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
Test2 = type("Test2",(),{}) #定了一個Test2類
4.使用type創建帶有屬性的類
Foo = type('Foo', (), {'bar':True})
5.使用type創建帶有方法的類
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) # 這是添加實例方法echo_bar
Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic": testStatic}) # 添加靜態方法
Foochild = type('Foochild',(Foo,),{"echo_bar":echo_bar, "testStatic":testStatic, "testClass":testClass}) # 添加類方法
6.到底什么是元類
元類就是用來創建類的東西,元類就是用來創建這些類 (對象) 的,元類就是類的類,元類又由元類創建,Python中所有的東西都是對象。這包括整數、字符串、函數以及類
7.__metaclass__屬性
class Foo(object):
__metaclass__ = something…
如果這么做了,Python就會用元類來創建類Foo,這里面有些技巧。首先寫下class Foo(object),但是類Foo還沒有在內存中創建。Python會在類的定義中尋找__metaclass__屬性,如果找到了,Python就會用它來創建類Foo,如果沒有找到,就會用內建的type來創建這個類
十、GC垃圾回收
1.GC垃圾回收
①小整數對象池:Python為了優化速度,使用了小整數對象池, 避免為整數頻繁申請和銷毀內存空間。Python 對小整數的定義是[-5, 257) 這些整數對象是提前建立好的,不會被垃圾回收
②大整數對象池:每一個大整數,均創建一個新的對象
③intern機制:假如要創建n個對象的是一樣的字符串,那么python只會創建一個內存空間來存儲,其他對象都是引用,但如果字符串中出現空格或其他符號就表示為不同的對象
2.GC(Garbage collection)垃圾回收
Python里也同Java一樣采用了垃圾收集機制,不過不一樣的是: Python采用的是引用計數機制為主,標記-清除和分代收集兩種機制為輔的策略
3.引用計數機制的優點
①簡單
②實時性:一旦沒有引用,內存就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時
4. 引用計數機制的缺點
①維護引用計數消耗資源
②循環引用
5.GC系統所承擔的工作遠比"垃圾回收"多得多。實際上,它們負責三個重要任務
①為新生成的對象分配內存
③從垃圾對象那回收內存
6.垃圾回收機制
Python中的垃圾回收是以引用計數為主,分代收集為輔
①導致引用計數+1的情況:
對象被創建,例如a=23
對象被引用,例如b=a
對象被作為參數,傳入到一個函數中,例如func(a)
對象作為一個元素,存儲在容器中,例如list1=[a,a]
②導致引用計數-1的情況:
對象的別名被顯式銷毀,例如del a
對象的別名被賦予新的對象,例如a=24
一個對象離開它的作用域,例如f函數執行完畢時,func函數中的局部變量(全局變量不會)
對象所在的容器被銷毀,或從容器中刪除對象
7.查看一個對象的引用計數
import sys
a = "hello world"
sys.getrefcount(a)
①可以查看a對象的引用計數,但是比正常計數大1,因為調用函數的時候傳入a,這會讓a的引用計數+1
②有三種情況會觸發垃圾回收
調用gc.collect()
當gc模塊的計數器達到閥值的時候
程序退出的時候
8.gc模塊的自動垃圾回收機制
①必須要import gc模塊,並且is_enable()=True才會啟動自動垃圾回收。
②這個機制的主要作用就是發現並處理不可達的垃圾對象。
③垃圾回收 = 垃圾檢查 + 垃圾回收
④在Python中,采用分代收集的方法。把對象分為三代,一開始,對象在創建的時候,放在一代中,如果在一次一代的垃圾檢查中,該對象存活下來,就會被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會被放到三代中
⑤gc模塊里面會有一個長度為3的列表計數器,可以通過gc.get_count()獲取,gc.set_threshold(threshold0[, threshold1[, threshold2]) 設置自動執行垃圾回收的頻率,例如(700,10,10)每一次計數器的增加,gc模塊就會檢查增加后的計數是否達到閥值的數目,700表示閾值,10表示沒清理10次零代就清理一次二代,第二個10表示每清理10次一代鏈表就清理二代一次
注意點:
gc模塊唯一處理不了的是循環引用的類都有__del__方法,所以項目中要避免定義__del__方法
十一、內建屬性
常用專有屬性 說明 觸發方式
__init__ 構造初始化函數 創建實例后,賦值時使用,在__new__后
__new__ 生成實例所需屬性 創建實例時
__class__ 實例所在的類 實例.__class__
__str__ 實例字符串表示,可讀性 print(類實例),如沒實現,使用repr結果
__repr__ 實例字符串表示,准確性 類實例 回車 或者 print(repr(類實例))
__del__ 析構 del刪除實例
__dict__ 實例自定義屬性 vars(實例.__dict__)
__doc__ 類文檔,子類不繼承 help(類或實例)
__getattribute__ 屬性訪問攔截器 訪問實例屬性時
__bases__ 類的所有父類構成元素 類名.__bases__
def __getattribute__(self,obj):
if obj == 'subject1':
print('log subject1')
return 'redirect python'
else: # 測試時注釋掉這2行,將找不到subject2
return object.__getattribute__(self,obj)
__getattribute__的作用可以用來打印Log日志
__getattribute__的坑:
class Person(object):
def __getattribute__(self,obj):
print("---test---")
if obj.startswith("a"):
return "hahha"
else:
return self.test
def test(self):
print("heihei")
t.Person()
t.a #返回hahha
t.b #會讓程序死掉
# 原因是:當t.b執行時,會調用Person類中定義的__getattribute__方法,但是在這個方法的執行過程中if條件不滿足,所以 程序執行else里面的代碼,即return self.test 問題就在這,因為return 需要把self.test的值返回,那么首先要獲self.test的值,因為self此時就是t這個對象,所以self.test就是t.test 此時要獲取t這個對象的test屬性,那么就會跳轉到__getattribute__方法去執行,即此時產生了遞歸調用,由於這個遞歸過程中 沒有判斷什么時候推出,所以這個程序會永無休止的運行下去,又因為每次調用函數,就需要保存一些數據,那么隨着調用的次數越來越多,最終內存吃光,所以程序崩潰
# 注意:以后不要在__getattribute__方法中調用self.xxxx
十二、調試
1.調試:pdb是基於命令行的調試工具,非常類似gnu的gdb(調試c/c++)
執行時調試
程序啟動,停止在第一行等待單步調試
python -m pdb xxx.py
2.調試方法
①n(next)執行下一步
②l(list)顯示當前執行進度
③c(continue)繼續執行代碼
④b(break)添加斷點
⑤q(quit)中止並退出
⑥clear num刪除指定斷點
⑦p(print)打印變量的值
⑧a(args)打印所有的形參數據
⑨s(step)進入到一個函數
r執行代碼直到從當前函數返回
num = property(getNum,setNum)