在本文中,我將我的一些筆記變成了20 個面試問題,涵蓋了數(shù)據(jù)結(jié)構(gòu)、核心編程概念和 Python/ target=_blank class=infotextkey>Python 最佳實(shí)踐。
希望你能完成其中的一些并重溫你的 python 技能。
事不宜遲,讓我們直接進(jìn)入。
1. 列表和元組有什么區(qū)別?你應(yīng)該什么時(shí)候使用哪一個?
列表是可變數(shù)據(jù)結(jié)構(gòu),而元組是不可變數(shù)據(jù)結(jié)構(gòu)。
Python 中的可變對象具有更改其值的能力。
列表是動態(tài)的:你可以向其中添加項(xiàng)目或覆蓋和刪除現(xiàn)有項(xiàng)目。
元組是固定大小的:它們沒有方法Append或extend方法。你也不能從中刪除項(xiàng)目。
元組和列表都支持索引并允許使用in運(yùn)算符檢查其中的現(xiàn)有元素。
→ 在某些情況下,我認(rèn)為元組可能有用。
- 如果你聲明一個你知道永遠(yuǎn)不會更改的項(xiàng)目集合,或者你將只循環(huán)而不更改其值,請使用元組。
- 如果你尋找性能,元組比列表更快,因?yàn)樗鼈兪侵蛔x結(jié)構(gòu)。如果你不需要寫操作,請考慮使用元組。
- 如果你想防止意外寫入不需要更改的數(shù)據(jù),元組可以使你的代碼更安全。
這是一個代碼示例,顯示了元組與列表的不同之處。
>>> numbers = [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)in----> 1 names[0] = "bob"TypeError: 'tuple' object does not support item assignment
2 — 多處理和多線程有什么區(qū)別?你應(yīng)該什么時(shí)候使用哪個?
多處理和多線程是旨在加快代碼速度的編程范例。
當(dāng)你使用多處理時(shí),你可以在進(jìn)程上并行計(jì)算。進(jìn)程是獨(dú)立的,不相互通信:它們不共享相同的內(nèi)存區(qū)域,并且相互之間有嚴(yán)格的隔離。在應(yīng)用方面,多處理適用于 CPU 密集型工作負(fù)載。但是,它確實(shí)具有與進(jìn)程數(shù)量成正比的大量內(nèi)存占用。
另一方面,在多線程應(yīng)用程序中,線程存在于單個進(jìn)程中。因此,它們共享相同的內(nèi)存區(qū)域:它們可以修改相同的變量并且可以相互干擾。雖然進(jìn)程是嚴(yán)格并行執(zhí)行的,但在 Python 中的給定時(shí)間點(diǎn)只執(zhí)行一個線程,這是由于全局解釋器鎖 ( GIL )。多線程適用于受 IO 限制的應(yīng)用程序,例如網(wǎng)頁抓取或從數(shù)據(jù)庫中獲取數(shù)據(jù)。
→ 如果你想了解有關(guān)多線程和多處理的更多信息,我建議你閱讀我之前關(guān)于多進(jìn)程跟線程的文章關(guān)于多線程你知道多少呢?,該文章全面介紹了這兩個概念。
3 — 模塊、包和庫之間有什么區(qū)別?
模塊只是一個 Python 文件,旨在導(dǎo)入腳本或其他模塊。它包含函數(shù)、類和全局變量。
包是模塊的集合,它們在文件夾中組合在一起以提供一致的功能。包可以像模塊一樣被導(dǎo)入。它們通常有一個__init__.py文件告訴 Python 解釋器按原樣處理它們。
庫是包的集合。
4 — python 中的多線程有什么問題?
全局解釋器鎖(或 GIL)可防止 Python 解釋器同時(shí)執(zhí)行多個線程。簡而言之,GIL 強(qiáng)制在 Python 中的任何時(shí)間點(diǎn)只執(zhí)行一個線程。
這代表了依賴多線程代碼的 CPU 密集型應(yīng)用程序的一個很大的性能瓶頸。
5 — 什么是裝飾器?你能描述一下裝飾器值得使用的情況嗎?
裝飾器是一個接收函數(shù)作為輸入并返回函數(shù)作為輸出的函數(shù)。裝飾器的目標(biāo)是在不改變其核心機(jī)制的情況下擴(kuò)展輸入函數(shù)的行為。
使用裝飾器還可以防止你重復(fù)自己。它迫使你編寫一次通用代碼,然后將其用于需要它的每個功能。
裝飾器大放異彩的典型用例是日志記錄。
例如,想象一下,你希望將傳遞給程序中調(diào)用的每個函數(shù)的所有參數(shù)值記錄到終端。你可以遍歷每個函數(shù)定義并將其寫下來,或者你可以只編寫一個裝飾器來執(zhí)行此日志記錄任務(wù)并將其應(yīng)用于所有需要它的函數(shù)。
將裝飾器應(yīng)用于函數(shù)只需在該函數(shù)的定義上方添加一行即可。
#沒有裝飾器def my_awesome_function():# 做一些很棒的事情# 帶有裝飾器@my_awesome_decoratordef my_awesome_function():# 做更棒的事情
下面是一個代碼示例,它創(chuàng)建了一個名為的裝飾器,該裝飾器log記錄了傳遞給函數(shù)的參數(shù)的值。
import logginglogging.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 outputreturn wrapper@logdef 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'}
裝飾器還可以用于其他目的,例如計(jì)時(shí)功能、驗(yàn)證輸入數(shù)據(jù)、執(zhí)行訪問控制和身份驗(yàn)證、緩存等。
6 — 如何正確地將數(shù)據(jù)寫入文件?否則會出什么問題?
使用上下文管理器是關(guān)鍵。
當(dāng)你使用open沒有上下文管理器的語句并且在關(guān)閉文件之前發(fā)生一些異常時(shí)(關(guān)閉文件是你在以這種方式打開文件時(shí)必須記住的事情)可能會發(fā)生內(nèi)存問題并且文件可能會在此過程中損壞。
當(dāng)你with用來打開一個文件并且發(fā)生異常時(shí),Python 保證該文件是關(guān)閉的。
d = {"foo": 1}# bad practicef = 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 — 函數(shù)參數(shù)是按引用傳遞還是按值傳遞?
在 Python 中,所有函數(shù)參數(shù)都是通過引用傳遞的:這意味著如果將參數(shù)傳遞給函數(shù),則函數(shù)將獲得對同一對象的引用。
如果對象是可變的并且函數(shù)改變了它,則參數(shù)將在函數(shù)的外部范圍內(nèi)發(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 類中的實(shí)例在打印到控制臺時(shí)如何被很好地格式化。
class Person:def __init__(self, first_name, last_name, age):self.first_name = first_nameself.last_name = last_nameself.age = agedef __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 — 編寫一個計(jì)算整數(shù) n 階乘的函數(shù)
遞歸是關(guān)鍵
def factorial(n):if n == 0:return 1else:return n * factorial(n-1)
10 — is 和 == 運(yùn)算符有什么區(qū)別?
==是一個測試相等性的運(yùn)算符,而is是一個測試身份的運(yùn)算符。
兩個對象可以具有相同的值,但不一定相同(即具有相同的內(nèi)存地址)。
請記住,這a is b是id(a) == id(b).
11 — 什么時(shí)候不應(yīng)該使用 assert 語句?
assert語句對于內(nèi)部測試和完整性檢查很有用。
但是,它不應(yīng)該用于執(zhí)行數(shù)據(jù)驗(yàn)證或錯誤處理,因?yàn)槌鲇谛阅茉颍ǔT谏a(chǎn)代碼中被禁用。
想象一下,如果你使用斷言檢查管理員權(quán)限:這可能會在生產(chǎn)中引入很大的安全漏洞。
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 生成器是一個生成一系列項(xiàng)目的函數(shù)。
生成器看起來像典型的函數(shù),但它們的行為是不同的。對于初學(xué)者,不使用return語句,而是使用yield語句。
然后,調(diào)用生成器函數(shù)不會運(yùn)行該函數(shù):它只會創(chuàng)建一個生成器對象。生成器的代碼僅在next函數(shù)應(yīng)用于生成器對象或生成器被迭代時(shí)執(zhí)行(在這種情況下,next函數(shù)被隱式調(diào)用)
在生成器對象上調(diào)用函數(shù)的次數(shù)next等于yield在生成器函數(shù)中調(diào)用語句的次數(shù)。
你可以使用 for 循環(huán)或生成器表達(dá)式定義生成器。
>>> def repeat(n, message):for _ in range(n):yield messagerepeat_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_timesat 0x7fb64f2362d0>>>> for message in repeat_hello_five_times:print(message)"hello""hello""hello""hello""hello"
13 — 類方法和靜態(tài)方法有什么區(qū)別?什么時(shí)候應(yīng)該使用哪個?
靜態(tài)方法是一種對調(diào)用它的類或?qū)嵗腥魏瘟私獾姆椒ā_@是一種邏輯上屬于該類但沒有隱式參數(shù)的方法。
可以在類或其任何實(shí)例上調(diào)用靜態(tài)方法。
類方法是傳遞給調(diào)用它的類的方法,就像self傳遞給類中的其他實(shí)例方法一樣。類方法的強(qiáng)制參數(shù)不是類實(shí)例:它實(shí)際上是類本身。
類方法的一個典型用例是提供另一種構(gòu)造實(shí)例的方法:執(zhí)行此操作的類方法稱為類的工廠。
這是一個使用類方法的 Employee 類,該類方法創(chuàng)建實(shí)例的方式與類的主構(gòu)造函數(shù)略有不同。
class Employee(object):def __init__(self, first_name, last_name):self.first_name = first_nameself.last_name = last_nale@classmethoddef from_string(cls, name_str):first_name, last_name = map(str, name_str.split(' '))employee = cls(first_name, last_name)return employeeahmed = Employee.from_string('Ahmed Besbes')
14 — 舉一個例子說明你如何使用 zip 和枚舉
該zip函數(shù)將多個迭代作為輸入并將它們聚合到一個元組中。例如,如果你想同時(shí)遍歷兩個列表,這可能很有用。
>>> names = ["john", "bob", "alice"]>>> ages = [10, 16, 20]>>> for name, age in zip(names, ages):print(name, age)john 10bob 16alice 20
該enumerate函數(shù)允許循環(huán)遍歷一個可迭代對象并同時(shí)訪問正在運(yùn)行的索引和項(xiàng)目。
>>> names = ["john", "bob", "alice"]>>> for index, name in enumerate(names):print(index, name)0 john1 bob2 alice
15 — 你會如何在給定的函數(shù)中使用 *args 和 **kwargs?
*args 和 **kwargs 通過接受可變數(shù)量的參數(shù)使 Python 函數(shù)更加靈活。
- *args 在列表中傳遞可變數(shù)量的非關(guān)鍵字參數(shù)
- **kwargs 在字典中傳遞可變數(shù)量的關(guān)鍵字參數(shù)
這是一個函數(shù)示例,該函數(shù)采用可變數(shù)量的關(guān)鍵字參數(shù),這些參數(shù)收集在名為的字典中data(請注意,它不需要命名kwargs)
16 — 給出一個使用 map 的函數(shù)式編程示例>>> 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)外部范圍內(nèi)的代碼段。
另一方面,該continue語句跳過當(dāng)前迭代的其余代碼并移至下一個迭代。
18 - 如何防止函數(shù)被調(diào)用不必要的時(shí)間?
使用緩存。
如果與給定輸入關(guān)聯(lián)的輸出在一段時(shí)間內(nèi)沒有變化,則使用緩存對函數(shù)有意義。
一個典型的場景是查詢一個 web 服務(wù)器:如果你第一次查詢一個 URL,并且你知道響應(yīng)不會改變,你可以緩存結(jié)果。
from cachetools import cached, TTLCachecache = TTLCache(maxsize=100, ttl=86400)@cached(cache)def extract_article_content(url):response = requests.get(url)content = response.contentreturn content
19 — 給出一些 PEP8 指南
- 每個縮進(jìn)級別使用 4 個空格。
- 進(jìn)口應(yīng)按以下順序分組:
- 標(biāo)準(zhǔn)庫導(dǎo)入。
- 相關(guān)第三方進(jìn)口。
- 本地應(yīng)用程序/庫特定的導(dǎo)入。
- 函數(shù)名和變量名應(yīng)為小寫并用下劃線分隔
- 類名使用 Capwords 約定。
此解決方案適用于任何大型(甚至更大)文件。
當(dāng)你打開文件時(shí),你需要做的就是將文件對象用作迭代器:在循環(huán)此文件對象時(shí),你將一次獲取一行,并且前面的行將從內(nèi)存中清除(即它們是垃圾收集)。
這樣,文件將永遠(yuǎn)不會完全加載到內(nèi)存中。
with open("./large_dataset.txt") as input_file:for line in input_file:process_line(line)
感謝閱讀
這是我在面試中經(jīng)常看到的一些問題的概述。我希望你從文章中學(xué)到了一些東西。20 個 Python 面試題來挑戰(zhàn)你的知識