python粘性拓展_Python基础之:拓展解决问题的思路
0、錘子原理
在手里拿著一把錘子的人眼中,世界就像一根釘子。
大多人試圖以一種思維模型來解決問題,而其思維往往只來自某一專業學科,
但你必須知道各種重要學科的重要理論。
一一《窮查理寶典》
在過去十年的工作中,我經??吹揭恍┎豢伤甲h的代碼,這些代碼有時候看起來相當的愚蠢。而且大部分時候,這些代碼都有非常簡單而高效的替代方案。而寫出這些代碼的人,往往是因為沒有掌握相關的基礎知識,或者是因為總是用一個思路去解決問題,形成了思維慣性。
要巧妙地解決某些問題,有時候可能需要掌握非常專業和生僻的知識;但大部分時候,你只需要掌握一些非?;A的知識,和一個拓展性的思維。
本文皆在拋磚引玉,用非?;A的Python知識,用不同的思路巧妙地解決相似的問題。
1、判定元素是否存在
在列表在查找一個元素,判定元素是否存在,是一個相當常見的操作。
在貫穿本小節的所有例子中,我們都是為了查找符合某個條件的元素是否存在。如果存在,則做dealWhenFound操作;如果不存在,則做dealWhenNotFound操作。后文中用到這兩個函數,我們將直接使用,不再進行聲明。
def dealWhenFound(elem):
# 如果元素找到了,做點什么
print("{}is found".format(elem))
def dealWhenNotFound(elem):
# 如果元素沒有找到,做點什么
print("{}is not found".format(elem))
假如我們有一個名字列表,現在需要在其中查找某個元素是否存在。通用我們可以這么做:
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby"]
is_found = False
target = "Tom"
is_found = False
for name in names:
if name == target:
is_found = True
break
if is_found:
dealWhenFound(target)
else;
dealWhenNotFound(target)
這是一份通用可行且非常樣板式的代碼。但在Python中,我們有更加高效且解決方案。正如本小節的標題所述的,用in關鍵字就可以了。
if name in names:
dealWhenFound(target)
else:
dealWhenNotFound(target)
in操作符是用來判定一個元素是否存在一個可迭代對象中(list、tuple、dict、set等)。對于這種查找條件比較簡單的搜索,思路就是這么簡單,甚至不值得一提。但對于稍微復雜一點的查找條件,in就不那么勝任了。
我們把查找條件修改為:判定是否存在以某個字母開頭的名字。這個時候,我們就沒有辦法用in操作符來直接判定了。我們發現反而是第1份for代碼,才能更好地解決我們的問題。
prefix = "J"
is_found = False
for name in names:
if name.startswith(prefix): # 判定name是否以J開頭
is_found = True
break
if is_found:
dealWhenFound(prefix)
else;
dealWhenNotFound(prefix)
Python考慮到了這種情況的普遍性,為我們提供了for/else結構。
for iter in a_list:
if some_test(iter):
break
else:
# 如果循環結果,且沒有break語句被執行,則else塊會被執行
在for/else結構中,如果for循環正常結束(即沒有break語句被執行),則else下的代碼會被執行;否則else下的代碼不會被執行。
利用這個特性,我們可以將代碼進行如下的優化:
prefix = "J"
for name in names:
if name.startswith(prefix): # 判定name是否以J開頭
dealWhenFound(prefix)
break
else:
dealWhenNotFound(prefix)
如果你知道any函數,那你應該知道這份代碼還會優化的空間。any函數接受一個可迭代對象(包括生成器)做為參數,并且只要任意一個元素被判定為True,則返回True。配合map,我們的代碼可以進一步地簡化:
prefix = "J"
if any(map(lambda name: name.startswith(prefix), names)):
dealWhenFound(prefix)
else:
dealWhenNotFound(prefix)
如果你覺得上面這份代碼不好理解,我們可以進行拆解。
test_func = lambda name: name.startswith(prefix)
map_obj = map(test_func, names)
if any(map_obj):
dealWhenFound(prefix)
else:
dealWhenNotFound(prefix)
除了本小節用到的一些關鍵字和函數之外,Python也為我們提供了很多其它便利。在這里我們列舉一些比較常用的,但不再深入介紹用法。
in
any
all
for/else
map
reduce
filter
enumerate
zip
2、頻數統計
在實際開發過程,統計是另一個常見的需求。
還是以名字列表為例,將首字母相同的名字放在同一個分組(列表)里邊。我們很容易想到使用dict數據結構:用首字母做為key,以一個list對象做為value即可。
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = dict()
for name in names:
key = name[0]
if key in groups:
groups[key].append(name)
else:
g = [name]
groups[key] = g
使用dict的setdefault函數,上面這段代碼可以進行簡化:
for name in names:
key = name[0]
groups.setdefault(key, []).append(name)
d.setdefault(key, dft_val)的操作是,檢測key是否存在,如果存在則返回value;如果不存在,則將dft_val存儲到d[key],并返回dft_val。在上面的例子中, 我們在dict中存儲了list對象,所以我們可以通過鏈式調用,在一行代碼里完成比較復雜的操作。
到目前為止,一切都簡單到不值得一提。但如果我們把分組的需求改為,編者首字母相同的名字的個數,那就是另一個情況了。這時候dict的value類型是int,我們不可以進行簡單的鏈式操作,所以使用setdfault也就不存在優勢了。一個比較直觀的實現,還是對第一段代碼進行簡單的改造:
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = dict()
for name in names:
key = name[0]
if key in groups:
groups[key] += 1
else:
groups[key] = 1
或者使用get函數來簡化代碼:
for name in names:
key = name[0]
val = groups.get(key, 0)
groups[key] = val + 1
對于集合類型的操作,Python提供了一個更加高效便捷的庫collections。利用collections,我們可以對代碼進行進一步的簡化:
import collections
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = collections.defaultdict(int)
for name in names:
groups[name[0]] += 1
collections.Counter類為我們提供了統計列表(可迭代對象)元素數量的便利,配合列表解析表達式(list comprehension),我們可以用一行代碼就完成統計的操作。
import collections
names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]
groups = collections.Counter(name[0] for name in names)
print(groups)
# 打印結果:
# Counter({'J': 3, 'T': 2, 'H': 2, 'A': 2, 'M': 1})
collections為我們提供了更容易使用的容器類型(如list、tuple、dict等)的子類及其它一些便利。本文只是拋磚引玉,并不打算深入介紹collections的用法。在閱讀本文之后,各位讀者可自行深入學習。以下兩個鏈接都來自Python官方文檔,第一個是英文鏈接,第二個是中文鏈接。8.3. collections - High-performance container datatypes - Python 2.7.18 documentation?docs.python.orghttps://docs.python.org/zh-cn/3/library/collections.html?docs.python.org
3、多次條件判定
我們經常會遇到一種情況,在執行特定操作之前,往往需要通過多次的條件判定。只有在所有的條件都滿足的情況下,才會進行目標操作。
有一個改名的需要求,只有當名字滿足一系列的條件,才可以對名字進行更改;否則提示改名失敗的原因。名字需要滿足的一系列條件是:
1、長度不得大于10
2、只包含26個英文字母
3、有且只有首字母大寫,其它字母都是小寫
4、最后一個字母必須是元音字母
我們先為各種判定結果定義一些常量,方便后面使用:
# Python沒有enum類型,我們可以通過class來模擬
class EAlterRet:
Succ = 0,
SizeOutOfRange = 1,
InvalidCharacter = 2,
NotCapitalized = 3,
EndWithConsonant = 4,
AlterNameErrors = (
"Succ", # 成功
"SizeOutOfRange", # 太長
"InvalidCharacter", # 非法字符
"NotCapitalized", # 非首字母大寫的
"EndWithConsonant", # 未以元音字母結尾
)
第一個實現方式,也就是最容易想到的實現方式,自然是多個if語句了。
import re
class Human:
def __init__(self, name):
self.name = name
def dealWithErrors(self, target, code):
ret = AlterNameErrors[code]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
def alterName(self, target):
# 長度是否大于10
if len(target) > 10:
self.dealWithErrors(target, EAlterRet.SizeOutOfRange)
return
# 是否存在非法字符
if re.search(r'[^a-zA-z]', target):
self.dealWithErrors(target, EAlterRet.InvalidCharacter)
return
# 是否有且只有首字母大寫
if target.lower().capitalize() != target:
self.dealWithErrors(target, EAlterRet.NotCapitalized)
return
# 是否以無意結尾
if not re.search(r'[AEIOUaeiou]$', target):
self.dealWithErrors(target, EAlterRet.EndWithConsonant)
return
# 改名成功
self.name = target
第二種方式是使用類似于do/while(false)的結構。由于Python沒有do/while(false)結構,我們可以使用一次for循環來替換。
import re
class Human:
def __init__(self, name):
self.name = name
def alterName(self, target):
error = EAlterRet.Succ
for i in range(0, 1):
if len(target) > 10:
error = EAlterRet.SizeOutOfRange
break
if re.search(r'[^a-zA-z]', target):
error = EAlterRet.InvalidCharacter
break
if target.lower().capitalize() != target:
error = EAlterRet.NotCapitalized
break
if not re.search(r'[AEIOUaeiou]$', target):
error = EAlterRet.EndWithConsonant
break
if error == EAlterRet.Succ: # 條件滿足,改名成功
self.name = target
else: # 條件不滿足,處理錯誤
ret = AlterNameErrors[code]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
使用for/break的好處是,我們可以把錯誤放到后面統一處理,避免使用重復的錯誤處理代碼。
第三種方式是得用異常。雖然我們這個例子引入異常有點牽強,但舉一反三,各位讀者在以后的實際開發過程中,就可以多一個思路。
import re
class Human:
def __init__(self, name):
self.name = name
def alterName(self, target):
error = EAlterRet.Succ
try:
if len(target) > 10:
raise Exception(EAlterRet.SizeOutOfRange)
if re.search(r'[^a-zA-z]', target):
raise Exception(EAlterRet.InvalidCharacter)
if target.lower().capitalize() != target:
raise Exception(EAlterRet.NotCapitalized)
if not re.search(r'[AEIOUaeiou]$', target):
raise Exception(EAlterRet.EndWithConsonant)
expect Exception as ex:
ret = AlterNameErrors[ex.args[0]]
msg = "Alter name to '{}', result:{}".format(target, ret)
print(msg)
else:
self.name = target
finally: # 如果有需要的話,可以有finally語句
# 做點別的什么事情
在不考慮效率的情況下,使用異常應該是三種方式中最簡潔的方式。使用異常還有一個好處,就是可以在finally中做點別的什么事件。因為無論try中有raise還是有return,finally的語句總是會被執行。也就是說,無論發生什么情況,我們總是可以在finally做一些清理工作,如關閉之前打開的文件、關閉socket、或者打一些日志……
4、猜猜看
猜猜下面的這段代碼中,構造函數做了些什么。請在評價區中進行留言和討論 。
class FancyConstructor:
def __init__(self, a, b, c, d, e, f, g):
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
總結
以上是生活随笔為你收集整理的python粘性拓展_Python基础之:拓展解决问题的思路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python查找字符串出现次数_Pyth
- 下一篇: 上海脂肪填充面部大概价格是多少?