20 個 Python 面試題來挑戰(zhàn)你的知識

在本文中,我將我的一些筆記變成了20 個面試問題,涵蓋了數據結構、核心編程概念和 Python 最佳實踐。
希望你能完成其中的一些并重溫你的 Python 技能。
事不宜遲,讓我們直接進入。
1. 列表和元組有什么區(qū)別?你應該什么時候使用哪一個?
列表是可變數據結構,而元組是不可變數據結構。
Python 中的可變對象具有更改其值的能力。
列表是動態(tài)的:你可以向其中添加項目或覆蓋和刪除現有項目。
元組是固定大小的:它們沒有方法append或extend方法。你也不能從中刪除項目。
元組和列表都支持索引并允許使用in運算符檢查其中的現有元素。
→ 在某些情況下,我認為元組可能有用。
如果你聲明一個你知道永遠不會更改的項目集合,或者你將只循環(huán)而不更改其值,請使用元組。
如果你尋找性能,元組比列表更快,因為它們是只讀結構。如果你不需要寫操作,請考慮使用元組。
如果你想防止意外寫入不需要更改的數據,元組可以使你的代碼更安全。
這是一個代碼示例,顯示了元組與列表的不同之處。
1, 2, 3, 4, 5] > numbers[1] = 100 > print(numbers)
[1, 100, 3, 4, 5] > names = ("john", "joe", "alice") > names[0] = "bob")
---------------------------------------------------------------------------
TypeError ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Traceback (most recent call last)
<ipython-input-2-25012ce34a87> in <module>----> 1 names[0] = "bob"TypeError: 'tuple' object does not support item assignment
> numbers = [
2 — 多處理和多線程有什么區(qū)別?你應該什么時候使用哪個?
多處理和多線程是旨在加快代碼速度的編程范例。
當你使用多處理時,你可以在進程上并行計算。進程是獨立的,不相互通信:它們不共享相同的內存區(qū)域,并且相互之間有嚴格的隔離。在應用方面,多處理適用于 CPU 密集型工作負載。但是,它確實具有與進程數量成正比的大量內存占用。
另一方面,在多線程應用程序中,線程存在于單個進程中。因此,它們共享相同的內存區(qū)域:它們可以修改相同的變量并且可以相互干擾。雖然進程是嚴格并行執(zhí)行的,但在 Python 中的給定時間點只執(zhí)行一個線程,這是由于全局解釋器鎖 (?GIL?)。多線程適用于受 IO 限制的應用程序,例如網頁抓取或從數據庫中獲取數據。
→ 如果你想了解有關多線程和多處理的更多信息,我建議你閱讀我之前關于多進程跟線程的文章關于多線程你知道多少呢?,該文章全面介紹了這兩個概念。
3 — 模塊、包和庫之間有什么區(qū)別?
模塊只是一個 Python 文件,旨在導入腳本或其他模塊。它包含函數、類和全局變量。
包是模塊的集合,它們在文件夾中組合在一起以提供一致的功能。包可以像模塊一樣被導入。它們通常有一個__init__.py文件告訴 Python 解釋器按原樣處理它們。
庫是包的集合。
4 — python 中的多線程有什么問題?
全局解釋器鎖(或 GIL)可防止 Python 解釋器同時執(zhí)行多個線程。簡而言之,GIL 強制在 Python 中的任何時間點只執(zhí)行一個線程。
這代表了依賴多線程代碼的 CPU 密集型應用程序的一個很大的性能瓶頸。
5 — 什么是裝飾器?你能描述一下裝飾器值得使用的情況嗎?
裝飾器是一個接收函數作為輸入并返回函數作為輸出的函數。裝飾器的目標是在不改變其核心機制的情況下擴展輸入函數的行為。
使用裝飾器還可以防止你重復自己。它迫使你編寫一次通用代碼,然后將其用于需要它的每個功能。
裝飾器大放異彩的典型用例是日志記錄。
例如,想象一下,你希望將傳遞給程序中調用的每個函數的所有參數值記錄到終端。你可以遍歷每個函數定義并將其寫下來,或者你可以只編寫一個裝飾器來執(zhí)行此日志記錄任務并將其應用于所有需要它的函數。
將裝飾器應用于函數只需在該函數的定義上方添加一行即可。
#沒有裝飾器def my_awesome_function(): ? ? ?
? ?# 做一些很棒的事情 ?# 帶有裝飾器 ?def my_awesome_function(): ? ?
? ?# 做更棒的事情
下面是一個代碼示例,它創(chuàng)建了一個名為的裝飾器,該裝飾器log記錄了傳遞給函數的參數的值。
import logging
logging.basicConfig(
? ?format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
? ?level=logging.INFO,
? ?datefmt="%Y-%m-%d %H:%M:%S",
? ?stream=sys.stdout,
)
logger = logging.getLogger("notebook")def log(func):
? ?def wrapper(*args, **kwargs):
? ? ? ?output = func(*args, **kwargs)
? ? ? ?msg = f"{func.__name__} was run with the following args: {args} and the following kwargs {kwargs}"
? ? ? ?logger.info(msg) ? ? ? ?return output ? ?return wrapperdef print_args(*args, **kwargs):
? ?print(args)
? ?print(kwargs) print_args(10, a=2, b="test")
(10,)
{'a': 2, 'b': 'test'}2022-03-06 18:07:05,248 - notebook - INFO - print_args was run with the following args: (10,) and the following kwargs {'a': 2, 'b': 'test'} print_args(10, 100, a=2, b="test")
(10, 100)
{'a': 2, 'b': 'test'}2022-03-06 18:07:05,562 - notebook - INFO - print_args was run with the following args: (10, 100) and the following kwargs {'a': 2, 'b': 'test'}
裝飾器還可以用于其他目的,例如計時功能、驗證輸入數據、執(zhí)行訪問控制和身份驗證、緩存等。
6 — 如何正確地將數據寫入文件?否則會出什么問題?
使用上下文管理器是關鍵。
當你使用open沒有上下文管理器的語句并且在關閉文件之前發(fā)生一些異常時(關閉文件是你在以這種方式打開文件時必須記住的事情)可能會發(fā)生內存問題并且文件可能會在此過程中損壞。
當你with用來打開一個文件并且發(fā)生異常時,Python 保證該文件是關閉的。
d = {"foo": 1}# bad practice f = open("./data.csv", "wb")
f.write("some data")
v = d["bar"] # KeyError# f.close() never executes which leads to memory issuesf.close()# good practicewith open("./data.csv", "wb") as f:
? ?f.write("some data")
? ?v = d["bar"]# python still executes f.close() even if the KeyError exception occurs
7 — 函數參數是按引用傳遞還是按值傳遞?
在 Python 中,所有函數參數都是通過引用傳遞的:這意味著如果將參數傳遞給函數,則函數將獲得對同一對象的引用。
如果對象是可變的并且函數改變了它,則參數將在函數的外部范圍內發(fā)生變異。讓我們看一個例子:
def append_number(numbers):
? ? ? ?numbers.append(5) > numbers = [1, 2, 3, 4] > print(f"before: {numbers}"[1, 2, 3, 4] > append_number(numbers) > numbers
[1, 2, 3, 4, 5]
>
8 — 如何覆蓋對象的打印方式?
使用 the__str__和__repr__dunder 方法。
這是一個示例,它演示了 Person 類中的實例在打印到控制臺時如何被很好地格式化。
class Person:
? ?def __init__(self, first_name, last_name, age): ? ? ? ?self.first_name = first_name ? ? ? ?self.last_name = last_name ? ? ? ?self.age = age ? ?def __str__(self): ? ? ? ?return f"{self.first_name} {self.last_name} ({self.age})"
? ?def __repr__(self): ? ? ? ?return f"{self.first_name} {self.last_name} ({self.age})"
> person = Person("John", "Doe", 30) # thanks to __str__John Doe (30) > person # thanks to __repr__John Doe (30)
9 — 編寫一個計算整數 n 階乘的函數
遞歸是關鍵
def factorial(n):
? ?if n == 0: ? ? ? ?return 1
? ?else: ? ? ? ?return n * factorial(n-1)
10 — is 和 == 運算符有什么區(qū)別?
==是一個測試相等性的運算符,而is是一個測試身份的運算符。
兩個對象可以具有相同的值,但不一定相同(即具有相同的內存地址)。
請記住,這a is b是id(a) == id(b).
11 — 什么時候不應該使用 assert 語句?
assert語句對于內部測試和完整性檢查很有用。
但是,它不應該用于執(zhí)行數據驗證或錯誤處理,因為出于性能原因,它通常在生產代碼中被禁用。
想象一下,如果你使用斷言檢查管理員權限:這可能會在生產中引入很大的安全漏洞。
assert你可以拋出自定義錯誤,而不是使用該語句。
# Dangerous code!def delete_product(user, product_id):
? ?assert user.is_admin()
? ?user.delete_product(product_id)# Handle this properly by raising an errordef delete_product(user, product_id):
? ?if not user.is_admin(): ? ? ? ?raise AuthError("User must have admin privileges") ? ?else:
? ? ? ?user.delete_product(product_id)
12 — 什么是 Python 生成器?
Python 生成器是一個生成一系列項目的函數。
生成器看起來像典型的函數,但它們的行為是不同的。對于初學者,不使用return語句,而是使用yield語句。
然后,調用生成器函數不會運行該函數:它只會創(chuàng)建一個生成器對象。生成器的代碼僅在next函數應用于生成器對象或生成器被迭代時執(zhí)行(在這種情況下,next函數被隱式調用)
在生成器對象上調用函數的次數next等于yield在生成器函數中調用語句的次數。
你可以使用 for 循環(huán)或生成器表達式定義生成器。
def repeat(n, message):
? ? ? ?for _ in range(n):
? ? ? ? ? ?yield message
repeat_hello_five_times = repeat(5, hello) > for message in repeat_hello_five_times:
? ? ? ?print(message)"hello""hello""hello""hello""hello" > repeat_hello_five_time = ("hello" for _ in range(5)) > repeat_hello_five_times
<generator object <genexpr> at 0x7fb64f2362d0> > for message in repeat_hello_five_times:
? ? ? ?print(message)"hello""hello""hello""hello""hello"
>
13 — 類方法和靜態(tài)方法有什么區(qū)別?什么時候應該使用哪個?
靜態(tài)方法是一種對調用它的類或實例有任何了解的方法。這是一種邏輯上屬于該類但沒有隱式參數的方法。
可以在類或其任何實例上調用靜態(tài)方法。
類方法是傳遞給調用它的類的方法,就像self傳遞給類中的其他實例方法一樣。類方法的強制參數不是類實例:它實際上是類本身。
類方法的一個典型用例是提供另一種構造實例的方法:執(zhí)行此操作的類方法稱為類的工廠。
這是一個使用類方法的 Employee 類,該類方法創(chuàng)建實例的方式與類的主構造函數略有不同。
class Employee(object):
? ?def __init__(self, first_name, last_name): ? ? ? ?self.first_name = first_name ? ? ? ?self.last_name = last_nale
? ?@classmethod ? ?def from_string(cls, name_str):
? ? ? ?first_name, last_name = map(str, name_str.split(' '))
? ? ? ?employee = cls(first_name, last_name) ? ? ? ?return employee
ahmed = Employee.from_string('Ahmed Besbes')
14 — 舉一個例子說明你如何使用 zip 和枚舉
該zip函數將多個迭代作為輸入并將它們聚合到一個元組中。例如,如果你想同時遍歷兩個列表,這可能很有用。
"john", "bob", "alice"] > ages = [10, 16, 20] > for name, age in zip(names, ages):
? ? ? ?print(name, age)
john 10bob 16alice 20
> names = [
該enumerate函數允許循環(huán)遍歷一個可迭代對象并同時訪問正在運行的索引和項目。
"john", "bob", "alice"] > for index, name in enumerate(names):
? ? ? ?print(index, name)0 john1 bob2 alice
> names = [
15 — 你會如何在給定的函數中使用 *args 和 **kwargs?
*args 和 **kwargs 通過接受可變數量的參數使 Python 函數更加靈活。
*args 在列表中傳遞可變數量的非關鍵字參數
**kwargs 在字典中傳遞可變數量的關鍵字參數
這是一個函數示例,該函數采用可變數量的關鍵字參數,這些參數收集在名為的字典中data(請注意,它不需要命名kwargs)

16 — 給出一個使用 map 的函數式編程示例
>>> numbers = [1, 2, 3, 4, 5]>>> numbers_times_2 = list(map(lambda n: n * 2, numbers))>>> numbers_times_2[2, 4, 6, 8, 10]
17 — continue 和 break 語句有什么區(qū)別
該break語句終止包含它的循環(huán)。程序立即移動到循環(huán)外部范圍內的代碼段。

另一方面,該continue語句跳過當前迭代的其余代碼并移至下一個迭代。

18 - 如何防止函數被調用不必要的時間?
使用緩存。
如果與給定輸入關聯的輸出在一段時間內沒有變化,則使用緩存對函數有意義。
一個典型的場景是查詢一個 web 服務器:如果你第一次查詢一個 URL,并且你知道響應不會改變,你可以緩存結果。
from cachetools import cached, TTLCachecache = TTLCache(maxsize=100, ttl=86400)
@cached(cache)def extract_article_content(url):
? ?response = requests.get(url) ? ?content = response.content ? ?return content
19 — 給出一些 PEP8 指南
每個縮進級別使用 4 個空格。
進口應按以下順序分組:
標準庫導入。
相關第三方進口。
本地應用程序/庫特定的導入。
函數名和變量名應為小寫并用下劃線分隔
類名使用 CapWords 約定。
20 — 如何使用具有 2GB RAM 的計算機在 Python 中讀取 8GB 文件?
此解決方案適用于任何大型(甚至更大)文件。
當你打開文件時,你需要做的就是將文件對象用作迭代器:在循環(huán)此文件對象時,你將一次獲取一行,并且前面的行將從內存中清除(即它們是垃圾收集)。
這樣,文件將永遠不會完全加載到內存中。
with open("./large_dataset.txt") as input_file: ? ?for line in input_file:
? ? ? ?process_line(line)
感謝閱讀
這是我在面試中經??吹降囊恍﹩栴}的概述。我希望你從文章中學到了一些東西。20 個 Python 面試題來挑戰(zhàn)你的知識
資料領?。?/p>
