【學習筆記】python

基礎

  • print()每執行一次默認在最後加上了\n,也就是默認會換行;
  • break可以立即退出循環語句,包括else;pass是用來在循環或者判斷中進行佔位的(比如暫時沒想好功能該怎麼寫,就先佔位);
- == != 比較的是對象的值是否相等;
- is  is not 比較的是對象的id是否相等(也就是比較是否是同一個對象)

序列

序列就是用來保存一組有序的數據,所有的數據在序列中都有唯一的索引;

  • 可變序列:列表(list);
  • 不可變序列:字元串(str),元祖(tuple);
# 遍歷序列
for s in stus:
    print(s)     # 每執行一次將序列中的元素賦給變數s
    
range(3,0,-1) #[3,2,1],都是左閉右開;range就是生成一個自然數的序列;
for i in range(30):
    程式碼塊    #循環30次

可變對象

每個對象都保存了三個數據:

  • id(標識/地址)
  • type(類型)
  • value(值)

列表就是可變對象,可變對象是說其值(value)是可變的;

a[0] = 10
# 這個是在改對象,不會改變變數的指向,只是指向的變數的值變了;
a = [4,5,6]
# 這個是在改變數,會改變變數指向的對象;

列表

  • 列表就是存儲對象的對象;
#列表的切片;
stus[-1]  #從倒數第一個開始;
stus[0:2] #第一個元素和第二個元素;左閉右開;
stus[0:5:-1]  #[起始:結束:步長],if步長是負數表示從後開始;步長不能是0;
s.index['a',1,3]   #獲取a在列表中在[1,3)中首次出現的索引;
s.count['a']   #獲取a在列表中出現的次數;  這兩個都是方法,必須通過xxx.method()來調用;

# 列表的替換
# 在給切片進行賦值時,只能使用序列
stus[0:2] = [1,2,3] # 序列的個數可以和切片的個數不一致;
stus[::2] = [1,2]  #當設置了步長時,兩者個數必須相同;
  • 列表的方法
s.append(1)   # 向列表最後添加元素;
s.insert(2,'a') #向列表的2索引處插入a;
s.extend([1,2,3])  #參數必須是序列,擴展s序列;
s.pop(2)   #刪除索引為2的元素,有返回值;
s.remove(2) #刪除值為2的元素,if有多個值,只刪第一個;
s.clear()  #清空序列;
s.reverse() #反轉列表;
s.sort()  #對列表排序,默認是升序,可以傳遞參數reverse=true來變成降序;

元組(tuple)

  • 元祖就是不可變的列表
t = ()  # 創建空元組;
t = 1,2,3,4  # 當元組不為空時,括弧可以不寫;
t = 1,  # if元組不為空,至少要有一個逗號;
a,b,c,d = t # 元組的解包,這時候a=1,b=2;
# 所以if想要交換兩個元素,其實可以直接
a,b = b,a  # b,a就是一個元組,進行了解包;
# 在對一個元組進行解包時,變數的數量必須和元組中的元素的數量一致
# 也可以在變數前邊添加一個*,這樣變數將會獲取元組中所有剩餘的元素
a , b , *c = t # c = [3,4];
# 解包是對所有序列都可以的,不僅是元組;

字典(dict)

  • 列表存儲數據很好,但是查詢的性能很差;而字典從查詢效率很高;
  • 字典中每個對象都有一個名字(key),而每個對象就是value;這樣一個鍵值對稱為一項(item);
  • 字典的值可以是任意對象;字典的鍵可以是任意的不可變對象(int、str、bool、tuple …),但是一般我們都會使用str
d = {}   #空字典
d = {'name':a, 'age':18}
print(d['name]')   # a;注意name一定要加引號;
----------------------
# 使用 dict()函數來創建字典
# 每一個參數都是一個鍵值對,參數名就是鍵,參數名就是值(這種方式創建的字典,key都是字元串)
d = dict(name='孫悟空',age=18,gender='男') 
len(d)  #獲取字典中鍵值對的個數;
in/not in   #字典中是否有指定的鍵;
-----------------------
d['name']   #獲取字典的值;
d.get('hello',default)  # if hello不存在,會返回默認值;
-----------------------
d['name'] = 'sunwukong' # 修改字典的key-value
setdefault(key, default) 可以用來向字典中添加key-value
#   如果key已經存在於字典中,則返回key的值,不會對字典做任何操作
#   如果key不存在,則向字典中添加這個key,並設置value
d = {'a':1,'b':2,'c':3}
d2 = {'d':4,'e':5,'f':6, 'a':7}
d.update(d2)  #將d2中的鍵值對添加到d,if 有相同的key會進行替代;
-----------------------
del d['name']  # 刪除鍵值對;
result = d.pop('name') #孫悟空,返回value;
d.clear()  # 清空字典;
-----------------------
d1 = d2  # 兩個指向的是同一個對象,修改一個另一個也會變;
d1 = d2.copy # 這是複製品,是相互獨立;

注意,淺複製會簡單複製對象內部的值,如果值也是一個可變對象,這個可變對象不會被複制
d = {'a':{'name':'孫悟空','age':18},'b':2,'c':3}
d2 = d.copy()
d2['a']['name'] = '豬八戒'  # d和d2都會變成豬八戒;
-----------------------
for k in d.keys():
    print(d[k])    #通過keys()遍歷;
for v in d.values():
    print(v)       #通過values()遍歷:
for k,v in d.items():
    print(k, '=', v)  #items()會返回字典中所有的項;   
# 會返回一個序列,序列中包含雙值子序列,[('name',a),('age',18)],這時候賦值其實是解包;

集合(set)

  • 集合和列表非常相似
  • 不同點:
    1.集合中只能存儲不可變對象 ( s= {[1,2,3]},這就是錯誤的,因為列表是可變的)
    2.集合中存儲的對象是無序(不是按照元素的插入順序保存)
    3.集合中不能出現重複的元素
s = {1,2,3}   #創建集合;
s = set([1,2,1]) # set可以將列表和字典變為集合,字典轉的時候,只有鍵;
len(s)  # 元素數量;
in/not in 
-----------------------
s.add(4)  # 增加元素;
s1.upadte(s2) # 將s2中的元素添加到s1中;
-----------------------
s.pop() # 隨機刪除集合中的一個元素;
s.remove # 刪除集合中的指定元素;
s.clear()
-----------------------
s1 & s2 # 集合的交集;
s1 | s2 # 集合的並集;
s1 - s2 # 集合的差集;
s1 ^ s2 # 集合的異或集:獲取只在一個集合中出現的元素;
s1 <= s2 # s1 是否是s2的子集
s1 < s2 # s1 是否是s2的真子集

函數

  • 函數也是對象;函數可以用來保存一些可執行的程式碼,並且可以在需要時,對這些語句進行多次的調用
def fn(a,b):
    程式碼塊    #fn是函數對象,fn()調用函數;
# 函數在調用時,解析器不會檢查實參的類型;實參可以傳遞任意類型;比如列表甚至是函數都行(fn(fn1));
def fn1(*a,b):
    程式碼塊  #可變參數,會將所有的實參保存到一個元組中(裝包)
    # 可變參數不是必須寫在最後,但是注意,帶*的參數後的所有參數,必須以關鍵字參數的形式傳遞

# *形參不能接收關鍵字參數;
# **形參可以接收關鍵字參數,會將這些參數統一保存到一個字典中;**參數必須在所有參數的最後;
----------------------------
t = (1,2)
fn(*t)     
#傳遞實參時,可以在序列類型的參數前添加星號,這樣他會自動將序列中的元素依次作為參數傳遞(解包),序列的個數必須和參數個數相同;
# 同樣,也可以用**來對字典進行解包,字典的key和參數形參名一樣;
----------------------------
return 後面可以跟任意的值,甚至可以是函數;

# 文檔字元串
def fn(a:int,b:bool,c:str='hello') -> int:
    #這些裡面的類型沒有任何意義,僅僅是指示說明;
    ···
    可以寫一些說明文字
    ···
----------------------------
if 想在函數內部修改全局變數的值,要加關鍵字 global;
scope = locals()  # locals() 獲取當前作用域的命名空間;   
# 命名空間其實就是一個字典,是一個專門用來存儲變數的字典;所以scope是一個字典,這個字典就是當前作用域中所有的變數;   
scope['c'] = 1000  # 向字典中添加鍵值對就相當於在全局中創建了一個變數c;   
globals_scope = globals()  #globals()這個函數可以在任意位置獲取全局的命名空間;

高階函數

  • 將函數作為參數,或者將函數作為返回值的函數是高階函數;當將一個函數作為參數時,實際上是將指定的程式碼傳遞給了目標函數;
  • 這樣的會就可以訂製一些函數,然後將函數作為一種「規則」傳遞給目標函數,然後目標函數根據這種「規則」對參數(原料)做出相應的處理;

1.將函數作為參數傳遞:

filter()

filter()可以從序列中過濾出符合條件的元素,保存到一個新的序列中

  • 參數:
    1.函數,根據該函數來過濾序列(可迭代的結構)
    2.需要過濾的序列(可迭代的結構)
  • 返回值:
    過濾後的新序列(可迭代的結構)
匿名函數(lambda函數表達式)

有時候一個函數用一次就再也不用了,就可以用lambda表達式;匿名韓式一般都是作為參數使用的;
語法:lambda 參數列表 : 返回值

r = filter(lambda i : i > 5 , l)

2.將函數作為返回值

閉包
  • 將函數作為返回值返回,這就是一種閉包,通過閉包可以創建一些只有當前函數能訪問的變數,可以將一些私有的數據藏到閉包里;
形成閉包的條件:
1.函數嵌套;
2.將內部函數作為返回值返回;
3.內部函數必須要使用到外部函數的變數;
def fn():

    a = 10

    # 函數內部再定義一個函數
    def inner():
        print('我是fn2' , a)

    # 將內部函數 inner作為返回值返回   
    return inner

# r是一個函數,是調用fn()後返回的函數
# 這個函數實在fn()內部定義,並不是全局函數
# 所以這個函數總是能訪問到fn()函數內的變數
r = fn()
變數是裡面能看到外面的,但是外面看不到裡面的;這樣閉包就可以了;

裝飾器

在寫程式的時候,if我們想擴展一個函數,但是我們要盡量不去動原始的函數,而是要遵從開閉原則,即開放對程式的擴展,而關閉對程式的修改;

裝飾器其實就是一個函數,這個函數什麼功能呢?它接收一個舊函數作為參數,然後在裝飾器里對它進行裝飾包裝,然後以一個新函數作為返回;這樣就可以在不修改原始函數的情況下對函數進行擴展;

可以直接在舊函數上聲明@裝飾器函數
可以給函數多個裝飾器,裝飾的時候從內向外;

對象

  • 類用大駝峰來命名:MyClass; 類也是對象,類就是一個用來創建對象的對象,一切皆對象!

  • 如果是函數調用,則調用時傳幾個參數,就會有幾個實參;但是如果是方法調用,默認傳遞一個參數,這個實參是解析器自動傳的,所以方法中至少要定義一個形參(self),這個默認傳遞的實參其實就是調用方法的對象本身!if是p1調的,那第一個參數就是p1,if是p2調的,那第一個參數就是p2;所以我們把這個參數命名為self,這個self就是指的是誰調用這個方法了,這個誰就是self;self就是當前對象

  • 當我們調用一個對象的屬性時,解析器會先在當前對象中尋找是否含有該屬性,
    如果有,則直接返回當前的對象的屬性值,
    如果沒有,則去當前對象的類對象中去尋找,如果有則返回類對象的屬性值,如果類對象中依然沒有,則報錯!

  • 類對象和實例對象中都可以保存屬性(方法)
    – 如果這個屬性(方法)是所有的實例共享的,則應該將其保存到類對象中
    – 如果這個屬性(方法)是某個實例獨有,則應該保存到實例對象中

  • 在python中,xxx 稱為特殊方法,或者稱為魔術方法;這種方法不需要我們自己調用,會在一些時刻自動調用;

p1 = Person()的運行流程
  1.創建一個變數p1
  2.在記憶體中創建一個新變數,這個變數的類型是Person
  3.__init__(self)方法執行
  4.將對象的id(地址)賦值給變數
  • 使用__開頭的屬性,實際上依然可以在外部訪問,所以這種方式我們一般不用,(因為其實就是python將__name自己改名成了_類名__屬性)
    一般我們會將一些私有屬性(不希望被外部訪問的屬性)以_開頭,一般情況下,使用_開頭的屬性都是私有屬性,沒有特殊需要不要修改私有屬性

  • 繼承,在類的括弧里寫父類,if沒寫,就是繼承自object.

issubclass(a,b)  檢查a是否是b的子類
isinstance(a,A)  檢查a是否是A的實例

父類中所有的方法和屬性都可以被繼承,包括特殊方法__init__,可以重寫初始化方法,if想要增添多個屬性,那可以調用super()來調用當前類的父類,然後再增添自己特有的就行了


# 父類中的所有方法都會被子類繼承,包括特殊方法,也可以重寫特殊方法
class Dog(Animal):

    def __init__(self,name,age):
        # 希望可以直接調用父類的__init__來初始化父類中定義的屬性
        # super() 可以用來獲取當前類的父類,
        #   並且通過super()返回對象調用父類方法時,不需要傳遞self
        super().__init__(name)
        self._age = age

python中可以多重繼承:

class C(A,B):
    pass  
# C繼承自兩個父類,AB,ifA,B中有同名的方法,會先調用A的,
  • 多態:在函數的參數中,if要傳入一個對象,其實不關注對象是什麼類型的,只關注這個對象是否有某些屬性和方法,只要有某些屬性和方法,那就可以傳遞進去,這樣保證了程式的靈活性;
  • 類屬性可以通過A.屬性和a.屬性訪問,實例屬性只能通過a.屬性訪問,類.屬性無法訪問;在類中以self作為第一個參數的方法是實例方法,實例方法可以通過類和實例調用,通過實例調用時,會自動將當前對象傳遞給self,但是通過類調用時,不會自動傳,所有,A.fangfa(a) = a.fangfa()
    # 類方法    
    # 在類內部使用 @classmethod 來修飾的方法屬於類方法
    # 類方法的第一個參數是cls,也會被自動傳遞,cls就是當前的類對象
    #   類方法和實例方法的區別,實例方法的第一個參數是self,而類方法的第一個參數是cls
    #   類方法可以通過類去調用,也可以通過實例調用,沒有區別
    @classmethod
    def test_2(cls):
        print('這是test_2方法,他是一個類方法~~~ ',cls)
        print(cls.count)
        
    # 靜態方法
    # 在類中使用 @staticmethod 來修飾的方法屬於靜態方法  
    # 靜態方法不需要指定任何的默認參數,靜態方法可以通過類和實例去調用  
    # 靜態方法,基本上是一個和當前類無關的方法,它只是一個保存到當前類中的函數
    # 靜態方法一般都是一些工具方法,和當前類無關
    @staticmethod
    def test_3():
        print('test_3執行了~~~')
  • 特殊方法也叫魔法方法,一般不需要手動調用,在一些特殊的時候會自動執行,以__開始,比如__init__在對象創建時調用,__del__會在結束時調用,進行垃圾回收

  • 一個py文件就是一個模組,模組也是一個對象!在每一個模組內部都有一個__name__屬性,通過這個屬性可以獲取到模組的名字__name__屬性值為 __main__的模組是主模組,一個程式中只會有一個主模組,主模組就是我們直接通過 python 執行的模組

  • 一個文件夾就是一個包,一個包里可以有多個模組,包中必須含有一個__init__.py文件,一個包里會有__pycache__文件夾,這是模組的快取文件, py程式碼在執行前,需要被解析器先轉換為機器碼,然後再執行,所以我們在使用模組(包)時,也需要將模組的程式碼先轉換為機器碼然後再交由電腦執行
    而為了提高程式運行的性能,python會在編譯過一次以後,將程式碼保存到一個快取文件中,這樣在下次載入這個模組(包)時,就可以不再重新編譯而是直接載入快取中編譯好的程式碼即可

異常

    try:
        程式碼塊(可能出現錯誤的語句)
    except 異常類型 as 異常名:
        程式碼塊(出現錯誤以後的處理方式)
    else:
        程式碼塊(沒出錯時要執行的語句))
    finally:
        程式碼塊(該程式碼塊總會執行) 
  • 異常也是一個對象,比如 : ZeroDivisionError類的對象專門用來表示除0的異常
    NameError類的對象專門用來處理變數錯誤的異常
  • Exception 是所有異常類的父類,所以如果except後跟的是Exception,他也會捕獲到所有的異常;可以在異常類後邊跟著一個 as xx 此時xx就是異常對象
  • 可以使用 raise 語句來拋出異常,
    raise語句後需要跟一個異常類 或 異常的實例

文件

  • 打開文件,opne(file_name)會返回一個對象,if當前文件和目標文件在同一級目錄下,則直接寫名字就可以,其他的時候就必須用路徑了。可以使用..來向上返回一級目錄
  • read()用來讀,會將內容保存為一個字元串返回;
  • 關閉文件;對象.close(),這時候其實經常是忘記的,所以有了with..as語句
with open(file_name) as file_obj:   #這其實就是和file_obj = open(file_name)一樣,當出去這個with後,會自動關閉;
#打開file_name並返回一個變數file_obj
    file_obj.read()   
with open(file_name , 'x' , encoding='utf-8') as file_obj:
# 使用open()打開文件時必須要指定打開文件所要做的操作(讀、寫、追加)
# 如果不指定操作類型,則默認是 讀取文件 , 而讀取文件時是不能向文件中寫入的
# r 表示只讀的
# w 表示是可寫的,使用w來寫入文件時,如果文件不存在會創建文件,如果文件存在則會截斷文件
#   截斷文件指刪除原來文件中的所有內容
# a 表示追加內容,如果文件不存在會創建文件,如果文件存在則會向文件中追加內容
# x 用來新建文件,如果文件不存在則創建,存在則報錯
# + 為操作符增加功能
#   r+ 即可讀又可寫,文件不存在會報錯
# seek() 可以修改當前讀取的位置
    seek()需要兩個參數
    第一個 是要切換到的位置
         第二個 計算位置方式
            可選值:
             0 從頭計算,默認值
             1 從當前位置計算
             2 從最後位置開始計算
# tell() 方法用來查看當前讀取的位置