Python進階細節

Python進階細節

根據慕課網七月老師視頻整理html

一切皆對象

對與Python來講,一切皆對象,包括函數。在其餘語言好比c++中,函數只是一段可執行的代碼,只要你得到入口地址就能夠調用這段代碼。可是Python中不同,Python中一切皆對象。Python中的函數,能夠做爲另外一個函數的參數傳入到另一個函數裏,也能夠看成另一個函數的返回值,甚至能夠賦值給一個變量。python

def a():
    pass

print(type(a))


<class 'function'>   # 可見是一個class,是一個類。

閉包

閉包指的是:函數+環境變量(環境變量不能是全局變量)。
python在函數內部還能夠定義函數,但該函數做用域只在外部函數內部有效,除非做爲外部函數的返回值被返回,在外部函數的外部用一個變量接收後就能夠調用它了。c++

def curve_pre():
    a = 25  # 這裏定義了環境變量
    def curve(x):
        return a*x*x
    return curve

a = 10   # 在外部定義了a爲10
f = curve_pre()
print(f(2))
print(f.__closure__)    # 能夠經過這個內置變量來查看閉包裏的內容
print(f.__closure__[0].cell_contents)


100
(<cell at 0x0000016832868708: int object at 0x0000000065DCECD0>,)
25

可見返回了一個對象。你再在外面定義了變量,也不會改變閉包內的環境變量。
閉包的意義在於返回了一個現場,若是沒有閉包,很容易被外部的變量所影響。算法

1. 閉包的經典誤區

閉包返回現場的環境變量,不能在閉包裏定義的函數裏面再被定義了,並且函數裏必需要有調用環境變量的地方,不然就不叫作閉包了。express

def f1():
    a = 20
    def f2():
        a = 10  # 重複定義
        return a
    return f2
f = f1()
print(f.__closure__)

None  # 可見此時返回了None,再也不是閉包了。本質上是認爲此時a被認爲是一個局部變量,再也不是環境變量了!

--------------------------------------------------------------------------

# 若是想要環境變量在函數裏被改變,能夠這樣:

def f1():
    a = 25
    def f2():
        nonlocal a  # nonlocal關鍵字強制讓a再也不爲局部變量,跳到上一級做爲了環境變量。
        a = a + 10
        return a
    return f2
f = f1()
print(f.__closure__)
print(f())
print(f())
print(f())

(<cell at 0x000002A5CF348708: int object at 0x0000000065DCECD0>,)
35
45
55
# 能夠看到a的值是能夠保存的,這是由於閉包的環境變量具備保存現場的功能,記憶住上次調用的狀態,因此能夠這樣作。

---------------------------------------------------------------------------


def f1():
    a = 20
    def f2():
        return 2  # 裏面再也不調用a了
    return f2
f = f1()
print(f.__closure__)

None # 可見此時仍然不是閉包

---------------------------------------------------------------------------

def f1():
    a = 20
    def f2():
        s = a+20
        return 2
    return f2
f = f1()
print(f.__closure__)

(<cell at 0x00000294E8568708: int object at 0x0000000065DCEC30>,)
# 可見就算返回值裏不包括a,可是隻要調用了,就能夠是一個閉包。

2. 閉包的優勢

從閉包能夠看出函數式編程的優勢。若是出現須要保存值進行迭代的狀況,就不得不定義一個全局變量來保存上一次的值。可是在閉包裏不須要使用到全局變量,只須要閉包裏定義的環境變量便可記憶上一次的狀態,這樣就具備了封閉性,不然過多的使用全局變量會使代碼變得混亂。這裏再注意一個問題:編程

a = 10

def f1(x):
    a_new = a + x
    a = a_new

print(f1(5))

Traceback (most recent call last):
  File "c4.py", line 7, in <module>
    print(f1(5))
  File "c4.py", line 4, in f1
    a_new = a + x
UnboundLocalError: local variable 'a' referenced before assignment

# 看起來美滋滋其實報錯了。再Python裏,若是再定義了a的話,不管是在哪裏,在定義的時候系統會默認a是局部變量再也不是全局變量了。因此在執行代碼的時候,就會出現了找不到局部變量a的狀況,由於f1中第一段代碼中用到了局部變量a,可是此時尚未定義啊。

----------------------------------------------------------------------

# 能夠這麼解決:
a = 10

def f1(x):
    global a   # 定義global關鍵字強制認爲是全局變量
    a_new = a + x
    a = a_new
    return a

print(f1(5))
print(f1(10))

15
25

# 可見這時候全局變量起到了保存的功能,但相對閉包,就顯得很Low了。

3. 閉包的一個經典例子

def testFun():  
    temp = [lambda x : i*x for i in range(4)]
    return temp

for everyLambda in testFun(): 
    print (everyLambda(2))
    
# 運行後結果居然是:    
6
6
6
6

這裏testfun()返回一個temp,temp是一個列表,everyLambda每次返回的都是temp裏列表的值,參數2賦給x。數據結構

三元表達式

python中的三元表達式和其餘語言中的不太同樣。
條件爲真時返回的結果 if 判斷條件 else 條件爲假時返回的結果
x if x > y else y
其餘不少語言中是這麼定義的:
x > y ? x:y閉包

map類

map不是一個函數而是一個類。map和lambda表示結合起來一塊兒用會很是好。
map(func, *iterables) --> map object
iterables是可迭代的類型,序列和元組均可以。*號表示是可變參數,能夠傳入多個不一樣值的取值。app

a = [1,2,3,4,5]
def square(x):
    return x*x
r = map(square, a)
print(r)
print(list(r))


<map object at 0x000001DC6AD0B0F0>
[1, 4, 9, 16, 25]

匿名函數(lambda表達式)

lambda表達式也叫作匿名函數。
lambda的定義:lambda parameter_list: expression
expression意思是表達式,因此後面只能是表達式,不能是語句,好比賦值語句等是不能夠的。
lambda表達式最後返回的是表達式計算出的值,和return後是同樣的。函數式編程

def add(x, y):
    return x+y

lambda x, y : x + y

lambda表達式通常和三元表達式和map鏈接在一塊兒用會更加整潔。

x = 1,2,3,4,5,6
y = 1,2,3,4,5,6
r = map(lambda x,y:x+y, x,y)
print(tuple(r))


(2, 4, 6, 8, 10, 12)

reduce函數

reduce 是一個函數。
def reduce(function, sequence, initial=None)

  • function : 這裏須要注意函數必須且只能有兩個參數
  • sequence: 序列
  • initial: 初始值
  • reduce的含義是連續調用函數進行連續計算
from functools import reduce # 須要引入這個函數纔可使用
list_x = ['1','2','3']
r = reduce(lambda x,y:x+y, list_x, 'a')
print(r)

a123
# 執行狀況 :(('a'+'1')+'2')+'3'
# 每次執行完一次函數的結果在下一次調用函數的時候會傳入到函數的參數中進行計算。初始值是給出的開始的參數之一。

filter類

class filter(function or None, iterable)
表示過濾不符合條件的值。當函數返回爲True時保留,False時剔除。當函數爲None時,剔除調iterable中原本就爲False的值

# ord()返回ascII碼值

list_x = ['a', 'B', 'c', 'D']
r = filter(lambda x: False if ord(x)>64 and ord(x)<91 else True, list_x)
print(list(r))


['a', 'c']

裝飾器

編寫代碼一個原則是:對修改是封閉的,對拓展是開放的。
若是想在不少個函數裏,每一個函數都實現相同的功能,用裝飾器是最方便的,不用在每一個函數裏重複定義,並且調用起來很方便,和「裝飾」的意思很像。

import time  

def decorator(func):
    def wrapper(*args, **kw): # *args是可變參數,**kw是可變關鍵字參數,這樣能夠接受除了有默認參數類型之外全部類型的函數參數
        print(time.time())
        func(*args, **kw)
    return wrapper
 # 就是一個閉包,傳入的函數func是環境變量

@decorator  # @ 是一個語法糖
def f1(func_name):  
    print("this is f1" + func_name)

@decorator
def f2(func_name1, func_name2):
    print("this is f2" + func_name1 + func_name2)

@decorator
def f3(func_name1, func_name2='f3',*args, **kw):
    print("this is f3"+func_name1+func_name2)
    print(kw)
    
f1('f1')   # 可見雖然在定義的時候麻煩了一些,可是調用的時候很方便。
f2('f2','f2')
f3('f3','f3',a='1',b='2',c='3') # 可變關鍵字參數


1519276076.973657  #時間戳
this is f1f1
1519276076.9746575
this is f2f2f2
1519276076.9746575
this is f3f3f3
{'a': '1', 'b': '2', 'c': '3'}

生成器

經過列表生成式能夠直接建立一個列表,可是咱們若是隻想訪問前面幾個元素,不想利用後面的元素,咱們能夠定義一個生成器(generator),一邊循環一邊計算,有一種方法很簡單,只要把列表生成式的[]改成()就能夠建立一個generator.

L = [x * x for x in range(10)]
L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]


g = (x * x for x in range(10))
g
<generator object <genexpr> at 0x104feab40>

若是須要打印出來元素,能夠經過生成器的next()方法。

g.next()
0
g.next()
1
g.next()
4

generator是能夠迭代的:

g = (x * x for x in range(10))
for n in g:
...     print n
...
0
1
4
9

能夠把一個函數寫成生成器,把return改成yield

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
        
fib(6)
<generator object fib at 0x104feaaa0>

for n in fib(6):
...     print n
...
1
1
2
3
5
8

定義成生成器後,generator的執行流程和函數並不同,函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。

另一個例子:

def odd():
...     print 'step 1'
...     yield 1
...     print 'step 2'
...     yield 3
...     print 'step 3'
...     yield 5
...
o = odd()
o.next()
step 1
1
o.next()
step 2
3
o.next()
step 3
5
o.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

字典來代替swich

Python中沒有swich這種結構,能夠用字典來實現。

day = 0

def get_monday():
    return 'Monday'

def get_Tuseday():
    return 'Tuseday'

def get_Wednesday():
    return 'Wednesday'

def default():
    return 'Unknow'

#返回函數能夠定義跟多操做,也能夠直接返回值
swicher = {
    0: get_monday,
    1: get_Tuseday,
    2: get_Wednesday
}

# 字典的get方法能夠獲取鍵對應的值,若是鍵不存在則返回指定的默認值

day_time = swicher.get(day, default())() # 後面再加一個括號來調用函數

print(day_time)


monday

列表推導式

對於列表推導式,其實不僅是列表,也能夠是元組,集合,字典進行推倒

a = [1,2,3,4,5]

b = [i**2 for i in a if i>=3]

print(b)


[9,16,25]
---------------------------------------------------

# 對於字典能夠這樣:

sdict = {
    'q':'烈焰衝擊',
    'w':'天女散花',
    'e':'致命一擊'
}

dic = {value:key for key,value in sdict.items()} # 最外面加花括號就是字典或者集合
dic1 = [key for key,value in sdict.items()] 
dic2 = (key for key,value in sdict.items()) 
print(dic)
print(dic1)
print(dic2)

# 若是是元組,元組是不可遍歷的對象,因此須要下面這樣取出對象
for i in dic2:
    print(i,end='\\ ')


{'烈焰衝擊': 'q', '天女散花': 'w', '致命一擊': 'e'}
['q', 'w', 'e']
<generator object <genexpr> at 0x0000021A3430E150>
q\ w\ e\

None

None表明空,並非False,也不是[],''

print(type(None))
print(type(False))
print(type([]))
print(type(''))

<class 'NoneType'>
<class 'bool'>
<class 'list'>
<class 'str'>

咱們能夠看見,這些在本質上都是不同的,類型都不同,只不過咱們在進行邏輯判斷的時候,有時會像下面這樣作:

a = None/false/[]/''
if not a:
.....

# 判斷的時候會這樣作

不建議使用if a is None:這種語句,咱們能夠看到類型是不同的,有時會出錯。

對象存在不必定是True

在上面對None的分析中,在邏輯判斷的時候之因此能夠判斷None爲False,是由於每一個對象和bool類型之間都是有聯繫的,因此能夠進行邏輯判斷。可是咱們自定義的對象卻不必定了,返回True和False因不一樣的對象而不一樣。咱們自定義的對象,如類,和咱們的內置方法有關係。
類中有兩個內置方法會影響對類的布爾類型的取值:

  • __len__:這個內置方法返回的是類的長度,外部調用len()時會返回該方法的返回值,返回值只有布爾類型或者int類型。
  • __bool__:這個內置方法返回的類的bool類型的取值,當這個方法在類裏面定義之後,返回值只看其返回值,__len__的返回值再也不起做用。注意該方法的返回值只能是布爾類型,即True或False。
class Rest():
    def __len__(self):
        return 5
    def __bool__(self):
        return False

print(len(Rest()))
print(bool(Rest()))

5
False

裝飾器的反作用

加上裝飾器後會改變函數的名字。

def decorator(func):
    def wrapper():
        print('this is decorator')
        func()
    return wrapper
@decorator
def f1():
    print(f1.__name__)  # 打印函數名字,不加裝飾器是f1
f1()


this is decorator
wrapper

可見會出錯,加上裝飾器後,若是不想改變名字,能夠這樣作:

from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper():
        print('this is decorator')
        func()
    return wrapper

@decorator
def f1():
    print(f1.__name__)  # 打印函數名字,不加裝飾器是f1

f1()



this is decorator
f1

可哈希(hashable)對象和不可變性(immutable)

  • 可哈希(hashable)和不可改變性(immutable)

若是一個對象在本身的生命週期中有一哈希值(hash value)是不可改變的,那麼它就是可哈希的(hashable)的,由於這些數據結構內置了哈希值,每一個可哈希的對象都內置了__hash__方法,因此可哈希的對象能夠經過哈希值進行對比,也能夠做爲字典的鍵值和做爲set函數的參數,可是list是unhashable,因此不能夠做爲字典的鍵值。全部python中全部不可改變的的對象(imutable objects)都是可哈希的,好比字符串,元組,也就是說可改變的容器如字典,列表不可哈希(unhashable)。咱們用戶所定義的類的實例對象默認是可哈希的(hashable),它們都是惟一的,而hash值也就是它們的id()

  • 哈希

它是一個將大致量數據轉化爲很小數據的過程,甚至能夠僅僅是一個數字,以便咱們能夠用在固定的時間複雜度下查詢它,因此,哈希對高效的算法和數據結構很重要。

  • 不可改變性

它指一些對象在被建立以後不會由於某些方式改變,特別是針對任何能夠改變哈希對象的哈希值的方式

  • 聯繫

由於哈希鍵必定是不可改變的,因此它們對應的哈希值也不改變。若是容許它們改變,,那麼它們在數據結構如哈希表中的存儲位置也會改變,所以會與哈希的概念違背,效率會大打折扣

具體的咱們能夠參考官方文檔和如下博客:
關於python內置__eq__函數的探索
關於可哈希對象的理解

python的深拷貝和淺拷貝

1.賦值方法:

list1 = [1,2,3]
list2 = list1
print(id(list1),id(list2))

2577180416904 2577180416904

可見這和在c語言中不同,兩者的id是同樣的,換句話說,你改變 list2,同時也會改變 list1
2.淺拷貝:

import copy

list1 = [1,2,3]
list2 = copy.copy(list1)
print(id(list1),id(list2))
list2.append(4)
print(list1,list2)


2522465131400 2522465130824
[1, 2, 3] [1, 2, 3, 4]

可是淺拷貝,對於裏面的元素,若是是不可變類型,會直接拷貝,可是對於可變類型只是指向它而已,例如看下面的代碼:

import copy

list1 = [1,2,[1,2,3]]
list2 = copy.copy(list1)
print(id(list1),id(list2))

list2[2].append(4)

print(list1,list2)

2710366738760 2710368236680
[1, 2, [1, 2, 3, 4]] [1, 2, [1, 2, 3, 4]] # 都變化了

3.深拷貝

import copy


list1 = [1,2,[1,2,3]]
list2 = copy.deepcopy(list1)
print(id(list1),id(list2))

list2[2].append(4)

print(list1,list2)

1660389185864 1660390683784
[1, 2, [1, 2, 3]] [1, 2, [1, 2, 3, 4]]

可見深拷貝纔是真正的拷貝。

相關文章
相關標籤/搜索