天天看點

Python 核心程式設計 (全)

淺拷貝和深拷貝

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.為了達到限制的目的,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解釋器自動建立這個對象           

2.使用type建立類

    ①type還有一種完全不同的功能,動态的建立類,type可以像這樣工作:

    ②type(類名, 由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值)) 

Test2 = type("Test2",(),{}) #定了一個Test2類           

3.使用type建立帶有屬性的類

Foo = type('Foo', (), {'bar':True})      

4.使用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}) # 添加類方法           

5.到底什麼是元類

    元類就是用來建立類的東西,元類就是用來建立這些類 (對象) 的,元類就是類的類,元類又由元類建立,Python中所有的東西,注意,我是指所有的東西——都是對象。這包括整數、字元串、函數以及類

6.__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執行代碼直到從目前函數傳回