1,裝飾器:decorator(又叫語法糖)
本質是函數,功能是用來裝飾的,裝飾其他函數:就是為其他函數添加附加功能。
原則:a,不能修改被裝飾的函數的源代碼
b,函數的調用方式也不能修改
說明:盡管加了裝飾函數,對原函數沒有任何影響,原函數感覺不到裝飾器的存在,原函數該怎么運行還怎么運行。
2、基本常識:
a,函數即“變量”:函數就是變量。
b,高階函數:滿足兩個條件之一就是,
之一:把一個函數名當做實參傳給另外一個函數 (在不修改被裝飾函數的源代碼的情況下為其添加裝飾功能)
例子:
def bar():
print('111111111')
def test1(func):
print(func)
test1(bar)
------------>
運行后打印出來的是時內存地址:
<function bar at 0x0000000000000D521453> 這個內存地址里存的就是對應變量的內容
之二:返回值中包含函數名,返回值也可以字符串,數字,列表,函數名,函數執行。
c,嵌套函數(在一個函數的內容主體內又定義了一個新的函數)
def bar():
def foo():
print('In the foo')
return 'aa'
print('In the bar')
return foo()
bar()
運行結果如下:
In the bar
In the foo
d,匿名函數:沒有名稱的函數(注意,此時的內存回收機制),他在內存中保存機制:此時的門牌號
如:
calc = lambda x:x*3
calc(3)
e,高階函數+嵌套函數=裝飾器
實例1:函數的調用,不是裝飾器
def bar(): ---------> 存到內存里的一個名稱,相當於門牌號,只用通過這個門牌號才能里面具體的內容
print('in the bar') -------> 存到內存里的對應門牌號的具體的內容,實實在在的存下來。python的內存機制,當門牌號沒有時才會回收里面的具體的內容,才是釋放內存,有多個門牌號時,所有的門牌都回收才會釋放該內存
def foo():
print('in the foo')
bar()
foo()
實例2:python的內存回收機制
x = 1 直到python的程序結束,才會回收x =1這個內存,要么在程序中定義del來結束這個變量,否則就是直到程序結束才回收。del清除的就是1這個具體的內容的門牌號,內核里里有個內存清除器,當它到點啟動時,發現這個1沒有人引用了,此時才會清空1的這個內容。這就是內存回收機制
實例3:
def bar():
pass --------> pass :當代碼運行到這一行的時候。跳過。相當於執行了一個空值。
bar
---->
<function bar at 0X000000000000D50158> 此時就是內存地址
bar() 此時就是基於內存地址在調用,即調用該函數(變量)的具體內容
實例4:高階函數之將一個函數作為另外一個函數的實參傳進去:
def bar():
pass
def test1(func):
print(func) --------> 打印出來的是內存地址
func() --------> 調用基於func內存地址的具體內容。
test1(bar)
實例5:高階函數之將一個函數作為另外一個函數的實參傳進去::
import time
def bar():
print(555555)
def test1(func): -------> 此處的func就是形參的參數名,會被后面的實參的bar代替
start_time=time.time()
func() --------> 運行bar函數
stop_time=time.time()
print("the func run time is %s" %(stop_time-start_time))
test1(bar)
實例6:高階函數之將一個函數作為另外一個函數的返回值傳進去::
import time
def bar():
time.sleep(3)
print('in the bar')
def test2(func):
print(func) --------> 打印出來內存地址
return func --------> 將func這個參數作為返回值
print(test2(bar)) --------> 打印一個運行后的函數就是打印他的返回值(即return后的內容)!!!!!!!
----------->
解說:
test2(bar) 相當於將bar的內存地址傳給test2了
test2(bar())相當於將bar的變量內容傳給test2了
import time
def bar():
time.sleep(3)
print('in the bar')
def test2(func):
print(func) -------> 打印出來內存地址
return func --------> 將bar的內存地址返回回來
t=test2(bar) --------> 將test2(bar)運行后的返回值再賦值給t這個變量!!!!!!
print(t)
----->
<function bar at 0X000000000000D50158>
<function bar at 0X000000000000D50158>
注意:這里的bar函數並沒有運行。只是調用了bar這個函數的名稱而已。所以只有上面的內存地址顯示出來。
---------------------------------------------------------------------------------------------------------------------
import time
def bar():
time.sleep(3)
print('in the bar')
def test2(func):
print(func) --------> 打印出來內存地址
return func --------> 將bar的內存地址返回回來
t=test2(bar) -------->這一步的運行結果就是打印了bar()函數的內存地址,並將該內存地址賦值給了t
t() ---- 運行bar,注意bar只是門牌號,運行t這個內存地址的主體內容,就是運行bar()這個實體。
----->
<function bar at 0X000000000000D50158>
in the bar
-----------------------------------------------------------------------------------------------------------------------
import time
def bar():
time.sleep(3)
print('in the bar')
def test2(func):
print(func) ---------> 打印出來內存地址
return func ---------> 將bar的內存地址作為返回值
bar=test2(bar) ---------> 重新給bar賦值
bar() -----------> 再次運行bar
總結:返回值中包含函數名(不修改函數的調用方式)
------>
<function bar at 0x00000000004C3E18>
In the bar.
總結:該例中兩次給bar賦值,一次是定義成一個函數,第二次是定義成了內存地址賦值。但是該內存地址的主體內容仍然沒變。故仍然可以運行。
-----------------------------------------------------------------------------------------------------------------------
實例7,嵌套函數:在一個函數體內,用def去申明一個新的變量
def foo():
print('000000')
def bar(): ---------> 在一個函數體內定義一個新的函數,其作用域也就在這個函數范圍內,這就是嵌套函數。出來外面這個函數就失效。
print('11111111111')
bar()
foo()
注意:與函數調用不同:下例為函數的調用
def test1():
print('2222222222')
def test2():
print("3333333333")
test1() ----------> 函數的調用,不是嵌套函數。
print(test2) ----------> 僅僅打印test2的內存地址
print(test2()) ----------> 分兩步運行:1,運行的結果就是先運行test2()函數;2,最后打印test2()的返回值
--------->
<function test2 at 0x00000000021DF730>
222222222222222222233333
2222222222
None ----------> 因為函數中沒有定義返回值,所以返回值為None.
補充說明:
def test1():
print(2222222222)
return 'xxxxxx'
print(test1()) ----------> 分兩步執行:1,test1()函數執行,2,打印該函數的執行結果
------->
2222222222
xxxxxx
--------------------------------------------------------------------------------------------------
實例8,@timer 的作用:
實例9,
import time
def timer(func): ----------------> timer(test1) ,將test1的內存地址傳給func了,
def deco(): ----------------> 嵌套函數
start_time=time.time()
func() ---------------> 運行test1()
stop_time=time.time()
print("the func run time is %s" %(stop_time-start_time)) --------> 打印出test1 的運行時間
return deco -------------> 高階函數,返回這個函數(或者叫變量)的內存地址
def test1():
time.sleep(3)
print('in the test1')
test1=timer(test1)
test1() ------------- 此時實際執行的就是deco的函數
-------> 等3秒后出現以下結果
in the test1
the func time is 3.00001785546222
裝飾器的用法:@,給哪個函數加,就在哪個函數的上頭加上@
實例10,
import time
def timer(func): ---------> timer(test1) ,將test1的內存地址傳給func了,
def deco(): ---------> 嵌套函數
start_time=time.time()
func() --------> 運行test1()
stop_time=time.time()
print("the func run time is %s" %(stop_time-start_time)) --------> 打印出test1 的運行時間
return deco --------> 高階函數,返回這個函數(或者叫變量)的內存地址
@timer --------> 作用相當於:test1=timer(test1)
def test1():
time.sleep(3)
print('in the test1')
@timer
def test2():
time.sleep(3)
print('in the test2')
test1()
test2()
實例11:
裝飾器:可以滿足日常90%的需求:針對任意的傳入的參數
import time
def timer(func):
def deco(*args,**args):
start_time=time.time()
func(*args,**args)
stop_time=time.time()
print("the func run time is %s" %(stop_time-start_time))
return deco
@timer
def test1():
time.sleep(3)
print('in the test1')
@timer
def test2():
time.sleep(3)
print('in the test2')
test1()
test2()
------------------------------------------------------------------------------------------------------------------------------------------------------------------
實例12:
模擬網站,一個頁面就是一個函數
import time
user,passwd ='alex','abac123'
def auth(func):
def wrapper(*args,**kwargs):
username = input("Username:").strip()
password = input("Password:").strip()
if user == username and passwd == password:
print("user has passed authz")
func(*args,**args)
else:
exit
return wrapper
def index():
print("welcome to index page")
@auth
def home():
print("welcome to home page")
return "from home"
@auth
def bbs():
print("welcome to bbs page")
index()
home()
bbs()
------------------------------------------------------------------------------------------------------
3、迭代器&生成器
3.1、 列表生成式(在cmd的python命令行里執行),使代碼更簡潔。
[i*2 for i in range(10)]
等同於:
a = []
for i in range(10):
a.append(i*2)
print(a)
3.2、 通過列表生成式,我們可以直接創建一個列表。
但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。
生成器特點:
(1)、生成器只有在調用時候才會生成相應的數據。並且生成器省內存的方法就是只保留一個值
(2)、只記錄當前位置
(3)、只有一個next方法:__next__() 雙下划線
在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
在cmd的python命令下運行:
生成1個1百萬的列表:
>>>[i*2 fro i in range(1000000)]
此時就會打印
...
賦值的形式:
>>>a = [i*2 fro i in range(1000000)] 后面的數字越大,則這一步的時間越長。
>>>len(a) 可以查看
調用方式:
>>>a[1000]
>>>c = ( i*2 for i in range(100000000)) ---->馬上出來,因為它不生成具體的每一個數字,只生成一個內存地址,此時,只有你訪問這個地址,它才會生成。不訪問的話,它都不會存在。和列表的區別,只有在調用的時候才會生成。不調用的時候不會生成。
>>>print(c)
<generator object <genexpr> at 0x000000000000D4E308>
訪問的范式:不能通過列表的下標去取值
生成器取值只能是一個一個的去取
>>> c._ _next_ _() --------------- 實際中next用得並不多,實際中用循環去調取數據。
5122
>>> c._ _next_ _()
5124
3.3、利用函數來做生成器
generator非常強大,如果用推算的算法比較復雜,用類型表達式的for循環無法實現的時候,還可用函數來實現
斐波拉契數列(Fibonacci),除第一個和第二個數外,任意一個數都可由前兩個數相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b --------->此時a、b是同時賦值的
n = n + 1
return 'done'
注意,賦值語句:
a, b = b, a + b
相當於:
t = (b, a + b) # t是一個tuple
a = t[0]
b = t[1]
>>> fib(10) 調用:意思是生成10個這個序列
1
1
2
3
5
8
13
21
34
55
done
要把fib函數變成generator,只需要把print(b)改為yield b就可以了:
def fib(max):
n,a,b = 0,0,1
while n < max:
#print(b)
yield b
a,b = b,a+b ==========》 注意這個書寫方式,是兩個等式同時賦值的意思。即 a=b,b=a+b 同時進行賦值!!!。
n += 1
return 'done'
print(fib(100))
運行后:
<generator object fib at 0x000000000000D4E308>
這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator。
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
pytharm里面是這樣的:
<generator object f at 0x0000000001DC68E0>
這里,最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最后一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的 yield 語句處繼續執行。
作用:上面的f函數是可以在程序外來隨時中斷的,又可以隨時的恢復啟用,中間可以夾雜別的程序。
當f程序是個很大的很慢的程序時,就可以在這中間夾雜別的程序了
data = fib(10)
print(data)
print(data.__next__())
print(data.__next__())
print("干點別的事") ----------> 可以跳出程序外做別的任務。然后又可以隨時進入在生成器程序。
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())
print(data.__next__())
#輸出
<generator object fib at 0x101be02b0>
1
1
干點別的事
2
3
5
8
13
在上面fib的例子,我們在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。
同樣的,把函數改成generator后,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來迭代:
def f(max):
n,a,b=0,0,1
while n<max:
yield b
a,b = b,a+b
n = n+1
return "done"
for n in f(10):
print(n)
1
1
2
3
5
8
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
def f(max):
n,a,b=0,0,1
while n<max:
yield b
a,b = b,a+b
n = n+1
return "done"
for n in f(10):
print(n)
g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('Generator return value:', e.value)
break
意思是:
while True :一直循環下去
try:一直抓值,
except ... 直到抓取到特定的StopIteration這個錯誤的時候才執行其下面的語句:print和break操作。e代表上文代碼里定義的返回值:"done",
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
關於如何捕獲錯誤,后面的錯誤處理還會詳細講解
----------------------------------------------------------------------------------
例:
def f(max):
n,a,b =0,0,1
while n<max:
yield b
a,b = b,a+b
n =n+1
return "done"
g = f(10)
print("========== start loop ==========")
for i in g:
print(i)
def f(max):
n,a,b =0,0,1
while n<max:
yield b
a,b = b,a+b
n =n+1
return "done"
g = f(10)
------>
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
運行:報錯一個"done"的異常錯誤。因為取值超過了定義的10次,已經取不到值了
-----------------------------------------------------------------------------------------------------------------------------------------------------------
改進方法:引進try:
def f(max):
n,a,b =0,0,1
while n<max:
yield b
a,b = b,a+b
n =n+1
return "done" ---------> 就是超值范圍后的報錯信息,異常的時候打印的消息。
g = f(10)
while True: --------> 死循環
try: --------> s是一種不斷的抓的方法
x = next(g)
print("g:",x)
except StopIteration as e:
print("Generator return value:",e.value)
break
意思是try下面的代碼一旦出這個錯:StopIteration (出來別的任何錯都不處理)就執行:print("Generator return value:",e.value) 和break 跳出循環
總結:代碼帶有yield的就不叫函數了。就是一個生成器了。
yield作用:使得函數保存當前位置,並退出該函數,下次任何再調用函數的時候再回來
-------------------------------------------------------------------------------
應用:通過yield實現在單線程的情況下實現並發運算的效果
實例1:
def con(name):
print("%s prepare to eat box."%name)
while True:
baozi = yield
print("box %s coming,and eated by %s"%(baozi,name))
c =con("chen")
c.__next__()
c.__next__()
運行:
chen prepare to eat box.
box None coming,and eated by chen
-----------------------------------------------------------------------------------------------------------------------------------------------
例2:
def con(name):
print("%s prepare to eat box."%name)
while True:
baozi = yield
print("box %s coming,and eated by %s"%(baozi,name))
c =con("chen")
c.__next__()
b1 = "韭菜的"
c.send(b1)
--->
chen prepare to eat box.
box 韭菜的 coming,and eated by chen
解釋:
send 可以給yield傳值同時調用該值
next僅僅是調用yield的值。
--------------------------------------------------------------------------------
例3:
def con(name):
print("%s prepare to eat box."%name)
while True:
baozi = yield
print("box %s coming,and eated by %s"%(baozi,name))
c =con("chen")
c.__next__()
b1 = "韭菜的"
c.send(b1)
c.__next__()
---->
chen prepare to eat box.
box 韭菜的 coming,and eated by chen
box None coming,and eated by chen
---------------------------------------------------------------------------------------------
迭代器:
迭代器是訪問集合元素的一種方式。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能往前不會后退,不過這也沒什么,因為人們很少在迭代途中往后退。另外,迭代器的一大優點是不要求事先准備好整個迭代過程中所有的元素。迭代器僅僅在迭代到某個元素時才計算該元素,而在這之前或之后,元素可以不存在或者被銷毀。這個特點使得它特別適合用於遍歷一些巨大的或是無限的集合,比如幾個G的文件
特點:
1.訪問者不需要關心迭代器內部的結構,僅需通過next()方法不斷去取下一個內容
2.不能隨機訪問集合中的某個值,只能從頭到尾依次訪問
3.訪問到一半時不能往回退
4.便於循環比較大的數據集合,節省內存
>>> a = iter([1,2,3,4,5])
>>> a
<list_iterator object at 0x101402630>
>>> a.__next__()
1
>>> a.__next__()
2
>>> a.__next__()
3
>>> a.__next__()
4
>>> a.__next__()
5
>>> a.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
可以直接作用於for循環的數據類型有以下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等;
一類是generator,包括生成器和帶yield的generator function。
這些可以直接作用於for循環的對象統稱為可迭代對象:Iterable。可以使用isinstance()判斷一個對象是否是Iterable對象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable) ----------> 單個的數字不是可迭代對象,數字的列表就是
False
而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。
可以被next()函數調用並不斷返回下一個值的對象稱為迭代器:Iterator。
判斷:一個函數里有next的方法的就叫迭代器(反之,沒有這個方法的就不叫迭代器)
>>> a =[1,2,3]
>>> dir(a) -------> 查看有無next方法
生成器肯定就是迭代器(因為生成器有next方法):
>>> from collection import Iterator
>>> isinstance((x for x in range(5)).Iterator)
True
但迭代器不一定就是生成器
可以使用isinstance()判斷一個對象是否是Iterator對象:
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator) ------》判斷列表是否是
False
>>> isinstance({}, Iterator) —————》 字典
False
>>> isinstance('abc', Iterator) --------》字符串
False
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。
>>>a=[1,2,3]
>>>iter(a)
>>>b=iter(a) ---------> 此時的b就是一個迭代器了,可以通過next的方法取值了。
>>>b.__next__()
>>>b.__next__()
>>>b.__next__()
把list、dict、str等Iterable變成Iterator可以使用iter()函數:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
你可能會問,為什么list、dict、str等數據類型不是Iterator?
這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。
Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
小結
凡是可作用於for循環的對象都是Iterable類型;
凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
集合數據類型如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。
Python的for循環本質上就是通過不斷調用next()函數實現的,例如:
for x in [1, 2, 3, 4, 5]:
pass
實際上完全等價於:
# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5]) # 循環:
while True:
try: # 獲得下一個值:
x = next(it)
except StopIteration: # 遇到StopIteration就退出循環
break
例:range(10) 在python3.0里就是一個迭代器
>>> range(10) -------> python3.0
range(1.10)
>>> range(10) -------> python2.0 變成跌代器:xrange(10)
[0,1,2...9]
本質:3.0里面的for循環就是通過next的方法去取值的。
for line in f.readlines():
xxx
這個也是使用迭代的方法。一次讀一行。
以后通過socket傳文件的額時候可以用這個,因為底層很多的都是迭代器封裝的。
內置方法(內置參數)
pycharm里演示:
abs():取絕對值
all():
Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to:
def all(iterable):
for element in iterable:
if not element:
return False
return True
實例:
print(all[0,12,-45345]) ------> 因為0為非真,故返回假
print(all[12210,12,-45345])
any() 如果可迭代對象的任意的一個數據為真,就返回真,(有一個數據為真就返回真)。列表為空就返回假
def any(iterable):
for element in iterable:
if element:
return True
return False
實例:
print(any[0,12,-45345])
print(all[])
print(all[0])
ascii(): 沒用:將一個內存對象變成一個可打印的字符串
實例:print(ascii([1,3,"是否發生李開復"]))
a=[1,3,"是否發生李開復"]
print(a,type(a))
--------------------------------------------------------------------------------------
bin() 將一個整數轉換成二進制(1進制轉二進制)
>>>bin(1)
>>>bin(234432143)
--------------------------------------------------------------------------------------
bool() 布爾判斷。0就是FALSE,1就是true
>>>bool(1)
>>>bool(0)
--------------------------------------------------------------------------------------
bytearray() 字節數組,將二進制變成列表的形式可以修改了。用不上,知道有這個東西就行了
a=bytes("abcde",encoding="utf-8")
print(a)
print(a.capitalize(),a)
理解:字符串不可以修改,列表可以修改。
b=bytearray("abcde",encoding="utf-8")
b[0]
print(b[0]) ---------> 97 ascii碼里a這個字符對應的就是97
print(b[1]) ---------> 98 ascii碼里b這個字符對應的就是98
b[1] = 100
print(b)
b[1] = 50
print(b) -----> bytearary(b"xxxxxx")
---------------------------------------------------------------------------------------
callable(object) 判斷對象是否可以調用:在這個對象后面可以加()的就是屬於可以調用
比如:
print(callable([1,2,3])) ----> false
def a():pass
print(callable(a)) -----> true
--------------------------------------------------------------------------------
chr() ---> 反映數字!!對應的ASCII碼的字符
chr(97)
---------------------------------------------------------------------------------
ord() ----->反映字符在ascii碼里對應的數字
ord(a)
---------------------------------------------------------------------------------
classmothod()類方法
---------------------------------------------------------------------------------
compile() 底層的,將代碼用於編譯的。用不上
---------------------------------------------------------------------------------
complex() 復數,用不上
---------------------------------------------------------------------------------
delattr( ) 面向對象
-----------------------------------------------------------------------------------
dir():查看某個對象的所有方法:
a= {1,2,212}
dir(a) ------->結果里面有兩個下划線的除了next都是內置方法,我們不能用,其他的方法可以用
-----------------------------------------------------------------------------------
divmod() 相除並返回商和余數
divmod(5,3)
-->
(1,2)
-----------------------------------------------------------------------------------------
filter(function,iterable):對傳入的值按照function的方法過濾下在處理,從一組數據里面過濾出你想要的。
例:
a=filter(lambda n:n>5,range(10))
for i in a:
print(a)
----------------------------------------------------------------------------------------
map(function,iterable) 對傳入的值都安裝function的方法來處理
a=map(lambda n:n**n,range(10)) == a=[lambda i:i*2 for i in range(10)]
for i in a:
print(i)
-----------------------------------------------------------------------------------------
reduce():是一個標注庫里的模塊了,逐漸相加
import functools
a = functools.reduce(lambda x,y:x+y,range(10))
print(a)
___>45
0+1+2+3+4+5+6+7+8+9==45
---------------------------------------------------
frozenset([iterable]) 不可變的
a= [1,2,3,4323,4,24,234,234,,124,13,21,3]
a=frozenset([1,2,3,4323,4,24,234,234,,124,13,21,3])
此時的a就不可變了。沒有了改,增,刪的方法了
----------------------------------------------------
globals() 返回當前程序所有變量的key.value的格式
print(globals()) 用得少
-----------------------------------------------------
解釋:哈希算法:固定映射關系:
張三 =“xxx” --------》 python自動給"張飛" 映射成 1,
李四 = “xxx” --------》 ..........................2,
下次尋址:尋張三,就安裝對應表找1,等於就是尋找:1,2,3,4,5,6...等
而找1000000里面的值的方法:
假如找5000,在100萬里打半,50萬 》5千
再判斷:25萬 》 5千
再打半判斷:12.5萬 > 5千
........... 6.75萬 ....
3.875 ....
............1.9875 ...
............ 1萬 > 5千
。。。。。。5 qian = 5qian 找到了。等於在100萬里找個數通過7次就找到了
例:
hash(10) ----》打印出16進制
hash(“Jack”)
----------------------------------------------------------------------------
locals() -----> 用不上
----------------------------------------------------------------------------
max() 返回列表里的最大值
min() 返回列表里的最小值
-----------------------------------------------------------------------------
oct() 轉8進制,沒什么用
oct(1)
--->0o1
oct(9)
--->
0o11
-----------------------------------------------------------------------------
pow(x,y), x的y次方
pow(2,3)
--》8
----------------------------------------------------------------------------------
round(1.2342342342) --- 保留2位小數點
--->
1.33
----------------------------------------------------------------------------------
setattr() 非常重要的!!
----------------------------------------------------------------------------------
slice() 切片 ---- 沒用
例:
d = range(20)
d[slice(2,5)]
語法等於:
d[2,5]
-----------------------------------------------------------------------------
sorted()
例:
a = {
6:2,
8:0,
1:4,
9:11
}
print(a)
因為字典本身即是無序的,通過sort()可以排序,默認安裝key來排序
print(sorted(a.iteams())
安裝value來排序:
print(sorted(a.items(),key=lambda x:x[1])) x代表:字典里的元素 key:value
-----------------------------------------------------------------------------
zip() 拉鏈,
a=[1,2,3,4]
b=['a','b','c','d']
zip(a,b)
print(zip(a,b))
for i in zip(a,b):
print(i)
當a,b 的元素個數不一樣的時候,按照最少的那個來組合
---------------------------------------------------------------------------------------------------
json 和pickle
用於序列化的兩個模塊
json,用於字符串和 python數據類型間進行轉換
pickle,用於python特有的類型 和 python的數據類型間進行轉換
Json模塊提供了四個功能:dumps、dump、loads、load
pickle模塊提供了四個功能:dumps、dump、loads、load
序列化:將內存對象變成字符串
例:將字典存成字符串:
info = {
name="alex",
age=22,
}
f= open('text','w')
f.write(str(info))
f.close
反序列化: 將字符串變成內存對象
d = f.read()
f.close()
print(d)
d = eval(f.read())
f.close()
print(d['age'])
----->22
---------------------------------------------------------
# 序列化dumps: 將內存上的數據寫到硬盤上
info = {
name="alex",
age=22,
}
f = open('text','w')
print(json.dumps(info))
---------------------------------------------------------
反序列化loads:將內存的數據對象存到硬盤上了
import json
f= open("text",'r')
d = json.loads(f.read())
print(d["age"])
右鍵運行:反序列化:
22
例:
import json
def sayhi(name):
print("hello",name)
info = {
'name':"alex",
"age":22,
"func":sayhi
}
f = open("text","w")
f.write(json.dumps(info))
f.close()
報錯:該內存地址不是一個json的可序列化的數據,json只能處理簡單的數據,比如:字典,列表,字符串等
因為json是所有的語言里都通用的。作用在於在不同語言之間進行交互。
處理復雜的可序列化的數據:用pikle:
import json
def sayhi(name):
print("hello",name)
info = {
'name':"alex",
"age":22,
"func":sayhi
}
f = open("text","wb")
print()
f.write(pickle.dumps(info))
f.close()
反序列化:
d = pickle.loads(f.read)
print(d['age'])
f=open('text',"wb")
pickle.dump(info,f) == f.write(pickle.dumps(info))
f.close()
d = pickle.load(f) == d = pickle.loads(f.read())
print(d['func']("alex"))
要點:json的序列化,反序列化
pickle的序列化,反序列化
引申:dump兩次、三次
------------------------------------------------------------------------------------------------
軟件目錄開發規范:
為什么要設計好目錄結構
"設計項目目錄結構",就和"代碼編碼風格"一樣,屬於個人風格問題。對於這種風格上的規范,一直都存在兩種態度:
一類同學認為,這種個人風格問題"無關緊要"。理由是能讓程序work就好,風格問題根本不是問題。
另一類同學認為,規范化能更好的控制程序結構,讓程序具有更高的可讀性。
我是比較偏向於后者的,因為我是前一類同學思想行為下的直接受害者。我曾經維護過一個非常不好讀的項目,其實現的邏輯並不復雜,但是卻耗費了我非常長的時間去理解它想表達的意思。從此我個人對於提高項目可讀性、可維護性的
要求就很高了。"項目目錄結構"其實也是屬於"可讀性和可維護性"的范疇,我們設計一個層次清晰的目錄結構,就是為了達到以下兩點:
可讀性高: 不熟悉這個項目的代碼的人,一眼就能看懂目錄結構,知道程序啟動腳本是哪個,測試目錄在哪兒,配置文件在哪兒等等。從而非常快速的了解這個項目。
可維護性高: 定義好組織規則后,維護者就能很明確地知道,新增的哪個文件和代碼應該放在什么目錄之下。這個好處是,隨着時間的推移,代碼/配置的規模增加,項目結構不會混亂,仍然能夠組織良好。
所以,我認為,保持一個層次清晰的目錄結構是有必要的。更何況組織一個良好的工程目錄,其實是一件很簡單的事兒。
目錄組織方式
關於如何組織一個較好的Python工程目錄結構,已經有一些得到了共識的目錄結構。在Stackoverflow的這個問題上,能看到大家對Python目錄結構的討論。
這里面說的已經很好了,我也不打算重新造輪子列舉各種不同的方式,這里面我說一下我的理解和體會。
假設你的項目名為foo, 我比較建議的最方便快捷目錄結構這樣就足夠了:
Foo/
|-- bin/
| |-- foo ----------》 執行這里的文件,去調用下面的main.py的主程序,從而實現整個python程序運行。
|-- conf/
|
|-- foo/
| |-- tests/ ------------------>測試的
| | |-- __init__.py
| | |-- test_main.py
| |
| |-- __init__.py
| |-- main.py ---------------> 正式的
|
|-- docs/ --------------> 文檔
| |-- conf.py
| |-- abc.rst
|
|-- setup.py -----------> 安裝程序
|-- requirements.txt --------> 依賴哪些別的軟件
|-- README
簡要解釋一下:
bin/: 存放項目的一些可執行文件,當然你可以起名script/之類的也行。
foo/: 存放項目的所有源代碼。(1) 源代碼中的所有模塊、包都應該放在此目錄。不要置於頂層目錄。(2) 其子目錄tests/存放單元測試代碼; (3) 程序的入口最好命名為main.py。
docs/: 存放一些文檔。
setup.py: 安裝、部署、打包的腳本。
requirements.txt: 存放軟件依賴的外部Python包列表。
README: 項目說明文件。
除此之外,有一些方案給出了更加多的內容。比如LICENSE.txt,ChangeLog.txt文件等,我沒有列在這里,因為這些東西主要是項目開源的時候需要用到。如果你想寫一個開源軟件,目錄該如何組織,可以參考這篇文章。
下面,再簡單講一下我對這些目錄的理解和個人要求吧。
關於README的內容
這個我覺得是每個項目都應該有的一個文件,目的是能簡要描述該項目的信息,讓讀者快速了解這個項目。
它需要說明以下幾個事項:
1、軟件定位,軟件的基本功能。
2、運行代碼的方法: 安裝環境、啟動命令等。
3、簡要的使用說明。
4、代碼目錄結構說明,更詳細點可以說明軟件的基本原理。
5、常見問題說明。
我覺得有以上幾點是比較好的一個README。在軟件開發初期,由於開發過程中以上內容可能不明確或者發生變化,並不是一定要在一開始就將所有信息都補全。但是在項目完結的時候,是需要撰寫這樣的一個文檔的。
可以參考Redis源碼中Readme的寫法,這里面簡潔但是清晰的描述了Redis功能和源碼結構。
關於requirements.txt和setup.py
setup.py
一般來說,用setup.py來管理代碼的打包、安裝、部署問題。業界標准的寫法是用Python流行的打包工具setuptools來管理這些事情。這種方式普遍應用於開源項目中。不過這里的核心思想不是用標准化的工具來解決這些問題,而是說,
一個項目一定要有一個安裝部署工具,能快速便捷的在一台新機器上將環境裝好、代碼部署好和將程序運行起來。
這個我是踩過坑的。
我剛開始接觸Python寫項目的時候,安裝環境、部署代碼、運行程序這個過程全是手動完成,遇到過以下問題:
安裝環境時經常忘了最近又添加了一個新的Python包,結果一到線上運行,程序就出錯了。
Python包的版本依賴問題,有時候我們程序中使用的是一個版本的Python包,但是官方的已經是最新的包了,通過手動安裝就可能裝錯了。
如果依賴的包很多的話,一個一個安裝這些依賴是很費時的事情。
新同學開始寫項目的時候,將程序跑起來非常麻煩,因為可能經常忘了要怎么安裝各種依賴。
setup.py可以將這些事情自動化起來,提高效率、減少出錯的概率。"復雜的東西自動化,能自動化的東西一定要自動化。"是一個非常好的習慣。
setuptools的文檔比較龐大,剛接觸的話,可能不太好找到切入點。學習技術的方式就是看他人是怎么用的,可以參考一下Python的一個Web框架,flask是如何寫的: setup.py
當然,簡單點自己寫個安裝腳本(deploy.sh)替代setup.py也未嘗不可。
requirements.txt
這個文件存在的目的是:
方便開發者維護軟件的包依賴。將開發過程中新增的包添加進這個列表中,避免在setup.py安裝依賴時漏掉軟件包。
方便讀者明確項目使用了哪些Python包。
這個文件的格式是每一行包含一個包依賴的說明,通常是flask>=0.10這種格式,要求是這個格式能被pip識別,這樣就可以簡單的通過 pip install -r requirements.txt來把所有Python包依賴都裝好了。具體格式說明: 點這里。
關於配置文件的使用方法
注意,在上面的目錄結構中,沒有將conf.py放在源碼目錄下,而是放在docs/目錄下。
很多項目對配置文件的使用做法是:
配置文件寫在一個或多個python文件中,比如此處的conf.py。
項目中哪個模塊用到這個配置文件就直接通過import conf這種形式來在代碼中使用配置。
這種做法我不太贊同:
這讓單元測試變得困難(因為模塊內部依賴了外部配置)
另一方面配置文件作為用戶控制程序的接口,應當可以由用戶自由指定該文件的路徑。
程序組件可復用性太差,因為這種貫穿所有模塊的代碼硬編碼方式,使得大部分模塊都依賴conf.py這個文件。
所以,我認為配置的使用,更好的方式是,
模塊的配置都是可以靈活配置的,不受外部配置文件的影響。
程序的配置也是可以靈活控制的。
能夠佐證這個思想的是,用過nginx和mysql的同學都知道,nginx、mysql這些程序都可以自由的指定用戶配置。
所以,不應當在代碼中直接import conf來使用配置文件。上面目錄結構中的conf.py,是給出的一個配置樣例,不是在寫死在程序中直接引用的配置文件。可以通過給main.py啟動參數指定配置路徑的方式來讓程序讀取配置內容。當然,這
里的conf.py你可以換個類似的名字,比如settings.py。或者你也可以使用其他格式的內容來編寫配置文件,比如settings.yaml之類的。
跨目錄調用文件:atm.py 調用settings.py和main.py
atm
|-bin
| |- __init__.py
| |- atm.py
|-conf
| |- __init__.py
| |- settings.py
|-core
|- __init__.py
|- main.py
def login():
print("welcome to my atm")
print(__file__) ----------------> 當前文件的相對路徑
import os
#print(os.path.abspath(__file__)) ---> 當前文件的絕對路徑
#通過dir命令:返回路徑名,不要文件名
#print(os.path.dirname(os.path.abspath(__file__))) -----> 可以找到父一級的目錄:bin目錄
#再向上一級,到atm的總目錄
import sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
import conf,core
from conf import settings -----------> 這樣就可以調用 conf 目錄下的文件了
from core import main -----------> 這樣就可以調用core目錄下的文件了
main.login()
程序結構:
day4-atm/
├── README
├── atm #ATM主程目錄
│ ├── __init__.py
│ ├── bin #ATM 執行文件 目錄
│ │ ├── __init__.py
│ │ ├── atm.py #ATM 執行程序
│ │ └── manage.py #ATM 管理端,未實現
│ ├── conf #配置文件
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── core #主要程序邏輯都 在這個目錄 里
│ │ ├── __init__.py
│ │ ├── accounts.py #用於從文件里加載和存儲賬戶數據
│ │ ├── auth.py #用戶認證模塊
│ │ ├── db_handler.py #數據庫連接引擎
│ │ ├── logger.py #日志記錄模塊
│ │ ├── main.py #主邏輯交互程序
│ │ └── transaction.py #記賬\還錢\取錢等所有的與賬戶金額相關的操作都 在這
│ ├── db #用戶數據存儲的地方
│ │ ├── __init__.py
│ │ ├── account_sample.py #生成一個初始的賬戶數據 ,把這個數據 存成一個 以這個賬戶id為文件名的文件,放在accounts目錄 就行了,程序自己去會這里找
│ │ └── accounts #存各個用戶的賬戶數據 ,一個用戶一個文件
│ │ └── 1234.json #一個用戶賬戶示例文件
│ └── log #日志目錄
│ ├── __init__.py
│ ├── access.log #用戶訪問和操作的相關日志
│ └── transactions.log #所有的交易日志
└── shopping_mall #電子商城程序,需單獨實現
└── __init__.py