Python編程常用技巧,你知道幾個(gè)?
現(xiàn)在Python是個(gè)炙手可熱的技能,很多人都想著入手學(xué)學(xué)Python編程,甚至包括一些知名人士,比如知名地產(chǎn)商潘石屹就開始學(xué)Python。關(guān)于Python編程的內(nèi)容在網(wǎng)絡(luò)上也非常多,本文給大家總結(jié)一些Python編程的常見技巧,以幫助初學(xué)者快速入門。
一、字符串處理技巧
1. 清理用戶輸入
對(duì)輸入的的值進(jìn)行清理處理,是常見的程序要求。比如要做大小寫轉(zhuǎn)化、要驗(yàn)證輸入字符的注入,通??梢酝ㄟ^寫正則用Regex來做專項(xiàng)任務(wù)。但是對(duì)于復(fù)雜的情況,可以用一些技巧,比如下面:
- user_input = "This\nstring has\tsome whitespaces...\r\n"
- character_map = {
- ord('\n') : ' ',
- ord('\t') : ' ',
- ord('\r') : None
- }
在此示例中,可以看到空格字符"\n"和"\t"都被替換為空格,而 "\r"被刪除。
這是一個(gè)簡(jiǎn)單的示例,我們還可以使用unicodedata包和combinin()函數(shù)來生成大的映射表,以生成映射來替換字符串。
2. 提示用戶輸入
命令行工具或腳本需要輸入用戶名和密碼才能操作。要用這個(gè)功能,一個(gè)很有用的技巧是使用getpass模塊:
- import getpass
- user = getpass.getuser()
- password = getpass.getpass()
這三行代碼就可以讓我們優(yōu)雅的交互提醒用戶輸入輸入密碼并捕獲當(dāng)前的系統(tǒng)用戶和輸入的密碼,而且輸入密碼時(shí)候會(huì)自動(dòng)屏蔽顯示,以防止被人竊取。
3. 查找字符串頻率
如果需要使用查找類似于某些輸入字符串的單詞,可以使用difflib來實(shí)現(xiàn):
- import difflib
- difflib.get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'], n=2)
# 返回['apple', 'ape']
difflib.get_close_matches會(huì)查找相似度最匹配的字串。本例中,第一個(gè)參數(shù)與第二個(gè)參數(shù)匹配。提供可選參數(shù)n,該參數(shù)指定要返回的最大匹配數(shù),以及參數(shù)cutoff(默認(rèn)值為0.6)設(shè)置為thr確定匹配字符串的分?jǐn)?shù)。
4. 多行字符串
Python中可以使用反斜杠:
- In [20]: multistr = " select * from test \
- ...: where id < 5"
- In [21]: multistr
- Out[21]: ' select * from test where id < 5'
還可以使用三引號(hào):
- In [23]: multistr ="""select * from test
- ...: where id < 5"""
- In [24]: multistr
- Out[24]: 'select * test where id < 5'
上面方法共有的問題是缺少合適的縮進(jìn),如果我們嘗試縮進(jìn)會(huì)在字符串中插入空格。所以最后的解決方案是將字符串分為多行并且將整個(gè)字符串包含在括號(hào)中:
- In [25]: multistr = ("select * from multi_row "
- ...: "where row_id < 5 "
- ...: "order by age")
- In [26]: multistr
- Out[26]: 'select * from multi_row where row_id < 5 order by age'
5. 處理IP地址
日常常用的一個(gè)是驗(yàn)證和匹配IP地址,這個(gè)功能有個(gè)專門的模塊ipaddress可以來處理。比如我們要用IP網(wǎng)段(CIDR用IP和掩碼位)生成一個(gè)IP地址列表:
- import ipaddress
- net = ipaddress.ip_network('192.168.1.0/27')
結(jié)果:
- #192.168.1.0
- #192.168.1.1
- #192.168.1.2
- #192.168.1.3
- #...
另一個(gè)不錯(cuò)的功能IP地址是否在IP段的驗(yàn)證:
- ip = ipaddress.ip_address("192.168.1.2")
- ip in net
- # True
- ip = ipaddress.ip_address("192.168.1. 253")
- ip in net
- # False
ip地址轉(zhuǎn)字符串、整數(shù)值的互轉(zhuǎn):
- >>> str(ipaddress.IPv4Address('192.168.0.1'))
- '192.168.0.1'
- >>> int(ipaddress.IPv4Address('192.168.0.1'))
- 3232235521
- >>> str(ipaddress.IPv6Address('::1'))
- '::1'
- >>> int(ipaddress.IPv6Address('::1'))
- 1
注意ipaddress還支持很多其他的功能,比如支持ipv4和ipv6等,具體可以參考模塊的文檔。
二、性能優(yōu)化技巧
1. 限制CPU和內(nèi)存使用量
如果Python程序占用資源太大,想限制資源的使用,可以使用resource包。
- # CPU限制
- def time_exceeded(signo, frame):
- print("CPU 超額...")
- raise SystemExit(1)
- def set_max_runtime(seconds):
- soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
- resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard))
- signal.signal(signal.SIGXCPU, time_exceeded)
- # 限制內(nèi)存使用
- def set_max_memory(size):
- soft, hard = resource.getrlimit(resource.RLIMIT_AS)
- resource.setrlimit(resource.RLIMIT_AS, (size, hard))
對(duì)CPU限制時(shí)候,先獲取特定資源(RLIMIT_CPU)的軟限制和硬限制,然后使用參數(shù)指定的秒數(shù)和獲取的硬限制來設(shè)置。如果超過CPU時(shí)間,將注冊(cè)導(dǎo)致系統(tǒng)退出的信號(hào)。
對(duì)內(nèi)存限制,也先獲取軟限制和硬限制,并用帶有size參數(shù)的setrlimit對(duì)其進(jìn)行設(shè)置。
2. 通過__slots__節(jié)省內(nèi)存
如果程序中有一個(gè)類需要?jiǎng)?chuàng)建大量實(shí)例,那么可能會(huì)對(duì)內(nèi)存占用會(huì)非常大。因?yàn)镻ython使用字典來表示類實(shí)例的屬性,這可以加速執(zhí)行,但內(nèi)存效率很差,通常這不是問題??梢允褂胈_slots__來優(yōu)化:
- import sys
- class FileSystem(object):
- def __init__(self, files, folders, devices):
- self.files = files
- self.folders = folders
- self.devices = devices
- print(sys.getsizeof( FileSystem ))
- class FileSystem1(object):
- __slots__ = ['files', 'folders', 'devices']
- def __init__(self, files, folders, devices):
- self.files = files
- self.folders = folders
- self.devices = devices
- print(sys.getsizeof( FileSystem1 ))
- # Python 3.5下
- #1-> 1016
- #2-> 888
當(dāng)定義__slots__屬性時(shí),Python使用固定大小的數(shù)組作為屬性,而不用字典,這大大減少了每個(gè)實(shí)例所需的內(nèi)存。當(dāng)然使用__slots__也有缺點(diǎn),比如,無法聲明任何新屬性,而且只能在__slots__上使用它們,__slots__的類也不能使用多重繼承。
3. 用lru_cache緩存函數(shù)調(diào)用
都說Python性能差,尤其是一些計(jì)算的時(shí)候,其實(shí)是有一些通用的方法可以解決程序能的問題,比如緩存和記憶術(shù)。使用functools中的lru_cache可以解決迭代計(jì)算中大量重復(fù)迭代調(diào)用問題:
- # CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
在上例中,我們執(zhí)行正在緩存的GET請(qǐng)求(最多3個(gè)緩存結(jié)果)。還使用cache_info方法檢查函數(shù)的緩存信息。裝飾器還提供了clear_cache方法,用于刪除緩存。
4. __all__控制import
某些語言支持import成員(變量,方法,接口)的機(jī)制。在Python中,默認(rèn)所有內(nèi)容都會(huì)import,但是可以使用__all__來限制
- def foo():
- pass
- def bar():
- pass
- __all__ = ["bar"]
通過這樣的方式我們可以限制從some_module import *使用時(shí)可以導(dǎo)入的內(nèi)容。該實(shí)例中,則僅import bar函數(shù)。如果將__all__保留為空,并且在使用通配符import時(shí),不會(huì)import任何東西,會(huì)觸發(fā)AttributeError錯(cuò)誤。
三、面向?qū)ο?/strong>
1. 創(chuàng)建支持With語句的對(duì)象
我們都知道如何使用打開或關(guān)閉語句,例如打開文件或獲取鎖,但是如何實(shí)現(xiàn)自己的方法呢?
可以使用__enter__和__exit__方法實(shí)現(xiàn):
- class Connection:
- def __init__(self):
- ...
- def __enter__(self):
- # Initialize connection...
- def __exit__(self, type, value, traceback):
- # Close connection...
- with Connection() as c:
- # __enter__() executes
- ...
- # conn.__exit__() executes
這是在Python中實(shí)現(xiàn)上下文管理的最常見方法,但是有一種更簡(jiǎn)單的方法:
- from contextlib import contextmanager
- @contextmanager
- def tag(name):
- print(f"<{name}>")
- yield
- print(f"</{name}>")
- with tag("h1"):
- print("This is Title.")
上面的代碼段使用contextmanager管理器裝飾器實(shí)現(xiàn)了內(nèi)容管理協(xié)議。進(jìn)入with塊時(shí),執(zhí)行標(biāo)記函數(shù)的第一部分(在yield之前),然后執(zhí)行該塊,最后執(zhí)行其余的標(biāo)記函數(shù)。
2. 重載運(yùn)算符號(hào)的技巧
考慮到有很多比較運(yùn)算符:__lt__ , __le__ , __gt__,對(duì)于一個(gè)類實(shí)現(xiàn)所有比較運(yùn)算符可能會(huì)很煩人。這時(shí)候可以使用functools.total_ordering:
- from functools import total_ordering
- @total_ordering
- class Number:
- def __init__(self, value):
- self.value = value
- def __lt__(self, other):
- return self.value < other.value
- def __eq__(self, other):
- return self.value == other.value
- print(Number(20) > Number(3))
- print(Number(1) < Number(5))
- print(Number(15) >= Number(15))
- print(Number(10) <= Number(2))
該代碼使用total_ordering裝飾器用于簡(jiǎn)化為類實(shí)現(xiàn)實(shí)例排序的過程。只需要定義__lt__和__eq__。
3. 在一個(gè)類中定義多個(gè)構(gòu)造函數(shù)
函數(shù)重載是編程語言中非常常見的功能。即使Python不能重載正常的函數(shù),我們也可以使用類方法重載構(gòu)造函數(shù):
- import datetime
- class Date:
- def __init__(self, year, month, day):
- self.year = year
- self.month = month
- self.day = day
- @classmethod
- def today(cls):
- t = datetime.datetime.now()
- return cls(t.year, t.month, t.day)
- d = Date.today()
- print(f"{d.day}/{d.month}/{d.year}")
# 14/9/2019
可以不使用構(gòu)造函數(shù)將所有邏輯都放入__init__并使用*args,**kwargs和一堆if語句來解決,但是比較丑陋,沒有可讀性和可維護(hù)性。
4. 獲取對(duì)象信息
Python提供了幾個(gè)函數(shù)以便我們更好的獲取對(duì)象的信息,這些函數(shù)包括:type、isinstance和dir。
其中type():用于判斷對(duì)象類型:
- >>> type(None)
- <class 'NoneType'>
- >>> type(abs)
- <class 'builtin_function_or_method'>
對(duì)類對(duì)象type()返回的是對(duì)應(yīng)class類型。下面是判斷兩個(gè)變量的type類型是否相同:
- >>> type(11) == type(22)
- True
- >>> type('abc') == str
- True
- >>> type('abc') == type(33)
- False
isinstance():可以顯示對(duì)象是否是某種類型
- >>> class Husty(Dog):
- ... pass
- ...
- >>> a = Animal()
- >>> b = Dog()
- >>> c = Husty()
- >>> isinstance(c,Husty)
- True
- >>> isinstance(c,Dog)
- True
- >>> isinstance(c,Animal)
- True
- >>> isinstance(b,Husty)
- False
Husty是Husty、Dog、Animal類型的對(duì)象,卻不能說Dog是Husty的對(duì)象。
dir():用于獲取一個(gè)對(duì)象的所有方法和屬性。返回值是一個(gè)包含字符串的list:
- >>> dir('abc')
- ['__add__', '__class__',…… '__hash__', '__init__', '__i
- ……'isalnum
- 'isidentifier', 'islower', …… 'translate', 'upper', 'zfill']
其中,類似__xx__的屬性和方法都是有特殊用途的。如果調(diào)用len()函數(shù)視圖獲取一個(gè)對(duì)象的長度,其實(shí)在len()函數(shù)內(nèi)部會(huì)自動(dòng)去調(diào)用該對(duì)象的__len__()方法。
5. Iterator和切片
如果直接對(duì)Iterator切片,則會(huì)得到TypeError,指出生成器對(duì)象不可下標(biāo)反問,但是有一個(gè)技巧:
- import itertools
- s = itertools.islice(range(50), 10, 20)
- for val in s:
- ...
使用itertools.islice,可以創(chuàng)建一個(gè)islice對(duì)象,該對(duì)象是生成所需項(xiàng)目的迭代器。但是,這會(huì)消耗所有生成器項(xiàng),直到分片開始為止,而且還會(huì)消耗islice對(duì)象中的所有項(xiàng)。
6. 跳過一些行
有時(shí),必須使用已知以可變數(shù)量的不需要的行(例如注釋)。也可以使用itertools:
- string_from_file = """
- // Author: ...
- // License: ...
- //
- // Date: ...
- Actual content...
- """
- import itertools
- for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")):
- print(line)
該代碼段僅在初始注釋部分之后產(chǎn)生行。如果只想在迭代器的開頭丟棄并且不知道其中有多少個(gè)項(xiàng)目,則此方法很有用。
7. 命名切片
使用大量硬編碼的索引值會(huì)很容易引起代碼繁瑣和破壞代碼可讀性。常用的技巧是對(duì)索引值使用常量,除此之外我們可以使用命名切片:
示例中,可以看到可以索引,方法是先使用slice函數(shù)命名它們,然后在切出一部分字符串時(shí)使用它們。還可以使用切片對(duì)象的屬性.start,.stop和.step獲得更多信息。
四、調(diào)試技巧
1. 腳本調(diào)試
Python的腳本調(diào)試可以是使用pdb模塊。它可以讓我們?cè)谀_本中隨意設(shè)置設(shè)置斷點(diǎn):
- import pdb
- pdb.set_trace()
可以在腳本中任何位置指定pdb.set_trace()并設(shè)置斷點(diǎn),非常便捷
2. 在shell中調(diào)試程序
在shell中,可以使用python的-i選項(xiàng)就可以啟動(dòng)交互式環(huán)境,在該環(huán)境下可以打印運(yùn)行時(shí)變量值并調(diào)用函數(shù)的操作等,比如下面的test.py腳本
- def func():
- return 0 / 0
- func()
在shell中通過python -i test.py運(yùn)行腳本
我們import pdb然后調(diào)用pdb.pm()啟動(dòng)調(diào)試器
會(huì)顯示程序到崩潰的地方,我們退出程序的在該處設(shè)置一個(gè)斷點(diǎn):
- import pdb;
- def func():
- pdb.set_trace()
- return 0 / 0
- func()
再次運(yùn)行它,會(huì)在斷點(diǎn)處停止,step到下一步
用這樣的方法,我們可以調(diào)試和回溯程序的執(zhí)行。通過設(shè)置斷點(diǎn),然后在運(yùn)行程序時(shí),執(zhí)行將在斷點(diǎn)處停止,可以檢查程序,例如列出函數(shù)參數(shù),對(duì)表達(dá)式求值,列出變量或step逐步執(zhí)行等。
五、有用的小工具
1. 一鍵web服務(wù)共享
在Python中可以使用http.server一鍵啟用一個(gè) HTTP 服務(wù)器,這是一個(gè)非常方便的共享工具:
- python -m http.server
在默認(rèn)監(jiān)聽端口為 8000 開啟一個(gè)服務(wù)器,可以自定義端口,比如8888
- python -m http.server 8888
代碼自動(dòng)補(bǔ)齊Jedi
Jedi是一個(gè)用于Python代碼自動(dòng)補(bǔ)齊和靜態(tài)分析的庫。Jedi可以讓我們高效的敲代碼。
目前Jedi已經(jīng)提供了絕大多數(shù)的編輯器插件,包括Vim(jedi-vim),VSC,Emacs,Sublime,Atom等。
2. 美化異常輸出pretty-errors
Python默認(rèn)的報(bào)錯(cuò)輸出非常亂,看的人頭大,可讀性差。這時(shí)候就需要用pretty-errors這個(gè)錯(cuò)誤美化工具了。
結(jié)論
本文我們總結(jié)了一些Python日常并使用中常見的一些技巧,拋磚引玉以給大家一些幫助和啟發(fā)。所有這些功能是Python標(biāo)準(zhǔn)庫中內(nèi)容,在日常使用中也建議大家盡量使用python標(biāo)準(zhǔn)庫,避免使用第三方庫。