python的模塊與包進階篇,自定義導入語句的祕籍
將代碼組織成很多分層模塊構成的包
folder/
__init__.py
sub_folder/
__init__.py
a.py
b.py
封裝成包只用確保每個目錄都定義了一個__init__.py
文件,該文件的主要目的是要包含不同運行級別的包的可選初始化代碼,絕大多數時候都讓該文件空着就好。
但是我們還是會發現即使沒有__init__.py
文件,依然可以導入包,實際上是創建了一個“命名空間包”
控制模塊被全部導入的內容
當希望模塊內有些內部函數不要被導出,並且需要使用
from module import *
的時候,可以使用__all__
變量在模塊中列出需要導出的內容
def A():
pass
def B():
pass
__all__ = ['A'] # 該變量需要定義一個list,如果是空列表,那麼將沒有東西導出
使用相對路徑名導入模塊
example:
package/
__init__.py
A/
__init__.py
xxx.py
zzz.py
B/
__init__.py
yyy.py
如果需要導入同目錄的模塊,比如xxx需要導入zzz中的模塊:
from . import zzz
如果需要導入不同目錄的模塊,比如xxx需要導入yyy中的模塊:
from ..B import yyy
導入模塊可以使用絕對路徑也可以使用相對路徑,但是絕對路徑的不好的地方是這將頂層包名硬編碼到源碼中,如果我們重新組織代碼,代碼將很難繼續工作。
這裏有一個很常見的問題:相對導入只適用於在合適的包中的模塊。在頂層的腳本的簡單模塊內,他們將不起作用。所以如果包的部分被作爲腳本直接執行,那麼會報no module name ...
的錯誤
在工作目錄裏面執行:
python modules/database/function.py
的時候就會報錯,因爲function.py裏面使用的是相對路徑來導入模塊,我們可以使用-m
的方式來解決:
python -m modules.database.function
就可以成功運行
-m
的作用就是將python中的模塊當做腳本來執行
直接運行目錄而不是單獨文件
如果應用已經不是一個簡單的腳本而是一個包含多個文件的應用,可以將應用程序放進它自己的目錄並且添加一個__main__.py
文件,那麼就可以直接在頂級目錄裏面運行Python解釋器,比如頂級目錄的名字爲folder:
運行python folder
不使用open()函數來讀取位於包中的數據
如果要讀取數據文件,可能會直接使用內置的I/O功能代碼,比方說open(),後面再說這種方法的缺點,這裏推薦使用的是:
import pkgutil
data = pkgutil.get_data(__package__, 'metadata.txt')
# 這個生成的變量是包含該文件原始內容的字節字符串
使用內置open()方法的缺點:
- 包對解釋器的當前目錄幾乎沒有控制權,所以每次都需要使用絕對文件名,雖然可以弄清文件路徑,但是很雜亂
- 如果用open()對一個包含數據文件的歸檔文件進行操作,根本不會工作
通過字符串導入模塊
如果想導入一個模塊,但是模塊名需要手動輸入,然後模塊的名字在字符串內
import importlib
math = importlib.import_module('math')
math.sin(x)
string類型倒序:[::-1]
算法問題:根據對象特定的屬性進行排序(比如對字典排序)
基本思想:配合使用sorted()
裏面的key關鍵字,以及operator.itemgetter()
方法
以列表的某個值爲key進行排序:
alist = [[1,2], [2,3], [3,4], [2,6], [4,7]]
sorted_list = sorted(alist, key=operator.itemgetter(1))
這樣的話就是對大列表中的每個小列表的第二個值進行排序
以字典的value進行排序:
adict = {'1': 10, '2': 22....}
sorted_list = sorted(adict.items(), key=operator.itemgetter(1))
這裏我們對字典使用了items()方法,這樣就將字典轉換成一個類列表的對象,然後遍歷列表內的每一對鍵值對,以位置1的值爲關鍵字進行排序。如果不使用items()方法進行轉換,那麼直接遍歷字典得到是字典的key,一個key是沒有itemgetter屬性的,所以會報錯。
算法問題:從數組中找出數字之和等於特定值
Partition Equal Subset Sum
這個問題是是否能把一個列表劃分爲兩個所有數之和相等的小列表?
基本思路: 每個小列表的數字之和等於大列表數字之和的二分之一,設爲target_value
方法1:
比較簡單的思路就是建立一個集合,將列表內所有可能的總和值都放到這個集合內,如果target_value在這個集合中,那就表明列表內部有一堆數字總和是整體列表總和的一半,那麼剩餘的那堆數字的總和就是列表總和的另一半。所以就可以劃分,返回True。
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
target = sum(nums)/2
if sum(nums) % 2 != 0:
return False
all_sum = {0}
for i in nums:
all_sum.update({(x + i) for x in all_sum})
return target in all_sum
上面代碼的{0}不是一個字典,python的集合是一個無序不重複元素序列,可以使用大括號{}
或者set()
函數創建集合。但是如果要創建一個空集合一定要使用set()而不是{},因爲空的{}是用來創建一個空字典的。集合添加元素的方法用set.update()
Python數據結構與算法 – 進階篇
同時將序列中所有值賦值給多個變量:
假設現在有一個包含n個元素的元組或者是序列, 我們需要將裏面的n個元素賦值給n個變量:
alist = [1, 2, 3]
a, b, c = alist
這樣a,b,c就同時賦值了,唯一要求是變量數量需要和序列中元素數量一致。
那麼如果變量的數量不一致,或者不知道序列中的元素數量怎麼辦?:
可以使用python的星號表達式
alist = [1,2,3,4,5]
a, b, *c = alist
c
>>> [3,4,5]
上面的加了星號的c,不管解壓的數量是多少,就算是0,返回的也一定是列表!!!
從集合中抽取最大或者最小的n個元素:
可以使用heapq模塊的nlargest()和nsmallest()函數獲取:
import heapq
alist = [1,2,3,4,5,6]
print(heapq.nlargest(3, alist))
>>> [4,5,6]
這兩個函數還可以接受一個關鍵字參數用於更復雜的數據結構。
adic = [{...}, {...},...]
result = heapq.nsmallest(3, adic, key=lambda s: s['price']
上面會對列表中的每一個字典進行比較,但是如何比較字典呢?我們上面指定了關鍵字爲price,所以會對每一個字典的price進行比較。
將list進行排序並且針對特定算法進行排序
我們使用sorted()方法進行排序,該方法有三個屬性:
sotred(iterable, reverse = True, key = function)
該方法第一個iterable是待排序的可迭代對象,reverse屬性指明是否需要反向排序,最後一個key屬性指定特定算法進行排序,下面例子針對特定算法排序:
def f(x):
return x-10
alist = [1,2,3,4,5,6]
sortedList = sorted(alist, key=f)
或者
sortedList = sorted(alist, key=lambda x: x-10)
找出序列中出現次數最多的元素
我們使用collections.Counter
類,這個類專門用於解決這個問題,它還有一個most_common()
方法直接能得到我們想要的答案,所以遇到這類問題,優先使用這個類,而不是手動建立字典。
word = ['a', 'a', 'b']
word_counts = Counter(word)
# 生成一個counter對象
top_three = word_counts.most_common(3)
# 找到頻率最高的三個,返回成[('a',2), ('b', 1)]
對於一個字典列表,通過某字段排序或分組
通過使用operator
的itemgetter
函數可以獲取字段
通過字段排序:
rows = [{}, {}, ...]
rows_by_id = sorted(rows, key=itemgetter('id'))
通過字段分組:
首先需要對字段排序,然後需要用到itertools.groupby()
函數對數據分組
rows.sort(key=itemgetter('date')
groupby(rows, key=itemgetter('date')
這裏需要注意的是groupby只檢查連續的元素,所以如果沒有先對字段進行排序,那麼就返回不了我們想要的結果。
過濾列表元素和字典
過濾列表元素除了可以使用filter以外,還可以使用最基本的列表推導方法:
new_list = [n for n in mylist if n>0]
這樣當輸入很大的時候,就會輸出一個非常大的結果集
從字典中提取符合條件的子集:使用字典推導:
new_dict = {key:value for key, value in old_dict.items() if value>100}
映射名稱到序列元素上(namedtuple):
我們需要使用collection.namedtuple()
函數來解決,這個函數需要傳遞一個類型名和需要的字段,然後就會返回一個類,可以初始化這個類爲定義的字段傳值。
from collections import namedtuple
builder = namedtuple('builder', ['name', 'age'])
# 這裏創建一個類,規定了namedtuple的字段名,按順序排序
# 下面開始初始化這個類,給定義的字段傳遞值
build = builder('Rob', 16)
# 然後就可以通過字段名獲取值了
build.name
>> Rob
Python元編程:Python裝飾器
如果代碼中只要存在高度重複的東西,都應該想想是否有更好的解決方案,在python中通常都可以使用元編程來解決這類問題,元編程就是關於創建操作源代碼的函數和類, 主要技術有裝飾器,類裝飾器,元編程等等。
函數的裝飾器:
import time
from functools import wraps
def timethis(func):
# 這個裝飾器用於計算函數運行的時間
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time
print(func.__name__, end-start)
return result
return wrapper
# 使用這個裝飾器
@timethis
def countdown(n):
while n > 0:
n -= 1
上面的使用裝飾器方法其實等同於countdown = timethis(countdown)
, 所以裝飾器的原理是將一個函數作爲參數傳給另一個函數。
裝飾器並不會修改原始函數的參數簽名以及返回值,使用*args和**kwargs的目的是確保任何參數都能試用,而返回結果值基本都是調用原始函數的返回結果。
-
任何時候我們定義裝飾器,我們都需要使用@wrap來註解底層包裝函數,因爲它可以保留這個函數的重要元信息,比如
__name__
,@wrap還有一個重要的作用就是可以通過屬性__wrapped__
直接訪問被裝飾器包裝的函數。 -
解除一個函數的裝飾器:
如果一個函數已經被裝飾器包裝了,但是我們想要訪問未包裝的原始函數,上一條已經說了@wrap的__wrapped__
屬性可以直接訪問被包裝的函數,所以我們也可以使用這個屬性來獲取未包裝的函數
original_add = add.__wrapped__
但是如果函數被多個裝飾器包裝,這個屬性的行爲是不可預知的。如果使用的內置裝飾器@staticmethod和@classmethod,這個屬性是不適用的。 -
構建帶有參數的裝飾器:
如果我們想構建一個裝飾器,給函數添加日誌功能,同時允許用戶指定日誌的級別和其他選項:
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging,getLogger(logname)
logmsg = message if message else func.__name__
@wrap(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
# 使用裝飾器
@logged(logging.DEBUG)
def add(x, y):
return x+y
這樣寫的原理就是最外層的logged函數接受參數並將它們作用在內部的裝飾器上,內層的decorate()函數接受一個函數作爲參數,然後在接受的函數上包裝一個包裝器,該包裝器可以使用傳遞給logged的參數。
@decorator(x, y, z)
def func(a, b):
pass
等價於
def func(a, b):
pass
func = decorator(x,y,z)(func)
這裏decorator()函數返回的結果應該是一個可調用對象,並且接受一個函數作爲參數。
介紹python中的@staticmethod@classmethod方法
class A(object):
def m1(self, n): # 普通的實例方法
print("self:", self)
@classmethod
def m2(cls, n): # 類方法,第一個參數必須是cls
print("cls:", cls)
@staticmethod
def m3(n): # 靜態方法
pass
a = A()
- 實例方法:
A.m1
是一個沒有綁定對象的方法,所以調用時必須顯式地傳入一個實例對象,但是a.m1
已經綁定了實例的方法,所以隱式地把對象a傳遞給了self參數。所以A.m1(a, 1)
等同於a.m1(1)
- 類方法:如果要在方法裏面調用靜態類,那麼定義成類方法合適,因爲如果要是定義成靜態方法,那麼就要顯式地引用類A,不利於繼承
A.m2
和a.m2
都已經自動綁定了類對象A的方法,對於a.m2
,python可以通過實例對象a找到它的類對象是A,然後把A傳遞給了cls。所以A.m2(1)
等同於a.m2(1)
- 靜態方法:如果方法中不需要訪問任何實例方法和屬性,純粹通過傳入參數的功能性方法。
靜態方法跟普通函數沒有區別,跟類和實例都沒有綁定關係,所以不管通過類還是實例都可以使用該方法
裝飾器實例:
裝飾器一般用於插入日誌,事務處理,緩存,權限效驗等場景
1. 簡單的不帶參數的裝飾器:
其實就是定義一個函數,參數爲func
,然後裏面再定義一個wrapper()
函數,然後最後這整個定義的函數需要return這個wrapper
def add_log(func):
def wrapper():
logger.info('func {} is running'.format(func.__name__))
return func()
return wrapper
@add_log
def a():
print('aaa')
a()
2. 被裝飾的函數需要帶有參數:
def add_log(func):
def wrapper(*args, **kwargs):
logger.info('func {} is running'.format(func.__name__))
return func(*args, **kwargs)
return wrapper
3. 帶參數的裝飾器:
上面的裝飾器接受的唯一參數就是被裝飾的函數,但是在使用裝飾器的時候,我們可能會需要給這個裝飾器傳遞參數,比如給裝飾器指定日誌等級:
def add_log(level):
def decorator(func):
def wrapper(*args, **kwargs):
logger.info('func {} is running'.format(func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator
其實可以發現,就是將之前的裝飾器函數名改爲了decorator,然後在這個裝飾器的外面再進行一層函數封裝,然後這個封裝函數返回這個裝飾器。
Python 類與對象的高級用法
讓對象以字符串顯示:
平時我們想打印一個python對象,只會顯示一個object對象,但是現在我們需要直接打印出這個對象裏面的內容,讓其更具有可讀性。
Class Pair:
def __init__(self, x ,y):
self.x = x
self.y = y
def __repr__(self):
# 這個方法返回一個實例的代碼,跟我們使用交互式解釋器顯示的值一樣
return 'Pair({0.x!r}, {o, y!r})'.format(self)
def __str__(self):
# 將實例轉換爲一個字符串,使用str(), print()會輸出這個字符串
return '({0.x!s}, {o, y!s})'.format(self)
這個format不是很容易看懂,其實{0.x}的意思是對應format裏面第一個參數的x屬性,所有在上述代碼裏面,0就是self, 也就是返回本身對象的x屬性。
如果__str__()屬性沒有被定義的話,就會使用__repr__()屬性來代替,所以我們可以簡單的只定義repr屬性
我們也可以使用一種看起來更直觀的方式:
def __repr__(self):
return 'Pair('%r, %r)' % (self.x, self.y)
對象支持上下文
如果我們想要我們的對象支持上下文管理協議,with語句,怎麼辦?
爲了讓對象支持with,我們需要實現兩個方法,__enter__()
和__exit__()
:
class Connection:
def __init__(self, address,....):
self.address = address
...
def __enter__(self):
...
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
這個對象用於創建網絡連接,但是初始化這個對象的時候,它並沒有創建一個網絡連接,因爲需要使用with在上下文環境裏面纔會觸發enter和exit:
conn = Connection(...)
with conn as s: # 這樣纔會真正創建一個連接
s.send('safa')
....
with中發生了異常也不用擔心,因爲exit的第三個參數會自動控制異常。
類中封裝私有屬性
- 任何以單一下劃線開頭的屬性或者方法都應該是內部實現,但是這不意味着我們從外面不能訪問這些內部屬性和方法,只是一種約束,這樣做肯定不好
- 任何以兩個下劃線爲開頭的屬性和方法會導致訪問名稱變成其他形式,比如在一個class A中有一個
__data
的屬性,它會被直接重命名爲_A__data
屬性。這樣的目的就是保證在繼承的時候永遠不會被覆蓋
爲類中屬性增加附加條件@property
(如:規定屬性類型)
class Person:
def __init__(self, name):
self.name = name
# 這裏不使用self._name的原因是當初始化對象的時候,也能直接調用下面的setter
@property # 這裏也相當於一個getter
# 將下面的方法定義爲一個屬性。
# 只有這裏使之成爲了一個屬性,後面的兩個方法纔可以實現
def name(self):
return self._name
@name.setter
def name(self, value):
if 規定的條件:
error
self._name = value
@name.deleter
def name(self):
error # 表示不允許刪除這個屬性
使用實例:
a = Person('jaden')
a.name # 調用了getter,返回正確的值
a.name = 1 # 調用了setter,報錯,因爲setter裏面規定了不是字符串就報錯
del a.name # 調用了deleter,報錯,因爲deleter裏面規定了不能刪除
使用延遲計算屬性功能
如果想將只讀屬性定義爲一個property,只在訪問的時候纔會計算結果,但是一旦被訪問過後,又希望下次不用再次計算,希望結果能被緩存起來。
先使用一個描述器定義它,然後在類中使用。。。
定義藉口或者抽象基類: ABC和collections等模塊
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self,...):
pass
@abstractmethod
def write(self, ....):
pass
抽象基類的特點是它不能直接被實例化,所以使用a = IStream()
會報錯。抽象類的目的就是讓別的類集成它的特定的方法。
標準庫裏面有很多用到抽象基類的地方,比如:
- collections模塊裏面有很多實現容器和迭代器,序列,映射,集合之類的抽象基類
- numbers庫裏面有很多數據對象的抽象基類
- io庫裏面有很多跟I/O操作相關的抽象基類
抽象基類還有一個很常用的用法是對類進行類型檢查:
雖然ABCs模塊可以很方便的做類型檢查,但是儘量不要使用它,因爲強制類型檢查只會讓代碼變得更加複雜。
import collections
if isinstance(x, collections.Iterable): # 檢查該類是不是一個迭代器
pass
使用collections模塊裏面的抽象基類,實現自定義類支持迭代等功能:
比如我們要使我們的自定義抽象基類能支持迭代,我們需要讓該自定義類繼承 collections.Iterable
import collections
class A(collections.Iterable):
pass
a = A() # 報錯:無法實例化,因爲我們需要繼承collections.Iterable的所有抽象方法
# 但是對於迭代的功能,我們知道,只需要實現__iter__方法即可
=======================================================================
a = collections.Sequence() # 同上面,報錯,也會顯示所有的需要實現的抽象方法
# 我們可以看到需要實現__getitem__和__len__方法
# 所以下面我們在自定義類中實現這些抽象方法
class AList(collections.Sequence):
def __init__(self):
pass
def __getitem__(self, index):
return self._items[index]
def __len__(self):
return len(self._items)
def additem(self ,item):
# 這是一個在排序列表中插入元素的高效方法,保證插入後的列表順序
bisect.insort(self._items, item)
現在這個自定義類跟列表沒有什麼區別,支持普通列表的幾乎所有操作
使用Mixins拓展類的功能
加入我們需要一組類都使用一種你定義的方法,但是這些類都是一些完全不同的類,之間沒有任何的繼承關係,所以並不能將你定義的方法放入一個基類,然後讓這一組類都繼承這個基類。
如果我們想讓自己定義的類具有添加日誌,唯一性設置,類型檢查等功能的話,我們可以繼承一些混入類,於此同時我們仍然可以繼承其他的類:
class SetOnceMappingMixin:
# this class allow key only be setted once
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError
return super().__setitenm__(key, value)
這樣一個單獨的混入類沒有任何的作用,而且當我們實例化它的話會報錯,但是我們結合其他的類一起使用:
from collections import defaultdict
class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
pass
上面這個類同時繼承了混入類和其他類,該類擁有兩者的功能。
混入類沒有自己的狀態信息,因爲沒有__init__
方法,沒有實力屬性,所以混入類會明確定義__slots__ = ()
。這裏需要記住slots是用來對類限制屬性的,只有在tuple裏面的屬性才能被添加,這裏爲空,表示不能添加任何實例屬性。
狀態機
如果不想在代碼中出現很多次重複的條件判斷,可以創建一個狀態機。
待完成
讓類支持比較操作
如果我們想讓類的實例能支持標準的運算符 !=, >, <…,但是我們又不想操作一系列複雜的方法,我們就可以使用裝飾器functools.total_ordering
來實現。
我們首先需要使用這個裝飾器裝飾一個類,然後我們需要在類中定義一個__eq__()
方法,外加lt,le,gt,ge中的任意一個,這個裝飾器就會自動爲類填充其他的比較方法了
@total_ordering
class House:
def __init__(self):
...
@property
# 這個是屬性裝飾器,將下面的方法定義爲一個屬性,只有被調用的時候纔會計算
# 所以下面調用的時候都不需要括號
def living_space(self):
return sum(r.square_feet for r in self.rooms)
def __eq__(self, other):
return self.living_space == other.living_space
def __lt__(self, other):
return return self.living_space < other.living_space
使用這個類實例化出來的實例都可以進行運算符比較了。
未完待續。。。