• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • python編程從入門到實踐 一

    1、計算機核心基礎

    1.1 什么是語言?什么是編程語言?為何要有編程語言?

    語言其實就是人與人之間溝通的介質,如英語,漢語,俄語等。
    編程語言則是人與計算機之間溝通的介質,
    編程的目的就是為了讓計算機按照人類的思維邏輯(程序)自發地去工作從而把人力解放出來
    

    二 計算機組成原理

    2.1、什么是計算機?

    俗稱電腦,即通電的大腦,電腦二字蘊含了人類對計算機的終極期望,希望它能真的像人腦一樣去工作,從而解放人力。
    

    2.2、為什么要用計算機?

    世界是由聰明的懶人統治的,任何時期,總有一群聰明的懶人想要奴隸別人。在奴隸制社會,聰明的懶人奴役的是真正的人,而人是無法不吃、不喝、不睡覺一直工作的,但是計算機作為一臺機器是可以做到的,所以把計算機當奴隸是上上之選
    

    2.3、計算機的五大組成部分

    1.控制器
    	:控制器是計算機的指揮系統,用來控制計算機其他組件的運行,相當于人類的大腦
    2.運算器
    	:運算器是計算機的運算功能,用來做算術運算和邏輯運算,相當于人腦
    ps:控制器+運算器=CPU,cpu相當于人的大腦
    
    3.存儲器
    	:存儲器是計算機的記憶功能,用來存取數據。
    
    存儲器主要分為內存與外存:
    
    ? 內存相當于人的短期記憶。斷電數據丟失
    
    ? 外存(如磁盤),相當于記事的本子,斷電數據不會丟失,是用來永久保存數據的
    
    ? ps:內存的存取速度要遠遠高于外存
    4.輸入設備input
    	:輸入設備是計算接收外界輸入數據的工具,如鍵盤、鼠標,相當于人的眼睛或耳朵。
    5.輸出設備output
    	:輸出設備是計算機向外輸出數據的工具,如顯示器、打印機,相當于人說的話,寫出的文章。
    
    ps:存儲器如內存、磁盤等既是輸入設備又是輸出設備,統稱為IO設備
    

    三大核心硬件為CPU、內存、硬盤。

    程序最先是存放于硬盤中的,程序的運行是先從硬盤把代碼加載到內存中,然后cpu是從內存中讀取指令運行。

    三 操作系統概述

    3.1、操作系統的由來

    操作系統的功能就是幫我們把復雜的硬件的控制封裝成簡單的接口,對于開發應用程序來說只需要調用操作系統提供給我們的接口即可
    

    3.2、系統軟件與應用軟件

    硬件以上運行的都是軟件,而軟件分為兩類:

    一、應用軟件(例如qq、word、暴風影音,我們學習python就是為了開發應用軟件的)
    
    二、操作系統,操作系統應用軟件與硬件之間的一個橋梁,是協調、管理、控制計算機硬件與應用軟件資源的控制程序。
    

    3.3、計算機系統三層結構

    應用程序
    操作系統
    計算機硬件
    

    硬件 + 操作系統 == 平臺

    2、編程語言與Python介紹

    編程語言分類:

    機器語言

    機器語言是站在計算機(奴隸)的角度,說計算機能聽懂/理解的語言,而計算機能直接理解的就是二進制指令,所以機器語言就是直接用二進制編程,這意味著機器語言是直接操作硬件的,因此機器語言屬于低級語言,此處的低級指的是底層、貼近計算機硬件

    #機器語言
        用二進制代碼0和1描述的指令稱為機器指令,由于計算機內部是基于二進制指令工作的,所以機器語言是直接控制計算機硬件。
    
        用機器語言編寫程序,編程人員要首先熟記所用計算機的全部指令代碼以及代碼的含義,然后在編寫程序時,程序員得自己處理每條指令和每一數據的存儲分配和輸入輸出,還得記住編程過程中每步所使用的工作單元處在何種狀態。這是一件十分繁瑣的工作。編寫程序花費的時間往往是實際運行時間的幾十倍或幾百倍。而且,編出的程序全是些0和1的指令代碼,直觀性差,不便閱讀和書寫,還容易出錯,且依賴于具體的計算機硬件型號,局限性很大。除了計算機生產廠家的專業人員外,絕大多數的程序員已經不再去學習機器語言了。
    
        機器語言是被微處理器理解和使用的,存在有多至100000種機器語言的指令,下述是一些簡單示例
    
        #指令部份的示例
        0000 代表 加載(LOAD)
        0001 代表 存儲(STORE)
        ...
    
        #暫存器部份的示例
        0000 代表暫存器 A
        0001 代表暫存器 B
        ...
    
        #存儲器部份的示例
        000000000000 代表地址為 0 的存儲器
        000000000001 代表地址為 1 的存儲器
        000000010000 代表地址為 16 的存儲器
        100000000000 代表地址為 2^11 的存儲器
    
        #集成示例
        0000,0000,000000010000 代表 LOAD A, 16
        0000,0001,000000000001 代表 LOAD B, 1
        0001,0001,000000010000 代表 STORE B, 16
        0001,0001,000000000001 代表 STORE B, 1[1]
    

    總結機器語言

    # 1、執行效率最高
    編寫的程序可以被計算機無障礙理解、直接運行,執行效率高 。
    
    # 2、開發效率最低
    復雜,開發效率低
    
    # 3、跨平臺性差
    貼近\依賴具體的硬件,跨平臺性差
    

    匯編語言

    匯編語言僅僅是用一個英文標簽代表一組二進制指令,毫無疑問,比起機器語言,匯編語言是一種進步,但匯編語言的本質仍然是直接操作硬件,因此匯編語言仍是比較低級/底層的語言、貼近計算機硬件

    #匯編語言
    匯編語言的實質和機器語言是相同的,都是直接對硬件操作,只不過指令采用了英文縮寫的標識符,更容易識別和記憶。它同樣需要編程者將每一步具體的操作用命令的形式寫出來。匯編程序的每一句指令只能對應實際操作過程中的一個很細微的動作。例如移動、自增,因此匯編源程序一般比較冗長、復雜、容易出錯,而且使用匯編語言編程需要有更多的計算機專業知識,但匯編語言的優點也是顯而易見的,用匯編語言所能完成的操作不是一般高級語言所能夠實現的,而且源程序經匯編生成的可執行文件不僅比較小,而且執行速度很快。
    
    匯編的hello world,打印一句hello world, 需要寫十多行,如下
    
    ; hello.asm 
    section .data            ; 數據段聲明
            msg db "Hello, world!", 0xA     ; 要輸出的字符串
            len equ $ - msg                 ; 字串長度
            section .text            ; 代碼段聲明
            global _start            ; 指定入口函數
            _start:                  ; 在屏幕上顯示一個字符串
            mov edx, len     ; 參數三:字符串長度
            mov ecx, msg     ; 參數二:要顯示的字符串
            mov ebx, 1       ; 參數一:文件描述符(stdout) 
            mov eax, 4       ; 系統調用號(sys_write) 
            int 0x80         ; 調用內核功能
                             ; 退出程序
            mov ebx, 0       ; 參數一:退出代碼
            mov eax, 1       ; 系統調用號(sys_exit) 
            int 0x80         ; 調用內核功能
    

    總結匯編語言

    # 1、執行效率高
    相對于機器語言,使用英文標簽編寫程序相對簡單,執行效率高,但較之機器語言稍低,
    
    # 2、開發效率低:
    仍然是直接操作硬件,比起機器語言來說,復雜度稍低,但依舊居高不下,所以開發效率依舊較低
    
    # 3、跨平臺性差
    同樣依賴具體的硬件,跨平臺性差
    

    高級語言

    高級語言是站在人(奴隸主)的角度,說人話,即用人類的字符去編寫程序,而人類的字符是在向操作系統發送指令,而非直接操作硬件,所以高級語言是與操作系統打交道的,此處的高級指的是高層、開發者無需考慮硬件細節,因而開發效率可以得到極大的提升,但正因為高級語言離硬件較遠,更貼近人類語言,人類可以理解,而計算機則需要通過翻譯才能理解,所以執行效率會低于低級語言。

    按照翻譯的方式的不同,高級語言又分為兩種:

    編譯型(如C語言):

    類似谷歌翻譯,是把程序所有代碼編譯成計算機能識別的二進制指令,之后操作系統會拿著編譯好的二進制指令直接操作硬件,詳細如下

    # 1、執行效率高
    編譯是指在應用源程序執行之前,就將程序源代碼“翻譯”成目標代碼(即機器語言),
    因此其目標程序可以脫離其語言環境獨立執行,使用比較方便,執行效率較高。
    
    # 2、開發效率低:
    應用程序一旦需要修改,必須先修改源代碼,然后重新編譯、生成新的目標文件才能執行,
    而在只有目標文件而沒有源代碼,修改會很不方便。所以開發效率低于解釋型
    
    # 3、跨平臺性差
    編譯型代碼是針對某一個平臺翻譯的,當前平臺翻譯的結果無法拿到不同的平臺使用,針對不同的平臺必須重新編譯,即跨平臺性差
    
    # 其他
    現在大多數的編程語言都是編譯型的。
    編譯程序將源程序翻譯成目標程序后保存在另一個文件中,該目標程序可脫離編譯程序直接在計算機上多次運行。
    大多數軟件產品都是以目標程序形式發行給用戶的,不僅便于直接運行,同時又使他人難于盜用其中的技術。
    C、C++、Ada、Pascal都是編譯實現的
    

    解釋型(如python):

    類似同聲翻譯,需要有一個解釋器,解釋器會讀取程序代碼,一邊翻譯一邊執行,詳細如下
    
    # 1、執行效率低
    解釋型語言的實現中,翻譯器并不產生目標機器代碼,而是產生易于執行的中間代碼。
    這種中間代碼與機器代碼是不同的,中間代碼的解釋是由軟件支持的,不能直接使用硬件,
    軟件解釋器通常會導致執行效率較低。
    
    # 2、開發效率高
    用解釋型語言編寫的程序是由另一個可以理解中間代碼的解釋程序執行的,與編譯程序不同的是,
    解釋程序的任務是逐一將源程序的語句解釋成可執行的機器指令,不需要將源程序翻譯成目標代碼再執行。
    解釋程序的優點是當語句出現語法錯誤時,可以立即引起程序員的注意,而程序員在程序開發期間就能進行校正。
    
    
    # 3、跨平臺性強
    代碼運行是依賴于解釋器,不同平臺有對應版本的解釋器,所以解釋型的跨平臺性強
    
    # 其他
    對于解釋型Basic語言,需要一個專門的解釋器解釋執行Basic程序,每條語句只有在執行時才被翻譯,
    這種解釋型語言每執行一次就翻譯一次,因而效率低下。一般地,動態語言都是解釋型的,
    例如:Tcl、Perl、Ruby、VBScript、JavaScript等
    

    ps:混合型語言

    java是一類特殊的編程語言,Java程序也需要編譯,但是卻沒有直接編譯為機器語言,而是編譯為字節碼,
    然后在Java虛擬機上以解釋方式執行字節碼。
    

    總結

    綜上選擇不同編程語言來開發應用程序對比

    #1、執行效率:機器語言>匯編語言>高級語言(編譯型>解釋型)
    
    #2、開發效率:機器語言<匯編語言<高級語言(編譯型<解釋型)
    
    #3、跨平臺性:解釋型具有極強的跨平臺型
    

    三 python介紹

    談及python,涉及兩層意思,一層代表的是python這門語言的語法風格,另外一層代表的則是專門用來解釋該語法風格的應用程序:python解釋器。

    Python崇尚優美、清晰、簡單,是一個優秀并廣泛使用的語言

    Python解釋器的發展史

    從一出生,Python已經具有了:類,函數,異常處理,包含表和詞典在內的核心數據類型,以及模塊為基礎的拓展系統。

    Python解釋器有哪些種類?

    官方的Python解釋器本質就是基于C語言開發的一個軟件,該軟件的功能就是讀取以.py結尾的文件內容,然后按照Guido定義好的語法和規則去翻譯并執行相應的代碼。

    # Jython
    JPython解釋器是用JAVA編寫的python解釋器,可以直接把Python代碼編譯成Java字節碼并執行,它不但使基于java的項目之上嵌入python腳本成為可能,同時也可以將java程序引入到python程序之中。
    
    # IPython
    IPython是基于CPython之上的一個交互式解釋器,也就是說,IPython只是在交互方式上有所增強,但是執行Python代碼的功能和CPython是完全一樣的。這就好比很多國產瀏覽器雖然外觀不同,但內核其實都是調用了IE。
    CPython用>>>作為提示符,而IPython用In [序號]:作為提示符。
    
    # PyPy
    PyPy是Python開發者為了更好地Hack Python而用Python語言實現的Python解釋器。PyPy提供了JIT編譯器和沙盒功能,對Python代碼進行動態編譯(注意不是解釋),因此運行速度比CPython還要快。
    
    # IronPython
    IronPython和Jython類似,只不過IronPython是運行在微軟.Net平臺上的Python解釋器,可以直接把Python代碼編譯成.Net的字節碼。
    

    四 安裝Cpython解釋器

    Python解釋器目前已支持所有主流操作系統,在Linux,Unix,Mac系統上自帶Python解釋器,在Windows系統上需要安裝一下,具體步驟如下。

    4.1、下載python解釋器

    https://www.python.org

    img

    img

    img

    4.2、安裝python解釋器

    img

    img

    img

    4.3、測試安裝是否成功

    windows --> 運行 --> 輸入cmd ,然后回車,彈出cmd程序,輸入python,如果能進入交互環境 ,代表安裝成功。

    img

    img

    五 第一個python程序

    5.1 運行python程序有兩種方式

    方式一: 交互式模式

    img

    方式二:腳本文件

    # 1、打開一個文本編輯工具,寫入下述代碼,并保存文件,此處文件的路徑為D:\test.py。強調:python解釋器執行程序是解釋執行,解釋的根本就是打開文件讀內容,因此文件的后綴名沒有硬性限制,但通常定義為.py結尾
    print('hello world')
    
    # 2、打開cmd,運行命令,如下圖
    

    img

    總結:

    #1、交互式模式下可以即時得到代碼執行結果,調試程序十分方便
    #2、若想將代碼永久保存下來,則必須將代碼寫入文件中
    #3、我們以后主要就是在代碼寫入文件中,偶爾需要打開交互式模式調試某段代碼、驗證結果
    

    5.2 注釋

    在正式學習python語法前,我們必須事先介紹一個非常重要的語法:注釋

    1、什么是注釋

    注釋就是就是對代碼的解釋說明,注釋的內容不會被當作代碼運行
    

    2、為什么要注釋

    增強代碼的可讀性
    

    3、怎么用注釋?

    代碼注釋分單行和多行注釋
    
    1、單行注釋用#號,可以跟在代碼的正上方或者正后方
    
    2、多行注釋可以用三對雙引號""" """
    

    4、代碼注釋的原則:

    1、不用全部加注釋,只需要為自己覺得重要或不好理解的部分加注釋即可
    
    2、注釋可以用中文或英文,但不要用拼音
    

    六 IDE工具pycharm的使用

    在編寫第一個python程序時,存在以下問題,嚴重影響開發效率

    問題一:我們了解到一個python程序從開發到運行需要操作至少兩個軟件

    1、打開一個軟件:文本編輯器,創建文本來編寫程序
    2、打開cmd,然后輸入命令執行pyton程序
    

    問題二:在開發過程中,并沒代碼提示以及糾錯功能

    綜上,如果能有一款工具能夠集成n個軟件的功能,同時又代碼提示以及糾錯等功能,那么將會極大地提升程序員的開發效率,這就是IDE的由來,IDE全稱Integrated Development Environment,即集成開發環境,最好的開發Python程序的IDE就是PyCharm。
    

    6.2、pychram安裝

    # 下載地址: https://www.jetbrains.com/pycharm/download  選擇Professional專業版
    

    6.3、Pycharm創建文件夾

    6.4、如何創建文件并編寫程序執行

    創建py文件test.py

    在test.py中寫代碼,輸入關鍵字的開頭可以用tab鍵補全后續,并且會有代碼的錯誤提示

    3、Python語法入門之變量

    一 引入

    我們學習python語言是為了控制計算機、讓計算機能夠像人一樣去工作,所以在python這門語言中,所有語法存在的意義都是為了讓計算機具備人的某一項技能,這句話是我們理解后續所有python語法的根本。

    二 變量

    一、什么是變量?

    # 變量就是可以變化的量,量指的是事物的狀態,比如人的年齡、性別,游戲角色的等級、金錢等等
    

    二、為什么要有變量?

    # 為了讓計算機能夠像人一樣去記憶事物的某種狀態,并且狀態是可以發生變化的
    # 詳細地說:
    # 程序執行的本質就是一系列狀態的變化,變是程序執行的直接體現,所以我們需要有一種機制能夠反映或者說是保存下來程序執行時狀態,以及狀態的變化。
    

    三、怎么使用變量(先定義、后使用)

    3.1、變量的定義與使用

    img

    定義變量示范如下

    name = 'harry' # 記下人的名字為'harry'
    sex = '男'    # 記下人的性別為男性
    age = 18      # 記下人的年齡為18歲
    salary = 30000.1  # 記下人的薪資為30000.1元
    

    解釋器執行到變量定義的代碼時會申請內存空間存放變量值,然后將變量值的內存地址綁定給變量名,以變量的定義age=18為例,如下圖

    插圖:定義變量申請內存

    通過變量名即可引用到對應的值

    # 通過變量名即可引用到值,我們可以結合print()功能將其打印出來
    print(age) # 通過變量名age找到值18,然后執行print(18),輸出:18
    
    # 命名規范
    1. 變量名只能是 字母、數字或下劃線的任意組合
    2. 變量名的第一個字符不能是數字
    3. 關鍵字不能聲明為變量名,常用關鍵字如下
    ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from','global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
    年齡=18 # 強烈建議不要使用中文命名
    
    

    3.3、變量名的命名風格

    # 風格一:駝峰體
    AgeOfTony = 56 
    NumberOfStudents = 80
    # 風格二:純小寫下劃線(在python中,變量名的命名推薦使用該風格)
    age_of_tony = 56 
    number_of_students = 80
    

    3.4、變量值的三大特性

    #1、id
    反應的是變量在內存中的唯一編號,內存地址不同id肯定不同
    
    #2、type
    變量值的類型
    
    #3、value
    變量值
    

    三、常量

    3.1、什么是常量?

    常量指在程序運行過程中不會改變的量

    3.2、為什么要有常量?

    在程序運行過程中,有些值是固定的、不應該被改變,比如圓周率 3.141592653…

    3.3、怎么使用常量?

    在Python中沒有一個專門的語法定義常量,約定俗成是用全部大寫的變量名表示常量。如:PI=3.14159。所以單從語法層面去講,常量的使用與變量完全一致。

    4、Python語法入門之基本數據類型

    一 引入

    變量值也有不同的類型

    salary = 3.1 # 用浮點型去記錄薪資
    age = 18 # 用整型去記錄年齡
    name = 'lili' # 用字符串類型去記錄人名
    

    二 數字類型

    2.1 int整型

    2.1.1 作用

    用來記錄人的年齡,出生年份,學生人數等整數相關的狀態

    2.1.2 定義

    age=18
    
    birthday=1990
    
    student_count=48
    

    2.2 float浮點型

    2.2.1 作用

    用來記錄人的身高,體重,薪資等小數相關的狀態

    2.2.2 定義

    height=172.3
    
    weight=103.5
    
    salary=15000.89
    

    2.3 數字類型的使用

    1 、數學運算

    >>> a = 1
    >>> b = 3
    >>> c = a + b
    >>> c
    4
    

    2、比較大小

    >>> x = 10
    >>> y = 11
    >>> x > y
    False
    

    三 字符串類型str

    3.1 作用

    用來記錄人的名字,家庭住址,性別等描述性質的狀態

    3.2 定義

    name = 'harry'
    
    address = '上海市浦東新區'
    
    sex = '男'
    

    用單引號、雙引號、多引號,都可以定義字符串,本質上是沒有區別的,但是

    #1、需要考慮引號嵌套的配對問題
    msg = "My name is Tony , I'm 18 years old!" #內層有單引號,外層就需要用雙引號
    #2、多引號可以寫多行字符串
    msg = '''
            天下只有兩種人。比如一串葡萄到手,一種人挑最好的先吃,另一種人把最好的留到最后吃。
            照例第一種人應該樂觀,因為他每吃一顆都是吃剩的葡萄里最好的;第二種人應該悲觀,因為他每吃一顆都是吃剩的葡萄里最壞的。
            不過事實卻適得其反,緣故是第二種人還有希望,第一種人只有回憶。
          '''
    

    3.3 使用

    數字可以進行加減乘除等運算,字符串呢?也可以,但只能進行"相加"和"相乘"運算。
    >>> name = 'tony'
    >>> age = '18'
    >>> name + age #相加其實就是簡單的字符串拼接
    'tony18'
    >>> name * 5 #相乘就相當于將字符串相加了5次
    'tonytonytonytonytony'
    

    四 列表list

    4.1 作用

    如果我們需要用一個變量記錄多個學生的姓名,用數字類型是無法實現,字符串類型確實可以記錄下來,比如

    stu_names=‘張三 李四 王五’,但存的目的是為了取,此時若想取出第二個學生的姓名實現起來相當麻煩,而列表類型就是專門用來記錄多個同種屬性的值(比如同一個班級多個學生的姓名、同一個人的多個愛好等),并且存取都十分方便

    4.2 定義

    >>> stu_names=['張三','李四','王五']
    

    4.3 使用

    # 1、列表類型是用索引來對應值,索引代表的是數據的位置,從0開始計數
    >>> stu_names=['張三','李四','王五']
    >>> stu_names[0] 
    '張三'
    >>> stu_names[1]
    '李四'
    >>> stu_names[2]
    '王五'
    # 2、列表可以嵌套,嵌套取值如下
    >>> students_info=[['tony',18,['jack',]],['jason',18,['play','sleep']]]
    >>> students_info[0][2][0] #取出第一個學生的第一個愛好
    'play'
    

    五 字典dict

    5.1 作用

    如果我們需要用一個變量記錄多個值,但多個值是不同屬性的,比如人的姓名、年齡、身高,用列表可以存,但列表是用索引對應值的,而索引不能明確地表示值的含義,這就用到字典類型,字典類型是用key:value形式來存儲數據,其中key可以對value有描述性的功能

    5.2 定義

    >>> person_info={'name':'tony','age':18,'height':185.3}
    

    5.3 使用

    # 1、字典類型是用key來對應值,key可以對值有描述性的功能,通常為字符串類型
    >>> person_info={'name':'tony','age':18,'height':185.3}
    >>> person_info['name']
    'tony'
    >>> person_info['age']
    18
    >>> person_info['height']
    185.3
    # 2、字典可以嵌套,嵌套取值如下
    >>> students=[
    ... {'name':'tony','age':38,'hobbies':['play','sleep']},
    ... {'name':'jack','age':18,'hobbies':['read','sleep']},
    ... {'name':'rose','age':58,'hobbies':['music','read','sleep']},
    ... ]
    >>> students[1]['hobbies'][1] #取第二個學生的第二個愛好
    'sleep'
    

    六 布爾bool

    6.1 作用

    用來記錄真假這兩種狀態

    6.2 定義

    >>> is_ok = True
    >>> is_ok = False
    

    6.3 使用

    通常用來當作判斷的條件,我們將在if判斷中用到它
    

    5、Python語法入門之垃圾回收機制

    一 引入

    解釋器在執行到定義變量的語法時,會申請內存空間來存放變量的值,而內存的容量是有限的,這就涉及到變量值所占用內存空間的回收問題,當一個變量值沒有用了(簡稱垃圾)就應該將其占用的內存給回收掉,那什么樣的變量值是沒有用的呢?
    
    ? 單從邏輯層面分析,我們定義變量將變量值存起來的目的是為了以后取出來使用,而取得變量值需要通過其綁定的直接引用(如x=10,10被x直接引用)或間接引用(如l=[x,],x=10,10被x直接引用,而被容器類型l間接引用),所以當一個變量值不再綁定任何引用時,我們就無法再訪問到該變量值了,該變量值自然就是沒有用的,就應該被當成一個垃圾回收。
    
    ? 毫無疑問,內存空間的申請與回收都是非常耗費精力的事情,而且存在很大的危險性,稍有不慎就有可能引發內存溢出問題,好在Cpython解釋器提供了自動的垃圾回收機制來幫我們解決了這件事。
    

    二、什么是垃圾回收機制?

    垃圾回收機制(簡稱GC)是Python解釋器自帶一種機,專門用來回收不可用的變量值所占用的內存空間
    

    三、為什么要用垃圾回收機制?

    程序運行過程中會申請大量的內存空間,而對于一些無用的內存空間如果不及時清理的話會導致內存使用殆盡(內存溢出),導致程序崩潰,因此管理內存是一件重要且繁雜的事情,而python解釋器自帶的垃圾回收機制把程序員從繁雜的內存管理中解放出來。
    

    四、理解GC原理需要儲備的知識

    4.1、堆區與棧區

    在定義變量時,變量名與變量值都是需要存儲的,分別對應內存中的兩塊區域:堆區與棧區

    # 1、變量名與值內存地址的關聯關系存放于棧區
    
    # 2、變量值存放于堆區,內存管理回收的則是堆區的內容,
    

    4.2 直接引用與間接引用

    直接引用指的是從棧區出發直接引用到的內存地址。

    間接引用指的是從棧區出發引用到堆區后,再通過進一步引用才能到達的內存地址。

    l2 = [20, 30]  # 列表本身被變量名l2直接引用,包含的元素被列表間接引用
    x = 10  # 值10被變量名x直接引用
    l1 = [x, l2]  # 列表本身被變量名l1直接引用,包含的元素被列表間接引用
    

    img

    五、垃圾回收機制原理分析

    Python的GC模塊主要運用了“引用計數”(reference counting)來跟蹤和回收垃圾。在引用計數的基礎上,還可以通過“標記-清除”(mark and sweep)解決容器對象可能產生的循環引用的問題,并且通過“分代回收”(generation collection)以空間換取時間的方式來進一步提高垃圾回收的效率。

    5.1、引用計數

    引用計數就是:變量值被變量名關聯的次數

    如:age=18

    變量值18被關聯了一個變量名age,稱之為引用計數為1

    img

    引用計數增加:

    age=18 (此時,變量值18的引用計數為1)

    m=age (把age的內存地址給了m,此時,m,age都關聯了18,所以變量值18的引用計數為2)

    img

    引用計數減少:

    age=10(名字age先與值18解除關聯,再與3建立了關聯,變量值18的引用計數為1)

    del m(del的意思是解除變量名x與變量值18的關聯關系,此時,變量18的引用計數為0)

    img

    值18的引用計數一旦變為0,其占用的內存地址就應該被解釋器的垃圾回收機制回收

    5.2、引用計數的問題與解決方案

    5.2.1 問題一:循環引用

    引用計數機制存在著一個致命的弱點,即循環引用(也稱交叉引用)

    # 如下我們定義了兩個列表,簡稱列表1與列表2,變量名l1指向列表1,變量名l2指向列表2
    >>> l1=['xxx']  # 列表1被引用一次,列表1的引用計數變為1   
    >>> l2=['yyy']  # 列表2被引用一次,列表2的引用計數變為1   
    >>> l1.append(l2)             # 把列表2追加到l1中作為第二個元素,列表2的引用計數變為2
    >>> l2.append(l1)             # 把列表1追加到l2中作為第二個元素,列表1的引用計數變為2
    
    # l1與l2之間有相互引用
    # l1 = ['xxx'的內存地址,列表2的內存地址]
    # l2 = ['yyy'的內存地址,列表1的內存地址]
    >>> l1
    ['xxx', ['yyy', [...]]]
    >>> l2
    ['yyy', ['xxx', [...]]]
    >>> l1[1][1][0]
    'xxx'
    

    img

    python引入了“標記-清除” 與“分代回收”來分別解決引用計數的循環引用與效率低的問題

    5.2.2 解決方案:標記-清除

    容器對象(比如:list,set,dict,class,instance)都可以包含對其他對象的引用,所以都可能產生循環引用。而“標記-清除”計數就是為了解決循環引用的問題。

    標記/清除算法的做法是當應用程序可用的內存空間被耗盡的時,就會停止整個程序,然后進行兩項工作,第一項則是標記,第二項則是清除

    5.2.3 問題二:效率問題

    基于引用計數的回收機制,每次回收內存,都需要把所有對象的引用計數都遍歷一遍,這是非常消耗時間的,于是引入了分代回收來提高回收效率,分代回收采用的是用“空間換時間”的策略。
    

    5.2.4 解決方案:分代回收

    代:

    分代回收的核心思想是:在歷經多次掃描的情況下,都沒有被回收的變量,gc機制就會認為,該變量是常用變量,gc對其掃描的頻率會降低,具體實現原理如下:

    分代指的是根據存活時間來為變量劃分不同等級(也就是不同的代)
    
    新定義的變量,放到新生代這個等級中,假設每隔1分鐘掃描新生代一次,如果發現變量依然被引用,那么該對象的權重(權重本質就是個整數)加一,當變量的權重大于某個設定得值(假設為3),會將它移動到更高一級的青春代,青春代的gc掃描的頻率低于新生代(掃描時間間隔更長),假設5分鐘掃描青春代一次,這樣每次gc需要掃描的變量的總個數就變少了,節省了掃描的總時間,接下來,青春代中的對象,也會以同樣的方式被移動到老年代中。也就是等級(代)越高,被垃圾回收機制掃描的頻率越低
    

    回收:

    回收依然是使用引用計數作為回收的依據

    img

    雖然分代回收可以起到提升效率的效果,但也存在一定的缺點:

    例如一個變量剛剛從新生代移入青春代,該變量的綁定關系就解除了,該變量應該被回收,但青春代的掃描頻率低于新生代,這就到導致了應該被回收的垃圾沒有得到及時地清理。
    
    沒有十全十美的方案:
    毫無疑問,如果沒有分代回收,即引用計數機制一直不停地對所有變量進行全體掃描,可以更及時地清理掉垃圾占用的內存,但這種一直不停地對所有變量進行全體掃描的方式效率極低,所以我們只能將二者中和。
    
    綜上
    垃圾回收機制是在清理垃圾&釋放內存的大背景下,允許分代回收以極小部分垃圾不會被及時釋放為代價,以此換取引用計數整體掃描頻率的降低,從而提升其性能,這是一種以空間換時間的解決方案目錄
    

    6、Python語法入門之與用戶交互、運算符

    一 程序與用戶交互

    1.1、什么是與用戶交互

    用戶交互就是人往計算機中input/輸入數據,計算機print/輸出結果

    1.2、為什么要與用戶交互?

    為了讓計算機能夠像人一樣與用戶溝通交流
    

    1.3、如何與用戶交互

    交互的本質就是輸入、輸出
    

    1.3.1 輸入input:

    # 在python3中input功能會等待用戶的輸入,用戶輸入任何內容,都存成字符串類型,然后賦值給等號左邊的變量名
    >>> username=input('請輸入您的用戶名:') 
    請輸入您的用戶名:jack # username = "jack"
    >>> password=input('請輸入您的密碼:') 
    請輸入您的密碼:123 # password = "123"
    
    # 了解知識:
    # 1、在python2中存在一個raw_input功能與python3中的input功能一模一樣
    # 2、在python2中還存在一個input功能,需要用戶輸入一個明確的數據類型,輸入什么類型就存成什么類型
    >>> l=input('輸入什么類型就存成什么類型: ')
    輸入什么類型就存成什么類型: [1,2,3]
    >>> type(l)
    <type 'list'>
    

    1.3.2 輸出print:

    >>> print('hello world')  # 只輸出一個值
    hello world
    >>> print('first','second','third')  # 一次性輸出多個值,值用逗號隔開
    first second third
    
    # 默認print功能有一個end參數,該參數的默認值為"\n"(代表換行),可以將end參數的值改成任意其它字符
    print("aaaa",end='')
    print("bbbb",end='&')
    print("cccc",end='@')
    #整體輸出結果為:aaaabbbb&cccc@
    

    1.3.3 輸出之格式化輸出

    (1)什么是格式化輸出?

    把一段字符串里面的某些內容替換掉之后再輸出,就是格式化輸出。

    (2)為什么要格式化輸出?

    我們經常會輸出具有某種固定格式的內容,比如:'親愛的xxx你好!你xxx月的話費是xxx,余額是xxx‘,我們需要做的就是將xxx替換為具體的內容。

    (3)如何格式化輸出?

    這就用到了占位符,如:%s、%d:

    # %s占位符:可以接收任意類型的值
    # %d占位符:只能接收數字
    >>> print('親愛的%s你好!你%s月的話費是%d,余額是%d' %('tony',12,103,11))
    親愛的tony你好!你12月的話費是103,余額是11
    
    # 練習1:接收用戶輸入,打印成指定格式
    name = input('your name: ')
    age = input('your age: ') #用戶輸入18,會存成字符串18,無法傳給%d
    print('My name is %s,my age is %s' %(name,age))
    
    # 練習2:用戶輸入姓名、年齡、工作、愛好 ,然后打印成以下格式
    ------------ info of Tony -----------
    Name  : Tony
    Age   : 22
    Sex   : male
    Job   : Teacher 
    ------------- end -----------------
    

    二 基本運算符

    2.1 算術運算符

    python支持的算數運算符與數學上計算的符號使用是一致的,我們以x=9,y=2為例來依次介紹它們

    img

    2.2 比較運算符

    比較運算用來對兩個值進行比較,返回的是布爾值True或False,我們以x=9,y=2為例來依次介紹它們

    img

    2.3 賦值運算符

    python語法中除了有=號這種簡單的賦值運算外,還支持增量賦值、鏈式賦值、交叉賦值、解壓賦值,這些賦值運算符存在的意義都是為了讓我們的代碼看起來更加精簡。我們以x=9,y=2為例先來介紹一下增量賦值

    2.3.1 增量賦值

    img

    2.3.2 鏈式賦值

    如果我們想把同一個值同時賦值給多個變量名,可以這么做

    >>> z=10
    >>> y=z
    >>> x=y
    >>> x,y,z
    (10, 10, 10)
    

    鏈式賦值指的是可以用一行代碼搞定這件事

    >>> x=y=z=10
    >>> x,y,z
    (10, 10, 10)
    

    2.3.3 交叉賦值

    我們定義兩個變量m與n

    如果我們想將m與n的值交換過來,可以這么做

    >>> temp=m
    >>> m=n
    >>> n=temp
    >>> m,n
    (20, 10)
    

    交叉賦值指的是一行代碼可以搞定這件事

    >>> m=10
    >>> n=20
    >>> m,n=n,m # 交叉賦值
    >>> m,n
    (20, 10)
    

    2.3.4 解壓賦值

    如果我們想把列表中的多個值取出來依次賦值給多個變量名,可以這么做

    >>> nums=[11,22,33,44,55]
    >>> 
    >>> a=nums[0]
    >>> b=nums[1]
    >>> c=nums[2]
    >>> d=nums[3]
    >>> e=nums[4]
    >>> a,b,c,d,e
    (11, 22, 33, 44, 55)
    

    解壓賦值指的是一行代碼可以搞定這件事

    >>> a,b,c,d,e=nums # nums包含多個值,就好比一個壓縮包,解壓賦值因此得名
    >>> a,b,c,d,e
    (11, 22, 33, 44, 55)
    

    注意,上述解壓賦值,等號左邊的變量名個數必須與右面包含值的個數相同,否則會報錯

    #1、變量名少了
    >>> a,b=nums
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: too many values to unpack (expected 2)
    
    #2、變量名多了
    >>> a,b,c,d,e,f=nums
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: not enough values to unpack (expected 6, got 5)
    

    但如果我們只想取頭尾的幾個值,可以用*_匹配

    >>> a,b,*_=nums
    >>> a,b
    (11, 22)
    

    ps:字符串、字典、元組、集合類型都支持解壓賦值

    2.4 邏輯運算符

    邏輯運算符用于連接多個條件,進行關聯判斷,會返回布爾值True或False

    img

    2.4.1 連續多個and

    可以用and連接多個條件,會按照從左到右的順序依次判斷,一旦某一個條件為False,則無需再往右判斷,可以立即判定最終結果就為False,只有在所有條件的結果都為True的情況下,最終結果才為True。

    >>> 2 > 1 and 1 != 1 and True and 3 > 2 # 判斷完第二個條件,就立即結束,得的最終結果為False
    False
    

    2.4.2 連續多個or

    可以用or連接多個條件,會按照從左到右的順序依次判斷,一旦某一個條件為True,則無需再往右判斷,可以立即判定最終結果就為True,只有在所有條件的結果都為False的情況下,最終結果才為False

    >>> 2 > 1 or 1 != 1 or True or 3 > 2 # 判斷完第一個條件,就立即結束,得的最終結果為True
    True
    

    2.4.3 優先級not>and>or

    #1、三者的優先級關系:not>and>or,同一優先級默認從左往右計算。
    >>> 3>4 and 4>3 or 1==3 and 'x' == 'x' or 3 >3
    False
    
    #2、最好使用括號來區別優先級,其實意義與上面的一樣
    '''
    原理為:
    (1) not的優先級最高,就是把緊跟其后的那個條件結果取反,所以not與緊跟其后的條件不可分割
    
    (2) 如果語句中全部是用and連接,或者全部用or連接,那么按照從左到右的順序依次計算即可
    
    (3) 如果語句中既有and也有or,那么先用括號把and的左右兩個條件給括起來,然后再進行運算
    '''
    >>> (3>4 and 4>3) or (1==3 and 'x' == 'x') or 3 >3
    False 
    
    #3、短路運算:邏輯運算的結果一旦可以確定,那么就以當前處計算到的值作為最終結果返回
    >>> 10 and 0 or '' and 0 or 'abc' or 'egon' == 'dsb' and 333 or 10 > 4
    我們用括號來明確一下優先級
    >>> (10 and 0) or ('' and 0) or 'abc' or ('egon' == 'dsb' and 333) or 10 > 4
    短路:       0      ''            'abc'                    
                假     假              真
    
    返回:                            'abc'
    
    #4、短路運算面試題:
    >>> 1 or 3
    1
    >>> 1 and 3
    3
    >>> 0 and 2 and 1
    0
    >>> 0 and 2 or 1
    1
    >>> 0 and 2 or 1 or 4
    1
    >>> 0 or False and 1
    False 
    

    2.5 成員運算符

    img

    注意:雖然下述兩種判斷可以達到相同的效果,但我們推薦使用第二種格式,因為not in語義更加明確

    >>> not 'lili' in ['jack','tom','robin']
    True
    >>> 'lili' not in ['jack','tom','robin']
    True
    

    2.6 身份運算符

    img

    需要強調的是:==雙等號比較的是value是否相等,而is比較的是id是否相等

    #1. id相同,內存地址必定相同,意味著type和value必定相同
    #2. value相同type肯定相同,但id可能不同,如下
    >>> x='Info Tony:18'
    >>> y='Info Tony:18'
    >>> id(x),id(y) # x與y的id不同,但是二者的值相同
    (4327422640, 4327422256)
    
    
    >>> x == y # 等號比較的是value
    True
    >>> type(x),type(y) # 值相同type肯定相同
    (<class 'str'>, <class 'str'>)
    >>> x is y # is比較的是id,x與y的值相等但id可以不同
    False
    
    

    7、Python語法入門之流程控制

    一 引子:

    流程控制即控制流程,具體指控制程序的執行流程,而程序的執行流程分為三種結構:順序結構(之前我們寫的代碼都是順序結構)、分支結構(用到if判斷)、循環結構(用到while與for)

    二 分支結構

    2.1 什么是分支結構

    分支結構就是根據條件判斷的真假去執行不同分支對應的子代碼

    2.2 為什么要用分支結構

    人類某些時候需要根據條件來決定做什么事情,比如:如果今天下雨,就帶傘

    所以程序中必須有相應的機制來控制計算機具備人的這種判斷能力

    2.3 如何使用分支結構

    2.3.1 if語法

    用if關鍵字來實現分支結構,完整語法如下

    if 條件1:   # 如果條件1的結果為True,就依次執行:代碼1、代碼2,......
       代碼1
        代碼2
        ......
    elif 條件2: # 如果條件2的結果為True,就依次執行:代碼3、代碼4,......
       代碼3
        代碼4
        ......
    elif 條件3: # 如果條件3的結果為True,就依次執行:代碼5、代碼6,......
       代碼5
        代碼6
        ......
    else:     # 其它情況,就依次執行:代碼7、代碼8,......
        代碼7
        代碼8
        ......
    # 注意:
    # 1、python用相同縮進(4個空格表示一個縮進)來標識一組代碼塊,同一組代碼會自上而下依次運行
    # 2、條件可以是任意表達式,但執行結果必須為布爾類型
         # 在if判斷中所有的數據類型也都會自動轉換成布爾類型
           # 2.1、None,0,空(空字符串,空列表,空字典等)三種情況下轉換成的布爾值為False
           # 2.2、其余均為True
    

    2.3.2 if應用案例

    案例1:

    如果:女人的年齡>30歲,那么:叫阿姨

    age_of_girl=31
    if age_of_girl > 30:
        print('阿姨好')
    

    案例2:

    如果:女人的年齡>30歲,那么:叫阿姨,否則:叫小姐

    age_of_girl=18
    if age_of_girl > 30:
        print('阿姨好')
    else:
        print('小姐好')
    

    案例3:

    如果:女人的年齡>=18并且<22歲并且身高>170并且體重<100并且是漂亮的,那么:表白,否則:叫阿姨**

    age_of_girl=18
    height=171
    weight=99
    is_pretty=True
    if age_of_girl >= 18 and age_of_girl < 22 and height > 170 and weight < 100 and is_pretty == True:
        print('表白...')
    else:
        print('阿姨好')
    

    案例4:

    如果:成績>=90,那么:優秀

    如果成績>=80且<90,那么:良好

    如果成績>=70且<80,那么:普通

    其他情況:很差

    score=input('>>: ')
    score=int(score)
    
    if score >= 90:
        print('優秀')
    elif score >= 80:
        print('良好')
    elif score >= 70:
        print('普通')
    else:
        print('很差')
    

    案例 5:if 嵌套

    #在表白的基礎上繼續:
    #如果表白成功,那么:在一起
    #否則:打印。。。
    
    age_of_girl=18
    height=171
    weight=99
    is_pretty=True
    success=False
    
    if age_of_girl >= 18 and age_of_girl < 22 and height > 170 and weight < 100 and is_pretty == True:
        if success:
            print('表白成功,在一起')
        else:
            print('什么愛情不愛情的,愛nmlgb的愛情,愛nmlg啊...')
    else:
        print('阿姨好')
    

    練習1: 登陸功能

    name=input('請輸入用戶名字:').strip()
    password=input('請輸入密碼:').strip()
    if name == 'tony' and password == '123':
        print('tony login success')
    else:
        print('用戶名或密碼錯誤')
    

    練習2:

    #!/usr/bin/env python
    #根據用戶輸入內容打印其權限
    
    '''
    egon --> 超級管理員
    tom  --> 普通管理員
    jack,rain --> 業務主管
    其他 --> 普通用戶
    '''
    name=input('請輸入用戶名字:')
    
    if name == 'egon':
        print('超級管理員')
    elif name == 'tom':
        print('普通管理員')
    elif name == 'jack' or name == 'rain':
        print('業務主管')
    else:
        print('普通用戶')
    

    三 循環結構

    3.1 什么是循環結構

    循環結構就是重復執行某段代碼塊

    3.2 為什么要用循環結構

    人類某些時候需要重復做某件事情

    所以程序中必須有相應的機制來控制計算機具備人的這種循環做事的能力

    3.3 如何使用循環結構

    3.3.1 while循環語法

    python中有while與for兩種循環機制,其中while循環稱之為條件循環,語法如下

    while 條件:
         代碼1     
         代碼2     
         代碼3
    while的運行步驟:
    步驟1:如果條件為真,那么依次執行:代碼1、代碼2、代碼3、......
    步驟2:執行完畢后再次判斷條件,如果條件為True則再次執行:代碼1、代碼2、代碼3、......,如果條件為False,則循環終止
    

    img

    3.3.2 while循環應用案例

    案例一:while循環的基本使用

    用戶認證程序

    #用戶認證程序的基本邏輯就是接收用戶輸入的用戶名密碼然后與程序中存放的用戶名密碼進行判斷,判斷成功則登陸成功,判斷失敗則輸出賬號或密碼錯誤
    username = "jason"
    password = "123"
    
    inp_name =  input("請輸入用戶名:")
    inp_pwd =  input("請輸入密碼:")
    if inp_name == username and inp_pwd == password:
        print("登陸成功")
    else:
        print("輸入的用戶名或密碼錯誤!")
    #通常認證失敗的情況下,會要求用戶重新輸入用戶名和密碼進行驗證,如果我們想給用戶三次試錯機會,本質就是將上述代碼重復運行三遍,你總不會想著把代碼復制3次吧。。。。
    username = "jason"
    password = "123"
    
    # 第一次驗證
    inp_name =  input("請輸入用戶名:")
    inp_pwd =  input("請輸入密碼:")
    if inp_name == username and inp_pwd == password:
        print("登陸成功")
    else:
        print("輸入的用戶名或密碼錯誤!")
    
    # 第二次驗證
    inp_name =  input("請輸入用戶名:")
    inp_pwd =  input("請輸入密碼:")
    if inp_name == username and inp_pwd == password:
        print("登陸成功")
    else:
        print("輸入的用戶名或密碼錯誤!")
    
    # 第三次驗證
    inp_name =  input("請輸入用戶名:")
    inp_pwd =  input("請輸入密碼:")
    if inp_name == username and inp_pwd == password:
        print("登陸成功")
    else:
        print("輸入的用戶名或密碼錯誤!")
    
    #即使是小白的你,也覺得的太low了是不是,以后要修改功能還得修改3次,因此記住,寫重復的代碼是程序員最不恥的行為。
    #那么如何做到不用寫重復代碼又能讓程序重復一段代碼多次呢? 循環語句就派上用場啦(使用while循環實現)
    
    username = "jason"
    password = "123"
    # 記錄錯誤驗證的次數
    count = 0
    while count < 3:
        inp_name = input("請輸入用戶名:")
        inp_pwd = input("請輸入密碼:")
        if inp_name == username and inp_pwd == password:
            print("登陸成功")
        else:
            print("輸入的用戶名或密碼錯誤!")
            count += 1
    

    案例二:while+break的使用

    使用了while循環后,代碼確實精簡多了,但問題是用戶輸入正確的用戶名密碼以后無法結束循環,那如何結束掉一個循環呢?這就需要用到break了!

    username = "jason"
    password = "123"
    # 記錄錯誤驗證的次數
    count = 0
    while count < 3:
        inp_name = input("請輸入用戶名:")
        inp_pwd = input("請輸入密碼:")
        if inp_name == username and inp_pwd == password:
            print("登陸成功")
            break # 用于結束本層循環
        else:
            print("輸入的用戶名或密碼錯誤!")
            count += 1
    

    案例三:while循環嵌套+break

    如果while循環嵌套了很多層,要想退出每一層循環則需要在每一層循環都有一個break

    username = "jason"
    password = "123"
    count = 0
    while count < 3:  # 第一層循環
        inp_name = input("請輸入用戶名:")
        inp_pwd = input("請輸入密碼:")
        if inp_name == username and inp_pwd == password:
            print("登陸成功")
            while True:  # 第二層循環
                cmd = input('>>: ')
                if cmd == 'quit':
                    break  # 用于結束本層循環,即第二層循環
                print('run <%s>' % cmd)
            break  # 用于結束本層循環,即第一層循環
        else:
            print("輸入的用戶名或密碼錯誤!")
            count += 1
    

    案例四:while循環嵌套+tag的使用

    針對嵌套多層的while循環,如果我們的目的很明確就是要在某一層直接退出所有層的循環,其實有一個竅門,就讓所有while循環的條件都用同一個變量,該變量的初始值為True,一旦在某一層將該變量的值改成False,則所有層的循環都結束

    username = "jason"
    password = "123"
    count = 0
    
    tag = True
    while tag: 
        inp_name = input("請輸入用戶名:")
        inp_pwd = input("請輸入密碼:")
        if inp_name == username and inp_pwd == password:
            print("登陸成功")
            while tag:  
                cmd = input('>>: ')
                if cmd == 'quit':
                    tag = False  # tag變為False, 所有while循環的條件都變為False 
                    break
                print('run <%s>' % cmd)
            break  # 用于結束本層循環,即第一層循環
        else:
            print("輸入的用戶名或密碼錯誤!")
            count += 1
    

    案例五:while+continue的使用

    break代表結束本層循環,而continue則用于結束本次循環,直接進入下一次循環

    # 打印1到10之間,除7以外的所有數字
    number=11
    while number>1:
        number -= 1
        if number==7:
            continue # 結束掉本次循環,即本次循環continue之后的代碼都不會運行了,而是直接進入下一次循環
        print(number)
    

    案例五:while+else的使用

    在while循環的后面,我們可以跟else語句,當while 循環正常執行完并且中間沒有被break 中止的話,就會執行else后面的語句,所以我們可以用else來驗證,循環是否正常結束

    count = 0
    while count <= 5 :
        count += 1
        print("Loop",count)
    else:
        print("循環正常執行完啦")
    print("-----out of while loop ------")
    輸出
    Loop 1
    Loop 2
    Loop 3
    Loop 4
    Loop 5
    Loop 6
    循環正常執行完啦   #沒有被break打斷,所以執行了該行代碼
    -----out of while loop ------
    

    如果執行過程中被break,就不會執行else的語句

    count = 0
    while count <= 5 :
        count += 1
        if count == 3:
            break
        print("Loop",count)
    else:
        print("循環正常執行完啦")
    print("-----out of while loop ------")
    輸出
    Loop 1
    Loop 2
    -----out of while loop ------ #由于循環被break打斷了,所以不執行else后的輸出語句
    

    練習1:

    尋找1到100之間數字7最大的倍數(結果是98)

    number = 100
    while number > 0:
    	if number %7 == 0:
    		print(number)
    		break
    	number -= 1
    

    練習2:

    age=18
    count=0
    while count<3:
        count+=1
        guess = int(input(">>:"))
        if guess > age :
            print("猜的太大了,往小里試試...")
        elif guess < age :
            print("猜的太小了,往大里試試...")
        else:
            print("恭喜你,猜對了...")
    

    3.3.3 for循環語法

    循環結構的第二種實現方式是for循環,for循環可以做的事情while循環都可以實現,之所以用for循環是因為在循環取值(即遍歷值)時for循環比while循環的使用更為簡潔,

    for循環語法如下

    for 變量名 in 可迭代對象: # 此時只需知道可迭代對象可以是字符串\列表\字典,我們之后會專門講解可迭代對象
        代碼一
        代碼二
        ...
    
    #例1
    for item in ['a','b','c']:
        print(item)
    # 運行結果
    a
    b
    c
    
    # 參照例1來介紹for循環的運行步驟
    # 步驟1:從列表['a','b','c']中讀出第一個值賦值給item(item=‘a’),然后執行循環體代碼
    # 步驟2:從列表['a','b','c']中讀出第二個值賦值給item(item=‘b’),然后執行循環體代碼
    # 步驟3: 重復以上過程直到列表中的值讀盡
    

    img

    3.3.4 for循環應用案例

    # 簡單版:for循環的實現方式
    for count in range(6):  # range(6)會產生從0-5這6個數
        print(count)
    
    # 復雜版:while循環的實現方式
    count = 0
    while count < 6:
        print(count)
        count += 1
    

    案例二:遍歷字典

    # 簡單版:for循環的實現方式
    for k in {'name':'jason','age':18,'gender':'male'}:  # for 循環默認取的是字典的key賦值給變量名k
        print(k)
    
    # 復雜版:while循環確實可以遍歷字典,后續將會迭代器部分詳細介紹
    

    案例三:for循環嵌套

    #請用for循環嵌套的方式打印如下圖形:
    *****
    *****
    *****
    
    for i in range(3):
        for j in range(5):
            print("*",end='')
        print()  # print()表示換行
    

    注意:break 與 continue也可以用于for循環,使用語法同while循環

    練習一:

    打印九九乘法表

    for i in range(1,10):
        for j in range(1,i+1):
            print('%s*%s=%s' %(i,j,i*j),end=' ')
        print()
    

    練習二:

    打印金字塔

    # 分析
    '''
    #max_level=5
         *        # current_level=1,空格數=4,*號數=1
        ***       # current_level=2,空格數=3,*號數=3
       *****      # current_level=3,空格數=2,*號數=5
      *******     # current_level=4,空格數=1,*號數=7
     *********    # current_level=5,空格數=0,*號數=9
    
    # 數學表達式
    空格數=max_level-current_level
    *號數=2*current_level-1
    '''
    # 實現:
    max_level=5
    for current_level in range(1,max_level+1):
        for i in range(max_level-current_level):
            print(' ',end='') #在一行中連續打印多個空格
        for j in range(2*current_level-1):
            print('*',end='') #在一行中連續打印多個空格
        print()
    

    8、基本數據類型及內置方法

    一 引子

    數據類型是用來記錄事物狀態的,而事物的狀態是不斷變化的(如:一個人年齡的增長(操作int類型) ,單個人名的修改(操作str類型),學生列表中增加學生(操作list類型)等),這意味著我們在開發程序時需要頻繁對數據進行操作,為了提升我們的開發效率, python針對這些常用的操作,為每一種數據類型內置了一系列方法。本章的主題就是帶大家詳細了解下它們,以及每種數據類型的詳細定義、類型轉換。

    二 數字類型int與float

    2.1 定義

    # 1、定義:
    # 1.1 整型int的定義
    age=10  # 本質age = int(10)
    
    # 1.2 浮點型float的定義
    salary=3000.3  # 本質salary=float(3000.3)
    
    # 注意:名字+括號的意思就是調用某個功能,比如
    # print(...)調用打印功能
    # int(...)調用創建整型數據的功能
    # float(...)調用創建浮點型數據的功能
    

    2.2 類型轉換

    # 1、數據類型轉換
    # 1.1 int可以將由純整數構成的字符串直接轉換成整型,若包含其他任意非整數符號,則會報錯
    >>> s = '123'
    >>> res = int(s)
    >>> res,type(res)
    (123, <class 'int'>)
    
    >>> int('12.3') # 錯誤演示:字符串內包含了非整數符號.
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: invalid literal for int() with base 10: '12.3'
    
    # 1.2 進制轉換
    # 十進制轉其他進制
    >>> bin(3)
    '0b11'
    >>> oct(9)
    '0o11'
    >>> hex(17)
    '0x11'
    # 其他進制轉十進制
    >>> int('0b11',2)
    3
    >>> int('0o11',8)
    9
    >>> int('0x11',16)
    17
    
    # 1.3 float同樣可以用來做數據類型的轉換
    >>> s = '12.3'
    >>> res=float(s)
    >>> res,type(res)
    (12.3, <class 'float'>)
    

    2.3 使用

    數字類型主要就是用來做數學運算與比較運算,因此數字類型除了與運算符結合使用之外,并無需要掌握的內置方法

    三 字符串

    3.1 定義:

    # 定義:在單引號\雙引號\三引號內包含一串字符
    name1 = 'jason'  # 本質:name = str('任意形式內容')
    name2 = "lili"  # 本質:name = str("任意形式內容")
    name3 = """ricky"""  # 本質:name = str("""任意形式內容""")
    

    3.2 類型轉換

    # 數據類型轉換:str()可以將任意數據類型轉換成字符串類型,例如 
    >>> type(str([1,2,3])) # list->str
    <class 'str'>
    >>> type(str({"name":"jason","age":18})) # dict->str
    <class 'str'>
    >>> type(str((1,2,3)))  # tuple->str
    <class 'str'>
    >>> type(str({1,2,3,4})) # set->str
    <class 'str'>
    

    3.3 使用

    3.3.1 優先掌握的操作

    >>> str1 = 'hello python!'
    
    
    # 1.按索引取值(正向取,反向取):
    # 1.1 正向取(從左往右)
    >>> str1[6]
    p
    # 1.2 反向取(負號表示從右往左)
    >>> str1[-4]
    h
    # 1.3 對于str來說,只能按照索引取值,不能改
    >>> str1[0]='H' # 報錯TypeError
    
    
    # 2.切片(顧頭不顧尾,步長)
    # 2.1 顧頭不顧尾:取出索引為0到8的所有字符
    >>> str1[0:9]  
    hello pyt
    # 2.2 步長:0:9:2,第三個參數2代表步長,會從0開始,每次累加一個2即可,所以會取出索引0、2、4、6、8的字符
    >>> str1[0:9:2]  
    hlopt 
    # 2.3 反向切片
    >>> str1[::-1]  # -1表示從右往左依次取值
    !nohtyp olleh
    
    # 3.長度len
    # 3.1 獲取字符串的長度,即字符的個數,但凡存在于引號內的都算作字符)
    >>> len(str1) # 空格也算字符
    13
    
    # 4.成員運算 in 和 not in    
    # 4.1 int:判斷hello 是否在 str1里面
    >>> 'hello' in str1  
    True
    # 4.2 not in:判斷tony 是否不在 str1里面
    >>> 'tony' not in str1 
    True
    
    # 5.strip移除字符串首尾指定的字符(默認移除空格)
    # 5.1 括號內不指定字符,默認移除首尾空白字符(空格、\n、\t)
    >>> str1 = '  life is short!  '
    >>> str1.strip()  
    life is short!
    
    # 5.2 括號內指定字符,移除首尾指定的字符
    >>> str2 = '**tony**'  
    >>> str2.strip('*')  
    tony
    
    # 6.切分split
    # 6.1 括號內不指定字符,默認以空格作為切分符號
    >>> str3='hello world'
    >>> str3.split()
    ['hello', 'world']
    # 6.2 括號內指定分隔字符,則按照括號內指定的字符切割字符串
    >>> str4 = '127.0.0.1'
    >>> str4.split('.')  
    ['127', '0', '0', '1']  # 注意:split切割得到的結果是列表數據類型
    
    
    # 7.循環
    >>> str5 = '今天你好嗎?'
    >>> for line in str5:  # 依次取出字符串中每一個字符
    ...     print(line)
    ...
    今
    天
    你
    好
    嗎
    ?
    

    3.3.2 需要掌握的操作

    1. strip, lstrip, rstrip

    >>> str1 = '**tony***'
    
    >>> str1.strip('*')  # 移除左右兩邊的指定字符
    'tony'
    >>> str1.lstrip('*')  # 只移除左邊的指定字符
    tony***
    >>> str1.rstrip('*')  # 只移除右邊的指定字符
    **tony
    

    2. lower(),upper()

    >>> str2 = 'My nAme is tonY!'
    
    >>> str2.lower()  # 將英文字符串全部變小寫
    my name is tony!
    >>> str2.upper()  # 將英文字符串全部變大寫
    MY NAME IS TONY!
    

    3. startswith,endswith

    >>> str3 = 'tony jam'
    
    # startswith()判斷字符串是否以括號內指定的字符開頭,結果為布爾值True或False
    >>> str3.startswith('t') 
    True
    >>> str3.startswith('j')
    False
    # endswith()判斷字符串是否以括號內指定的字符結尾,結果為布爾值True或False
    >>> str3.endswith('jam')
    True
    >>> str3.endswith('tony')  
    False
    

    4.格式化輸出之format

    之前我們使用%s來做字符串的格式化輸出操作,在傳值時,必須嚴格按照位置與%s一一對應,而字符串的內置方法format則提供了一種不依賴位置的傳值方式

    案例:

    # format括號內在傳參數時完全可以打亂順序,但仍然能指名道姓地為指定的參數傳值,name=‘tony’就是傳給{name}
    >>> str4 = 'my name is {name}, my age is {age}!'.format(age=18,name='tony')
    >>> str4  
    'my name is tony, my age is 18!'
    
    >>> str4 = 'my name is {name}{name}{name}, my age is {name}!'.format(name='tony', age=18)
    >>> str4  
    'my name is tonytonytony, my age is tony!'
    

    format的其他使用方式(了解)

    # 類似于%s的用法,傳入的值會按照位置與{}一一對應
    >>> str4 = 'my name is {}, my age is {}!'.format('tony', 18)
    >>> str4 
    my name is tony, my age is 18!
    # 把format傳入的多個值當作一個列表,然后用{索引}取值
    >>> str4 = 'my name is {0}, my age is {1}!'.format('tony', 18)
    >>> str4
    my name is tony, my age is 18!
    
    >>> str4 = 'my name is {1}, my age is {0}!'.format('tony', 18)
    >>> str4  
    my name is 18, my age is tony!
    
    >>> str4 = 'my name is {1}, my age is {1}!'.format('tony', 18)
    >>> str4  
    my name is 18, my age is 18!
    

    5.split,rsplit

    # split會按照從左到右的順序對字符串進行切分,可以指定切割次數
    >>> str5='C:/a/b/c/d.txt'
    >>> str5.split('/',1)
    ['C:', 'a/b/c/d.txt']  
    
    # rsplit剛好與split相反,從右往左切割,可以指定切割次數
    >>> str5='a|b|c'
    >>> str5.rsplit('|',1)
    ['a|b', 'c']
    

    6. join

    # 從可迭代對象中取出多個字符串,然后按照指定的分隔符進行拼接,拼接的結果為字符串
    >>> '%'.join('hello') # 從字符串'hello'中取出多個字符串,然后按照%作為分隔符號進行拼接
    'h%e%l%l%o'
    >>> '|'.join(['tony','18','read'])  # 從列表中取出多個字符串,然后按照*作為分隔符號進行拼接
    'tony|18|read'
    

    7. replace

    # 用新的字符替換字符串中舊的字符
    >>> str7 = 'my name is tony, my age is 18!'  # 將tony的年齡由18歲改成73歲
    >>> str7 = str7.replace('18', '73')  # 語法:replace('舊內容', '新內容')
    >>> str7
    my name is tony, my age is 73!
    
    # 可以指定修改的個數
    >>> str7 = 'my name is tony, my age is 18!'
    >>> str7 = str7.replace('my', 'MY',1) # 只把一個my改為MY
    >>> str7
    'MY name is tony, my age is 18!'
    

    8.isdigit

    # 判斷字符串是否是純數字組成,返回結果為True或False
    >>> str8 = '5201314'
    >>> str8.isdigit()
    True
    
    >>> str8 = '123g123'
    >>> str8.isdigit()
    False
    

    3.3.3 了解操作

    # 1.find,rfind,index,rindex,count
    # 1.1 find:從指定范圍內查找子字符串的起始索引,找得到則返回數字1,找不到則返回-1
    >>> msg='tony say hello'
    >>> msg.find('o',1,3)  # 在索引為1和2(顧頭不顧尾)的字符中查找字符o的索引
    1  
    # 1.2 index:同find,但在找不到時會報錯
    >>> msg.index('e',2,4) # 報錯ValueError
    # 1.3 rfind與rindex:略
    # 1.4 count:統計字符串在大字符串中出現的次數
    >>> msg = "hello everyone"
    >>> msg.count('e')  # 統計字符串e出現的次數
    4
    >>> msg.count('e',1,6)  # 字符串e在索引1~5范圍內出現的次數
    1
    
    # 2.center,ljust,rjust,zfill
    >>> name='tony'
    >>> name.center(30,'-')  # 總寬度為30,字符串居中顯示,不夠用-填充
    -------------tony-------------
    >>> name.ljust(30,'*')  # 總寬度為30,字符串左對齊顯示,不夠用*填充
    tony**************************
    >>> name.rjust(30,'*')  # 總寬度為30,字符串右對齊顯示,不夠用*填充
    **************************tony
    >>> name.zfill(50)  # 總寬度為50,字符串右對齊顯示,不夠用0填充
    0000000000000000000000000000000000000000000000tony
    
    # 3.expandtabs
    >>> name = 'tony\thello'  # \t表示制表符(tab鍵)
    >>> name
    tony    hello
    >>> name.expandtabs(1)  # 修改\t制表符代表的空格數
    tony hello
    
    # 4.captalize,swapcase,title
    # 4.1 captalize:首字母大寫
    >>> message = 'hello everyone nice to meet you!'
    >>> message.capitalize()
    Hello everyone nice to meet you!  
    # 4.2 swapcase:大小寫翻轉
    >>> message1 = 'Hi girl, I want make friends with you!'
    >>> message1.swapcase()  
    hI GIRL, i WANT MAKE FRIENDS WITH YOU!  
    #4.3 title:每個單詞的首字母大寫
    >>> msg = 'dear my friend i miss you very much'
    >>> msg.title()
    Dear My Friend I Miss You Very Much 
    
    # 5.is數字系列
    #在python3中
    num1 = b'4' #bytes
    num2 = u'4' #unicode,python3中無需加u就是unicode
    num3 = '四' #中文數字
    num4 = 'Ⅳ' #羅馬數字
    
    #isdigt:bytes,unicode
    >>> num1.isdigit()
    True
    >>> num2.isdigit()
    True
    >>> num3.isdigit()
    False
    >>> num4.isdigit() 
    False
    
    #isdecimal:uncicode(bytes類型無isdecimal方法)
    >>> num2.isdecimal() 
    True
    >>> num3.isdecimal() 
    False
    >>> num4.isdecimal() 
    False
    
    #isnumberic:unicode,中文數字,羅馬數字(bytes類型無isnumberic方法)
    >>> num2.isnumeric() 
    True
    >>> num3.isnumeric() 
    True
    >>> num4.isnumeric() 
    True
    
    # 三者不能判斷浮點數
    >>> num5 = '4.3'
    >>> num5.isdigit()
    False
    >>> num5.isdecimal()
    False
    >>> num5.isnumeric()
    False
    
    '''
    總結:
        最常用的是isdigit,可以判斷bytes和unicode類型,這也是最常見的數字應用場景
        如果要判斷中文數字或羅馬數字,則需要用到isnumeric。
    '''
    
    # 6.is其他
    >>> name = 'tony123'
    >>> name.isalnum() #字符串中既可以包含數字也可以包含字母
    True
    >>> name.isalpha() #字符串中只包含字母
    False
    >>> name.isidentifier()
    True
    >>> name.islower()  # 字符串是否是純小寫
    True
    >>> name.isupper()  # 字符串是否是純大寫
    False
    >>> name.isspace()  # 字符串是否全是空格
    False
    >>> name.istitle()  # 字符串中的單詞首字母是否都是大寫
    False
    

    四 列表

    4.1 定義

    # 定義:在[]內,用逗號分隔開多個任意數據類型的值
    l1 = [1,'a',[1,2]]  # 本質:l1 = list([1,'a',[1,2]])
    

    4.2 類型轉換

    # 但凡能被for循環遍歷的數據類型都可以傳給list()轉換成列表類型,list()會跟for循環一樣遍歷出數據類型中包含的每一個元素然后放到列表中
    >>> list('wdad') # 結果:['w', 'd', 'a', 'd'] 
    >>> list([1,2,3]) # 結果:[1, 2, 3]
    >>> list({"name":"jason","age":18}) #結果:['name', 'age']
    >>> list((1,2,3)) # 結果:[1, 2, 3] 
    >>> list({1,2,3,4}) # 結果:[1, 2, 3, 4]
    

    4.3 使用

    4.3.1 優先掌握的操作

    # 1.按索引存取值(正向存取+反向存取):即可存也可以取  
    # 1.1 正向取(從左往右)
    >>> my_friends=['tony','jason','tom',4,5]
    >>> my_friends[0]  
    tony
    # 1.2 反向取(負號表示從右往左)
    >>> my_friends[-1]  
    5
    # 1.3 對于list來說,既可以按照索引取值,又可以按照索引修改指定位置的值,但如果索引不存在則報錯
    >>> my_friends = ['tony','jack','jason',4,5]
    >>> my_friends[1] = 'martthow'
    >>> my_friends
    ['tony', 'martthow', 'jason', 4, 5]
    
    
    # 2.切片(顧頭不顧尾,步長)
    # 2.1 顧頭不顧尾:取出索引為0到3的元素
    >>> my_friends[0:4] 
    ['tony', 'jason', 'tom', 4]
    # 2.2 步長:0:4:2,第三個參數2代表步長,會從0開始,每次累加一個2即可,所以會取出索引0、2的元素
    >>> my_friends[0:4:2]  
    ['tony', 'tom']
    
    # 3.長度
    >>> len(my_friends)
    5
    
    # 4.成員運算in和not in
    >>> 'tony' in my_friends
    True
    >>> 'xxx' not in my_friends
    True
    
    # 5.添加
    # 5.1 append()列表尾部追加元素
    >>> l1 = ['a','b','c']
    >>> l1.append('d')
    >>> l1
    ['a', 'b', 'c', 'd']
    
    # 5.2 extend()一次性在列表尾部添加多個元素
    >>> l1.extend(['a','b','c'])
    >>> l1
    ['a', 'b', 'c', 'd', 'a', 'b', 'c']
    
    # 5.3 insert()在指定位置插入元素
    >>> l1.insert(0,"first")  # 0表示按索引位置插值
    >>> l1
    ['first', 'a', 'b', 'c', 'alisa', 'a', 'b', 'c']
    
    # 6.刪除
    # 6.1 del
    >>> l = [11,22,33,44]
    >>> del l[2]  # 刪除索引為2的元素
    >>> l
    [11,22,44]
    
    # 6.2 pop()默認刪除列表最后一個元素,并將刪除的值返回,括號內可以通過加索引值來指定刪除元素
    >>> l = [11,22,33,22,44]
    >>> res=l.pop()
    >>> res
    44
    >>> res=l.pop(1)
    >>> res
    22
    
    # 6.3 remove()括號內指名道姓表示要刪除哪個元素,沒有返回值
    >>> l = [11,22,33,22,44]
    >>> res=l.remove(22) # 從左往右查找第一個括號內需要刪除的元素
    >>> print(res)
    None
    
    # 7.reverse()顛倒列表內元素順序
    >>> l = [11,22,33,44]
    >>> l.reverse() 
    >>> l
    [44,33,22,11]
    
    # 8.sort()給列表內所有元素排序
    # 8.1 排序時列表元素之間必須是相同數據類型,不可混搭,否則報錯
    >>> l = [11,22,3,42,7,55]
    >>> l.sort()
    >>> l 
    [3, 7, 11, 22, 42, 55]  # 默認從小到大排序
    >>> l = [11,22,3,42,7,55]
    >>> l.sort(reverse=True)  # reverse用來指定是否跌倒排序,默認為False
    >>> l 
    [55, 42, 22, 11, 7, 3]
    # 8.2 了解知識:
    # 我們常用的數字類型直接比較大小,但其實,字符串、列表等都可以比較大小,原理相同:都是依次比較對應位置的元素的大小,如果分出大小,則無需比較下一個元素,比如
    >>> l1=[1,2,3]
    >>> l2=[2,]
    >>> l2 > l1
    True
    # 字符之間的大小取決于它們在ASCII表中的先后順序,越往后越大
    >>> s1='abc'
    >>> s2='az'
    >>> s2 > s1 # s1與s2的第一個字符沒有分出勝負,但第二個字符'z'>'b',所以s2>s1成立
    True
    # 所以我們也可以對下面這個列表排序
    >>> l = ['A','z','adjk','hello','hea']
    >>> l.sort()
    >>> l
    ['A', 'adjk', 'hea', 'hello','z']
    
    # 9.循環
    # 循環遍歷my_friends列表里面的值
    for line in my_friends:
        print(line) 
    'tony'
    'jack'
    'jason'
    4
    5
    

    4.3.2 了解操作

    >>> l=[1,2,3,4,5,6]
    >>> l[0:3:1] 
    [1, 2, 3]  # 正向步長
    >>> l[2::-1] 
    [3, 2, 1]  # 反向步長
    
    # 通過索引取值實現列表翻轉
    >>> l[::-1]
    [6, 5, 4, 3, 2, 1]
    

    五 元組

    5.1 作用

    元組與列表類似,也是可以存多個任意類型的元素,不同之處在于元組的元素不能修改,即元組相當于不可變的列表,用于記錄多個固定不允許修改的值,單純用于取

    5.2 定義方式

    # 在()內用逗號分隔開多個任意類型的值
    >>> countries = ("中國","美國","英國")  # 本質:countries = tuple("中國","美國","英國")
    # 強調:如果元組內只有一個值,則必須加一個逗號,否則()就只是包含的意思而非定義元組
    >>> countries = ("中國",)  # 本質:countries = tuple("中國")
    

    5.3 類型轉換

    # 但凡能被for循環的遍歷的數據類型都可以傳給tuple()轉換成元組類型
    >>> tuple('wdad') # 結果:('w', 'd', 'a', 'd') 
    >>> tuple([1,2,3]) # 結果:(1, 2, 3)
    >>> tuple({"name":"jason","age":18}) # 結果:('name', 'age')
    >>> tuple((1,2,3)) # 結果:(1, 2, 3)
    >>> tuple({1,2,3,4}) # 結果:(1, 2, 3, 4)
    # tuple()會跟for循環一樣遍歷出數據類型中包含的每一個元素然后放到元組中
    

    5.4 使用

    >>> tuple1 = (1, 'hhaha', 15000.00, 11, 22, 33) 
    # 1、按索引取值(正向取+反向取):只能取,不能改否則報錯!  
    >>> tuple1[0]
    1
    >>> tuple1[-2]
    22
    >>> tuple1[0] = 'hehe'  # 報錯:TypeError:
    
    # 2、切片(顧頭不顧尾,步長)
    >>> tuple1[0:6:2] 
    (1, 15000.0, 22)
    
    # 3、長度
    >>> len(tuple1)  
    6
    
    # 4、成員運算 in 和 not in
    >>> 'hhaha' in tuple1 
    True
    >>> 'hhaha' not in tuple1  
    False 
    
    # 5、循環
    >>> for line in tuple1:
    ...     print(line)
    1
    hhaha
    15000.0
    11
    22
    33
    

    六 字典

    6.1 定義方式

    # 定義:在{}內用逗號分隔開多元素,每一個元素都是key:value的形式,其中value可以是任意類型,而key則必須是不可變類型,詳見第八小節,通常key應該是str類型,因為str類型會對value有描述性的功能
    info={'name':'tony','age':18,'sex':'male'} #本質info=dict({....})
    # 也可以這么定義字典
    info=dict(name='tony',age=18,sex='male') # info={'age': 18, 'sex': 'male', 'name': 'tony'}
    

    6.2 類型轉換

    # 轉換1: 
    >>> info=dict([['name','tony'],('age',18)])
    >>> info
    {'age': 18, 'name': 'tony'}
    
    # 轉換2:fromkeys會從元組中取出每個值當做key,然后與None組成key:value放到字典中
    >>> {}.fromkeys(('name','age','sex'),None)  
    {'age': None, 'sex': None, 'name': None}
    

    6.3 使用

    6.3.1 優先掌握的操作

    # 1、按key存取值:可存可取
    # 1.1 取
    >>> dic = {
    ...     'name': 'xxx',
    ...     'age': 18,
    ...     'hobbies': ['play game', 'basketball']
    ... }
    >>> dic['name']
    'xxx'
    >>> dic['hobbies'][1]
    'basketball'
    # 1.2 對于賦值操作,如果key原先不存在于字典,則會新增key:value
    >>> dic['gender'] = 'male'  
    >>> dic
    {'name': 'tony', 'age': 18, 'hobbies': ['play game', 'basketball'],'gender':'male'}
    # 1.3 對于賦值操作,如果key原先存在于字典,則會修改對應value的值
    >>> dic['name'] = 'tony'
    >>> dic
    {'name': 'tony', 'age': 18, 'hobbies': ['play game', 'basketball']}
    
    
    # 2、長度len
    >>> len(dic) 
    3
    
    
    # 3、成員運算in和not in
    >>> 'name' in dic  # 判斷某個值是否是字典的key
    True
    
    
    # 4、刪除
    >>> dic.pop('name')  # 通過指定字典的key來刪除字典的鍵值對
    >>> dic
    {'age': 18, 'hobbies': ['play game', 'basketball']}
    
    
    # 5、鍵keys(),值values(),鍵值對items()
    >>> dic = {'age': 18, 'hobbies': ['play game', 'basketball'], 'name': 'xxx'}
    # 獲取字典所有的key
    >>> dic.keys()  
    dict_keys(['name', 'age', 'hobbies'])
    # 獲取字典所有的value
    >>> dic.values()
    dict_values(['xxx', 18, ['play game', 'basketball']])
    # 獲取字典所有的鍵值對
    >>> dic.items()
    dict_items([('name', 'xxx'), ('age', 18), ('hobbies', ['play game', 'basketball'])])
    
    
    # 6、循環
    # 6.1 默認遍歷的是字典的key
    >>> for key in dic:
    ...     print(key)
    ... 
    age
    hobbies
    name
    # 6.2 只遍歷key
    >>> for key in dic.keys():
    ...     print(key)
    ... 
    age
    hobbies
    name
    # 6.3 只遍歷value
    >>> for key in dic.values():
    ...     print(key)
    ... 
    18
    ['play game', 'basketball']
    xxx
    # 6.4 遍歷key與value
    >>> for key in dic.items():
    ...     print(key)
    ... 
    ('age', 18)
    ('hobbies', ['play game', 'basketball'])
    ('name', 'xxx')
    

    6.3.2 需要掌握的操作

    1. get()

    >>> dic= {'k1':'jason','k2':'Tony','k3':'JY'}
    >>> dic.get('k1')
    'jason'  # key存在,則獲取key對應的value值
    >>> res=dic.get('xxx') # key不存在,不會報錯而是默認返回None
    >>> print(res)
    None  
    >>> res=dic.get('xxx',666) # key不存在時,可以設置默認返回的值
    >>> print(res)
    666 
    # ps:字典取值建議使用get方法
    

    2. pop()

    >>> dic= {'k1':'jason','k2':'Tony','k3':'JY'}
    >>> v = dic.pop('k2')  # 刪除指定的key對應的鍵值對,并返回值
    >>> dic
    {'k1': 'jason', 'kk2': 'JY'}
    >>> v
    'Tony'
    

    3. popitem()

    >>> dic= {'k1':'jason','k2':'Tony','k3':'JY'}
    >>> item = dic.popitem()  # 隨機刪除一組鍵值對,并將刪除的鍵值放到元組內返回
    >>> dic
    {'k3': 'JY', 'k2': 'Tony'}
    >>> item
    ('k1', 'jason')
    

    4. update()

    # 用新字典更新舊字典,有則修改,無則添加
    >>> dic= {'k1':'jason','k2':'Tony','k3':'JY'}
    >>> dic.update({'k1':'JN','k4':'xxx'})
    >>> dic
    {'k1': 'JN', 'k3': 'JY', 'k2': 'Tony', 'k4': 'xxx'}
    

    5. fromkeys()

    >>> dic = dict.fromkeys(['k1','k2','k3'],[])
    >>> dic
    {'k1': [], 'k2': [], 'k3': []}
    

    6. setdefault()

    # key不存在則新增鍵值對,并將新增的value返回
    >>> dic={'k1':111,'k2':222}
    >>> res=dic.setdefault('k3',333)
    >>> res
    333
    >>> dic # 字典中新增了鍵值對
    {'k1': 111, 'k3': 333, 'k2': 222}
    
    # key存在則不做任何修改,并返回已存在key對應的value值
    >>> dic={'k1':111,'k2':222}
    >>> res=dic.setdefault('k1',666)
    >>> res
    111
    >>> dic # 字典不變
    {'k1': 111, 'k2': 222}
    

    七 集合

    7.1 作用

    集合、list、tuple、dict一樣都可以存放多個值,但是集合主要用于:去重、關系運算

    7.2 定義

    """
    定義:在{}內用逗號分隔開多個元素,集合具備以下三個特點:
         1:每個元素必須是不可變類型
         2:集合內沒有重復的元素
         3:集合內元素無序
    """
    s = {1,2,3,4}  # 本質 s = set({1,2,3,4})
    
    # 注意1:列表類型是索引對應值,字典是key對應值,均可以取得單個指定的值,而集合類型既沒有索引也沒有key與值對應,所以無法取得單個的值,而且對于集合來說,主要用于去重與關系元素,根本沒有取出單個指定值這種需求。
    
    # 注意2:{}既可以用于定義dict,也可以用于定義集合,但是字典內的元素必須是key:value的格式,現在我們想定義一個空字典和空集合,該如何準確去定義兩者?
    d = {} # 默認是空字典 
    s = set() # 這才是定義空集合
    

    7.3 類型轉換

    # 但凡能被for循環的遍歷的數據類型(強調:遍歷出的每一個值都必須為不可變類型)都可以傳給set()轉換成集合類型
    >>> s = set([1,2,3,4])
    >>> s1 = set((1,2,3,4))
    >>> s2 = set({'name':'jason',})
    >>> s3 = set('egon')
    >>> s,s1,s2,s3
    {1, 2, 3, 4} {1, 2, 3, 4} {'name'} {'e', 'o', 'g', 'n'}
    

    7.4 使用

    7.4.1 關系運算

    我們定義兩個集合friends與friends2來分別存放兩個人的好友名字,然后以這兩個集合為例講解集合的關系運算

    >>> friends1 = {"zero","kevin","jason","egon"} # 用戶1的好友們 
    >>> friends2 = {"Jy","ricky","jason","egon"}   # 用戶2的好友們
    

    兩個集合的關系如下圖所示

    img

    # 1.合集/并集(|):求兩個用戶所有的好友(重復好友只留一個)
    >>> friends1 | friends2
    {'kevin', 'ricky', 'zero', 'jason', 'Jy', 'egon'}
    
    # 2.交集(&):求兩個用戶的共同好友
    >>> friends1 & friends2
    {'jason', 'egon'}
    
    # 3.差集(-):
    >>> friends1 - friends2 # 求用戶1獨有的好友
    {'kevin', 'zero'}
    >>> friends2 - friends1 # 求用戶2獨有的好友
    {'ricky', 'Jy'}
    
    # 4.對稱差集(^) # 求兩個用戶獨有的好友們(即去掉共有的好友)
    >>> friends1 ^ friends2
    {'kevin', 'zero', 'ricky', 'Jy'}
    
    # 5.值是否相等(==)
    >>> friends1 == friends2
    False
    
    # 6.父集:一個集合是否包含另外一個集合
    # 6.1 包含則返回True
    >>> {1,2,3} > {1,2}
    True
    >>> {1,2,3} >= {1,2}
    True
    # 6.2 不存在包含關系,則返回False
    >>> {1,2,3} > {1,3,4,5}
    False
    >>> {1,2,3} >= {1,3,4,5}
    False
    
    
    # 7.子集
    >>> {1,2} < {1,2,3}
    True
    >>> {1,2} <= {1,2,3}
    True
    

    7.4.2 去重

    集合去重復有局限性

    # 1. 只能針對不可變類型
    # 2. 集合本身是無序的,去重之后無法保留原來的順序
    

    示例如下

    >>> l=['a','b',1,'a','a']
    >>> s=set(l)
    >>> s # 將列表轉成了集合
    {'b', 'a', 1}
    >>> l_new=list(s) # 再將集合轉回列表
    >>> l_new
    ['b', 'a', 1] # 去除了重復,但是打亂了順序
    
    # 針對不可變類型,并且保證順序則需要我們自己寫代碼實現,例如
    l=[
        {'name':'lili','age':18,'sex':'male'},
        {'name':'jack','age':73,'sex':'male'},
        {'name':'tom','age':20,'sex':'female'},
        {'name':'lili','age':18,'sex':'male'},
        {'name':'lili','age':18,'sex':'male'},
    ]
    
    new_l=[]
    
    for dic in l:
        if dic not in new_l:
            new_l.append(dic)
    
    print(new_l)
    # 結果:既去除了重復,又保證了順序,而且是針對不可變類型的去重
    [
        {'age': 18, 'sex': 'male', 'name': 'lili'}, 
        {'age': 73, 'sex': 'male', 'name': 'jack'}, 
        {'age': 20, 'sex': 'female', 'name': 'tom'}
    ]
    

    7.4.3 其他操作

    # 1.長度
    >>> s={'a','b','c'}
    >>> len(s)
    3
    
    # 2.成員運算
    >>> 'c' in s
    True
    
    # 3.循環
    >>> for item in s:
    ...     print(item)
    ... 
    c
    a
    b
    

    7.5 練習

    """
    一.關系運算
      有如下兩個集合,pythons是報名python課程的學員名字集合,linuxs是報名linux課程的學員名字集合
      pythons={'jason','egon','kevin','ricky','gangdan','biubiu'}
      linuxs={'kermit','tony','gangdan'}
      1. 求出即報名python又報名linux課程的學員名字集合
      2. 求出所有報名的學生名字集合
      3. 求出只報名python課程的學員名字
      4. 求出沒有同時這兩門課程的學員名字集合
    """
    # 求出即報名python又報名linux課程的學員名字集合
    >>> pythons & linuxs
    # 求出所有報名的學生名字集合
    >>> pythons | linuxs
    # 求出只報名python課程的學員名字
    >>> pythons - linuxs
    # 求出沒有同時這兩門課程的學員名字集合
    >>> pythons ^ linuxs
    

    八 可變類型與不可變類型

    **可變數據類型:**值發生改變時,內存地址不變,即id不變,證明在改變原值

    **不可變類型:**值發生改變時,內存地址也發生改變,即id也變,證明是沒有在改變原值,是產生了新的值

    數字類型:

    >>> x = 10
    >>> id(x)
    1830448896 
    >>> x = 20
    >>> id(x)
    1830448928
    # 內存地址改變了,說明整型是不可變數據類型,浮點型也一樣
    

    img

    字符串

    >>> x = "Jy"
    >>> id(x)
    938809263920 
    >>> x = "Ricky"
    >>> id(x)
    938809264088
    # 內存地址改變了,說明字符串是不可變數據類型
    

    img

    列表

    >>> list1 = ['tom','jack','egon']
    >>> id(list1)
    486316639176
    >>> list1[2] = 'kevin'
    >>> id(list1)
    486316639176
    >>> list1.append('lili')
    >>> id(list1)
    486316639176
    # 對列表的值進行操作時,值改變但內存地址不變,所以列表是可變數據類型
    

    img

    元組

    >>> t1 = ("tom","jack",[1,2])
    >>> t1[0]='TOM' # 報錯:TypeError
    >>> t1.append('lili') # 報錯:TypeError
    # 元組內的元素無法修改,指的是元組內索引指向的內存地址不能被修改
    >>> t1 = ("tom","jack",[1,2])
    >>> id(t1[0]),id(t1[1]),id(t1[2])
    (4327403152, 4327403072, 4327422472)
    >>> t1[2][0]=111 # 如果元組中存在可變類型,是可以修改,但是修改后的內存地址不變
    >>> t1
    ('tom', 'jack', [111, 2])
    >>> id(t1[0]),id(t1[1]),id(t1[2]) # 查看id仍然不變
    (4327403152, 4327403072, 4327422472)
    

    img

    字典

    >>> dic = {'name':'egon','sex':'male','age':18}
    >>> 
    >>> id(dic)
    4327423112
    >>> dic['age']=19
    >>> dic
    {'age': 19, 'sex': 'male', 'name': 'egon'}
    >>> id(dic)
    4327423112
    # 對字典進行操作時,值改變的情況下,字典的id也是不變,即字典也是可變數據類型
    

    img

    九 數據類型總結

    img

    1、數字類型:
    2、字符串類型
    3、列表類型
    4、元組類型
    5、字典類型
    6、集合類型
    

    9、字符編碼

    一 引入

    字符串類型、文本文件的內容都是由字符組成的,但凡涉及到字符的存取,都需要考慮字符編碼的問題。

    二 知識儲備

    2.1 三大核心硬件

    所有軟件都是運行硬件之上的,與運行軟件相關的三大核心硬件為cpu、內存、硬盤,我們需要明確三點

    #1、軟件運行前,軟件的代碼及其相關數據都是存放于硬盤中的
    
    #2、任何軟件的啟動都是將數據從硬盤中讀入內存,然后cpu從內存中取出指令并執行
    
    #3、軟件運行過程中產生的數據最先都是存放于內存中的,若想永久保存軟件產生的數據,則需要將數據由內存寫入硬盤
    

    img

    2.2 文本編輯器讀取文件內容的流程

    #階段1、啟動一個文件編輯器(文本編輯器如nodepad++,pycharm,word)
    
    #階段2、文件編輯器會將文件內容從硬盤讀入內存
    
    #階段3、文本編輯器會將剛剛讀入內存中的內容顯示到屏幕上
    

    2.3 python解釋器執行文件的流程

    以python test.py為例,執行流程如下

    #階段1、啟動python解釋器,此時就相當于啟動了一個文本編輯器
    
    #階段2、python解釋器相當于文本編輯器,從硬盤上將test.py的內容讀入到內存中
    
    #階段3、python解釋器解釋執行剛剛讀入的內存的內容,開始識別python語法
    

    2.4 總結

    python解釋器與文件本編輯的異同如下

    #1、相同點:前兩個階段二者完全一致,都是將硬盤中文件的內容讀入內存,詳解如下
    python解釋器是解釋執行文件內容的,因而python解釋器具備讀py文件的功能,這一點與文本編輯器一樣
    
    #2、不同點:在階段3時,針對內存中讀入的內容處理方式不同,詳解如下
    文本編輯器將文件內容讀入內存后,是為了顯示或者編輯,根本不去理會python的語法,而python解釋器將文件內容讀入內存后,可不是為了給你瞅一眼python代碼寫的啥,而是為了執行python代碼、會識別python語法)
    

    三、字符編碼介紹

    3.1 什么是字符編碼?

    人類在與計算機交互時,用的都是人類能讀懂的字符,如中文字符、英文字符、日文字符等

    而計算機只能識別二進制數,詳解如下

    #二進制數即由0和1組成的數字,例如010010101010。計算機是基于電工作的,電的特性即高低電平,人類從邏輯層面將高電平對應為數字1,低電平對應為數字0,這直接決定了計算機可以識別的是由0和1組成的數字
    

    毫無疑問,由人類的字符到計算機中的數字,必須經歷一個過程,如下

    img

    翻譯的過程必須參照一個特定的標準,該標準稱之為字符編碼表,該表上存放的就是字符與數字一一對應的關系。

    字符編碼中的編碼指的是翻譯或者轉換的意思,即將人能理解的字符翻譯成計算機能識別的數字

    3.2 字符編碼表的發展史 (了解)

    字符編碼的發展經歷了三個重要的階段,如下

    3.2.1 階段一:一家獨大

    現代計算機起源于美國,所以最先考慮僅僅是讓計算機識別英文字符,于是誕生了ASCII表

    # ASCII表的特點:
        1、只有英文字符與數字的一一對應關系
        2、一個英文字符對應1Bytes,1Bytes=8bit,8bit最多包含256個數字,可以對應256個字符,足夠表示所有英文字符
    

    3.2.2 階段二:諸侯割據、天下大亂

    3.2.2 階段二:諸侯割據、天下大亂

    為了讓計算機能夠識別中文和英文,中國人定制了GBK

    # GBK表的特點:
        1、只有中文字符、英文字符與數字的一一對應關系
        2、一個英文字符對應1Bytes
           一個中文字符對應2Bytes   
           補充說明:
           1Bytes=8bit,8bit最多包含256個數字,可以對應256個字符,足夠表示所有英文字符
           2Bytes=16bit,16bit最多包含65536個數字,可以對應65536個字符,足夠表示所有中文字符
    

    每個國家都各自的字符,為讓計算機能夠識別自己國家的字符外加英文字符,各個國家都制定了自己的字符編碼表

    # Shift_JIS表的特點:
        1、只有日文字符、英文字符與數字的一一對應關系
    
    # Euc-kr表的特點:
        1、只有韓文字符、英文字符與數字的一一對應關系
    

    此時,美國人用的計算機里使用字符編碼標準是ASCII、中國人用的計算機里使用字符編碼標準是GBK、日本人用的計算機里使用字符編碼標準是Shift_JIS,如下圖所示,

    img

    字符編碼發展到了這個階段,可以用一句話概括:諸侯割據、天下大亂,詳解如下

    圖1中,文本編輯存取文件的原理如下

    文本文件內容全都為字符,無論存取都是涉及到字符編碼問題
    #1、存文本文件
    人類通過文本編輯器輸入的字符會被轉化成ASCII格式的二進制存放于內存中,如果需要永久保存,則直接將內存中的ASCII格式的二進制寫入硬盤
    
    #2、讀文本文件
    直接將硬盤中的ASCII格式的二進制讀入內存,然后通過ASCII表反解成英文字符
    

    圖2圖3都是相同的過程,此時無論是存還是取由于采用的字符編碼表一樣,所以肯定不會出現亂碼問題,但問題是在美國人用的計算機里只能輸入英文字符,而在中國人用的計算機里只能輸入中文字符和英文字符…,毫無疑問我們希望計算機允許我們輸入萬國字符均可識別、不亂碼,而現階段計算機采用的字符編碼ASCII、GBK、Shift_JIS都無法識別萬國字符,所以我們必須定制一個兼容萬國字符的編碼表,請看階段三

    3.2.3 階段三:分久必合

    unicode于1990年開始研發,1994年正式公布,具備兩大特點:

    1. 存在所有語言中的所有字符與數字的一一對應關系,即兼容萬國字符
    
    #2. 與傳統的字符編碼的二進制數都有對應關系,詳解如下
    

    很多地方或老的系統、應用軟件仍會采用各種各樣傳統的編碼,這是歷史遺留問題。此處需要強調:軟件是存放于硬盤的,而運行軟件是要將軟件加載到內存的,面對硬盤中存放的各種傳統編碼的軟件,想讓我們的計算機能夠將它們全都正常運行而不出現亂碼,內存中必須有一種兼容萬國的編碼,并且該編碼需要與其他編碼有相對應的映射/轉換關系,這就是unicode的第二大特點產生的緣由

    img

    文本編輯器輸入任何字符都是最新存在于內存中,是unicode編碼的,存放于硬盤中,則可以轉換成任意其他編碼,只要該編碼可以支持相應的字符

    # 英文字符可以被ASCII識別
    英文字符--->unciode格式的數字--->ASCII格式的數字
    
    # 中文字符、英文字符可以被GBK識別
    中文字符、英文字符--->unicode格式的數字--->gbk格式的數字
    
    # 日文字符、英文字符可以被shift-JIS識別
    日文字符、英文字符--->unicode格式的數字--->shift-JIS格式的數字
    

    3.3 編碼與解碼

    由字符轉換成內存中的unicode,以及由unicode轉換成其他編碼的過程,都稱為編碼encode

    img

    由內存中的unicode轉換成字符,以及由其他編碼轉換成unicode的過程,都稱為解碼decode

    img

    在諸多文件類型中,只有文本文件的內存是由字符組成的,因而文本文件的存取也涉及到字符編碼的問題

    3.4 utf-8的由來

    注意:如果保存到硬盤的是GBK格式二進制,當初用戶輸入的字符只能是中文或英文,同理如果保存到硬盤的是Shift_JIS格式二進制,當初用戶輸入的字符只能是日文或英文……如果我們輸入的字符中包含多國字符,那么該如何處理?

    #多國字符—√—》內存(unicode格式的二進制)——X—》硬盤(GBK格式的二進制)
    
    #多國字符—√—》內存(unicode格式的二進制)——X—》硬盤(Shift_JIS格式的二進制)
    
    #多國字符—√—》內存(unicode格式的二進制)——√—》硬盤(???格式的二進制)
    

    理論上是可以將內存中unicode格式的二進制直接存放于硬盤中的,但由于unicode固定使用兩個字節來存儲一個字符,如果多國字符中包含大量的英文字符時,使用unicode格式存放會額外占用一倍空間(英文字符其實只需要用一個字節存放即可),然而空間占用并不是最致命的問題,最致命地是當我們由內存寫入硬盤時會額外耗費一倍的時間,所以將內存中的unicode二進制寫入硬盤或者基于網絡傳輸時必須將其轉換成一種精簡的格式,這種格式即utf-8(全稱Unicode Transformation Format,即unicode的轉換格式)

    # 多國字符—√—》內存(unicode格式的二進制)——√—》硬盤(utf-8格式的二進制)
    

    img

    那為何在內存中不直接使用utf-8呢?

    utf-8是針對Unicode的可變長度字符編碼:一個英文字符占1Bytes,一個中文字符占3Bytes,生僻字用更多的Bytes存儲
    
    unicode更像是一個過渡版本,我們新開發的軟件或文件存入硬盤都采用utf-8格式,等過去幾十年,所有老編碼的文件都淘汰
    

    四 字符編碼的應用

    我們學習字符編碼就是為了存取字符時不發生亂碼問題:

    #1、內存中固定使用unicode無論輸入任何字符都不會發生亂碼
    
    #2、我們能夠修改的是存/取硬盤的編碼方式,如果編碼設置不正確將會出現亂碼問題。亂碼問題分為兩種:存亂了,讀亂了
    
    #2.1 存亂了:如果用戶輸入的內容中包含中文和日文字符,如果單純以shift_JIS存,日文可以正常寫入硬盤,而由于中文字符在shift_jis中沒有找到對應關系而導致存亂了
    
    #2.2 讀亂了:如果硬盤中的數據是shift_JIS格式存儲的,采GBK格式讀入內存就讀亂了
    

    總結:

    #1. 保證存的時候不亂:在由內存寫入硬盤時,必須將編碼格式設置為支持所輸入字符的編碼格式
    #2. 保證存的時候不亂:在由硬盤讀入內存時,必須采用與寫入硬盤時相同的編碼格式
    

    4.1 文本編輯器nodpad++存取文本文件

    文本編輯器存取的都是文本文件,而文本文件中包含的內容全為字符,所以存取文本文件都涉及到字符編碼的問題。

    img

    4.2 python解釋器執行文件的前兩個階段

    執行py文件的前兩個階段就是python解釋器讀文本文件的過程,與文本編輯讀文本文件的前兩個階段沒人任何區別,要保證讀不亂碼,則必須將python解釋器讀文件時采用的編碼方式設置為文件當初寫入硬盤時的編碼格式,如果沒有設置,python解釋器則才用默認的編碼方式,在python3中默認為utf-8,在python2中默認為ASCII,我們可以通過指定文件頭來修改默認的編碼

    • 在文件首行寫入包含#號在內的以下內容
    # coding: 當初文件寫入硬盤時采用的編碼格式
    

    解釋器會先用默認的編碼方式讀取文件的首行內容,由于首行是純英文組成,而任何編碼方式都可以識別英文字符。

    4.3 python解釋器執行文件的第三個階段

    設置文件頭的作用是保證運行python程序的前兩個階段不亂碼,經過前兩個階段后py文件的內容都會以unicode格式存放于內存中。

    在經歷第三個階段時開始識別python語法,當遇到特定的語法name = ‘上’(代碼本身也都全都是unicode格式存的)時,需要申請內存空間來存儲字符串’上’,這就又涉及到應該以什么編碼存儲‘上’的問題了。

    在Python3中,字符串類的值都是使用unicode格式來存儲

    由于Python2的盛行是早于unicode的,因此在Python2中是按照文件頭指定的編碼來存儲字符串類型的值的(如果文件頭中沒有指定編碼,那么解釋器會按照它自己默認的編碼方式來存儲‘上’),所以,這就有可能導致亂碼問題

    # coding:utf-8
    x = '上' # x的值為untf-8格式的二進制
    print(x) # 打印操作是將x的值,即utf-8格式的二進制交給終端,當終端收到后發現并不是unicode(只有unicode才與字符有對應關系),所以終端會執行操作:utf-8二進制---解碼-->unicode格式的二進制,解碼的過程終端會采用自己默認的編碼,而在pycharm的終端默認編碼為utf-8、windows下的cmd終端的默認編碼為gbk,所以該打印操作在pycharm中顯示正常,而在windows下的cmd中則亂碼
    
    # 在windows下的cmd中運行效果如下
    C:\Users\Administrator>python2 E:\aaa.py
    涓
    

    python2后推出了一種補救措施,就是在字符串類型前加u,則會將字符串類型強制存儲unicode,這就與python3保持一致了,對于unicode格式無論丟給任何終端進行打印,都可以直接對應字符不會出現亂碼問題

    # coding:utf-8
    x = u'上' # 即便文件頭為utf-8,x的值依然存成unicode
    

    4.4 字符串encode編碼與decode解碼的使用

    # 1、unicode格式------編碼encode-------->其它編碼格式
    >>> x='上' # 在python3在'上'被存成unicode
    >>> res=x.encode('utf-8')
    >>> res,type(res) # unicode編碼成了utf-8格式,而編碼的結果為bytes類型,可以當作直接當作二進制去使用
    (b'\xe4\xb8\x8a', <class 'bytes'>)
    
    # 2、其它編碼格式------解碼decode-------->unicode格式
    >>> res.decode('utf-8') 
    '上'
    

    10、文件處理

    應用程序運行過程中產生的數據最先都是存放于內存中的,若想永久保存下來,必須要保存于硬盤中。應用程序若想操作硬件必須通過操作系統,而文件就是操作系統提供給應用程序來操作硬盤的虛擬概念,用戶或應用程序對文件的操作,就是向操作系統發起調用,然后由操作系統完成對硬盤的具體操作。

    二 文件操作的基本流程

    2.1 基本流程

    有了文件的概念,我們無需再去考慮操作硬盤的細節,只需要關注操作文件的流程:

    # 1. 打開文件,由應用程序向操作系統發起系統調用open(...),操作系統打開該文件,對應一塊硬盤空間,并返回一個文件對象賦值給一個變量f
    f=open('a.txt','r',encoding='utf-8') #默認打開模式就為r
    
    # 2. 調用文件對象下的讀/寫方法,會被操作系統轉換為讀/寫硬盤的操作
    data=f.read()
    
    # 3. 向操作系統發起關閉文件的請求,回收系統資源
    f.close()
    

    2.2 資源回收與with上下文管理

    打開一個文件包含兩部分資源:應用程序的變量f和操作系統打開的文件。在操作完畢一個文件時,必須把與該文件的這兩部分資源全部回收,回收方法為:

    1、f.close() #回收操作系統打開的文件資源
    2del f #回收應用程序級的變量
    

    其中del f一定要發生在f.close()之后,否則就會導致操作系統打開的文件無法關閉,白白占用資源, 而python自動的垃圾回收機制決定了我們無需考慮del f,這就要求我們,在操作完畢文件后,一定要記住f.close(),雖然我們如此強調,但是大多數讀者還是會不由自主地忘記f.close(),考慮到這一點,python提供了with關鍵字來幫我們管理上下文

    # 1、在執行完子代碼塊后,with 會自動執行f.close()
    with open('a.txt','w') as f:
        pass 
    
    # 2、可用用with同時打開多個文件,用逗號分隔開即可
    with open('a.txt','r') as read_f,open('b.txt','w') as write_f:  
        data = read_f.read()
        write_f.write(data)
    

    2.3 指定操作文本文件的字符編碼

    f = open(...)是由操作系統打開文件,如果打開的是文本文件,會涉及到字符編碼問題,如果沒有為open指定編碼,那么打開文本文件的默認編碼很明顯是操作系統說了算了,操作系統會用自己的默認編碼去打開文件,在windows下是gbk,在linux下是utf-8。
    這就用到了上節課講的字符編碼的知識:若要保證不亂碼,文件以什么方式存的,就要以什么方式打開。
    
    f = open('a.txt','r',encoding='utf-8')
    

    三 文件的操作模式

    3.1 控制文件讀寫操作的模式

    r(默認的):只讀
    w:只寫
    a:只追加寫
    

    3.1.1 案例一:r 模式的使用

    # r只讀模式: 在文件不存在時則報錯,文件存在文件內指針直接跳到文件開頭
     with open('a.txt',mode='r',encoding='utf-8') as f:
         res=f.read() # 會將文件的內容由硬盤全部讀入內存,賦值給res
    
    # 小練習:實現用戶認證功能
     inp_name=input('請輸入你的名字: ').strip()
     inp_pwd=input('請輸入你的密碼: ').strip()
     with open(r'db.txt',mode='r',encoding='utf-8') as f:
         for line in f:
             # 把用戶輸入的名字與密碼與讀出內容做比對
             u,p=line.strip('\n').split(':')
             if inp_name == u and inp_pwd == p:
                 print('登錄成功')
                 break
         else:
             print('賬號名或者密碼錯誤')
    

    3.1.2 案例二:w 模式的使用

    # w只寫模式: 在文件不存在時會創建空文檔,文件存在會清空文件,文件指針跑到文件開頭
    with open('b.txt',mode='w',encoding='utf-8') as f:
        f.write('你好\n')
        f.write('我好\n') 
        f.write('大家好\n')
        f.write('111\n222\n333\n')
    #強調:
    # 1 在文件不關閉的情況下,連續的寫入,后寫的內容一定跟在前寫內容的后面
    # 2 如果重新以w模式打開文件,則會清空文件內容
    

    3.1.3 案例三:a 模式的使用

    # a只追加寫模式: 在文件不存在時會創建空文檔,文件存在會將文件指針直接移動到文件末尾
     with open('c.txt',mode='a',encoding='utf-8') as f:
         f.write('44444\n')
         f.write('55555\n')
    #強調 w 模式與 a 模式的異同:
    # 1 相同點:在打開的文件不關閉的情況下,連續的寫入,新寫的內容總會跟在前寫的內容之后
    # 2 不同點:以 a 模式重新打開文件,不會清空原文件內容,會將文件指針直接移動到文件末尾,新寫的內容永遠寫在最后
    
    # 小練習:實現注冊功能:
     name=input('username>>>: ').strip()
     pwd=input('password>>>: ').strip()
     with open('db1.txt',mode='a',encoding='utf-8') as f:
         info='%s:%s\n' %(name,pwd)
         f.write(info)
    

    3.1.4 案例四:+ 模式的使用(了解)

    # r+ w+ a+ :可讀可寫
    #在平時工作中,我們只單純使用r/w/a,要么只讀,要么只寫,一般不用可讀可寫的模式
    

    3.2 控制文件讀寫內容的模式

    大前提: tb模式均不能單獨使用,必須與r/w/a之一結合使用
    t(默認的):文本模式
        1. 讀寫文件都是以字符串為單位的
        2. 只能針對文本文件
        3. 必須指定encoding參數
    b:二進制模式:
       1.讀寫文件都是以bytes/二進制為單位的
       2. 可以針對所有文件
       3. 一定不能指定encoding參數
    

    3.2.1 案例一:t 模式的使用

    # t 模式:如果我們指定的文件打開模式為r/w/a,其實默認就是rt/wt/at
     with open('a.txt',mode='rt',encoding='utf-8') as f:
         res=f.read() 
         print(type(res)) # 輸出結果為:<class 'str'>
    
     with open('a.txt',mode='wt',encoding='utf-8') as f:
         s='abc'
         f.write(s) # 寫入的也必須是字符串類型
    
     #強調:t 模式只能用于操作文本文件,無論讀寫,都應該以字符串為單位,而存取硬盤本質都是二進制的形式,當指定 t 模式時,內部幫我們做了編碼與解碼
    

    3.2.2 案例二: b 模式的使用

    # b: 讀寫都是以二進制位單位
     with open('1.mp4',mode='rb') as f:
         data=f.read()
         print(type(data)) # 輸出結果為:<class 'bytes'>
    
     with open('a.txt',mode='wb') as f:
         msg="你好"
         res=msg.encode('utf-8') # res為bytes類型
         f.write(res) # 在b模式下寫入文件的只能是bytes類型
    
    #強調:b模式對比t模式
    1、在操作純文本文件方面t模式幫我們省去了編碼與解碼的環節,b模式則需要手動編碼與解碼,所以此時t模式更為方便
    2、針對非文本文件(如圖片、視頻、音頻等)只能使用b模式
    
    # 小練習: 編寫拷貝工具
    src_file=input('源文件路徑: ').strip()
    dst_file=input('目標文件路徑: ').strip()
    with open(r'%s' %src_file,mode='rb') as read_f,open(r'%s' %dst_file,mode='wb') as write_f:
        for line in read_f:
            # print(line)
            write_f.write(line)
    

    四 操作文件的方法

    4.1 重點

    # 讀操作
    f.read()  # 讀取所有內容,執行完該操作后,文件指針會移動到文件末尾
    f.readline()  # 讀取一行內容,光標移動到第二行首部
    f.readlines()  # 讀取每一行內容,存放于列表中
    
    # 強調:
    # f.read()與f.readlines()都是將內容一次性讀入內容,如果內容過大會導致內存溢出,若還想將內容全讀入內存,則必須分多次讀入,有兩種實現方式:
    # 方式一
    with open('a.txt',mode='rt',encoding='utf-8') as f:
        for line in f:
            print(line) # 同一時刻只讀入一行內容到內存中
    
    # 方式二
    with open('1.mp4',mode='rb') as f:
        while True:
            data=f.read(1024) # 同一時刻只讀入1024個Bytes到內存中
            if len(data) == 0:
                break
            print(data)
    
    # 寫操作
    f.write('1111\n222\n')  # 針對文本模式的寫,需要自己寫換行符
    f.write('1111\n222\n'.encode('utf-8'))  # 針對b模式的寫,需要自己寫換行符
    f.writelines(['333\n','444\n'])  # 文件模式
    f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式
    

    4.2 了解

    f.readable()  # 文件是否可讀
    f.writable()  # 文件是否可讀
    f.closed  # 文件是否關閉
    f.encoding  # 如果文件打開模式為b,則沒有該屬性
    f.flush()  # 立刻將文件內容從內存刷到硬盤
    f.name
    

    五 主動控制文件內指針移動

    #大前提:文件內指針的移動都是Bytes為單位的,唯一例外的是t模式下的read(n),n以字符為單位
    with open('a.txt',mode='rt',encoding='utf-8') as f:
         data=f.read(3) # 讀取3個字符
    
    
    with open('a.txt',mode='rb') as f:
         data=f.read(3) # 讀取3個Bytes
    
    
    # 之前文件內指針的移動都是由讀/寫操作而被動觸發的,若想讀取文件某一特定位置的數據,則則需要用f.seek方法主動控制文件內指針的移動,詳細用法如下:
    # f.seek(指針移動的字節數,模式控制): 
    # 模式控制:
    # 0: 默認的模式,該模式代表指針移動的字節數是以文件開頭為參照的
    # 1: 該模式代表指針移動的字節數是以當前所在的位置為參照的
    # 2: 該模式代表指針移動的字節數是以文件末尾的位置為參照的
    # 強調:其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用
    

    5.1 案例一: 0模式詳解

    # a.txt用utf-8編碼,內容如下(abc各占1個字節,中文“你好”各占3個字節)
    abc你好
    
    # 0模式的使用
    with open('a.txt',mode='rt',encoding='utf-8') as f:
        f.seek(3,0)     # 參照文件開頭移動了3個字節
        print(f.tell()) # 查看當前文件指針距離文件開頭的位置,輸出結果為3
        print(f.read()) # 從第3個字節的位置讀到文件末尾,輸出結果為:你好
        # 注意:由于在t模式下,會將讀取的內容自動解碼,所以必須保證讀取的內容是一個完整中文數據,否則解碼失敗
    
    with open('a.txt',mode='rb') as f:
        f.seek(6,0)
        print(f.read().decode('utf-8')) #輸出結果為: 好
    

    5.2 案例二: 1模式詳解

    # 1模式的使用
    with open('a.txt',mode='rb') as f:
        f.seek(3,1) # 從當前位置往后移動3個字節,而此時的當前位置就是文件開頭
        print(f.tell()) # 輸出結果為:3
        f.seek(4,1)     # 從當前位置往后移動4個字節,而此時的當前位置為3
        print(f.tell()) # 輸出結果為:7
    

    5.3 案例三: 2模式詳解

    # a.txt用utf-8編碼,內容如下(abc各占1個字節,中文“你好”各占3個字節)
    abc你好
    
    # 2模式的使用
    with open('a.txt',mode='rb') as f:
        f.seek(0,2)     # 參照文件末尾移動0個字節, 即直接跳到文件末尾
        print(f.tell()) # 輸出結果為:9
        f.seek(-3,2)     # 參照文件末尾往前移動了3個字節
        print(f.read().decode('utf-8')) # 輸出結果為:好
    
    # 小練習:實現動態查看最新一條日志的效果
    import time
    with open('access.log',mode='rb') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if len(line) == 0:
                # 沒有內容
                time.sleep(0.5)
            else:
                print(line.decode('utf-8'),end='')
    

    六 文件的修改

    # 文件a.txt內容如下
    張一蛋     山東    179    49    12344234523
    李二蛋     河北    163    57    13913453521
    王全蛋     山西    153    62    18651433422
    
    # 執行操作
    with open('a.txt',mode='r+t',encoding='utf-8') as f:
        f.seek(9)
        f.write('<婦女主任>')
    
    # 文件修改后的內容如下
    張一蛋<婦女主任> 179    49    12344234523
    李二蛋     河北    163    57    13913453521
    王全蛋     山西    153    62    18651433422
    
    # 強調:
    # 1、硬盤空間是無法修改的,硬盤中數據的更新都是用新內容覆蓋舊內容
    # 2、內存中的數據是可以修改的
    

    文件對應的是硬盤空間,硬盤不能修改對應著文件本質也不能修改, 那我們看到文件的內容可以修改,是如何實現的呢? 大致的思路是將硬盤中文件內容讀入內存,然后在內存中修改完畢后再覆蓋回硬盤 具體的實現方式分為兩種:

    6.1 文件修改方式一

    # 實現思路:將文件內容發一次性全部讀入內存,然后在內存中修改完畢后再覆蓋寫回原文件
    # 優點: 在文件修改過程中同一份數據只有一份
    # 缺點: 會過多地占用內存
    with open('db.txt',mode='rt',encoding='utf-8') as f:
        data=f.read()
    
    with open('db.txt',mode='wt',encoding='utf-8') as f:
        f.write(data.replace('kevin','SB'))
    

    6.1 文件修改方式二

    # 實現思路:以讀的方式打開原文件,以寫的方式打開一個臨時文件,一行行讀取原文件內容,修改完后寫入臨時文件...,刪掉原文件,將臨時文件重命名原文件名
    # 優點: 不會占用過多的內存
    # 缺點: 在文件修改過程中同一份數據存了兩份
    import os
    
    with open('db.txt',mode='rt',encoding='utf-8') as read_f,\
            open('.db.txt.swap',mode='wt',encoding='utf-8') as wrife_f:
        for line in read_f:
            wrife_f.write(line.replace('SB','kevin'))
    
    os.remove('db.txt')
    os.rename('.db.txt.swap','db.txt')
    

    11、函數的基本使用

    一 引入

    在程序中,具備某一功能的‘工具’指的就是函數,‘事先準備工具’的過程即函數的定義,‘拿來就用’即函數的調用。

    二 定義函數

    函數的使用必須遵循’先定義,后調用’的原則。函數的定義就相當于事先將函數體代碼保存起來,然后將內存地址賦值給函數名,函數名就是對這段代碼的引用,這和變量的定義是相似的。沒有事先定義函數而直接調用,就相當于在引用一個不存在的’變量名’。

    定義函數的語法

    def 函數名(參數1,參數2,...):
        """文檔描述"""
        函數體
        return 值
    
    1. def: 定義函數的關鍵字;
    2. 函數名:函數名指向函數內存地址,是對函數體代碼的引用。函數的命名應該反映出函數的功能;
    3. 括號:括號內定義參數,參數是可有可無的,且無需指定參數的類型;
    4. 冒號:括號后要加冒號,然后在下一行開始縮進編寫函數體的代碼;
    5. “”“文檔描述”"": 描述函數功能,參數介紹等信息的文檔,非必要,但是建議加上,從而增強函數的可讀性;
    6. 函數體:由語句和表達式組成;
    7. return 值:定義函數的返回值,return是可有可無的。

    參數是函數的調用者向函數體傳值的媒介,若函數體代碼邏輯依賴外部傳來的參數時則需要定義為參函數,

    def my_min(x,y):
        res=x if x < y else y
        return res
    

    否則定義為無參函數

    def interactive():
        user=input('user>>: ').strip()
        pwd=input('password>>: ').strip()
        return (user,pwd)
    

    函數體為pass代表什么都不做,稱之為空函數。定義空函數通常是有用的,因為在程序設計的開始,往往是先想好程序都需要完成什么功能,然后把所有功能都列舉出來用pass充當函數體“占位符”,這將使得程序的體系結構立見,清晰且可讀性強。例如要編寫一個ftp程序,我們可能想到的功能有用戶認證,下載,上傳,瀏覽,切換目錄等功能,可以先做出如下定義:

    def auth_user():
        """user authentication function"""
        pass
    
    def download_file():
        """download file function"""
        pass
    
    def upload_file():
        """upload file function"""
        pass
    
    def ls():
        """list contents function"""
        pass
    
    def cd():
        """change directory"""
        pass
    

    之后我們便可以統籌安排編程任務,有選擇性的去實現上述功能來替換掉pass,從而提高開發效率。

    三 調用函數與函數返回值

    函數的使用分為定義階段與調用階段,定義函數時只檢測語法,不執行函數體代碼,函數名加括號即函數調用,只有調用函數時才會執行函數體代碼

    #定義階段
    def foo():
        print('in the foo')
        bar()
    
    def bar():
        print('in the bar')
    
    #調用階段
    foo()
    

    執行結果:

    in the foo
    in the bar
    

    定義階段函數foo與bar均無語法錯誤,而在調用階段調用foo()時,函數foo與bar都早已經存在于內存中了,所以不會有任何問題。

    按照在程序出現的形式和位置,可將函數的調用形式分為三種

    #1、語句形式:
    foo()
    
    #2、表達式形式:
    m=my_min(1,2) #將調用函數的返回值賦值給x
    n=10*my_min(1,2) #將調用函數的返回值乘以10的結果賦值給n
    
    #3、函數調用作為參數的形式:
    # my_min(2,3)作為函數my_min的第二個參數,實現了取1,2,3中的較小者賦值給m
    m=my_min(1,my_min(23)
    

    若需要將函數體代碼執行的結果返回給調用者,則需要用到return。return后無值或直接省略return,則默認返回None,return的返回值無類型限制,且可以將多個返回值放到一個元組內。

    >>> def test(x,y,z):
    ...     return x,y,z #等同于return (x,y,z)
    ... 
    >>> res=test(1,2,3)
    >>> print(res)
    (1, 2, 3)
    

    return是一個函數結束的標志,函數內可以有多個return,但只執行一次函數就結束了,并把return后定義的值作為本次調用的結果返回。

    12、函數的參數

    一 形參與實參介紹

    函數的參數分為形式參數和實際參數,簡稱形參和實參:

    形參即在定義函數時,括號內聲明的參數。形參本質就是一個變量名,用來接收外部傳來的值。

    實參即在調用函數時,括號內傳入的值,值可以是常量、變量、表達式或三者的組合:

    #1:實參是常量
    res=my_min(1,2)
    
    #2:實參是變量
    a=1
    b=2
    res=my_min(a,b)
    
    #3:實參是表達式
    res=my_min(10*2,10*my_min(3,4))
    
    #4:實參可以是常量、變量、表達式的任意組合
    a=2
    my_min(1,a,10*my_min(3,4))
    

    在調用有參函數時,實參(值)會賦值給形參(變量名)。在Python中,變量名與值只是單純的綁定關系,而對于函數來說,這種綁定關系只在函數調用時生效,在調用結束后解除。

    二 形參與實參的具體使用

    2.1 位置參數

    位置即順序,位置參數指的是按順序定義的參數,需要從兩個角度去看:

    1. 在定義函數時,按照從左到右的順序依次定義形參,稱為位置形參,凡是按照這種形式定義的形參都必須被傳值
    def register(name,age,sex): #定義位置形參:name,age,sex,三者都必須被傳值
        print('Name:%s Age:%s Sex:%s' %(name,age,sex))
    register() #TypeError:缺少3個位置參數 
    

    在調用函數時,按照從左到右的順序依次定義實參,稱為位置實參,凡是按照這種形式定義的實參會按照從左到右的順序與形參一一對應

    def register(name,age,sex): #定義位置形參:name,age,sex,三者都必須被傳值
        print('Name:%s Age:%s Sex:%s' %(name,age,sex))
    register() #TypeError:缺少3個位置參數
    

    2.2 關鍵字參數

    在調用函數時,實參可以是key=value的形式,稱為關鍵字參數,凡是按照這種形式定義的實參,可以完全不按照從左到右的順序定義,但仍能為指定的形參賦值

    >>> register(sex='male',name='lili',age=18)
    Name:lili Age:18 Sex:male
    

    需要注意在調用函數時,實參也可以是按位置或按關鍵字的混合使用,但必須保證關鍵字參數在位置參數后面,且不可以對一個形參重復賦值

    >>> register('lili',sex='male',age=18) #正確使用
    >>> register(name='lili',18,sex='male') #SyntaxError:關鍵字參數name=‘lili’在位置參數18之前
    >>> register('lili',sex='male',age=18,name='jack') #TypeError:形參name被重復賦值
    

    2.3 默認參數

    在定義函數時,就已經為形參賦值,這類形參稱之為默認參數,當函數有多個參數時,需要將值經常改變的參數定義成位置參數,而將值改變較少的參數定義成默認參數。例如編寫一個注冊學生信息的函數,如果大多數學生的性別都為男,那完全可以將形參sex定義成默認參數

    >>> def register(name,age,sex='male'): #默認sex的值為male
    ...     print('Name:%s Age:%s Sex:%s' %(name,age,sex))
    ...
    

    定義時就已經為參數sex賦值,意味著調用時可以不對sex賦值,這降低了函數調用的復雜度

    >>> register('tom',17) #大多數情況,無需為sex傳值,默認為male
    Name:tom Age:17 Sex:male
    >>> register('Lili',18,'female') #少數情況,可以為sex傳值female
    Name:Lili Age:18 Sex:female
    

    需要注意:

    1. 默認參數必須在位置參數之后
    2. 默認參數的值僅在函數定義階段被賦值一次
    >>> x=1
    >>> def foo(arg=x):
    ...     print(arg)
    ... 
    >>> x=5 #定義階段arg已被賦值為1,此處的修改與默認參數arg無任何關系
    >>> foo()
    1
    
    1. 默認參數的值通常應設為不可變類型
    def foo(n,arg=[]):    
         arg.append(n)    
         return arg    
    foo(1)    
    [1] 
    foo(2)    
    [1, 2] 
    foo(3)    
    [1, 2, 3]
    

    每次調用是在上一次的基礎上向同一列表增加值,修改如下

    def foo(n,arg=None):    
         if arg is None:    
             arg=[]    
         arg.append(n)    
         return arg    
    foo(1)    
    [1] 
    foo(2)    
    [2] 
    foo(3)    
    [3]
    

    2.4 可變長度的參數(*與**的用法)

    參數的長度可變指的是在調用函數時,實參的個數可以不固定,而在調用函數時,實參的定義無非是按位置或者按關鍵字兩種形式,這就要求形參提供兩種解決方案來分別處理兩種形式的可變長度的參數

    2.4.1 可變長度的位置參數

    如果在最后一個形參名前加號,那么在調用函數時,溢出的位置實參,都會被接收,以元組的形式保存下來賦值給該形參

    >>> def foo(x,y,z=1,*args): #在最后一個形參名args前加*號
    ...     print(x)
    ...     print(y)
    ...     print(z)
    ...     print(args)
    ... 
    >>> foo(1,2,3,4,5,6,7)  #實參1、2、3按位置為形參x、y、z賦值,多余的位置實參4、5、6、7都被*接收,以元組的形式保存下來,賦值給args,即args=(4, 5, 6,7)
    
    1
    2
    3
    (4, 5, 6, 7)
    

    如果我們事先生成了一個列表,仍然是可以傳值給*args的

    >>> def foo(x,y,*args):
    ...     print(x)
    ...     print(y)
    ...     print(args)
    ... 
    >>> L=[3,4,5]
    >>> foo(1,2,*L) # *L就相當于位置參數3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
    1
    2
    (3, 4, 5)
    

    注意:如果在傳入L時沒有加*,那L就只是一個普通的位置參數了

    >>> foo(1,2,L) #僅多出一個位置實參L
    1
    2
    ([1, 2, 3],)
    

    如果形參為常規的參數(位置或默認),實參仍可以是*的形式

    >>> def foo(x,y,z=3):
    ...     print(x)
    ...     print(y)
    ...     print(z)
    ... 
    >>> foo(*[1,2]) #等同于foo(1,2)
    1
    2
    3
    

    如果我們想要求多個值的和,*args就派上用場了

    >>> def add(*args):
    ...     res=0
    ...     for i in args:
    ...         res+=i
    ...     return res
    ... 
    >>> add(1,2,3,4,5)
    15
    

    2.4.2 可變長度的關鍵字參數

    如果在最后一個形參名前加號,那么在調用函數時,溢出的關鍵字參數,都會被接收,以字典的形式保存下來賦值給該形參

    >>> def foo(x,**kwargs): #在最后一個參數kwargs前加**
    ...     print(x)        
    ...     print(kwargs)   
    ... 
    >>> foo(y=2,x=1,z=3) #溢出的關鍵字實參y=2,z=3都被**接收,以字典的形式保存下來,賦值給kwargs
    1
    {'z': 3, 'y': 2}
    

    如果我們事先生成了一個字典,仍然是可以傳值給**kwargs的

    >>> def foo(x,y,**kwargs):
    ...     print(x)
    ...     print(y)
    ...     print(kwargs)
    ... 
    >>> dic={'a':1,'b':2} 
    >>> foo(1,2,**dic) #**dic就相當于關鍵字參數a=1,b=2,foo(1,2,**dic)等同foo(1,2,a=1,b=2)
    1
    2
    {'a': 1, 'b': 2}
    

    注意:如果在傳入dic時沒有加**,那dic就只是一個普通的位置參數了

    >>> foo(1,2,dic) #TypeError:函數foo只需要2個位置參數,但是傳了3個
    

    如果形參為常規參數(位置或默認),實參仍可以是**的形式

    >>> def foo(x,y,z=3):
    ...     print(x)
    ...     print(y)
    ...     print(z)
    ... 
    >>> foo(**{'x':1,'y':2}) #等同于foo(y=2,x=1)
    1
    2
    3
    

    如果我們要編寫一個用戶認證的函數,起初可能只基于用戶名密碼的驗證就可以了,可以使用**kwargs為日后的擴展供良好的環境,同時保持了函數的簡潔性。

    >>> def auth(user,password,**kwargs): 
    ...     pass 
    ...
    

    2.5 命名關鍵字參數

    在定義了**kwargs參數后,函數調用者就可以傳入任意的關鍵字參數key=value,如果函數體代碼的執行需要依賴某個key,必須在函數內進行判斷

    >>> def register(name,age,**kwargs):
    ...     if 'sex' in kwargs:
    ...         #有sex參數
    ...         pass
    ...     if 'height' in kwargs:
    ...         #有height參數
    ...         pass
    ...
    

    想要限定函數的調用者必須以key=value的形式傳值,Python3提供了專門的語法:需要在定義形參時,用*作為一個分隔符號,*號之后的形參稱為命名關鍵字參數。對于這類參數,在函數調用時,必須按照key=value的形式為其傳值,且必須被傳值

    >>> def register(name,age,*,sex,height): #sex,height為命名關鍵字參數
    ...     pass
    ... 
    >>> register('lili',18,sex='male',height='1.8m') #正確使用
    >>> register('lili',18,'male','1.8m') # TypeError:未使用關鍵字的形式為sex和height傳值
    >>> register('lili',18,height='1.8m') # TypeError沒有為命名關鍵字參數height傳值。
    

    命名關鍵字參數也可以有默認值,從而簡化調用

    >>> def register(name,age,*,sex='male',height):
    ...     print('Name:%s,Age:%s,Sex:%s,Height:%s' %(name,age,sex,height))
    ... 
    >>> register('lili',18,height='1.8m')
    Name:lili,Age:18,Sex:male,Height:1.8m
    

    需要強調的是:sex不是默認參數,height也不是位置參數,因為二者均在后,所以都是命名關鍵字參數,形參sex=’male’屬于命名關鍵字參數的默認值,因而即便是放到形參height之前也不會有問題。另外,如果形參中已經有一個args了,命名關鍵字參數就不再需要一個單獨的*作為分隔符號了

    >>> def register(name,age,*args,sex='male',height):
    ...   print('Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s' %(name,age,args,sex,height))
    ... 
    >>> register('lili',18,1,2,3,height='1.8m') #sex與height仍為命名關鍵字參數
    Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m
    

    2.6 組合使用

    綜上所述所有參數可任意組合使用,但定義順序必須是:位置參數、默認參數、*args、命名關鍵字參數、**kwargs

    可變參數*args與關鍵字參數kwargs通常是組合在一起使用的,如果一個函數的形參為*args與kwargs,那么代表該函數可以接收任何形式、任意長度的參數

    >>> def wrapper(*args,**kwargs):
    ...     pass
    ...
    

    在該函數內部還可以把接收到的參數傳給另外一個函數(這在4.6小節裝飾器的實現中大有用處)

    >>> def func(x,y,z):
    ...     print(x,y,z)
    ... 
    >>> def wrapper(*args,**kwargs):
    ...     func(*args,**kwargs)
    ...
    >>> wrapper(1,z=3,y=2)
    1 2 3
    

    按照上述寫法,在為函數wrapper傳參時,其實遵循的是函數func的參數規則,調用函數wrapper的過程分析如下:

    1. 位置實參1被*接收,以元組的形式保存下來,賦值給args,即args=(1,),關鍵字實參z=3,y=2被**接收,以字典的形式保存下來,賦值給kwargs,即kwargs={‘y’: 2, ‘z’: 3}
    2. 執行func(args,kwargs),即func((1,),* {‘y’: 2, ‘z’: 3}),等同于func(1,z=3,y=2)
    提示: *args、**kwargs中的args和kwargs被替換成其他名字并無語法錯誤,但使用args、kwargs是約定
    

    13、名稱空間與作用域

    一 名稱空間

    名稱空間即存放名字與對象映射/綁定關系的地方。對于x=3,Python會申請內存空間存放對象3,然后將名字x與3的綁定關系存放于名稱空間中,del x表示清除該綁定關系。

    在程序執行期間最多會存在三種名稱空間

    1.1 內建名稱空間

    伴隨python解釋器的啟動/關閉而產生/回收,因而是第一個被加載的名稱空間,用來存放一些內置的名字,比如內建函數名

    >>> max
    <built-in function max> #built-in內建
    

    1.2 全局名稱空間

    伴隨python文件的開始執行/執行完畢而產生/回收,是第二個被加載的名稱空間,文件執行過程中產生的名字都會存放于該名稱空間中,如下名字

    import sys #模塊名sys
    
    x=1 #變量名x
    
    if x == 1:
        y=2 #變量名y
    
    def foo(x): #函數名foo
        y=1
        def bar():
            pass
    
    Class Bar: #類名Bar
        pass
    

    1.3 局部名稱空間

    伴隨函數的調用/結束而臨時產生/回收,函數的形參、函數內定義的名字都會被存放于該名稱空間中

    def foo(x):
        y=3 #調用函數時,才會執行函數代碼,名字x和y都存放于該函數的局部名稱空間中
    

    名稱空間的加載順序是:內置名稱空間->全局名稱空間->局部名稱空間,而查找一個名字,必須從三個名稱空間之一找到,查找順序為:局部名稱空間->全局名稱空間->內置名稱空間。

    二 作用域

    2.1 全局作用域與局部作用域

    按照名字作用范圍的不同可以將三個名稱空間劃分為兩個區域:

    1. 全局作用域:位于全局名稱空間、內建名稱空間中的名字屬于全局范圍,該范圍內的名字全局存活(除非被刪除,否則在整個文件執行過程中存活)、全局有效(在任意位置都可以使用);
    2. 局部作用域:位于局部名稱空間中的名字屬于局部范圍。該范圍內的名字臨時存活(即在函數調用時臨時生成,函數調用結束后就釋放)、局部有效(只能在函數內使用)。

    2.2 作用域與名字查找的優先級

    在局部作用域查找名字時,起始位置是局部作用域,所以先查找局部名稱空間,沒有找到,再去全局作用域查找:先查找全局名稱空間,沒有找到,再查找內置名稱空間,最后都沒有找到就會拋出異常

    x=100 #全局作用域的名字x
    def foo():
        x=300 #局部作用域的名字x
        print(x) #在局部找x
    foo()#結果為300
    

    在全局作用域查找名字時,起始位置便是全局作用域,所以先查找全局名稱空間,沒有找到,再查找內置名稱空間,最后都沒有找到就會拋出異常

    x=100
    def foo():
        x=300 #在函數調用時產生局部作用域的名字x
    foo()
    print(x) #在全局找x,結果為100
    

    提示:可以調用內建函數locals()和globals()來分別查看局部作用域和全局作用域的名字,查看的結果都是字典格式。在全局作用域查看到的locals()的結果等于globals()

    Python支持函數的嵌套定義,在內嵌的函數內查找名字時,會優先查找自己局部作用域的名字,然后由內而外一層層查找外部嵌套函數定義的作用域,沒有找到,則查找全局作用域

    x=1
    def outer():
        x=2
        def inner(): # 函數名inner屬于outer這一層作用域的名字
            x=3
            print('inner x:%s' %x)
    
        inner()
        print('outer x:%s' %x)
    
    outer() 
    #結果為
    inner x:3
    outer x:2
    

    在函數內,無論嵌套多少層,都可以查看到全局作用域的名字,若要在函數內修改全局名稱空間中名字的值,當值為不可變類型時,則需要用到global關鍵字

    x=1
    def outer():
        x=2
        def inner(): # 函數名inner屬于outer這一層作用域的名字
            x=3
            print('inner x:%s' %x)
    
        inner()
        print('outer x:%s' %x)
    
    outer() 
    #結果為
    inner x:3
    outer x:2
    

    在函數內,無論嵌套多少層,都可以查看到全局作用域的名字,若要在函數內修改全局名稱空間中名字的值,當值為不可變類型時,則需要用到global關鍵字

    x=1
    def foo():
        global x #聲明x為全局名稱空間的名字
        x=2
    foo()
    print(x) #結果為2
    

    當實參的值為可變類型時,函數體內對該值的修改將直接反應到原值,

    num_list=[1,2,3]
    def foo(nums):
        nums.append(5)
    
    foo(num_list)
    print(num_list)
    #結果為
    [1, 2, 3, 5]
    

    對于嵌套多層的函數,使用nonlocal關鍵字可以將名字聲明為來自外部嵌套函數定義的作用域(非全局)

    def  f1():
        x=2
        def f2():
            nonlocal x
            x=3
        f2() #調用f2(),修改f1作用域中名字x的值
        print(x) #在f1作用域查看x
    
    f1()
    
    #結果
    3
    

    nonlocal x會從當前函數的外層函數開始一層層去查找名字x,若是一直到最外層函數都找不到,則會拋出異常。

    14、函數對象和閉包

    一 函數對象

    函數對象指的是函數可以被當做’數據’來處理,具體可以分為四個方面的使用,我們如下

    1.1 函數可以被引用

    >>> def add(x,y):
    ...     return x+y
    ... 
    >>> func=add
    >>> func(1,2)
    3
    

    1.2 函數可以作為容器類型的元素

    >>> dic={'add':add,'max':max}
    >>> dic
    {'add': <function add at 0x100661e18>, 'max': <built-in function max>}
    >>> dic['add'](1,2)
    3
    

    1.3 函數可以作為參數傳入另外一個函數

    >>> def foo(x,y,func):
    ...     return func(x,y)
    ...
    >>> foo(1,2,add)
    3
    

    1.4 函數的返回值可以是一個函數

    def bar(): 
         return add 
    func=bar() 
    func(1,2)
    3 
    

    二 閉包函數

    2.1 閉與包

    基于函數對象的概念,可以將函數返回到任意位置去調用,但作用域的關系是在定義完函數時就已經被確定了的,與函數的調用位置無關。

    x=1
    
    def f1():
        def f2():
            print(x)
    
        return f2
    
    def f3():
        x=3
        f2=f1() #調用f1()返回函數f2
        f2() #需要按照函數定義時的作用關系去執行,與調用位置無關
    
    f3() #結果為1
    

    也就是說函數被當做數據處理時,始終以自帶的作用域為準。若內嵌函數包含對外部函數作用域(而非全局作用域)中變量的引用,那么該’內嵌函數’就是閉包函數,簡稱閉包(Closures)

    x=1
    def outer():
        x=2
        def inner():
            print(x)
        return inner
    
    func=outer()
    func() # 結果為2
    

    可以通過函數的closure屬性,查看到閉包函數所包裹的外部變量

    >>> func.__closure__
    (<cell at 0x10212af78: int object at 0x10028cca0>,)
    >>> func.__closure__[0].cell_contents
    2
    

    “閉”代表函數是內部的,“包”代表函數外’包裹’著對外層作用域的引用。因而無論在何處調用閉包函數,使用的仍然是包裹在其外層的變量。

    2.2 閉包的用途

    目前為止,我們得到了兩種為函數體傳值的方式,一種是直接將值以參數的形式傳入,另外一種就是將值包給函數

    import requests
    
    #方式一:
    def get(url):
        return requests.get(url).text
    
    #方式二:
    def page(url):
        def get():
            return requests.get(url).text
        return get
    

    提示:requests模塊是用來模擬瀏覽器向網站發送請求并將頁面內容下載到本地,需要事先安裝:pip3 install requests

    對比兩種方式,方式一在下載同一頁面時需要重復傳入url,而方式二只需要傳一次值,就會得到一個包含指定url的閉包函數,以后調用該閉包函數無需再傳url

    # 方式一下載同一頁面
    get('https://www.python.org')
    get('https://www.python.org')
    get('https://www.python.org')
    ……
    
    # 方式二下載同一頁面
    python=page('https://www.python.org')
    python()
    python()
    python()
    ……
    

    閉包函數的這種特性有時又稱為惰性計算。使用將值包給函數的方式,在接下來的裝飾器中也將大有用處

    15、裝飾器

    一 裝飾器介紹

    1.1 為何要用裝飾器

    軟件的設計應該遵循開放封閉原則,即對擴展是開放的,而對修改是封閉的。對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。對修改封閉,意味著對象一旦設計完成,就可以獨立完成其工作,而不要對其進行修改。

    軟件包含的所有功能的源代碼以及調用方式,都應該避免修改,否則一旦改錯,則極有可能產生連鎖反應,最終導致程序崩潰,而對于上線后的軟件,新需求或者變化又層出不窮,我們必須為程序提供擴展的可能性,這就用到了裝飾器。

    1.2 什么是裝飾器

    ’裝飾’代指為被裝飾對象添加新的功能,’器’代指器具/工具,裝飾器與被裝飾的對象均可以是任意可調用對象。概括地講,裝飾器的作用就是在不修改被裝飾對象源代碼和調用方式的前提下為被裝飾對象添加額外的功能。裝飾器經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等應用場景,裝飾器是解決這類問題的絕佳設計,有了裝飾器,就可以抽離出大量與函數功能本身無關的雷同代碼并繼續重用。

    提示:可調用對象有函數,方法或者類,此處我們單以本章主題函數為例,來介紹函數裝飾器,并且被裝飾的對象也是函數。

    二 裝飾器的實現

    函數裝飾器分為:無參裝飾器和有參裝飾兩種,二者的實現原理一樣,都是’函數嵌套+閉包+函數對象’的組合使用的產物。

    2.1 無參裝飾器的實現

    如果想為下述函數添加統計其執行時間的功能

    import time
    
    def index():
        time.sleep(3)
        print('Welcome to the index page’)
        return 200
    
    index() #函數執行
    

    遵循不修改被裝飾對象源代碼的原則,我們想到的解決方法可能是這樣

    start_time=time.time()
    index() #函數執行
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    

    考慮到還有可能要統計其他函數的執行時間,于是我們將其做成一個單獨的工具,函數體需要外部傳入被裝飾的函數從而進行調用,我們可以使用參數的形式傳入

    def wrapper(func): # 通過參數接收外部的值
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    

    但之后函數的調用方式都需要統一改成

    wrapper(index)
    wrapper(其他函數)
    

    這便違反了不能修改被裝飾對象調用方式的原則,于是我們換一種為函數體傳值的方式,即將值包給函數,如下

    def timer(func):
        def wrapper(): # 引用外部作用域的變量func
            start_time=time.time()
            res=func()
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        return wrapper
    

    這樣我們便可以在不修改被裝飾函數源代碼和調用方式的前提下為其加上統計時間的功能,只不過需要事先執行一次timer將被裝飾的函數傳入,返回一個閉包函數wrapper重新賦值給變量名 /函數名index,如下

    index=timer(index)  #得到index=wrapper,wrapper攜帶對外作用域的引用:func=原始的index
    index() # 執行的是wrapper(),在wrapper的函數體內再執行最原始的index
    

    至此我們便實現了一個無參裝飾器timer,可以在不修改被裝飾對象index源代碼和調用方式的前提下為其加上新功能。但我們忽略了若被裝飾的函數是一個有參函數,便會拋出異常

    def home(name):
        time.sleep(5)
        print('Welcome to the home page',name)
    
    home=timer(home)
    home('egon')
    #拋出異常
    TypeError: wrapper() takes 0 positional arguments but 1 was given
    

    之所以會拋出異常,是因為home(‘egon’)調用的其實是wrapper(‘egon’),而函數wrapper沒有參數。wrapper函數接收的參數其實是給最原始的func用的,為了能滿足被裝飾函數參數的所有情況,便用上*args+**kwargs組合(見4.3小節),于是修正裝飾器timer如下

    def timer(func):
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        return wrapper
    

    此時我們就可以用timer來裝飾帶參數或不帶參數的函數了,但是為了簡潔而優雅地使用裝飾器,Python提供了專門的裝飾器語法來取代index=timer(index)的形式,需要在被裝飾對象的正上方單獨一行添加@timer,當解釋器解釋到@timer時就會調用timer函數,且把它正下方的函數名當做實參傳入,然后將返回的結果重新賦值給原函數名

    @timer # index=timer(index)
    def index():
        time.sleep(3)
        print('Welcome to the index page')
        return 200
    @timer # index=timer(home)
              def home(name):
        time.sleep(5)
        print('Welcome to the home page’,name)
    

    如果我們有多個裝飾器,可以疊加多個

    @deco3
    @deco2
    @deco1
    def index():
        pass
    

    疊加多個裝飾器也無特殊之處,上述代碼語義如下:

    index=deco3(deco2(deco1(index)))
    

    2.2 有參裝飾器的實現

    了解無參裝飾器的實現原理后,我們可以再實現一個用來為被裝飾對象添加認證功能的裝飾器,實現的基本形式如下

    def deco(func):
        def wrapper(*args,**kwargs):
            編寫基于文件的認證,認證通過則執行res=func(*args,**kwargs),并返回res
        return wrapper
    

    如果我們想提供多種不同的認證方式以供選擇,單從wrapper函數的實現角度改寫如下

    def deco(func):
            def wrapper(*args,**kwargs):
                if driver == 'file':
                    編寫基于文件的認證,認證通過則執行res=func(*args,**kwargs),并返回res
                elif driver == 'mysql':
                    編寫基于mysql認證,認證通過則執行res=func(*args,**kwargs),并返回res
            return wrapper
    

    函數wrapper需要一個driver參數,而函數deco與wrapper的參數都有其特定的功能,不能用來接受其他類別的參數,可以在deco的外部再包一層函數auth,用來專門接受額外的參數,這樣便保證了在auth函數內無論多少層都可以引用到

    def auth(driver):
        def deco(func):
            ……
        return deco
    

    此時我們就實現了一個有參裝飾器,使用方式如下

    先調用auth_type(driver='file'),得到@deco,deco是一個閉包函數,
    包含了對外部作用域名字driver的引用,@deco的語法意義與無參裝飾器一樣
    @auth(driver='file') 
    def index():     
        pass
    @auth(driver='mysql') 
    def home():
        pass  
    

    可以使用help(函數名)來查看函數的文檔注釋,本質就是查看函數的doc屬性,但對于被裝飾之后的函數,查看文檔注釋

    @timer
    def home(name):
        '''
        home page function
        :param name: str
        :return: None
        '''
        time.sleep(5)
        print('Welcome to the home page',name)
    
    print(help(home))
    '''
    打印結果:
    
    Help on function wrapper in module __main__:
    
    wrapper(*args, **kwargs)
    
    None
    

    在被裝飾之后home=wrapper,查看home.name也可以發現home的函數名確實是wrapper,想要保留原函數的文檔和函數名屬性,需要修正裝飾器

    def timer(func):
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        wrapper.__doc__=func.__doc__
        wrapper.__name__=func.__name__
        return wrapper
    

    按照上述方式來實現保留原函數屬性過于麻煩,functools模塊下提供一個裝飾器wraps專門用來幫我們實現這件事,用法如下

    from functools import wraps
    
    def timer(func):
        @wraps(func)
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        return wrapper
    
    標準版裝飾器:
    def wrapper(func):
        def inner(*args,**kwargs):
            """執行被裝飾函數前的操作"""
            func(*args,**kwargs)
            """執行被裝飾函數后的操作"""
        return inner
    @wrapper
    def index():
        print("is index")
    index()
    
    語法糖要接受的變量就是語法糖下面的函數名、參數就是語法糖下面的函數名、調用的方式就是看調用的哪個語法糖
    def func(args):
        print("新加了一個功能")
        return args
    @func   #index = func(index)
    def index():
        print(2)
    index()
    結果:  新加了一個功能
    

    16、迭代器

    一 迭代器介紹

    迭代器即用來迭代取值的工具,而迭代是重復反饋過程的活動,其目的通常是為了逼近所需的目標或結果,每一次對過程的重復稱為一次“迭代”,而每一次迭代得到的結果會作為下一次迭代的初始值,單純的重復并不是迭代

    while True:
        msg = input('>>: ').strip()
        print(msg)
    

    下述while循環才是一個迭代過程,不僅滿足重復,而且以每次重新賦值后的index值作為下一次循環中新的索引進行取值,反復迭代,最終可以取盡列表中的值

    goods=['mac','lenovo','acer','dell','sony']
    
    index=0
    while index < len(goods):
        print(goods[index])
        index+=1
    

    1.1 可迭代對象

    通過索引的方式進行迭代取值,實現簡單,但僅適用于序列類型:字符串,列表,元組。對于沒有索引的字典、集合等非序列類型,必須找到一種不依賴索引來進行迭代取值的方式,這就用到了迭代器。

    要想了解迭代器為何物,必須事先搞清楚一個很重要的概念:可迭代對象(Iterable)。從語法形式上講,內置有__iter__方法的對象都是可迭代對象,字符串、列表、元組、字典、集合、打開的文件都是可迭代對象:

    {'name':'egon'}.__iter__
    {7,8,9}.__iter__
    ……
    

    1.2 迭代器對象

    調用obj.iter()方法返回的結果就是一個迭代器對象(Iterator)。迭代器對象是內置有iternext方法的對象,打開的文件本身就是一個迭代器對象,執行迭代器對象.iter()方法得到的仍然是迭代器本身,而執行迭代器.next()方法就會計算出迭代器中的下一個值。 迭代器是Python提供的一種統一的、不依賴于索引的迭代取值方式,只要存在多個“值”,無論序列類型還是非序列類型都可以按照迭代器的方式取值

    >>> s={1,2,3} # 可迭代對象s
    >>> i=iter(s)  # 本質就是在調用s.__iter__(),返回s的迭代器對象i,
    >>> next(i) # 本質就是在調用i.__next__()
    1
    >>> next(i)
    2
    >>> next(i)
    3
    >>> next(i)  #拋出StopIteration的異常,代表無值可取,迭代結束
    

    二 for循環原理

    有了迭代器后,我們便可以不依賴索引迭代取值了,使用while循環的實現方式如下

    goods=['mac','lenovo','acer','dell','sony']
    i=iter(goods) #每次都需要重新獲取一個迭代器對象
    while True:
        try:
            print(next(i))
        except StopIteration: #捕捉異常終止循環
            break
    

    for循環又稱為迭代循環,in后可以跟任意可迭代對象,上述while循環可以簡寫為

    goods=['mac','lenovo','acer','dell','sony']
    for item in goods:   
        print(item)
    

    for 循環在工作時,首先會調用可迭代對象goods內置的iter方法拿到一個迭代器對象,然后再調用該迭代器對象的next方法將取到的值賦給item,執行循環體完成一次循環,周而復始,直到捕捉StopIteration異常,結束迭代。

    三 迭代器的優缺點

    基于索引的迭代取值,所有迭代的狀態都保存在了索引中,而基于迭代器實現迭代的方式不再需要索引,所有迭代的狀態就保存在迭代器中,然而這種處理方式優點與缺點并存:

    3.1 優點:

    1、為序列和非序列類型提供了一種統一的迭代取值方式。

    2、惰性計算:迭代器對象表示的是一個數據流,可以只在需要時才去調用next來計算出一個值,就迭代器本身來說,同一時刻在內存中只有一個值,因而可以存放無限大的數據流,而對于其他容器類型,如列表,需要把所有的元素都存放于內存中,受內存大小的限制,可以存放的值的個數是有限的。

    3.2 缺點:

    1、除非取盡,否則無法獲取迭代器的長度

    2、只能取下一個值,不能回到開始,更像是‘一次性的’,迭代器產生后的唯一目標就是重復執行next方法直到值取盡,否則就會停留在某個位置,等待下一次調用next;若是要再次迭代同個對象,你只能重新調用iter方法去創建一個新的迭代器對象,如果有兩個或者多個循環使用同一個迭代器,必然只會有一個循環能取到值。

    17、生成器

    一 生成器與yield

    若函數體包含yield關鍵字,再調用函數,并不會執行函數體代碼,得到的返回值即生成器對象

    >>> def my_range(start,stop,step=1):
    ...     print('start...')
    ...     while start < stop:
    ...         yield start
    ...         start+=step
    ...     print('end...')
    ... 
    >>> g=my_range(0,3)
    >>> g
    <generator object my_range at 0x104105678>
    pper
    

    函數wrapper需要一個driver參數,而函數deco與wrapper的參數都有其特定的功能,不能用來接受其他類別的參數,可以在deco的外部再包一層函數auth,用來專門接受額外的參數,這樣便保證了在auth函數內無論多少層都可以引用到

    def auth(driver):
        def deco(func):
            ……
        return deco
    

    此時我們就實現了一個有參裝飾器,使用方式如下

    先調用auth_type(driver='file'),得到@deco,deco是一個閉包函數,
    包含了對外部作用域名字driver的引用,@deco的語法意義與無參裝飾器一樣
    @auth(driver='file') 
    def index():     
        pass
    @auth(driver='mysql') 
    def home():
        pass  
    

    可以使用help(函數名)來查看函數的文檔注釋,本質就是查看函數的doc屬性,但對于被裝飾之后的函數,查看文檔注釋

    @timer
    def home(name):
        '''
        home page function
        :param name: str
        :return: None
        '''
        time.sleep(5)
        print('Welcome to the home page',name)
    
    print(help(home))
    '''
    打印結果:
    
    Help on function wrapper in module __main__:
    
    wrapper(*args, **kwargs)
    
    None
    

    在被裝飾之后home=wrapper,查看home.name也可以發現home的函數名確實是wrapper,想要保留原函數的文檔和函數名屬性,需要修正裝飾器

    def timer(func):
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        wrapper.__doc__=func.__doc__
        wrapper.__name__=func.__name__
        return wrapper
    

    按照上述方式來實現保留原函數屬性過于麻煩,functools模塊下提供一個裝飾器wraps專門用來幫我們實現這件事,用法如下

    from functools import wraps
    
    def timer(func):
        @wraps(func)
        def wrapper(*args,**kwargs):
            start_time=time.time()
            res=func(*args,**kwargs)
            stop_time=time.time()
            print('run time is %s' %(stop_time-start_time))
            return res
        return wrapper
    
    標準版裝飾器:
    def wrapper(func):
        def inner(*args,**kwargs):
            """執行被裝飾函數前的操作"""
            func(*args,**kwargs)
            """執行被裝飾函數后的操作"""
        return inner
    @wrapper
    def index():
        print("is index")
    index()
    
    語法糖要接受的變量就是語法糖下面的函數名、參數就是語法糖下面的函數名、調用的方式就是看調用的哪個語法糖
    def func(args):
        print("新加了一個功能")
        return args
    @func   #index = func(index)
    def index():
        print(2)
    index()
    結果:  新加了一個功能
    

    16、迭代器

    一 迭代器介紹

    迭代器即用來迭代取值的工具,而迭代是重復反饋過程的活動,其目的通常是為了逼近所需的目標或結果,每一次對過程的重復稱為一次“迭代”,而每一次迭代得到的結果會作為下一次迭代的初始值,單純的重復并不是迭代

    while True:
        msg = input('>>: ').strip()
        print(msg)
    

    下述while循環才是一個迭代過程,不僅滿足重復,而且以每次重新賦值后的index值作為下一次循環中新的索引進行取值,反復迭代,最終可以取盡列表中的值

    goods=['mac','lenovo','acer','dell','sony']
    
    index=0
    while index < len(goods):
        print(goods[index])
        index+=1
    

    1.1 可迭代對象

    通過索引的方式進行迭代取值,實現簡單,但僅適用于序列類型:字符串,列表,元組。對于沒有索引的字典、集合等非序列類型,必須找到一種不依賴索引來進行迭代取值的方式,這就用到了迭代器。

    要想了解迭代器為何物,必須事先搞清楚一個很重要的概念:可迭代對象(Iterable)。從語法形式上講,內置有__iter__方法的對象都是可迭代對象,字符串、列表、元組、字典、集合、打開的文件都是可迭代對象:

    {'name':'egon'}.__iter__
    {7,8,9}.__iter__
    ……
    

    1.2 迭代器對象

    調用obj.iter()方法返回的結果就是一個迭代器對象(Iterator)。迭代器對象是內置有iternext方法的對象,打開的文件本身就是一個迭代器對象,執行迭代器對象.iter()方法得到的仍然是迭代器本身,而執行迭代器.next()方法就會計算出迭代器中的下一個值。 迭代器是Python提供的一種統一的、不依賴于索引的迭代取值方式,只要存在多個“值”,無論序列類型還是非序列類型都可以按照迭代器的方式取值

    >>> s={1,2,3} # 可迭代對象s
    >>> i=iter(s)  # 本質就是在調用s.__iter__(),返回s的迭代器對象i,
    >>> next(i) # 本質就是在調用i.__next__()
    1
    >>> next(i)
    2
    >>> next(i)
    3
    >>> next(i)  #拋出StopIteration的異常,代表無值可取,迭代結束
    

    二 for循環原理

    有了迭代器后,我們便可以不依賴索引迭代取值了,使用while循環的實現方式如下

    goods=['mac','lenovo','acer','dell','sony']
    i=iter(goods) #每次都需要重新獲取一個迭代器對象
    while True:
        try:
            print(next(i))
        except StopIteration: #捕捉異常終止循環
            break
    

    for循環又稱為迭代循環,in后可以跟任意可迭代對象,上述while循環可以簡寫為

    goods=['mac','lenovo','acer','dell','sony']
    for item in goods:   
        print(item)
    

    for 循環在工作時,首先會調用可迭代對象goods內置的iter方法拿到一個迭代器對象,然后再調用該迭代器對象的next方法將取到的值賦給item,執行循環體完成一次循環,周而復始,直到捕捉StopIteration異常,結束迭代。

    三 迭代器的優缺點

    基于索引的迭代取值,所有迭代的狀態都保存在了索引中,而基于迭代器實現迭代的方式不再需要索引,所有迭代的狀態就保存在迭代器中,然而這種處理方式優點與缺點并存:

    3.1 優點:

    1、為序列和非序列類型提供了一種統一的迭代取值方式。

    2、惰性計算:迭代器對象表示的是一個數據流,可以只在需要時才去調用next來計算出一個值,就迭代器本身來說,同一時刻在內存中只有一個值,因而可以存放無限大的數據流,而對于其他容器類型,如列表,需要把所有的元素都存放于內存中,受內存大小的限制,可以存放的值的個數是有限的。

    3.2 缺點:

    1、除非取盡,否則無法獲取迭代器的長度

    2、只能取下一個值,不能回到開始,更像是‘一次性的’,迭代器產生后的唯一目標就是重復執行next方法直到值取盡,否則就會停留在某個位置,等待下一次調用next;若是要再次迭代同個對象,你只能重新調用iter方法去創建一個新的迭代器對象,如果有兩個或者多個循環使用同一個迭代器,必然只會有一個循環能取到值。

    17、生成器

    一 生成器與yield

    若函數體包含yield關鍵字,再調用函數,并不會執行函數體代碼,得到的返回值即生成器對象

    >>> def my_range(start,stop,step=1):
    ...     print('start...')
    ...     while start < stop:
    ...         yield start
    ...         start+=step
    ...     print('end...')
    ... 
    >>> g=my_range(0,3)
    >>> g
    <generator object my_range at 0x104105678>
    
    版權聲明:本文為weixin_45523107原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/weixin_45523107/article/details/106806549

    智能推薦

    《python編程從入門到實踐》第八章函數_動手試一試(4)

    說在前面。 分享交流,樂于探討。《python編程從入門到實踐》自學。 8-9 魔術師:創建一個包含魔術師名字的列表,并將其傳遞給一個名為show_magicians()的函數,這個函數打印列表中每個魔術師的名字。 8-10 了不起的魔術師:在你為完成練習 8-9 而編寫的程序中,編寫一個名為make_great()的函數,對魔術師列表進行修改,在每個魔術師的名字中都加入字樣“the ...

    Python編程從入門到實踐的Web應用程序(一),Win10下親測可用

    Python編程從入門到實踐的Web應用程序(一),Win10下親測可用 1.安裝 vritualenv 首先安裝python物理環境Python3.6(django是基于Python3安裝的)。 Ctrl+R打開終端輸入命令: 再輸入命令: 2.建立和**虛擬環境 在自己的電腦上創建要開發項目的目錄,將其命名為learning_log,再在電腦終端切換到到該目錄下: 在該目錄下創建一個虛擬環境l...

    Nginx從入門到實踐(一)

    結合實踐、收集各種場景、常見問題,講解Nginx中最實用的Webserver場景,提供一套整體的搭建配置方式 Nginx中間件,不局限于業務邏輯,有效獨立于后臺開發框架(不論后端是Java開發、PHP開發、或者其他語言框架)都能做到平臺通用 不僅重實踐、也會結合原理(如:Http協議、操作系統),讓你理解背后的原理更有利于你解決實際問題(如:bug解決、二次開發等) 基礎篇 環境調試確認 關閉ip...

    第八天任務 (【基于Python編程從入門到實踐】第八章 函數 書本及動手試一試)

    【鬼知道我第八章拖了多少天… 第八章 :函數 (很重要的一章) 8.1 定義函數 打印問候語的一個簡單函數 8.1.1 向函數傳遞信息 調用時 無論傳入什么樣的名字 都會生成相應的輸出 8.1.2 實參和形參 形參: 函數完成其工作所需的一項信息 實參: 調用函數是傳遞給函數的信息 8.1 動手試一試 8-1 消息 - 8-2 喜歡的圖書 8.2 傳遞實參 向函數傳遞實參的方式有很多...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    猜你喜歡

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    精品国产乱码久久久久久蜜桃不卡