我做为一名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。
复制代码