我做爲一名python初學者,爲了強化記憶有必要把看過的一些優秀的文章中一些技巧經過notebook的方式練習一次。我認爲這麼作有幾個優勢:一來加深印象;二來也能夠將學習過的內容保存方便往後查閱;第三也能夠培養我寫博的習慣(一直都沒那個習慣)python
jupyter notebook格式的文件github下載:git
本文引用自 公衆號: Python數據科學 做者: wlsq程序員
身爲程序員除了須要具有解決問題的思路之外,代碼的質量和簡潔性也很關鍵,今天又學習到了一些以爲本身很高級的內容跟你們分享,內容包括:github
Python有一個大型標準庫,但只有一個內置函數的小型庫,這些函數老是可用的,不須要導入。它們每個都值得咱們仔細研究,可是在研究前,我仍是給你們一些小的提示,尤爲是在其中一些函數的狀況下,能夠用什麼替代更好。面試
在面試中,這種狀況可能比任何其餘狀況都要多:您有一個元素列表,您須要遍歷列表,同時訪問索引和值。算法
有一個名爲FizzBuzz的經典編碼面試問題能夠經過迭代索引和值來解決。在FizzBuzz中,你將得到一個整數列表,任務是執行如下操做:編程
用「fizz」替換全部可被3整除的整數
用「buzz」替換全部可被5整除的整數
將全部可被3和5整除的整數替換爲「fizzbuzz」
複製代碼
一般,開發人員將使用range()解決此問題:安全
numbers = [45, 22, 14, 65, 97, 7]
for i in range(len(numbers)):
if numbers[i] % 3 == 0 and numbers[i] % 5 == 0:
numbers[i] = 'fizzbuzz'
elif numbers[i] % 3 == 0:
numbers[i] = 'fizz'
elif numbers[i] % 5 == 0:
numbers[i] = 'buzz'
numbers
複製代碼
['fizzbuzz', 22, 14, 'buzz', 97, 7]
複製代碼
Range容許你經過索引訪問數字元素,而且對於某些特殊狀況也是一個頗有用的工具。但在這種狀況下,咱們但願同時獲取每一個元素的索引和值,更優雅的解決方案使用enumerate():數據結構
numbers = [45, 22, 14, 65, 97, 7]
for i, num in enumerate(numbers):
if num % 3 == 0 and num % 5 == 0:
numbers[i] = 'fizzbuzz'
elif num % 3 == 0:
numbers[i] = 'fizz'
elif num % 5 == 0:
numbers[i] = 'buzz'
numbers
複製代碼
['fizzbuzz', 22, 14, 'buzz', 97, 7]
複製代碼
對於每一個元素,enumerate()返回一個計數器和元素值。計數器默認爲0,也是元素的索引。不想在0開始你的計數?只需使用可選的start參數來設置偏移量:app
numbers = [45, 22, 14, 65, 97, 72]
for i, num in enumerate(numbers, start=52):
print(i, num)
複製代碼
52 45
53 22
54 14
55 65
56 97
57 72
複製代碼
經過使用start參數,咱們訪問全部相同的元素,從第一個索引開始,但如今咱們的計數從指定的整數值開始。
「我認爲刪除filter()和map()是很是有爭議的。」 複製代碼
- Guido van Rossum,Python的創造者
通常使用者可能錯誤地認爲它沒有爭議,但Guido有充分的理由想要從Python中刪除map()和filter()。一個緣由是Python支持遞推式構造列表,它一般更容易閱讀並支持與map()和filter()相同的功能。
讓咱們首先看看咱們如何構造對map()的調用以及等效的遞推構造列表:
numbers = [4, 2, 1, 6, 9, 7]
def square(x):
return x*x
list(map(square, numbers))
複製代碼
[16, 4, 1, 36, 81, 49]
複製代碼
[square(x) for x in numbers]
複製代碼
[16, 4, 1, 36, 81, 49]
複製代碼
使用map()和列表推導的兩種方法都返回相同的值,但列表推導更容易閱讀和理解。下面咱們能夠對filter()及其等效的列表推導作一樣的事情:
def is_odd(x):
return bool(x % 2)
list(filter(is_odd, numbers))
複製代碼
[1, 9, 7]
複製代碼
[x for x in numbers if is_odd(x)]
複製代碼
[1, 9, 7]
複製代碼
就像咱們在map中看到的那樣,filter和列表推導方法返回相同的值,但列表推導更容易理解。
來自其餘語言的開發人員可能不一樣意構造列表比map和filter更容易閱讀,但根據個人經驗,初學者可以更直觀地寫出列表推導。但不管哪一種方式,在編碼面試中使用列表推導不多會出錯,由於它會讓你知道Python中最多見的是什麼。
你可能經過在代碼中添加print並查看打印出的內容來調試一個小問題。這種方法起初效果很好,但很快變得很麻煩。另外,在編碼面試設置中,你幾乎不但願在整個代碼中調用print()。
相反,你應該使用調試器。對於不是很瑣碎的錯誤,它幾乎老是比使用print()更快,而且鑑於調試是編寫軟件的重要部分,它代表你知道如何使用能夠在工做中快速開發的工具。 **若是你使用的是Python 3.7,則無需導入任何內容,只需在代碼中要放入調試器的位置調用breakpoint(): **
# Some complicated code with bugs
breakpoint()
複製代碼
調用breakpoint()會將你帶入pdb,這是默認的Python調試器。在Python 3.6及更早版本中,你能夠經過顯式導入pdb來執行相同的操做:
import pdb; pdb.set_trace()
複製代碼
像breakpoint()同樣,pdb.set_trace()會將你帶入pdb調試器。它不是那麼簡潔,並且須要記住的多一點。你可能想要嘗試其餘調試器,但pdb是標準庫的一部分,所以它始終可用。不管你喜歡哪一種調試器,在進行編碼面試設置以前,都值得嘗試使用它們來適應工做流程。
import platform
print(platform.python_version())
# breakpoint()
複製代碼
3.7.2
複製代碼
Python有不少不一樣的方法來處理字符串格式化,有時候不知道使用哪一個。在coding的面試中,若是使用Python 3.6+,建議的格式化方法是Python的f-strings。
f-strings支持使用字符串格式化迷你語言,以及強大的字符串插值。這些功能容許你添加變量甚至有效的Python表達式,並在添加到字符串以前在運行時對它們進行評估:
def get_name_and_decades(name, age):
return f"My name is {name} and I'm {age / 10:.5f} decades old."
get_name_and_decades("Maria", 31)
複製代碼
"My name is Maria and I'm 3.10000 decades old."
複製代碼
f-string容許你將Maria放入字符串中,並在一個簡潔的操做中添加具備所需格式的年齡。須要注意的一個風險是,若是你輸出用戶生成的值,那麼可能會帶來安全風險,在這種狀況下,模板字符串多是更安全的選擇。
大量的編碼面試問題須要進行某種排序,而且有多種有效的方法能夠進行排序。除非面試官但願你實現本身的排序算法,不然一般最好使用sorted()。你可能已經看到了排序的最簡單用法,例如按升序或降序排序數字或字符串列表:
sorted([6,5,3,7,2,4,1])
複製代碼
[1, 2, 3, 4, 5, 6, 7]
複製代碼
sorted(['cat', 'dog', 'cheetah', 'rhino', 'bear'], reverse=True)
複製代碼
['rhino', 'dog', 'cheetah', 'cat', 'bear']
複製代碼
默認狀況下,sorted()已按升序對輸入進行排序,而reverse關鍵字參數則按降序排序。
值得了解的是可選關鍵字key,它容許你在排序以前指定將在每一個元素上調用的函數。添加函數容許自定義排序規則,若是要對更復雜的數據類型進行排序,這些規則特別有用:
animals = [
{'type': 'penguin', 'name': 'Stephanie', 'age': 8},
{'type': 'elephant', 'name': 'Devon', 'age': 3},
{'type': 'puma', 'name': 'Moe', 'age': 5},
]
sorted(animals, key=lambda animal: animal['age'])
複製代碼
[{'type': 'elephant', 'name': 'Devon', 'age': 3},
{'type': 'puma', 'name': 'Moe', 'age': 5},
{'type': 'penguin', 'name': 'Stephanie', 'age': 8}]
複製代碼
經過傳入一個返回每一個元素年齡的lambda函數,能夠輕鬆地按每一個字典的單個值對字典列表進行排序。在這種狀況下,字典如今按年齡按升序排序。
算法在面試中獲得了不少關注,但數據結構可能更爲重要。在coding面試環境中,選擇正確的數據結構會對性能產生重大影響。除了理論數據結構以外,Python還在其標準數據結構實現中內置了強大而方便的功能。這些數據結構在面試中很是有用,由於它們默認爲你提供了許多功能,讓你能夠將時間集中在問題的其餘部分。
咱們一般須要從現有數據集中刪除重複元素。新的開發人員有時會在列表應該使用集合時執行此操做,這會強制執行全部元素的惟一性。
僞裝你有一個名爲get_random_word()的函數。它將始終從一小組單詞中返回一個隨機選擇:
import random
all_words = "all the words in the world".split()
def get_random_word():
return random.choice(all_words)
複製代碼
你應該重複調用get_random_word()以獲取1000個隨機單詞,而後返回包含每一個惟一單詞的數據結構。如下是兩種常見的次優方法和一種好的方法。
糟糕的方法
get_unique_words()將值存儲在列表中,而後將列表轉換爲集合:
def get_unique_words():
words = []
for _ in range(1000):
words.append(get_random_word())
return set(words)
get_unique_words()
複製代碼
{'all', 'in', 'the', 'words', 'world'}
複製代碼
這種方法並不可怕,但它沒必要要地建立了一個列表,而後將其轉換爲集合。面試官幾乎老是注意到(並詢問)這種類型的設計選擇。
更糟糕的作法
爲避免從列表轉換爲集合,你如今能夠在不使用任何其餘數據結構的狀況下將值存儲在列表中。而後,經過將新值與列表中當前的全部元素進行比較來測試惟一性:
def get_unique_words():
words = []
for _ in range(1000):
word = get_random_word()
if word not in words:
words.append(word)
return words
get_unique_words()
複製代碼
['world', 'words', 'all', 'the', 'in']
複製代碼
優秀的方法
如今,你徹底跳過使用列表,而是從頭開始使用一組:
def get_unique_words():
words = set()
for _ in range(1000):
words.add(get_random_word())
return words
get_unique_words()
複製代碼
{'all', 'in', 'the', 'words', 'world'}
複製代碼
除了從頭開始使用集合以外,這可能與其餘方法沒有太大的不一樣。若是你考慮.add()中發生了什麼,它甚至聽起來像第二種方法:獲得單詞,檢查它是否已經在集合中,若是沒有,則將其添加到數據結構中。
那麼爲何使用與第二種方法不一樣的集合呢?
它們是不一樣的,。查找時間的差別意味着添加到集合的時間複雜度以O(N)的速率增加,這在大多數狀況下比第二種方法的O(N^2)好得多。
前面提到,列表推導是方便的工具,但有時會致使沒必要要的內存使用。想象一下,你被要求找到前1000個完美正方形的總和,從1開始。你知道列表推導,因此你快速編寫一個有效的解決方案:
sum([i * i for i in range(1, 1001)])
複製代碼
333833500
複製代碼
解決方案會列出1到1,000,000之間的每一個完美平方,並對值進行求和。你的代碼會返回正確的答案,但隨後您的面試官會開始增長您須要總和的完美正方形的數量。
起初,你的功能不斷彈出正確的答案,但很快就開始放慢速度,直到最後這個過程彷佛永遠持續下去。這不是你想要在面試中發生的一件事。
這裏發生了什麼?
它正在列出你要求的每一個完美的方塊,並將它們所有加起來。具備1000個完美正方形的列表在計算機術語中可能不會很大,可是1億或10億是至關多的信息,而且很容易佔用計算機的可用內存資源。這就是這裏發生的事情。
值得慶幸的是,有一種解決內存問題的快捷方法。你只需用括號替換方括號:
sum((i * i for i in range(1, 1001)))
複製代碼
333833500
複製代碼
換出括號會將列表推導更改成生成器表達式。當你知道要從序列中檢索數據,但不須要同時訪問全部數據的時候,生成器表達式很是適合。
生成器表達式返回生成器對象,而不是建立列表。該對象知道它在當前狀態中的位置(例如,i = 49)而且僅在被要求時計算下一個值。
所以,當sum經過重複調用.__ next __()來迭代生成器對象時,生成器檢查i
等於多少,計算i * i,在內部遞增i,並將正確的值返回到sum。該設計容許生成器用於大量數據序列,由於一次只有一個元素存在於內存中。
最多見的編程任務之一涉及添加,修改或檢索可能在字典中或可能不在字典中的項。Python字典具備優雅的功能,可使這些任務簡潔明瞭,但開發人員一般會在不須要時檢查值。
想象一下,你有一個名爲cowboy的字典,你想獲得那個cowboy的名字。一種方法是使用條件顯式檢查key:
cowboy = {'age': 32, 'horse': 'mustang', 'hat_size': 'large'}
if 'name' in cowboy:
name = cowboy['name']
else:
name = 'The Man with No Name'
name
複製代碼
'The Man with No Name'
複製代碼
此方法首先檢查字典中是否存在name鍵,若是存在,則返回相應的值。不然,它返回默認值。
雖然清楚地檢查key確實有效,但若是使用.get(),它能夠很容易地用一行代替:
name = cowboy.get('name', 'The Man with No Name')
name
複製代碼
'The Man with No Name'
複製代碼
get()執行與第一種方法相同的操做,但如今它們會自動處理。若是key存在,則返回適當的值。不然,將返回默認值。
可是,若是你想在仍然訪問name的key時使用默認值更新字典呢? .get()在這裏沒有真正幫助你,因此你只須要再次顯式檢查這個值:
if 'name' not in cowboy:
cowboy['name'] = 'The Man with No Name'
name = cowboy['name']
name
複製代碼
'The Man with No Name'
複製代碼
檢查values並設置默認值是一種有效的方法,而且易於閱讀,但Python再次使用.setdefault()提供了更優雅的方法:
name = cowboy.setdefault('name', 'The Man with No Name')
name
複製代碼
'The Man with No Name'
複製代碼
.setdefault()完成與上面代碼片斷徹底相同的操做。它檢查cowboy中是否存在名稱,若是是,則返回該值。不然,它將cowboy ['name']設置爲The Man with No Name並返回新值。
默認狀況下,Python提供了許多功能,這些功能只是一個導入語句。它自己就很強大,但知道如何利用標準庫能夠加強你的編碼面試技巧。
從全部可用模塊中挑選最有用的部分很困難,所以本節將僅關注其實用功能的一小部分。但願這些對您在編碼訪談中有用,而且您但願瞭解更多有關這些和其餘模塊的高級功能的信息。
當你爲單個鍵設置默認值時,.get()和.setdefault()能夠正常工做,但一般須要爲全部可能的未設置鍵設置默認值,尤爲是在面試環境中進行編程時。
僞裝你有一羣學生,你須要記錄他們在家庭做業上的成績。輸入值是具備格式(student_name,grade)的元組列表,可是你但願輕鬆查找單個學生的全部成績而無需迭代列表。
存儲成績數據的一種方法是使用將學生姓名映射到成績列表的字典:
student_grades = {}
grades = [
('elliot', 91),
('neelam', 98),
('bianca', 81),
('elliot', 88),
]
for name, grade in grades:
if name not in student_grades:
student_grades[name] = []
student_grades[name].append(grade)
student_grades
複製代碼
{'elliot': [91, 88], 'neelam': [98], 'bianca': [81]}
複製代碼
在這種方法中,你迭代學生並檢查他們的名字是否已是字典中的屬性。若是沒有,則將它們添加到字典中,並將空列表做爲默認值。而後將實際成績附加到該學生的成績列表中。
可是有一個更簡潔的方法,可使用defaultdict,它擴展了標準的dict功能,容許你設置一個默認值,若是key不存在,它將按默認值操做:
from collections import defaultdict
student_grades = defaultdict(list)
for name, grade in grades:
student_grades[name].append(grade)
student_grades
複製代碼
defaultdict(list, {'elliot': [91, 88], 'neelam': [98], 'bianca': [81]})
複製代碼
在這種狀況下,你將建立一個defaultdict,它使用不帶參數的list構造函數做爲默認方法。沒有參數的list返回一個空列表,所以若是名稱不存在則defaultdict調用list(),而後再把學生成績添加上。若是你想更炫一點,你也可使用lambda函數做爲值來返回任意常量。
利用defaultdict可使代碼更簡潔,由於你沒必要擔憂key的默認值。相反,你能夠在defaultdict裏處理它們一次,而後key就終存在了。
假如你有一長串沒有標點符號或大寫字母的單詞,你想要計算每一個單詞出現的次數。
你可使用字典或defaultdict增長計數,但collections.Counter提供了一種更清晰,更方便的方法。 Counter是dict的子類,它使用0做爲任何缺失元素的默認值,而且更容易計算對象的出現次數:
from collections import Counter
words = "if there was there was but if there was not there was not".split()
counts = Counter(words)
counts
複製代碼
Counter({'if': 2, 'there': 4, 'was': 4, 'but': 1, 'not': 2})
複製代碼
當你將單詞列表傳遞給Counter時,它會存儲每一個單詞以及該單詞在列表中出現的次數。
若是你好奇兩個最多見的詞是什麼?只需使用.most_common():
counts.most_common(2)
複製代碼
[('there', 4), ('was', 4)]
複製代碼
.most-common()是一個方便的方法,只需按計數返回n個最頻繁的輸入。
如今有一個雜事須要判斷!‘A’>‘a’是真是假?
這是假的,由於A的ASCII代碼是65,但a是97,65不大於97。爲何答案很重要?由於若是你想檢查一個字符是不是英語字母表的一部分,一種流行的方法是看它是否在A和Z之間(在ASCII圖表上是65和122)。
檢查ascii代碼是可行的,可是在面試時卻很笨拙,很容易弄亂,特別是當你記不清是小寫仍是大寫的ascii字符排在第一位的時候。這時候,使用定義在字符串模塊中的常量要容易得多。
你可使用is_upper(),它返回字符串中的全部字符是否都是大寫字母:
import string
def is_upper(word):
for letter in word:
if letter not in string.ascii_uppercase:
return False
return True
is_upper('Thanks Geir')
複製代碼
False
複製代碼
is_upper('LOL')
複製代碼
True
複製代碼
is_upper()迭代word中的字母,並檢查字母是否爲string.ascii_大寫字母的一部分。若是你打印出string.ascii_大寫,你會發現它只是一個字符串,該值設置爲文本「ABCDEFGHIJKLMNOPQRSTUVWXYZ」。
全部字符串常量都只是常常引用的字符串值的字符串。其中包括如下內容:
這些更容易使用,更重要的是,更容易閱讀。
print(string.ascii_uppercase)
複製代碼
ABCDEFGHIJKLMNOPQRSTUVWXYZ
複製代碼
這裏有一我的爲的例子:你去遊樂園,決定找出每一對可能坐在過山車上的朋友。
除非生成這些配對是面試問題的主要目的,不然極可能生成全部可能的配對只是朝着工做算法前進的一個乏味的步驟。你能夠本身用嵌套for循環計算它們,也可使用強大的itertools庫。
itertools有多個工具來生成可重複輸入數據序列,但如今咱們只關注兩個常見函數:itertools.permutations()和itertools.combinations()。
itertools.permutations()構建全部排列的列表,這意味着它是輸入值的每一個可能分組的列表,其長度與count參數匹配。r關鍵字參數容許咱們指定每一個分組中有多少值:
import itertools
friends = ['Monique', 'Ashish', 'Devon', 'Bernie']
list(itertools.permutations(friends, r=2))
複製代碼
[('Monique', 'Ashish'),
('Monique', 'Devon'),
('Monique', 'Bernie'),
('Ashish', 'Monique'),
('Ashish', 'Devon'),
('Ashish', 'Bernie'),
('Devon', 'Monique'),
('Devon', 'Ashish'),
('Devon', 'Bernie'),
('Bernie', 'Monique'),
('Bernie', 'Ashish'),
('Bernie', 'Devon')]
複製代碼
對於排列,元素的順序很重要,所以(「sam」、「devon」)表示與(「devon」、「sam」)不一樣的配對,這意味着它們都將包含在列表中。
itertools.combinations()生成組合。這些也是輸入值的可能分組,但如今值的順序可有可無。由於(‘sam’、‘devon’)和(‘devon’、‘sam’)表明同一對,因此輸出列表中只會包含它們中的一個:
list(itertools.combinations(friends, r=2))
複製代碼
[('Monique', 'Ashish'),
('Monique', 'Devon'),
('Monique', 'Bernie'),
('Ashish', 'Devon'),
('Ashish', 'Bernie'),
('Devon', 'Bernie')]
複製代碼
因爲值的順序與組合有關,所以同一輸入列表的組合比排列少。一樣,由於咱們將r設置爲2,因此每一個分組中都有兩個名稱。
.combinations和.permutations只是強大庫的一個小例子,可是當你試圖快速解決算法問題時,即便這兩個函數也很是有用。
在下一次面試中,你能夠放心地使用一些不太常見但功能更強大的標準特性。從總體上來講,要了解該語言有不少東西,但本文應該爲你們提供一個起點,讓你們可以更深刻地瞭解該語言,同時在面試時更有效地使用Python。
複製代碼