Python進階:全面解讀高級特性之切片!

来源:https://www.cnblogs.com/pythonista/archive/2018/12/31/10201184.html
-Advertisement-
Play Games

導讀:切片系列文章連續寫了三篇,本文是對它們做的彙總。為什麼要把序列文章合併呢?在此說明一下,本文絕不是簡單地將它們做了合併,主要是修正了一些嚴重的錯誤(如自定義序列切片的部分),還對行文結構與章節銜接做了大量改動,如此一來,本文結構的完整性與內容的質量都得到了很好的保證。 眾所周知,我們可以通過索 ...


導讀:切片系列文章連續寫了三篇,本文是對它們做的彙總。為什麼要把序列文章合併呢?在此說明一下,本文絕不是簡單地將它們做了合併,主要是修正了一些嚴重的錯誤(如自定義序列切片的部分),還對行文結構與章節銜接做了大量改動,如此一來,本文結構的完整性與內容的質量都得到了很好的保證。

眾所周知,我們可以通過索引值(或稱下標)來查找序列類型(如字元串、列表、元組…)中的單個元素,那麼,如果要獲取一個索引區間的元素該怎麼辦呢?

切片(slice)就是一種截取索引片段的技術,藉助切片技術,我們可以十分靈活地處理序列類型的對象。通常來說,切片的作用就是截取序列對象,然而,對於非序列對象,我們是否有辦法做到切片操作呢?在使用切片的過程中,有什麼要點值得重視,又有什麼底層原理值得關註呢?本文將主要跟大家一起來探討這些內容,希望我能與你共同學習進步。

1、切片的基礎用法

列表是 Python 中極為基礎且重要的一種數據結構,也是最能發揮切片的用處的一種數據結構,所以在前兩節,我將以列表為例介紹切片的一些常見用法。

首先是切片的書寫形式:[i : i+n : m] ;其中,i 是切片的起始索引值,為列表首位時可省略;i+n 是切片的結束位置,為列表末位時可省略;m 可以不提供,預設值是1,不允許為0 ,當m為負數時,列表翻轉。註意:這些值都可以大於列表長度,不會報越界。

切片的基本含義是:從序列的第i位索引起,向右取到後n位元素為止,按m間隔過濾

li = [145679111416]

# 以下寫法都可以表示整個列表,其中 X >= len(li)
li[0:X] == li[0:] == li[:X] == li[:
== li[::] == li[-X:X] == li[-X:]

li[1:5] == [4,5,6,7# 從1起,取5-1位元素
li[1:5:2] == [4,6# 從1起,取5-1位元素,按2間隔過濾
li[-1:] == [16# 取倒數第一個元素
li[-4:-2] == [911# 從倒數第四起,取-2-(-4)=2位元素
li[:-2] == li[-len(li):-2
== [1,4,5,6,7,9,11# 從頭開始,取-2-(-len(li))=7位元素

# 步長為負數時,列表先翻轉,再截取
li[::-1] == [16,14,11,9,7,6,5,4,1# 翻轉整個列表
li[::-2] == [16,11,7,5,1# 翻轉整個列表,再按2間隔過濾
li[:-5:-1] == [16,14,11,9# 翻轉整個列表,取-5-(-len(li))=4位元素
li[:-5:-3] == [16,9# 翻轉整個列表,取-5-(-len(li))=4位元素,再按3間隔過濾

# 切片的步長不可以為0
li[::0]  # 報錯(ValueError: slice step cannot be zero)

上述的某些例子對於初學者(甚至很多老手)來說,可能還不好理解,但是它們都離不開切片的基本語法,所以為方便起見,我將它們也歸入基礎用法中。

對於這些樣例,我個人總結出兩條經驗:

(1)牢牢記住公式[i : i+n : m] ,當出現預設值時,通過想象把公式補全;

(2)索引為負且步長為正時,按倒數計算索引位置;索引為負且步長為負時,先翻轉列表,再按倒數計算索引位置。

2、切片的高級用法

一般而言,切片操作的返回結果是一個新的獨立的序列(PS:也有例外,參見《Python是否支持複製字元串呢?》)。以列表為例,列表切片後得到的還是一個列表,占用新的記憶體地址。

當取出切片的結果時,它是一個獨立對象,因此,可以將其用於賦值操作,也可以用於其它傳遞值的場景。但是,切片只是淺拷貝 ,它拷貝的是原列表中元素的引用,所以,當存在變長對象的元素時,新列表將受制於原列表。

li = [1, 2, 3, 4]
ls = li[::]

li == ls # True
id(li) == id(ls) # False
li.append(li[2:4]) # [1, 2, 3, 4, [3, 4]]
ls.extend(ls[2:4]) # [1, 2, 3, 4, 3, 4]

# 下例等價於判斷li長度是否大於8
if(li[8:]):
    print("not empty")
else:
    print("empty")

# 切片列表受制於原列表
lo = [1,[1,1],2,3]
lp = lo[:2] # [1, [1, 1]]
lo[1].append(1) # [1, [1, 1, 1], 2, 3]
lp # [1, [1, 1, 1]]

由於可見,將切片結果取出,它可以作為獨立對象使用,但是也要註意,是否取出了變長對象的元素。

切片既可以作為獨立對象被“取出”原序列,也可以留在原序列,作為一種占位符使用。

不久前,我介紹了幾種拼接字元串的方法(鏈接見文末),其中三種格式化類的拼接方法(即 %、format()、template)就是使用了占位符的思想。對於列表來說,使用切片作為占位符,同樣能夠實現拼接列表的效果。特別需要註意的是,給切片賦值的必須是可迭代對象。

li = [1, 2, 3, 4]

# 在頭部拼接
li[:0] = [0] # [0, 1, 2, 3, 4]
# 在末尾拼接
li[len(li):] = [5,7] # [0, 1, 2, 3, 4, 5, 7]
# 在中部拼接
li[6:6] = [6] # [0, 1, 2, 3, 4, 5, 6, 7]

# 給切片賦值的必須是可迭代對象
li[-1:-1] = 6 # (報錯,TypeError: can only assign an iterable)
li[:0] = (9,) #  [9, 0, 1, 2, 3, 4, 5, 6, 7]
li[:0] = range(3) #  [0, 1, 2, 9, 0, 1, 2, 3, 4, 5, 6, 7]

上述例子中,若將切片作為獨立對象取出,那你會發現它們都是空列表,即 li[:0]==li[len(li):]==li[6:6]==[] ,我將這種占位符稱為“純占位符”,對純占位符賦值,並不會破壞原有的元素,只會在特定的索引位置中拼接進新的元素。刪除純占位符時,也不會影響列表中的元素。

與“純占位符”相對應,“非純占位符”的切片是非空列表,對它進行操作(賦值與刪除),將會影響原始列表。如果說純占位符可以實現列表的拼接,那麼,非純占位符可以實現列表的替換。

li = [1234]

# 不同位置的替換
li[:3] = [7,8,9# [7, 8, 9, 4]
li[3:] = [5,6,7# [7, 8, 9, 5, 6, 7]
li[2:4] = ['a','b'# [7, 8, 'a', 'b', 6, 7]

# 非等長替換
li[2:4] = [1,2,3,4# [7, 8, 1, 2, 3, 4, 6, 7]
li[2:6] = ['a']  # [7, 8, 'a', 6, 7]

# 刪除元素
del li[2:3# [7, 8, 6, 7]

切片占位符可以帶步長,從而實現連續跨越性的替換或刪除效果。需要註意的是,這種用法只支持等長替換。

li = [123456]

li[::2] = ['a','b','c'] # ['a'2'b'4'c'6]
li[::2] = [0]*3 # [020406]
li[::2] = ['w'] # 報錯,attempt to assign sequence of size 1 to extended slice of size 3

del li[::2] # [246]

3、自定義對象實現切片功能

切片是 Python 中最迷人最強大最 Amazing 的語言特性(幾乎沒有之一),以上兩小節雖然介紹了切片的基礎用法與高級用法,但這些還不足以充分地展露切片的魅力,所以,在接下來的兩章節中,我們將聚焦於它的更高級用法。

前兩節內容都是基於原生的序列類型(如字元串、列表、元組……),那麼,我們是否可以定義自己的序列類型並讓它支持切片語法呢?更進一步,我們是否可以自定義其它對象(如字典)並讓它支持切片呢?

3.1、魔術方法:`getitem()`

想要使自定義對象支持切片語法並不難,只需要在定義類的時候給它實現魔術方法 __getitem__() 即可。所以,這裡就先介紹一下這個方法。

語法: object.__getitem__(self, key)

官方文檔釋義:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the __getitem__() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.

概括翻譯一下:__getitem__() 方法用於返回參數 key 所對應的值,這個 key 可以是整型數值和切片對象,並且支持負數索引;如果 key 不是以上兩種類型,就會拋 TypeError;如果索引越界,會拋 IndexError ;如果定義的是映射類型,當 key 參數不是其對象的鍵值時,則會拋 KeyError 。

3.2、自定義序列實現切片功能

接下來,我們定義一個簡單的 MyList ,並給它加上切片功能。(PS:僅作演示,不保證其它功能的完備性)。

import numbers

class MyList():
    def __init__(self, anylist):
        self.data = anylist
    def __len__(self):
        return len(self.data)
    def __getitem__(self, index):
        print("key is : " + str(index))
        cls = type(self)
        if isinstance(index, slice):
            print("data is : " + str(self.data[index]))
            return cls(self.data[index])
        elif isinstance(index, numbers.Integral):
            return self.data[index]
        else:
            msg = "{cls.__name__} indices must be integers"
            raise TypeError(msg.format(cls=cls))

l = MyList(["My""name""is""Python貓"])

### 輸出結果:
key is : 3
Python貓
key is : slice(None2None)
data is : ['My''name']
<__main__.MyList object at 0x0000019CD83A7A90>
key is : hi
Traceback (most recent call last):
...
TypeError: MyList indices must be integers or slices

從輸出結果來看,自定義的 MyList 既支持按索引查找,也支持切片操作,這正是我們的目的。

3.3、自定義字典實現切片功能

切片是序列類型的特性,所以在上例中,我們不需要寫切片的具體實現邏輯。但是,對於其它非序列類型的自定義對象,就得自己實現切片邏輯。以自定義字典為例(PS:僅作演示,不保證其它功能的完備性):

class MyDict():
    def __init__(self):
        self.data = {}
    def __len__(self):
        return len(self.data)
    def append(self, item):
        self.data[len(self)] = item
    def __getitem__(self, key):
        if isinstance(key, int):
            return self.data[key]
        if isinstance(key, slice):
            slicedkeys = list(self.data.keys())[key]
            return {k: self.data[k] for k in slicedkeys}
        else:
            raise TypeError

d = MyDict()
d.append("My")
d.append("name")
d.append("is")
d.append("Python貓")
print(d[2])
print(d[:2])
print(d[-4:-2])
print(d['hi'])

### 輸出結果:
is
{0'My'1'name'}
{0'My'1'name'}
Traceback (most recent call last):
...
TypeError

上例的關鍵點在於將字典的鍵值取出,並對鍵值的列表做切片處理,其妙處在於,不用擔心索引越界和負數索引,將字典切片轉換成了字典鍵值的切片,最終實現目的。

4、迭代器實現切片功能

好了,介紹完一般的自定義對象如何實現切片功能,這裡將迎來另一類非同一般的對象。

迭代器是 Python 中獨特的一種高級對象,它本身不具備切片功能,然而若能將它用於切片,這便仿佛是錦上添花,能達到如虎添翼的效果。所以,本節將隆重地介紹迭代器如何實現切片功能。

4.1、迭代與迭代器

首先,有幾個基本概念要澄清:迭代、可迭代對象、迭代器。

迭代 是一種遍歷容器類型對象(例如字元串、列表、字典等等)的方式,例如,我們說迭代一個字元串“abc”,指的就是從左往右依次地、逐個地取出它的全部字元的過程。(PS:漢語中迭代一詞有迴圈反覆、層層遞進的意思,但 Python 中此詞要理解成單向水平線性 的,如果你不熟悉它,我建議直接將其理解為遍歷。)

那麼,怎麼寫出迭代操作的指令呢?最通用的書寫語法就是 for 迴圈。

# for迴圈實現迭代過程
for char in "abc":
    print(char, end=" ")
# 輸出結果:a b c

for 迴圈可以實現迭代的過程,但是,並非所有對象都可以用於 for 迴圈,例如,上例中若將字元串“abc”換成任意整型數字,則會報錯: 'int' object is not iterable .

這句報錯中的單詞“iterable”指的是“可迭代的”,即 int 類型不是可迭代的。而字元串(string)類型是可迭代的,同樣地,列表、元組、字典等類型,都是可迭代的。

那怎麼判斷一個對象是否可迭代呢?為什麼它們是可迭代的呢?怎麼讓一個對象可迭代呢?

要使一個對象可迭代,就要實現可迭代協議,即需要實現__iter__() 魔術方法,換言之,只要實現了這個魔術方法的對象都是可迭代對象。

那怎麼判斷一個對象是否實現了這個方法呢?除了上述的 for 迴圈外,我還知道四種方法:

# 方法1:dir()查看__iter__
dir(2
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、對象構造函數 設置節點與人名 2、生成隨機對象 3、開始事件 4、結束事件 代碼下載地址:https://pan.baidu.com/s/1DgOWML9OoOoA2fiddeYCTQ ...
  • Web前端開發規範手冊(適合新手入門)持續更新,歡迎閱讀,小白自學,一起共勉!!!加油!! ...
  • 1.transform瀏覽器支持情況 也就是說目前不考慮老瀏覽器的話是不用加首碼的,感謝菜鳥教程:https://www.runoob.com/cssref/css3-pr-transform.html transform預設值none;就是不轉換,不繼承,js中對其更改示例:object.styl ...
  • 從事這個行業轉眼已經6年了,從當初剛畢業的在北京朝八晚十,從二環到五環,仍每天精力充沛的小憤青;再到深圳一點一滴的辛勤在軟體行業的耕種,從當初單體應用架構到現在微服務架構的經歷,回想起來自己的收穫倒是不少。人生也許算是比較平淡,運氣到也還算不錯,做過的項目剛好讓我在這些方面能有不錯的認知和自我學習的 ...
  • 迭代器模式在 .Net 中使用很廣泛,其迴圈遍歷對於的集合已經實現了迭代器模式 介紹 迭代器模式屬於行為型模式,它通過提供一種方法來順序訪問集合對象中的每個元素,而又不用暴露其內部表示。 類圖描述 代碼實現 1、創建介面 2、創建可遍歷的實體類 C public class NameReposito ...
  • 作為架構師,首先要明確架構師的責任,要不然會再多的技術也是枉然。 簡單的說,帶領方向和難點攻剋。 帶領方向是指架構師應不斷地多讀書,多學習,跟隨最新技術,不斷地升華自己,並不停的為團隊傳輸最新知識,讓整個團隊不斷地進步。 難點攻剋是指架構師要有逢山開路,遇水搭橋的能力。當團隊遇到開發難題的時候,架構 ...
  • 假設文件名為:loga.txt 內容為:你說什麼呢 1 -- open() 打開文件 參數1: 要打開的文件路徑 + 文件名 參數2: 打開方式 r 只讀模式,文本必須存在 r+ 讀寫模式,文件必須存在( 常用這種方式操作文件 ) w 只寫模式,不能調用read()進行讀操作,如果打開一個已存在的文 ...
  • 題意 "題目鏈接" Sol 枚舉第二個球放的位置,用首碼和推一波之後發現可以斜率優化 cpp // luogu judger enable o2 include define Pair pair define MP(x, y) make_pair(x, y) define fi first defi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...