現在Python是個炙手可熱的技能,很多人都想著入手學學Python編程,甚至包括一些知名人士,比如知名地產商潘石屹就開始學Python。關於Python編程的內容在網絡上也非常多,本文蟲蟲給大家總結一些Python編程的常見技巧,以幫助初學者快速入門。
字符串處理技巧
清理用戶輸入
對輸入的的值進行清理處理,是常見的程序要求。比如要做大小寫轉化、要驗證輸入字符的注入,通常可以通過寫正則用Regex來做專項任務。但是對於複雜的情況,可以用一些技巧,比如下面:
user_input = "This\\nstring has\\tsome whitespaces...\\r\\n"
character_map = {
ord('\\n') : ' ',
ord('\\t') : ' ',
ord('\\r') : None
}
在此示例中,可以看到空格字符"\\n"和"\\t"都被替換為空格,而 "\\r"被刪除。
這是一個簡單的示例,我們還可以使用unicodedata包和combinin()函數來生成大的映射表,以生成映射來替換字符串。
提示用戶輸入
命令行工具或腳本需要輸入用戶名和密碼才能操作。要用這個功能,一個很有用的技巧是使用getpass模塊:
import getpass
user = getpass.getuser()
password = getpass.getpass()
這三行代碼就可以讓我們優雅的交互提醒用戶輸入輸入密碼並捕獲當前的系統用戶和輸入的密碼,而且輸入密碼時候會自動屏蔽顯示,以防止被人竊取。
查找字符串頻率
如果需要使用查找類似於某些輸入字符串的單詞,可以使用difflib來實現:
import difflib
difflib.get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'], n=2)
# 返回['apple', 'ape']
difflib.get_close_matches會查找相似度最匹配的字串。本例中,第一個參數與第二個參數匹配。提供可選參數n,該參數指定要返回的最大匹配數,以及參數cutoff(默認值為0.6)設置為thr確定匹配字符串的分數。
多行字符串
Python中可以使用反斜槓:
In [20]: multistr = " select * from test \\
...: where id < 5"
In [21]: multistr
Out[21]: ' select * from test where id < 5'
還可以使用三引號
In [23]: multistr ="""select * from test
...: where id < 5"""
In [24]: multistr
Out[24]: 'select * test where id < 5'
上面方法共有的問題是缺少合適的縮進,如果我們嘗試縮進會在字符串中插入空格。所以最後的解決方案是將字符串分為多行並且將整個字符串包含在括號中:
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'
處理IP地址
日常常用的一個是驗證和匹配IP地址,這個功能有個專門的模塊ipaddress可以來處理。比如我們要用IP網段(CIDR用IP和掩碼位)生成一個IP地址列表:
import ipaddress
net = ipaddress.ip_network('192.168.1.0/27')
結果:
#192.168.1.0
#192.168.1.1
#192.168.1.2
#192.168.1.3
#...
另一個不錯的功能IP地址是否在IP段的驗證:
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地址轉字符串、整數值的互轉:
>>> 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等,具體可以參考模塊的文檔。
性能優化技巧
限制CPU和內存使用量
如果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)
# 限制內存使用
def set_max_memory(size):
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
resource.setrlimit(resource.RLIMIT_AS, (size, hard))
對CPU限制時候,先獲取特定資源(RLIMIT_CPU)的軟限制和硬限制,然後使用參數指定的秒數和獲取的硬限制來設置。如果超過CPU時間,將註冊導致系統退出的信號。
對內存限制,也先獲取軟限制和硬限制,並用帶有size參數的setrlimit對其進行設置。
通過__slots__節省內存
如果程序中有一個類需要創建大量實例,那麼可能會對內存佔用會非常大。因為Python使用字典來表示類實例的屬性,這可以加速執行,但內存效率很差,通常這不是問題。可以使用__slots__來優化:
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
當定義__slots__屬性時,Python使用固定大小的數組作為屬性,而不用字典,這大大減少了每個實例所需的內存。當然使用__slots__也有缺點,比如,無法聲明任何新屬性,而且只能在__slots__上使用它們,__slots__的類也不能使用多重繼承。
用lru_cache緩存函數調用
都說Python性能差,尤其是一些計算的時候,其實是有一些通用的方法可以解決程序能的問題,比如緩存和記憶術。使用functools中的lru_cache可以解決迭代計算中大量重複迭代調用問題:
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
在上例中,我們執行正在緩存的GET請求(最多3個緩存結果)。還使用cache_info方法檢查函數的緩存信息。裝飾器還提供了clear_cache方法,用於刪除緩存。
__all__控制import
某些語言支持import成員(變量,方法,接口)的機制。在Python中,默認所有內容都會import,但是可以使用__all__來限制
def foo():
pass
def bar():
pass
__all__ = ["bar"]
通過這樣的方式我們可以限制從some_module import *使用時可以導入的內容。該實例中,則僅import bar函數。如果將__all__保留為空,並且在使用通配符import時,不會import任何東西,會觸發AttributeError錯誤。
面向對象
創建支持With語句的對象
我們都知道如何使用打開或關閉語句,例如打開文件或獲取鎖,但是如何實現自己的方法呢?
可以使用__enter__和__exit__方法實現:
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中實現上下文管理的最常見方法,但是有一種更簡單的方法:
from contextlib import contextmanager
@contextmanager
def tag(name):
print(f"")
yield
print(f"{name}>")
with tag("h1"):
print("This is Title.")
上面的代碼段使用contextmanager管理器裝飾器實現了內容管理協議。進入with塊時,執行標記函數的第一部分(在yield之前),然後執行該塊,最後執行其餘的標記函數。
重載運算符號的技巧
考慮到有很多比較運算符:__lt__ , __le__ , __gt__,對於一個類實現所有比較運算符可能會很煩人。這時候可以使用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裝飾器用於簡化為類實現實例排序的過程。只需要定義__lt__和__eq__。
在一個類中定義多個構造函數
函數重載是編程語言中非常常見的功能。即使Python不能重載正常的函數,我們也可以使用類方法重載構造函數:
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
可以不使用構造函數將所有邏輯都放入__init__並使用*args,**kwargs和一堆if語句來解決,但是比較醜陋,沒有可讀性和可維護性。
獲取對象信息
Python提供了幾個函數以便我們更好的獲取對象的信息,這些函數包括:type、isinstance和dir。
其中type():用於判斷對象類型:
>>> type(None)
<class>
>>> type(abs)
<class>
對類對象type()返回的是對應class類型。下面是判斷兩個變量的type類型是否相同:
>>> type(11) == type(22)
True
>>> type('abc') == str
True
>>> type('abc') == type(33)
False
isinstance():可以顯示對象是否是某種類型
>>> 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類型的對象,卻不能說Dog是Husty的對象。
dir():用於獲取一個對象的所有方法和屬性。返回值是一個包含字符串的list:
>>> dir('abc')
['__add__', '__class__',…… '__hash__', '__init__', '__i
……'isalnum
'isidentifier', 'islower', …… 'translate', 'upper', 'zfill']
其中,類似__xx__的屬性和方法都是有特殊用途的。如果調用len()函數視圖獲取一個對象的長度,其實在len()函數內部會自動去調用該對象的__len__()方法。
Iterator和切片
如果直接對Iterator切片,則會得到TypeError,指出生成器對象不可下標反問,但是有一個技巧:
import itertools
s = itertools.islice(range(50), 10, 20)
for val in s:
...
使用itertools.islice,可以創建一個islice對象,該對象是生成所需項目的迭代器。但是,這會消耗所有生成器項,直到分片開始為止,而且還會消耗islice對象中的所有項。
跳過一些行
有時,必須使用已知以可變數量的不需要的行(例如註釋)。也可以使用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)
該代碼段僅在初始註釋部分之後產生行。如果只想在迭代器的開頭丟棄並且不知道其中有多少個項目,則此方法很有用。
命名切片
使用大量硬編碼的索引值會很容易引起代碼繁瑣和破壞代碼可讀性。常用的技巧是對索引值使用常量,除此之外我們可以使用命名切片:
示例中,可以看到可以索引,方法是先使用slice函數命名它們,然後在切出一部分字符串時使用它們。還可以使用切片對象的屬性.start,.stop和.step獲得更多信息。
調試技巧
腳本調試
Python的腳本調試可以是使用pdb模塊。它可以讓我們在腳本中隨意設置設置斷點::
import pdb
pdb.set_trace()
可以在腳本中任何位置指定pdb.set_trace()並設置斷點,非常便捷
在shell中調試程序
在shell中,可以使用python的-i選項就可以啟動交互式環境,在該環境下可以打印運行時變量值並調用函數的操作等,比如下面的test.py腳本
def func():
return 0 / 0
func()
在shell中通過python -i test.py運行腳本
我們import pdb然後調用pdb.pm()啟動調試器
會顯示程序到崩潰的地方,我們退出程序的在該處設置一個斷點:
import pdb;
def func():
pdb.set_trace()
return 0 / 0
func()
再次運行它,會在斷點處停止,step到下一步
用這樣的方法,我們可以調試和回溯程序的執行。通過設置斷點,然後在運行程序時,執行將在斷點處停止,可以檢查程序,例如列出函數參數,對錶達式求值,列出變量或step逐步執行等。
有用的小工具
一鍵web服務共享
在Python中可以使用http.server一鍵啟用一個 HTTP 服務器,這是一個非常方便的共享工具:
python -m http.server
在默認監聽端口為 8000 開啟一個服務器,可以自定義端口,比如8888
python -m http.server 8888
代碼自動補齊Jedi
Jedi是一個用於Python代碼自動補齊和靜態分析的庫。Jedi可以讓我們高效的敲代碼。
目前Jedi已經提供了絕大多數的編輯器插件,包括Vim(jedi-vim),VSC,Emacs,Sublime,Atom等。
美化異常輸出pretty-errors
Python默認的報錯輸出非常亂,看的人頭大,可讀性差。這時候就需要用pretty-errors這個錯誤美化工具了。
結論
本文我們總結了一些Python日常並使用中常見的一些技巧,拋磚引玉以給大家一些幫助和啟發。所有這些功能是Python標準庫中內容,在日常使用中也建議大家儘量使用python標準庫,避免使用第三方庫。
閱讀更多 蟲蟲安全 的文章