首頁 後端開發 Python教學 Python容器使用的5個技巧和2個誤區

Python容器使用的5個技巧和2個誤區

Sep 24, 2019 pm 05:51 PM
python 容器 技巧 迷思

Python容器使用的5個技巧和2個誤區

Python容器所使用的5個技巧和2個誤解

「容器」這兩個字很少被 Python 技術文章提起。一看到“容器”,大家想到的多是那頭藍色小鯨魚:Docker,但這篇文章和它沒有任何關係。本文裡的容器,是 Python 中的一個抽象概念,是專門用來裝其他物件的資料型態的統稱。

在 Python 中,有四個類別最常見的內建容器類型: 列表(list)、 元組(tuple)、 字典(dict)、 集合(set)。透過單獨或是組合使用它們,可以有效率的完成很多事情。

Python 語言本身的內部實作細節也與這些容器類型息息相關。例如 Python 的類別實例屬性、全域變數 globals() 等就都是透過字典型別來儲存的。

在這篇文章裡,我首先會從容器類型的定義出發,試著總結出一些日常編碼的最佳實踐。之後再圍繞著各個容器類型提供的特殊機能,分享一些編程的小技巧。

當我們談論容器時,我們在談論什麼?

我在前面給了「容器」一個簡單的定義:專門用來裝其他物件的就是容器。但這個定義太廣泛了,無法對我們的日常程式產生什麼指導價值。要真正掌握 Python 裡的容器,需要分別從兩個層面著手:

    ·底層實作:內建容器型別使用了什麼資料結構?某項操作如何運作?

    ·高層抽象:是什麼決定了某個物件是不是容器?哪些行為定義了容器?

下面,讓我們一起站在這兩個不同的層面上,重新認識容器。

底層看容器

Python 是一門高階程式語言,它所提供的內建容器類型,都是經過高度封裝和抽象化後的結果。和「鍊錶」、「紅黑樹」、「哈希表」這些名字相比,所有Python 內建類型的名字,都只描述了這個類型的功能特點,其他人完全沒辦法只透過這些名字了解它們的哪怕一丁點內部細節。

這是 Python 程式語言的優點之一。相較於 C 語言這類更接近電腦底層的程式語言,Python 重新設計並實現了對程式設計者更友善的內建容器類型,屏蔽掉了記憶體管理等額外工作。為我們提供了更好的開發體驗。

但如果這是 Python 語言的優點的話,為什麼我們還要費勁去了解容器類型的實作細節呢?答案是:注意細節可以幫助我們寫出更快的程式碼。

寫入更快的程式碼

1. 避免頻繁擴充清單/建立新清單

所有的內建容器類型都不限制容量。如果你願意,你可以把遞增的數字不斷塞進一個空列表,最後撐爆整台機器的記憶體。

在 Python 語言的實作細節裡,列表的記憶體是按需分配的[註1],當某個清單目前擁有的記憶體不夠時,便會觸發記憶體擴充邏輯。而分配記憶體是一項昂貴的操作。雖然大部分情況下,它不會對你的程式效能產生什麼嚴重的影響。但是當你處理的資料量特別大時,很容易因為記憶體分配拖累整個程式的效能。

還好,Python 早就意識到了這個問題,並提供了官方的問題解決指引,那就是:「變懶」。

如何解釋「變懶」? range() 函數的演化就是一個非常好的例子。

在 Python 2 中,如果你呼叫 range(100000000),需要等待好幾秒鐘才能拿到結果,因為它需要返回一個巨大的列表,花費了非常多的時間在記憶體分配與計算上。但在 Python 3 中,同樣的呼叫馬上就能拿到結果。因為函數返回的不再是列表,而是類型為 range 的懶惰對象,只有在你迭代它、或對它進行切片時,它才會返回真正的數字給你。

所以說,為了提高效能,內建函數 range 「變懶」了。而為了避免過於頻繁的記憶體分配,在日常編碼中,我們的函數同樣也需要變懶,這包括:

·更多的使用yield 關鍵字,傳回生成器物件

·盡量使用生成器運算式替代清單推導式運算式

    ·產生器運算式: (iforinrange(100)) 

    ·列表推導表達式: [iforinrange(100)]

·盡量使用模組提供的懶惰物件:

·使用re.finditer 取代re.findall

    ·直接使用可迭代的檔案物件: forlineinfp,而不是forlineinfp.readlines()

2. 在清單頭部操作多的場景使用deque 模組

#

清單是基於數組結構(Array)實現的,當你在列表的頭部插入新成員( list.insert(0,item))時,它後面的所有其他成員都需要被移動,操作的時間複雜度是O(n)。這導致在清單的頭部插入成員遠比在尾部追加( list.append(item) 時間複雜度為 O(1))要慢。

如果你的程式碼需要執行很多次這類操作,請考慮使用 collections.deque 類型來取代清單。因為 deque 是基於雙端佇列實現的,無論是在頭部或尾部追加元素,時間複雜度都是 O(1)。

3. 使用集合/字典來判斷成員是否存在

當你需要判斷成員是否存在於某個容器時,用集合比列表更合適。因為 itemin[...] 操作的時間複雜度是 O(n),而 itemin{...} 的時間複雜度是 O(1)。這是因為字典與集合都是基於哈希表(Hash Table)資料結構實現的。

# 这个例子不是特别恰当,因为当目标集合特别小时,使用集合还是列表对效率的影响微乎其微
# 但这不是重点 :)
VALID_NAMES = ["piglei", "raymond", "bojack", "caroline"]
# 转换为集合类型专门用于成员判断
VALID_NAMES_SET = set(VALID_NAMES)
def validate_name(name):
    if name not in VALID_NAMES_SET:
        # 此处使用了 Python 3.6 添加的 f-strings 特性
        raise ValueError(f"{name} is not a valid name!")
登入後複製

Hint: 強烈建議閱讀 TimeComplexity - Python Wiki,以了解更多關於常見容器類型的時間複雜度相關內容。

如果你對字典的實作細節感興趣,也強烈建議觀看Raymond Hettinger 的演講Modern Dictionaries(YouTube)

相關推薦:《Python入門教學

高層看容器

Python 是一門「鴨子型」語言:「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。」所以,當我們說某個物件是什麼類型時,在根本上其實指的是:這個物件滿足了該類型的特定介面規範,可以被當成這個類型來使用。而對於所有內建容器類型來說,也是如此。

開啟位於 collections 模組下的 abc(「抽象類別 Abstract Base Classes」的縮寫)子模組,可以找到所有與容器相關的介面(抽象類別)[註2]定義。讓我們分別看看那些內建容器類型都滿足了什麼介面:

·清單(list):滿足Iterable、 Sequence、 MutableSequence 等介面

·元組(tuple):滿足Iterable、 Sequence

·字典(dict):滿足Iterable、 Mapping、 MutableMapping [註3]

·集合(set):滿足Iterable、 Set、 MutableSet [註4]

#每個內建容器類型,其實就是滿足了多個介面定義的組合實體。例如所有的容器類型都滿足 「可被迭代的」(Iterable) 這個接口,這意味著它們都是「可被迭代」的。但是反過來,不是所有「可被迭代」的物件都是容器。就像字串雖然可以被迭代,但我們通常不會把它當作「容器」來看待。

了解這個事實後,我們將在 Python 裡重新認識物件導向程式設計中最重要的原則之一:以介面而非具體實作來程式設計。

讓我們透過一個例子,看看如何理解 Python 裡的「面向介面程式設計」。

寫擴展性更好的程式碼

某日,我們接到一個需求:有一個列表,裡面裝著很多用戶評論,為了在頁面正常展示,需要將所有超過一定長度的評論以省略號取代。

這個需求很好做,很快我們就寫出了第一個版本的程式碼:

# 注:为了加强示例代码的说明性,本文中的部分代码片段使用了Python 3.5
# 版本添加的 Type Hinting 特性
 
def add_ellipsis(comments: typing.List[str], max_length: int = 12):
    """如果评论列表里的内容超过 max_length,剩下的字符用省略号代替
    """
    index = 0
    for comment in comments:
        comment = comment.strip()
        if len(comment) > max_length:
            comments[index] = comment[:max_length] + '...'
        index += 1
    return comments
comments = [
    "Implementation note",
    "Changed",
    "ABC for generator",
]
print("\n".join(add_ellipsis(comments)))
# OUTPUT:
# Implementati...
# Changed
# ABC for gene...
登入後複製

上面的程式碼裡, add_ellipsis 函數接收一個清單作為參數,然後遍歷它,替換掉需要修改的成員。這一切看起來很合理,因為我們接到的最原始需求就是:「有一個 列表,裡面...」。但如果有一天,我們拿到的評論不再是被繼續裝在清單裡,而是在不可變的元組裡呢?

那樣的話,現有的函式設計就會逼迫我們寫出 add_ellipsis(list(comments)) 這種即慢又難看的程式碼了。

面向容器介面程式設計

我們需要改進函數來避免這個問題。因為 add_ellipsis 函數強烈依賴了列表類型,所以當參數類型變成元組時,現在的函數就不再適用了(原因:給 comments[index] 賦值的地方會拋出 TypeError 例外)。如何改善這部分的設計?秘訣就是:讓函數依賴「可迭代物件」這個抽象概念,而非實體列表類型。

使用生成器特性,函數可以被改成這樣:

def add_ellipsis_gen(comments: typing.Iterable[str], max_length: int = 12):
    """如果可迭代评论里的内容超过 max_length,剩下的字符用省略号代替
    """
    for comment in comments:
        comment = comment.strip()
        if len(comment) > max_length:
            yield comment[:max_length] + '...'
        else:
            yield comment
print("\n".join(add_ellipsis_gen(comments)))
登入後複製

在新函數裡,我們將依賴的參數類型從列表改成了可迭代的抽象類別。這樣做有很多好處,一個最明顯的就是:無論評論是來自列表、元組或是某個文件,新函數都可以輕鬆滿足:

# 处理放在元组里的评论
comments = ("Implementation note", "Changed", "ABC for generator")
print("\n".join(add_ellipsis_gen(comments)))
# 处理放在文件里的评论
with open("comments") as fp:
    for comment in add_ellipsis_gen(fp):
        print(comment)
登入後複製

將依賴由某個具體的容器類型改為抽象介面後,函數的適用面變得更廣了。除此之外,新函數在執行效率等方面也都更有優勢。現在讓我們再回到之前的問題。從高層來看,什麼定義了容器?

答案是:各个容器类型实现的接口协议定义了容器。不同的容器类型在我们的眼里,应该是 是否可以迭代、 是否可以修改、 有没有长度 等各种特性的组合。我们需要在编写相关代码时,更多的关注容器的抽象属性,而非容器类型本身,这样可以帮助我们写出更优雅、扩展性更好的代码。

Hint:在 itertools 内置模块里可以找到更多关于处理可迭代对象的宝藏。

常用技巧

1. 使用元组改善分支代码

有时,我们的代码里会出现超过三个分支的 if/else 。就像下面这样:

import time
 
def from_now(ts):
    """接收一个过去的时间戳,返回距离当前时间的相对时间文字描述
    """
    now = time.time()
    seconds_delta = int(now - ts)
    if seconds_delta < 1:
        return "less than 1 second ago"
    elif seconds_delta < 60:
        return "{} seconds ago".format(seconds_delta)
    elif seconds_delta < 3600:
        return "{} minutes ago".format(seconds_delta // 60)
    elif seconds_delta < 3600 * 24:
        return "{} hours ago".format(seconds_delta // 3600)
    else:
        return "{} days ago".format(seconds_delta // (3600 * 24))
now = time.time()
print(from_now(now))
print(from_now(now - 24))
print(from_now(now - 600))
print(from_now(now - 7500))
print(from_now(now - 87500))
# OUTPUT:
# less than 1 second ago
# 24 seconds ago
# 10 minutes ago
# 2 hours ago
# 1 days ago
登入後複製

上面这个函数挑不出太多毛病,很多很多人都会写出类似的代码。但是,如果你仔细观察它,可以在分支代码部分找到一些明显的“边界”。比如,当函数判断某个时间是否应该用“秒数”展示时,用到了 60。而判断是否应该用分钟时,用到了 3600。

从边界提炼规律是优化这段代码的关键。如果我们将所有的这些边界放在一个有序元组中,然后配合二分查找模块 bisect。整个函数的控制流就能被大大简化:

import bisect
# BREAKPOINTS 必须是已经排好序的,不然无法进行二分查找
BREAKPOINTS = (1, 60, 3600, 3600 * 24)
TMPLS = (
    # unit, template
    (1, "less than 1 second ago"),
    (1, "{units} seconds ago"),
    (60, "{units} minutes ago"),
    (3600, "{units} hours ago"),
    (3600 * 24, "{units} days ago"),
)
def from_now(ts):
    """接收一个过去的时间戳,返回距离当前时间的相对时间文字描述
    """
    seconds_delta = int(time.time() - ts)
    unit, tmpl = TMPLS[bisect.bisect(BREAKPOINTS, seconds_delta)]
    return tmpl.format(units=seconds_delta // unit)
登入後複製

除了用元组可以优化过多的 if/else 分支外,有些情况下字典也能被用来做同样的事情。关键在于从现有代码找到重复的逻辑与规律,并多多尝试。

2. 在更多地方使用动态解包

动态解包操作是指使用 * 或 ** 运算符将可迭代对象“解开”的行为,在 Python 2 时代,这个操作只能被用在函数参数部分,并且对出现顺序和数量都有非常严格的要求,使用场景非常单一。

def calc(a, b, multiplier=1):
    return (a + b) * multiplier
# Python2 中只支持在函数参数部分进行动态解包
print calc(*[1, 2], **{"multiplier": 10})
# OUTPUT: 30
登入後複製

不过,Python 3 尤其是 3.5 版本后, * 和 ** 的使用场景被大大扩充了。举个例子,在 Python 2 中,如果我们需要合并两个字典,需要这么做:

def merge_dict(d1, d2):
    # 因为字典是可被修改的对象,为了避免修改原对象,此处需要复制一个 d1 的浅拷贝
    result = d1.copy()
    result.update(d2)
    return result
user = merge_dict({"name": "piglei"}, {"movies": ["Fight Club"]})
登入後複製

但是在 Python 3.5 以后的版本,你可以直接用 ** 运算符来快速完成字典的合并操作:

user = {**{"name": "piglei"}, **{"movies": ["Fight Club"]}}
登入後複製

除此之外,你还可以在普通赋值语句中使用 * 运算符来动态的解包可迭代对象。如果你想详细了解相关内容,可以阅读下面推荐的 PEP。

Hint:推进动态解包场景扩充的两个 PEP:

·PEP 3132 -- Extended Iterable Unpacking | Python.org

·PEP 448 -- Additional Unpacking Generalizations | Python.org

3. 最好不用“获取许可”,也无需“要求原谅”

这个小标题可能会稍微让人有点懵,让我来简短的解释一下:“获取许可”与“要求原谅”是两种不同的编程风格。如果用一个经典的需求:“计算列表内各个元素出现的次数” 来作为例子,两种不同风格的代码会是这样:

# AF: Ask for Forgiveness
# 要做就做,如果抛出异常了,再处理异常
def counter_af(l):
    result = {}
    for key in l:
        try:
            result[key] += 1
        except KeyError:
            result[key] = 1
    return result
# AP: Ask for Permission
# 做之前,先问问能不能做,可以做再做
def counter_ap(l):
    result = {}
    for key in l:
        if key in result:
            result[key] += 1
        else:
            result[key] = 1
    return result
登入後複製

整个 Python 社区对第一种 Ask for Forgiveness 的异常捕获式编程风格有着明显的偏爱。这其中有很多原因,首先,在 Python 中抛出异常是一个很轻量的操作。其次,第一种做法在性能上也要优于第二种,因为它不用在每次循环的时候都做一次额外的成员检查。

不过,示例里的两段代码在现实世界中都非常少见。为什么?因为如果你想统计次数的话,直接用 collections.defaultdict 就可以了:

from collections import defaultdict
 
def counter_by_collections(l):
    result = defaultdict(int)
    for key in l:
        result[key] += 1
    return result
登入後複製

这样的代码既不用“获取许可”,也无需“请求原谅”。整个代码的控制流变得更清晰自然了。所以,如果可能的话,请尽量想办法省略掉那些非核心的异常捕获逻辑。一些小提示:

·操作字典成员时:使用 collections.defaultdict 类型

·或者使用 dict[key]=dict.setdefault(key,0)+1 内建函数

·如果移除字典成员,不关心是否存在:

·调用 pop 函数时设置默认值,比如 dict.pop(key,None)

·在字典获取成员时指定默认值: dict.get(key,default_value)

·对列表进行不存在的切片访问不会抛出 IndexError 异常: ["foo"][100:200]

4. 使用 next() 函数

next() 是一个非常实用的内建函数,它接收一个迭代器作为参数,然后返回该迭代器的下一个元素。使用它配合生成器表达式,可以高效的实现“从列表中查找第一个满足条件的成员”之类的需求。

numbers = [3, 7, 8, 2, 21]
# 获取并 **立即返回** 列表里的第一个偶数
print(next(i for i in numbers if i % 2 == 0))
# OUTPUT: 8
登入後複製

5. 使用有序字典来去重

字典和集合的结构特点保证了它们的成员不会重复,所以它们经常被用来去重。但是,使用它们俩去重后的结果会丢失原有列表的顺序。这是由底层数据结构“哈希表(Hash Table)”的特点决定的。

>>> l = [10, 2, 3, 21, 10, 3]
# 去重但是丢失了顺序
>>> set(l)
{3, 10, 2, 21}
登入後複製

如果既需要去重又必须保留顺序怎么办?我们可以使用 collections.OrderedDict 模块:

Hint: 在 Python 3.6 中,默认的字典类型修改了实现方式,已经变成有序的了。并且在 Python 3.7 中,该功能已经从 语言的实现细节 变成了为 可依赖的正式语言特性。

但是我觉得让整个 Python 社区习惯这一点还需要一些时间,毕竟目前“字典是无序的”还是被印在无数本 Python 书上。所以,我仍然建议在一切需要有序字典的地方使用 OrderedDict。

常见误区

1. 当心那些已经枯竭的迭代器

在文章前面,我们提到了使用“懒惰”生成器的种种好处。但是,所有事物都有它的两面性。生成器的最大的缺点之一就是:它会枯竭。当你完整遍历过它们后,之后的重复遍历就不能拿到任何新内容了。

numbers = [1, 2, 3]
numbers = (i * 2 for i in numbers)
# 第一次循环会输出 2, 4, 6
for number in numbers:
    print(number)
# 这次循环什么都不会输出,因为迭代器已经枯竭了
for number in numbers:
    print(number)
登入後複製

而且不光是生成器表达式,Python 3 里的 map、filter 内建函数也都有一样的特点。忽视这个特点很容易导致代码中出现一些难以察觉的 Bug。

Instagram 就在项目从 Python 2 到 Python 3 的迁移过程中碰到了这个问题。它们在 PyCon 2017 上分享了对付这个问题的故事。访问文章 Instagram 在 PyCon 2017 的演讲摘要,搜索“迭代器”可以查看详细内容。

2. 别在循环体内修改被迭代对象

这是一个很多 Python 初学者会犯的错误。比如,我们需要一个函数来删掉列表里的所有偶数:

def remove_even(numbers):
   """去掉列表里所有的偶数
   """
    for i, number in enumerate(numbers):
        if number % 2 == 0:
            # 有问题的代码
            del numbers[i]
numbers = [1, 2, 7, 4, 8, 11]
remove_even(numbers)
print(numbers)
# OUTPUT: [1, 7, 8, 11]
登入後複製

注意到结果里那个多出来的“8”了吗?当你在遍历一个列表的同时修改它,就会出现这样的事情。因为被迭代的对象numbers在循环过程中被修改了。遍历的下标在不断增长,而列表本身的长度同时又在不断缩减。这样就会导致列表里的一些成员其实根本就没有被遍历到。

所以对于这类操作,请使用一个新的空列表保存结果,或者利用 yield 返回一个生成器。而不是修改被迭代的列表或是字典对象本身。

以上是Python容器使用的5個技巧和2個誤區的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

sublime怎麼運行代碼python sublime怎麼運行代碼python Apr 16, 2025 am 08:48 AM

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

notepad 怎麼運行python notepad 怎麼運行python Apr 16, 2025 pm 07:33 PM

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。

See all articles