面試准備——(二)專業知識(2)Python


面試遇到的問題:

滴滴:

1. Python的數據結構

2. list和tuple的區別

3. list中有哪些操作?append和extend的區別?

4. list和dict的卻別?dict是有序的嗎?

5. 如果a = dict, b =a 其中dict發生了改變,b會改變嗎?這是一個淺拷貝還是深拷貝?這是引用嗎?

如果把dict換成list呢?

6. 你用用過多線程嗎?多線程的作用是什么?

7. 如何實現Singleton?這個Singleton中有什么變量?

8. 靜態方法、類方法是什么?類的成員方法能訪問實例變量嗎?能訪問類的變量嗎?能訪問靜態類方法嗎?

9. 你用過哪些python的包

美團:

1. python語言的特點

2. Python裝飾器(兩面都問到)

 
今日頭條:

1. Python的數據結構

2. list和set區別

 

**1. Python數據結構

——美團(滴滴),介紹一下Python的數據結構,並說明它們有什么操作

四個基本數據結構:list、tuple、dict、set

 在python中,字符串 tuples, 和數字是不可更改的對象,而list,dict等則是可以修改的對象。

1. list

1). list的操作有哪些?——滴滴

可以用可以使用dir()和help()查詢

 

2)較list.append()和extend(),他們是生成一個新的列表還是原來的列表?

  • append()接收任意數據類型的參數,並且簡單的加在list的末尾,當作一個元素。——同一個list
  • extend():——同一個list
    • 可以接收list,將這個list中的每個元素都增加到原來的list中。所以如果增加的list有n個元素,原list有m個元素,則使用extend()擴充以后,list有m+n個元素
    • 也可以接收dic:將每個key值添加到原list中
    • 也可以接收tuple

例如:

>>> l1 = [1,2, 3, 4, 5]
>>> l2 = [7, 8, 9]
>>> l3 = ('a', 'b', 'c')
>>> t3 = ('a', 'b', 'c')
>>> d4 = {'a':1, 'b':2, 'c':3}

>>> id(l1)
4330496200
>>> l1.append(l2) 
>>> id(l1) #append()是同一個list
4330496200
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9]] #append()可以接收list,l2當作一個元素加入到l1中
>>> len(l1)
6
>>> l1.append(t3) #append()可以接收tuple,也當作一個元素
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c')]
>>> len(l1)
7
>>> l1.append(d4) #append()可以接收dict,也當作一個元素
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c'), {'a': 1, 'c': 3, 'b': 2}]
>>> len(l1[7])
3

# extend
>>> l1 = [1,2, 3, 4, 5]
>>> id(l1)
4330496200
>>> l1.extend(l2) #[1, 2, 3, 4, 5, 7, 8, 9]
>>> id(l1) # 使用extend()是同一個list
4330496200
>>> l1.extend(t3) #extend()可以接收tuple,將其中所有的元素擴充到list中
>>> l1
[1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c']
>>> l1.extend(d4) ##extend()可以接收dict,將其中所有的key值擴充到list中
>>> l1
[1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c', 'a', 'c', 'b']

 

3)合並list:——兩個方法

# 方法一:+ 一個新的list
l3 = l1 + l2

# 方法二:extend() #還是原來的list
l1.extend(l2)

 

4)list使用切片操作,會生成新的list嗎?——正向、負向、淺拷貝

會。因為切片操作相當於淺拷貝,深成了一個新的list對象。

list = [1, 2, 3, 4, 5]
# list是有序的,所以可以根據索引來查找值
# 1. 正向
list[1:4, 2] #[1, 3]
# 2. 負向
list[-1:-4:-2] #[5, 3]

#切面相當於淺拷貝——新的list
>>> id(list)  #4330445000
>>> id(list[-1:-5])  #4330479176

 

2. tuple

1)list和tuple的區別——滴滴

  list tuple
創建 list = [1, 2, 3, 4, 5] t1 =(1,) # 如果只有一個元素,必須用“,”隔開,否則默認為‘int’類型
t2 = (1, 2, 3)
t3 = (1, 2, 3, ['a', 'b']) # tuple中可以嵌套list

元素是否可更改

(最大的不同)

可更改
list.append()

list.extend()
list.pop()

list.remove()

不可更改,所以不具備增、刪、改

如果刪除:del t #刪除整個元素

對於t3,tuple中嵌套有list,則可以更改那個list的值

t3[3].append('c') #t3 = (1, 2, 3, ['a', 'b', 'c'])

切片操作

相當於淺拷貝(新的list、tuple)

正向、負向

是否有序 有序(按定義的次序進行排序)
優勢  

1. tuple操作比list更
2. 訪問的安全性。數據不可更改
2. 隱含的assert語句,說明這一數據是常量

轉換

1. tuple(l)
2. list(t)

 

 

2)基本操作:

 

3. dict:

1)dict有哪些操作?

 另外:dict推導式:

d = {key: value for (key, value) in iterable}

 

2)dict是有序的嗎?

——不是,list、tuple是有序的

 

3)如果a = dictR, b=a, dictR修改了,a、b會變化嗎?這屬於引用嗎?這是淺拷貝還是深拷貝?

這是賦值引用。如果更改a的值,b會隨之變化。如果更改b的值,a會隨之變化。

如果改成了list:同樣。

如果改成了tuple,或者一個非容器類型例如:數字、字符以及其他原子類型),沒有拷貝一說,只能是引用,a、b發生變化都不會影響彼此的值。但是如果tuple中含有嵌套的list,改變這個list中的值,會互相影響。

(詳情見:下面賦值、淺拷貝、深拷貝)

4. set

**2.  Python中賦值引用、淺拷貝與深拷貝

 Python中關於對象復制有三種類型的使用方式,賦值、淺拷貝與深拷貝。

1. 賦值(函數的參數/返回值)——對象引用

在Python中賦值就是對象的引用不會開辟新的空間,只是復制了對象的引用

包括兩種形式:

  1. b = a
  2. 對象作為函數參數或返回值

分為了兩種情況

  1. 不可更改對象tuple、數字、字符等原子類型——a、b的改變互不影響(相當於指向了另一個對象的內存空間)
  2. 可更改的對象:list、dict、set——a、b改變都會互相影響(相當於內存中的對象改變)
#一、不可更改對象
# 1. 數字
>>> a = 5
>>> b = a #b = 5
>>> a = 3 # b = 5 :a改變,b不會改變
>>> b = 2 # a = 3:b改變,a不會改變

# 2. 字符串
>>> a = 'sbw'
>>> b = a   #b = 'sbw'
>>> a = 'lf'  #b = 'sbw':a改變,b不會改變
>>> b = 'wf' #a = 'lf' :b改變,a不會改變

# 3. tuple
>>> a = (1, 2, 3)
>>> b = a   #b = (1, 2, 3)
>>> a = ('a', 'b', 'c')  #b = (1, 2, 3):a變化,b不會改變
>>> b = ('d', 'e') # a = ('a', 'b', 'c'):b改變,a不會改變
# 二、可更改的對象
# 1. list
>>> l1 = [1, 2 ,3, ['a', 'b']]
>>> l2 =l1 #l2 = [1, 2, 3, ['a', 'b']]
>>> l1.append('new_l1') #l2 = [1, 2, 3, ['a', 'b'], 'new_l1'] :b隨a變化
>>> l2.append('new_l2') #l1 = [1, 2, 3, ['a', 'b'], 'new_l1', 'new_l2']:a隨b變化

# 同理:dict和set

 

2. 淺拷貝

淺拷貝會創建新對象(兩個對象的id不同)但是其內容是原對象的引用

一般情況下,一方改變不會影響另一方,但是如果存在對嵌套對象改變,則雙方會互相影響

三種方式

  1. 切片操作:l2 = l1[:]
  2. 工廠函數:l2 = list(l1)
  3. copy函數:l2 = copy.copy(l1)
>>> l1 = [1, 2, 3, ['a', 'b']]
>>> l2 = copy.copy(l1)  #l2 = [1, 2, 3, ['a', 'b']]

>>> id(l1)
4330497160
>>> id(l2)
4330557320  # l1、l2指向的是不同的地址

>>> [id(x) for x in l1] #l2內容是原對象l1的引用(引用的元素地址相同)
[4297554880, 4297554912, 4297554944, 4330499912]
>>> [id(x) for x in l2]
[4297554880, 4297554912, 4297554944, 4330499912]

# 一般情況下,l1和l2的改變互不影響
# 但是如果其中一個對里面的嵌套list進行修改,另一個會發生變化
>>> l2.append('new_l2') #l2 = [1, 2, 3, ['a', 'b'], 'new_l2']
>>> l1 #l1不發生改變
[1, 2, 3, ['a', 'b']]

>>> l1.append('new_l1') #l1發生改變,l2不變
>>> l1  #[1, 2, 3, ['a', 'b'], 'new_l1']
>>> l2  #[1, 2, 3, ['a', 'b'], 'new_l2']

>>> l1[3].append('c') #l1更改里面的嵌套list,l2改變
>>> l1    #[1, 2, 3, ['a', 'b', 'c'], 'new_l1']
>>> l2    #[1, 2, 3, ['a', 'b', 'c'], 'new_l2']

 

3. 深拷貝

方式:copy.deepcopy()

和淺拷貝對應,深拷貝拷貝了對象的所有元素,包括多層嵌套的元素。因而,它的時間和空間開銷要高。

兩個對象完全獨立,互不影響

 

另外注意:

1、對於非容器類型,如數字,字符,以及其它“原子”類型,沒有拷貝一說。產生的都是原對象的引用

2、如果元組變量值包含原子類型對象,即使采用了深拷貝,也只能得到淺拷貝

 

**3. Python的特點——美團

1. 動態語言。

  • 動態類型語言:也就是可以不用事先聲明變量,就可以對它賦值編譯的時候,Python不會進行類型檢查,而是直至運行時,自動判斷變量類型並對變量賦值,所以可能拋出類型錯誤。
  • 靜態類型語言:變量在賦值前必須聲明數據類型。這樣的好處是在編譯器就可以發現類型錯誤。例如:C++/C、Java

2. 解釋型語言

  • 編譯型語言,例如C++,源文件需要通過編譯器使用一系列標志和選項轉換成計算機可以識別的二進制格式;在運行程序時,鏈接器/載入器軟件將程序從硬盤復制到內存,然后開始運行。
  • 對於解釋型語言,例如Python,在計算機內部,Python解釋器把源代碼轉換成稱為字節碼的中間形式,然后再把它翻譯成計算機使用的機器語言並運行

3. 支持面向對象編程和面向過程的編程。

  • 面向對象:圍繞着數據和功能
  • 面向過程:程序圍繞着過程或者函數(可重復使用的程序片段)構建

Python具有非常強大但是過於簡潔的執行面向對象編程的方式,特別是相對於C++或者Java這種大型語言來說

4. 高層語言

  • Python在編寫程序的時候,無需考慮如何管理你的程序使用內存一類的底層細節
  • C++語言:需要手動分配、銷毀對象的內存。

5. 速度快

Python 的底層是用 C 語言寫的,很多標准庫和第三方庫也都是用 C 寫的,運行速度非常快

6. 使用強制縮進來規范代碼,使得程序有極佳的可讀性。

7. 可移植性。基於其開源代碼的特性,Python已被移植到很多平台。

8.可擴展性。

如果需要一段運行很快的代碼,或者是不願開放的代碼,可以使用C/C++編寫,再再Python中調用

9. 擴展庫強大。特別是在數據處理方面

 

4. Python如何進行內存管理的?

從三個方面來說:1. 引用計數;2. 垃圾回收; 3.內存池機制;

1. 引用計數

Python是動態語言,即在使用前不需要聲明,對內存地址的分配是在程序運行時自動判斷變量類型並賦值

對每一個對象,都維護着一個指向改對象的引用的計數obj_refcnt

當變量引用一個對象,則obj_refcnt+1,如果引用它的變量被刪除,則obj_refcnt-1,系統會自動維護這個計數器,當其變成0時,被回收。

2. 內存池機制

Python內存機制分為6層,內存池用於管理對小塊內存的申請和釋放。

  • -1,-2層:主要由操作系統進行操作
  • 0層:是C中的malloc,free等內存分配和釋放函數進行操作
  • 1,2層:內存池,由Python的接口函數PyMem_Malloc函數實現,當對象<256字節的時候,由該層直接分配內存。雖然,對應的也會使用malloc直接分配256字節的內存快——解決C中malloc分配的內存碎片問題。
  • 3層:最上層,我們對Python對象的操作

內存池與C不同的是,:

  • 如果請求分配的內存在1~256字節之間就使用自己的內存管理系統,否則直接使用 malloc。
  • 這里還是會調用 malloc 分配內存,但每次會分配一塊大小為256k的大塊內存
  • 經由內存池登記的內存,最后被回收到內存池,而不會調用C的free釋放掉,以便下次使用。
  • 對於Python對象,如數值、字符串、元組,都有其獨立的私有內存池,對象間不共享他們的內存池。例如:如果分配又釋放了一個大的整數,那么這塊內存不能再被分配給浮點數。

3. Python的垃圾回收機制。

PythonGC主要是通過引用計數來跟蹤和回收垃圾,並使用“標記-清理”技術來解決容器對象可能產生的循環引用問題,使用“分代回收”提高回收的效率

1. 引用計數:在Python中存儲每個對象的引用計數obj_refcnt。當這個對象有新的引用,則計數+1,當引用它的對象被刪除,則計數-1,當技術為0時,則刪除該對象。

  • 優點:簡單、實時性
  • 缺點:維護引用奇數消耗資源、無法解決循環引用的問題。

2. 標記-清理:基本思路是按需分配等到沒有空閑時,從寄存器和程序棧中的引用出發,遍歷以對象為節點,引用為邊所構成的圖,把所有可以訪問到對象都打上標記,然后清掃一遍內存空間,把沒有標記的對象釋放。

3. 分代收集

總體的思想:Python將系統中所有的內存塊根據對象存活時間划分為不同的代(默認是三代),垃圾收集頻率隨着“代”的存活時間的增大而減小

舉例:

我們分為新生代、老年代、永久代。

在對對象進行分配時,首先分配到新生代。大部分的GC也是在新生代發生。

如果新生代的某內存M進過了N次GC以后還存在,則進入老年代,老年代的GC頻率遠低於新生代。

對於永久代,GC頻率特別低。

 

5. 單下划線、雙下划線

  • __name__一種約定,Python內部的名字,用來與用戶自定義的名字區分開,防止沖突
  • _name:一種約定,用來指定變量私有
    • import *情況下,解釋器會對單下划線開頭的名稱做處理
    • from module/package import *,任何單下划線開頭的名稱不會被導入,除非模塊/包的__all__列表明確包含了這些名稱
  • __name
    • 解釋器用_classname__name來代替這個名字用以區別和其他類相同的命名
    • 現在創建一個A的子類BB就不會輕易的覆蓋掉A中的__method_name了,相當於:Java中的final

 

**6. 類變量和實例變量

類變量就是供類使用的變量,實例變量就是供實例使用的

作為函數的參數/返回值——賦值引用——1. 對於不可更改對象,互不影響;2. 可更改對象:會互相影響。

class Person:
    name="aaa"  #類變量

p1=Person()
p2=Person()
p1.name="bbb" #實例p1調用類變量name='aaa',在實例作用域里將類變量的引用改變了,變成了實例變量。self.name指向實例變量。
print p1.name  # bbb
print p2.name  # aaa
print Person.name  # aaa

 

如果將類變量name =[]換成數組,則是不可變——相當於在內存中更改了對象的值

class Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1) #類變量
p1.name  # [1]
p2.name  # [1]
Person.name  # [1]

 

**7. @staticmethod和@classmethod

他們都是裝飾器。裝飾器是一種特殊的函數,要么接受函數作為參數,並返回一個函數;要么接受一個類作為參數,返回一個類。他們的作用是對運行中的程序動態的加入特定的功能而不改變函數的本身。@標記是語法糖,可以讓你簡單易讀的方式獲得方法的裝飾目標對象

  實例/成員方法 類方法 靜態方法
標記
例如:def __init__(self, name)
@classmethod @staticmethod
參數 實例self 類實例cls 無,不需要對類或者實例進行綁定
調用 a.foo(x)只可用實例調用
不可以用類調用
A.class_foo(x)、a.class_foo(x)
類、實例可調用類方法
A.static_foo(x)、a.static_foo(x)
類、實例可調用類方法
訪問類變量

可以訪問。若在成員方法內對:

1. 不可更改的類變量做成更改,
  則實例中的類變量發生變化,但是對於類對象中的類變量沒有改變。

2. 對可更改的類變量做成更改,實例中和類對象中的類變量都改變。

可以訪問,直接影響
(無論是可更改還是不可更改)

可以訪問。
訪問實例變量 能訪問
每一個實例的實例變量互不影響
(無論是不可更改對象還是可更改對象)
不可訪問  不可訪問
訪問靜態變量 不可以 不可訪問 可以

 

一、實例方法/成員函數。

定義:實例所使用的方法。

1. 類的成員函數能訪問類變量嗎?——滴滴

 

#coding=utf-8
class foo(object):
    class_var = 0 # 類變量,所有實例對象所共有
    class_var_dict = []

    def __init__(self):
        self.instance_var = 1 #實例/成員變量:針對每個實例對象自身持有
        self.instance_var_dict = []

        # 在成員方法中。對可變類變量class_var_dict進行修改——都發生變化
        self.class_var_dict.append('a') 
    
       # 在成員方法中,對不可變類變量class_var進行修改
        #不影響foo.class_var = 0
       # 兩個實例變量發生變化:foo1.class_var = foo2.class_var = 1 
        self.class_var += 1

# 創建兩個實例
foo1 = foo()
foo2 = foo()    

在成員方法中,

  • 對不可更改的類變量(數字、字符串、tuple)做成更改,則實例中的類變量發生變化,但是對於類對象中的類變量沒有改變
  • 可更改的類變量(list、set、dict)做成更改,則實例中和類對象中的類變量都改變。
# 查看類對象和兩個實例的變量:自己的命名空間__dict__(各不相同)
print('foo1的命名空間:', id(foo1.__dict__), (foo1.__dict__)) 
print('foo2的命名空間:',id(foo2.__dict__), (foo2.__dict__))
# foo1的命名空間: 4300908680 {'instance_var': 1, 'class_var': 1}
# foo2的命名空間: 4300909064 {'instance_var': 1, 'class_var': 1}
# foo類對象的命名空間: 4328743560 {'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'foo' objects>, 'class_var_dict': ['a', 'a'], '__init__': <function foo.__init__ at 0x102074950>, 'class_var': 0, '__dict__': <attribute '__dict__' of 'foo' objects>}

# 在實例中,更改不可更改類變量——互不影響
foo1.class_var = '2'
print('foo2.class_var:', foo2.class_var) #foo2.class_var: 1
print('foo類對象的類變量class_var:', foo.class_var) #foo.class_var: 0

# 在實例中,更改可更改的類變量——都會更改
foo1.class_var_dict.append('new_for_foo1')
print('foo2.class_var_dict:',foo2.class_var_dict) #foo2.class_var_dict: ['a', 'a', 'new_for_foo1']
print('foo類對象的類變量class_var_dict:', foo.class_var_dict)  # foo.class_var_dict: ['a', 'a', 'new_for_foo1']

2. 類的成員函數能夠訪問實例變量嗎?

每個實例的實例變量都完全獨立,互不影響

在成員函數中調用實例變量:self.instance_var

在實例中調用實例變量:實例名.instance_var

# 更改foo1中實例變量——即使是可更改對象dict,都互不影響
print('foo1中實例變量的ID:', id(foo1.instance_var_dict))#4321671176
print('foo2中實例變量的ID:', id(foo2.instance_var_dict))#4321669192

foo1.instance_var_dict.append('new_for_foo1')
print('foo1.instance_var_dict:', foo1.instance_var_dict)
#foo1.instance_var_dict: ['new_for_foo1']
print('foo2.instance_var_dict:', foo2.instance_var_dict)#foo2.instance_var_dict: []

 

二、靜態方法

靜態方法是一種普通函數,就位於類定義的命名空間中,它不會對任何實例類型進行操作。使用裝飾器@staticmethod定義靜態方法。類對象和實例都可以調用靜態方法:

1. 靜態方法能訪問成員變量嗎?

不能夠訪問成員變量,能訪問並更改類變量

   @staticmethod
    def static_foo():
        # 更改類屬性:
        foo.class_var = 10
        foo.class_var_dict.append('new_static_methoda')

        print('foo.class_var', foo.class_var)
        print('foo.class_var_dict', foo.class_var_dict)

2. 靜態方法能訪問類變量嗎?

可以

三、類方法

類方法是將類本身作為對象進行操作的方法。類方法使用@classmethod裝飾器定義,其第一個參數是類,約定寫為cls。類對象和實例都可以調用類方法

1. 類方法能訪問類成員變量嗎?

能夠。並且直接影響,無論是可更改還是不可更改屬性。

#coding=utf-8
class foo(object):
    class_var = 0 # 類變量,所有實例對象所共有
    class_var_dict = []

    def __init__(self):
        self.instance_var = 1
        self.instance_var_dict = []

        self.class_var_dict.append('a')
        self.class_var += 1

    @classmethod
    def class_foo(cls):
        # 類方法中調用實例變量——報錯!'foo' has no attribute 'instance_var'
        print('cls.isinstance:', cls.instance_var)
        print('cls.isinstance_dict:', cls.instance_var_dict)

        # 在類方法中調用類變量——直接影響
        cls.class_var_dict.append('new_class_foo')
        print('cls.class_var_dict:', cls.class_var_dict)
        print('foo.class_var_dict:', foo.class_var_dict)

        cls.class_var = 2
        print('cls.class_var:', cls.class_var)
        print('foo.class_var:', foo.class_var)


foo1 = foo()
foo2 = foo()

最后結果:

cls.class_var_dict: ['a', 'a', 'new_class_foo']
foo.class_var_dict: ['a', 'a', 'new_class_foo']
cls.class_var: 2
foo.class_var: 2

2. 類方法能訪問實例變量嗎?

不能

報錯:AttributeError: type object 'foo' has no attribute 'instance_var'

 

四、super方法

super()是調用父類的方法,在子類中,並不會主動調用父類的__init__方法

例如:

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

運行時,報錯

有兩種方法:

一、調用實例方法

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        Foo.__init__(self)   #類調用實例方法時,需要傳入self指代的實例
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

二、使用super()

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        super(Foo2, self).__init__()  
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

 

 

**8. 多線程

1. 滴滴問題:你有用過多線程嗎?多線程是用來干什么的?

2. 多線程作用:可以實現代碼的並發性,優化處理能力。同時更小的功能划分可以使代碼的重用性更好。

3. 實現GIL(全局解釋器鎖),用thread、threading和Queue模塊可以用來實現多線程編程

4. 解決方法:多進程和協程。協程是進程和線程的升級版。進程和線程都面臨着內核態和用戶態的切換問題。

協程可以自己控制切換的時機,不再需要陷入系統的內核態。

yield:協程的思想

1)GIL鎖原理

Python代碼的執行是由Python解釋器來控制,在同一時刻只能由一個線程在解釋器中運行。那么對Python解釋器的訪問就通過GIL來實現。

GIL能夠保證在同一時刻只有一個線程運行

在多線程的環境中,Python解釋器按以下方式執行

  1. 設置GIL
  2. 切換到一個線程去運行
  3. 當運行了指定數量的字節碼指令,或線程主動讓出控制(可以調用time.sleep(0)),則把該線程設置為睡眠狀態,解鎖GIL
  4. 當一個線程結束計算,它就退出了。
    • 可以調用thread.exit()之類的退出函數(但是不建議使用thread,因為主線程退出后,所有的子線程強制退出,且不會清理資源)
    • 也可以使用Python退出進程的標准方法,如sys.exit()或拋出一個SystemExit異常等。
    • 不過,不可以直接“殺掉”("kill")一個線程。
  5. 調用外部代碼(如C/C++擴展函數)時,GIL會被鎖定,直到這個函數執行完成。(由於在這期間沒有Python的字節碼被運行,所以不會做線程切換)編寫擴展的程序員可以主動解鎖GIL

2)比較三個模塊:thread、threading、queue

首先,thread和threading都允許程序員創建和管理線程

  1. thread:提供了基本的線程和鎖的支持。當主線程結束時,所有的線程都會被強制結束掉,沒有警告也不會有正常的清理工作。
  2. threading:能確保重要的子線程退出后進程才退出。
  3. Queue模塊:允許用戶創建一個可以用於多個線程之間共享數據隊列數據結構

對於threading模塊

import threading
from time import sleep, ctime

nsecs = [2, 4]

def loop(nloop, nsec):
    print("Start", nloop, "at", ctime())
    sleep(nsec)
    print("End", nloop, "at", ctime())

def main():
    print("All start at", ctime())
    nloops = range(len(nsecs))
    nthreads = [] #線程組

    # 創建線程
    for i in nloops:
        t = threading.Thread(target=loop, args=(i, nsecs[i]))
        nthreads.append(t) #添加到線程組

    # 執行線程
    for i in nloops:
        nthreads[i].start()

    # 等待所有線程執行完成
    for i in nloops:
        nthreads[i].join()

    print("All done at ", ctime())

if __name__ == "__main__":
    main()

對於thread模塊

import _thread
from time import sleep, ctime

loops = [4, 2]

def loop(nloop, nsec, lock):
    print("Start", nloop, "at", ctime())
    sleep(nsec)
    print("End", nloop, "at", ctime())
    lock.release()

def main():
    print("All start at ", ctime())
    nlocks = []
    nloops = range(len(loops))
    nsecs = loops

    for i in nloops:
        # 分配鎖
        lock = _thread.allocate_lock()
        # 鎖定鎖
        lock.acquire()
        # 追加到locks[]數組中
        nlocks.append(lock)

   # 對每一個線程調用loop函數
    for i in nloops:
        _thread.start_new_thread(loop, (nloops[i], nsecs[i], nlocks[i]))

    # 判斷所有的nloop線程都執行完以后結束
    for i in nloops:
        while nlocks[i].locked:
            pass

    print("All end at ", ctime())

if __name__ == "__main__":
    main()

 

9. Python自省

自省就是面向對象的語言所寫的程序在運行時,能知道對象的類型。

type()、dir():列出制定對象或類的屬性getattr()、hasattr()、isinstance()

 

10. @property裝飾器

作用:把一個方法變成屬性調用。在一個類中:

  1. getter方法變成屬性,只需要加上:@property
  2. setter方法變成屬性:需要創建另一個裝飾器@屬性名.setter
  3. 如果只有getter方法,沒有setter方法,則表明只讀,不可修改
class Student(object):

    @property #將score方法當作屬性調用(相當於score 的getter方法)
    def score(self):
        return self._score

    @score.setter #score的setter方法,其中包括了驗證score
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('Score must be an integer')
        if value < 0 or value > 100:
            raise ValueError('Score must between 0~100')
        self._score = value

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, str):
        if not isinstance(str, str):
            raise ValueError('Name must be a string')
        self._name = str

s = Student()
s.score = -11
s.score

運行結果:

Traceback (most recent call last):
  File "/Users/lesley/PycharmProjects/sort/Student.py", line 26, in <module>
    s.score = -11
  File "/Users/lesley/PycharmProjects/sort/Student.py", line 12, in score
    raise ValueError('Score must between 0~100')
ValueError: Score must between 0~100

 

11. Python中的pass語句作用是什么?

pass語句不會執行任何操作,一般作為占位符或者創建占位程序。

 

12. Python如何進行類型轉換?

Python提供了將變量或值從一個類型轉換成另一個類型的內置方法。

1. 數值類:int(x  [,base]), long(x [,base]), float(x), complex(real [, image])

2. 字符串:str(x)、chr(x)

3. 其他:list(s), tuple(s)

 

 

13. 字符串格式化:% 和 .format

.format()就是用{}:來代替%

1). %無法同時傳遞一個變量和元組

>>>name='sbw'
>>>"hi, there is %" %name and #輸出正確

>>>name=(1, 2, 3) #元組
>>>"hi there is %" %name #出錯
# 必須更改為:
>>>"hi, there is %" %(name, ) #提供一個單元素的元組

# 使用.format()
>>>"hi, there is {}".format(name) #'hi,there is (1, 2, 3)'

2). 可重用參數——通過位置映射

>>> '{name},{age}'.format(age=19,name='sbw') 'sbw,19'

 3). 格式限定符——通過{}:

它有着豐富的的“格式限定符”(語法是{}中帶:號),比如:

  • 填充與對齊

填充常跟對齊一起使用^、<、>分別是居中、左對齊、右對齊,后面帶寬、填充的字符,只能是一個字符,不指定的話默認是用空格填充

# 右對齊,填充字符為8個
>>> '{:>8}'.format('189')
 '     189'

# 用0填充8個字符位、右對齊
>>>'{:0>8}'.format('189')
 '00000189'

# 用字符a填充8個字符位、右對齊
>>>'{:a>8}'.format('189') 'aaaaa189'
  • 精度與類型f

精度常跟類型f一起使用

>>>'{:.2f}'.format(321.33345)
'321.33'

# .2表示:長度為2的精度;f表示:float

 

  • 其他類型

主要就是進制了,b、d、o、x分別是二進制、十進制、八進制、十六進制。

# 二進制:'{:b}
>>>'{:b}'.format(17)
 '10001'

# 十進制 {:d}
>>>'{:d}'.format(17)
 '17'

# 八進制 {:o}
>>>'{:o}'.format(17)
'21'

#十六進制 {:x}
>>>'{:x}'.format(17)
'11'

用,號還能用來做金額的千位分隔符。

# {:,}:金額的分隔符
>>> '{:,}'.format(1234567890)
 '1,234,567,890'

 

14. 迭代器(iteration)和生成器(generator)

14.1 迭代——可以通過for...in來遍歷——創建[for...in]

只要作用於一個可迭代對象,for都可以正常運行,不關心對象類型。

1. 可迭代對象——Iterable

可以直接作用於for循環的對象,稱為可迭代對象,有以下幾種:

  • 一類是集合數據類型,如listtupledictsetstr等;
  • 一類是generator,包括生成器和帶yield的generator function。

可以使用isinstance()判斷一個對象是否是Iterable對象:

#判斷一個對象是否可以迭代——collection.Iterable
>>> from collections import Iterable
>>> isinstance('abd', Iterable) #str可迭代
True
>>> isinstance([1, 2, 3], Iterable) #數組可迭代
True
>>> isinstance(123, Iterable)#數字不可迭代
False
>>> 

2. 迭代器——可以被next()函數調用並不斷返回下一個值的對象(Iterator)——計算是惰性的

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是可迭代對象,但是str、dict、list雖然Iterable,但是不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

為什么listdictstr等數據類型不是Iterator

這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

  • 對dict迭代
# 1. 對dict迭代
>>>dict = {'a':1, 'b':2, 'c':3}

#對key(默認;因為存儲不按照list,所以迭代結果順序可能不同)
>>> for key in dict:
...     print(key)
... 
c
b
a

# 對value進行迭代
>>> for value in dict.values():
...     print(value)
... 
3
2
1

# 同時對values和key迭代:
>>> for key,item in dict.items():
...     print((key, item))
... 
('c', 3)
('b', 2)
('a', 1)
  • 對字符串
>>> ch = 'ABC'
>>> for i in ch:
...     print(i)
... 
A
B
C
>>> 
  • Python內置的enumerate函數可以把一個list變成索引-元素對,這樣就可以在for循環中同時迭代索引和元素本身:
>>> for i in enumerate(['A', 'B', 'C']):
...         print(i, value)
... 
(0, 'A') 3
(1, 'B') 3
(2, 'C') 3

 

14.2 列表生成器/字典生成器

  1. 列表生成器

>>> L1 = ['Hello', 'World', 18, 'Apple', None]

>>> L2=[s.lower() for s in L1 if isinstance(s,str)==True]
>>> L2
['hello', 'world', 'apple']

例如:列出當前目錄下的所有文件和目錄名

>>> import os
>>> [dir for dir in os.listdir('.')]
['.bash_history', '.bash_profile', ...]

 

  2. 字典生成器

>>>dict = {'a':1, 'b':2, 'c':3}
>>> dict2 = {key:value for key,value in dict.items()}
>>> dict2
{'c': 3, 'b': 2, 'a': 1}

 

6.3 迭代器和生成器

引入生成器:列表容量大時,會占用很大的存儲空間——生成器可以一邊循環一邊計算

生成器 ——1. 創建:(x*x for x in range(3));2.邊循環邊計算(只迭代一次)

是迭代器的一種,但是你只能迭代它們一次。因為它們不是全部存在內存里,它們只在要調用的時候在內存里生成:

# 創建方法:()
>>> generator= (x*x for x in range(3))
>>> generator
<generator object <genexpr> at 0x1022ba990>

# 不能在生成器中用for i in mygenerator第二次調用生成器:
# 首先計算0,然后會在內存里丟掉0去計算1,直到計算完4.
# 只能調用一次
>>> for I in generator:
...     print(i)
... 
0
1
4
>>> for i in generator:  
...     print(i)
... 

 

14.4 yeild關鍵字

yeild 可以理解為返回一個生成器。對於一個生成器的函數,

  • 當for語句第一次調用函數里返回的生成器對象代碼才運行遇到yield語句就返回本次循環中的第一個返回值。
  • 下一次調用時,從上次返回的yield語句處繼續執行
  • 一旦函數運行並沒有碰到yeild語句就認為生成器已經為空了

例如:狄波拉數列

>>> def fib(max):
...     n, a, b = 0, 0, 1
...     while n < max:
...         yield b
...         a, b = b, a + b
...         n = n + 1
...     return 'done'
... 
>>> fib(10)
<generator object fib at 0x1022bae60>


# 輸出
>>> f =fib(10)
>>> for i in f:
...     print(i)
... 
1
1
2
3
5
8
13
21
34
55

# 可以用next()來獲取生成器的值(只能一次)>>> next(f)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

例如:楊輝三角

>>> def triangles():
...     L = [1]
...     while True:
...         yield L
...         L.append(0)
...         L = [L[i - 1] + L[i] for i in range(len(L))]
... 
>>> for t in triangles():
...         print(t)
...         n = n + 1
...         if n == 10:
...             break
... 
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

 

15. *args 和**kwargs

*args**kwargs只是為了方便並沒有強制使用它們。

  • *args:可變的位置參數:不確定函數里傳遞參數的個數(可傳遞任意數量)
>>> def print_everything(*args):
...     for count, thing in enumerate(args):
...             print('{0}, {1}'.format(count, thing))
... 
>>> print_everything(['a', 'b', 'c'])
0, ['a', 'b', 'c']
>>> print_everything('a', 'b', 'c')
0, a
1, b
2, c
  • **kwargs:可變的關鍵字參數:允許你使用沒有事先定義的參數名
>>> def table_things(**kwargs):
...     for name, value in kwargs.items():
...         print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit

*args**kwargs可以同時在函數的定義中,但是*args必須在**kwargs前面。

另外,調用函數時,也可以使用*、**

>>> def print_three_things(a, b, c):
...     print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)

a = aardvark, b = baboon, c = cat

 

16. 面向切面編程AOP和裝飾器——考的也非常多!!

1)AOP定義:在程序運行時,動態的將代碼切入到類的指定方法、指定位置上的編程思想。

  ——通過裝飾器實現

  • 切入到指定類/方法的代碼:切面
  • 切入到哪些類,哪些方法:切入點

2)用處:較為經典的有插入日志、性能測試、事務處理等,通過聲明的方式配置好。

——為什么不用公共類?如果我們只是提取了公共方法到一個獨立的類和獨立的方法中,然后再在其他類中調用,那么會在類與上面提到的獨立的類之間產生耦合,一旦這個獨立的類產生改變,那么會影響到調用他的類。

 

**17. 裝飾器

裝飾器高階函數,要么接受函數作為參數,並返回一個函數;要么接受一個類作為參數,返回一個類。他們的作用是對運行中的程序動態的加入特定的功能不改變函數的本身@標記是語法糖,可以讓你簡單易讀的方式獲得方法的裝飾目標對象

1. 基本的Decorator函數:

本質上來說:decorator是一個接受函數作為參數,並且返回函數的高階函數

舉例:在login()函數中輸出調試的信息

def printDebug(func):
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func()
        print('End %s()' % func.__name__)
    return wrapper

@printDebug #語法糖
def login():
    print('Login in')

login() # 執行順行:printDebug(login)()

輸出結果為:

Start login()
Login in
End login()

注意:

>>> login.__name__
'wrapper'

我們發現在decorator裝飾過后的now()函數的名字__name__變成了:wrapper。為了防止有些依賴函數簽名的代碼出錯,所以需要把原始函數的__name__等屬性復制到wrapper函數中:通過Python內置的functools.wraps實現:

import functools def printDebug(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func()
        print('End %s()' % func.__name__)
    return wrapper

@printDebug
def login():
    print('Login in')

2. login帶參數:login(user)

import functools
def printDebug(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func(*args, **kwargs)
        print('End %s()' % func.__name__)
    return wrapper

@printDebug
def login(user): # 函數帶有參數
    print('Login in '+ user)

login('sbw')

執行的順序:[decprated]login('sbw')——>printDebug(login)('sbw')——>wrapper('sbw')——>[real]login('sbw')

3. 裝飾器本身有參數——三層

我們在定義渣裝飾器的時候,也可以加入參數,比如我們傳入一個參數來指定Bug的級別

import functools
# 裝飾器帶有參數:Bug級別:level
def print_debug_level(level):
    def print_debug(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('Start login with level:' + str(level))
            func(*args, **kwargs)
            print('End %s()' % func.__name__)
        return wrapper
    return print_debug

@print_debug_level(level=5) def login(user): # 函數帶有參數
    print('Login in '+ user)

login('sbw')

輸出結果:

Start login with level:5
Login in sbw
End login()

 

執行過程:[decorated]login('sbw')——>print_debug_level(5)——>print_debug[with closure value 5](login)('sbw')——>wrapper('sbw')[use value 5] ——>[real]login('sbw')

4. 裝飾器帶有返回值的函數

例如:login返回值message用來判斷login是否成功

import functools
# 裝飾器帶有參數:Bug級別:level
def print_debug_level(level):
    def print_debug(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('Start login with level:' + str(level))
            result = func(*args, **kwargs)
            print('The Login result is:',result)
            return result
        return wrapper
    return print_debug

@print_debug_level(level=5)
def login(user): # 函數帶有參數
    print('Login in '+ user)
    msg='success' if user == 'sbw' else 'fail'
    return msg

login('sbw')

5. 改進

在上面這個例子中,因為裝飾器只能用於函數的整體,不能用於一部分,所以我們可以將驗證validation的代碼提取出來。同時因為validation()過程耗時,所以我們可以添加緩存cache

#coding=utf-8
import time
import functools
dict_cache={} # 存放所有驗證過的用戶和驗證的結果

def cache(func):
    @functools.wraps(func)
    def wrapper(user):
        now = time.time()
        if user in dict_cache: # 判斷這個用戶是否曾經驗證過
            print('曾經驗證過')
            user, cache_time = dict_cache[user]
            if now - cache_time > 30: # 如果上一次驗證的時間在30s之前,則重新進行驗證
                print(u'超過了緩存時間')
                result = func(user)
                dict_cache[user] = (result, now)
            else: # 還在緩存時間內
                print('還在緩存時間內')

        else:
            print('第一次驗證')
            result = func(user)
            dict_cache[user] = (result, now)

        return result
    return wrapper

def login(user):
    print('in login:'+user)
    msg = validation(user)
    print(msg)

@cache def validation(user):
    time.sleep(1)
    msg = 'success' if user == 'sbw' else 'fail'
    return msg

login('sbw')
login('lf')
login('lf')
login('sbw')
login('sbw')

6. 應用多個裝飾器

def print_debug(func):
    def wrapper():
        print('enter the login')
        func()
        print('exit the login')

    return wrapper


def others(func):  # define a other decorator
    def wrapper():
        print('***other decorator***')
        func()

    return wrapper


@others  # apply two of decorator
@print_debug
def login():
    print('in login:')


@print_debug  # switch decorator order
@others
def logout():
    print('in logout:')


login()
print('---------------------------')
logout()

運行結果:

***other decorator***
enter the login
in login:
exit the login
---------------------------
enter the login
***other decorator***
in logout:
exit the login

對於logout()函數應用於裝飾器可以看成:

@print_debug    #switch decorator order
(
    @others
    (
        def logout():
            print('in logout:')
    )
)

[print_debug decorated]logout() ——>print_debug.wrapper[call [others decorated]logout() ] ——>print_debug.wrapper.other.wrapper[call real logout]

 

18. 鴨子類型

簡單來說,如果一個東西,走路像鴨子,叫起來像鴨子,游泳起來像鴨子,我們就認為她是一只鴨子。——只關心行為

例如,有兩個類,一個是人類,一個是鴨子類,他們都會吃飯和走路,我們不管他們這兩個方法中返回的值是否相同,只要她們能夠吃飯和走路,我們統統當作鴨子,傳遞給一個函數當參數。

例如:list.extend(obj)方法中,只要obj可以迭代,那么我們不管他是list/dict/tuple/strs我們都可以傳遞進去當參數。

 

19. Python不支持函數重載

1)什么是函數重載?

函數重載,主要是為了解決兩個問題:

  • 可變參數類型
  • 可變參數個數

另外,一個基本的設計原則是,僅僅當兩個函數除了參數類型和參數個數不同以外,其功能是完全相同的,此時才使用函數重載,如果兩個函數的功能其實不同,那么不應當使用重載,而應當使用一個名字不同的函數。

2)Python調用函數機制:使用*agrs:可傳入任意數量的參數;**wkagrs:可傳入任意類型的闡述(事先未聲明的)

 

20. 新式類和舊式類

MBR問題:(新式類是廣度優先,舊式類是深度優先)

http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html

 

21. __new__ 和 __init__

  1. __new__是一個靜態方法,而__init__是一個實例方法.
  2. __new__方法會返回一個創建的實例,而__init__什么都不返回.
  3. 只有在__new__返回一個cls的實例時后面的__init__才能被調用.
  4. 當創建一個新實例時調用__new__,初始化一個實例時用__init__.

假如cls是一個類對象,當你調用C(*args, **kwargs)來創建一個類C的實例,python的內部機制:

  1. 通過:C.__new__(C, *args, **wkargs)創建這個類實例,並返回這個實例c
  2. 確認c是C的實例
  3. 調用C.__init(c, *args, **kwargs)初始化實例c
c = C.__new__(C, *arg, **kwargs)
if(is instance(c, C)):
    c.__init__(c, 23) #__init__第一個參數要為實例對象

 

22. Python命名空間

1)定義

在Python中,使用命名空間來記錄變量的軌跡,本質是一個字典,key是變量名,value是那些變量的值。命名空間分為:

  • Local(局部命名空間):函數的命名空間。記錄了:函數的參數、局部變量。可用locals()查看
  • Enclosing(內嵌函數命名空間):def、lambda
  • Global(模塊的命名空間)。記錄了:函數、類、其他導入模塊、模塊級的變量和常量。可用globals()查看
  • Built-in(內置命名空間):(Python標准庫中的函數)模塊,任何模塊均可訪問它,它存放着內置的函數和異常。

2)命名空間查找順序——Python函數作用域的LEGB順序

L:local 局部命名空間(當前函數)
E:enclosing 內嵌函數(def;lambda;父函數)
G:global 模塊命名空間
B:build-in 內置命名空間

如果找不到:NameError: name 'aa' is not defined。

3)命名空間的生命周期

  • 內置命名空間python解釋器啟動時創建,一直保留、不刪除
  • 模塊的全局命名空間模塊定義時被讀入創建,一直保留到解釋器退出
  • 當函數被調用時,創建一個局部命名空間當函數返回結果或拋出異常時,被刪除。

4)locals() & globals()

locals():返回的是局部命名空間的拷貝。——只讀

globals():返回的是全局命名空間。——可改動

5)from module import  & import module

  • 使用 import module,模塊自身被導入,但是它保持着自已的名字空間——>需要使用模塊名來訪問它的函數或屬性(module.function
  • 使用 from module import,實際上是從另一個模塊中將指定的函數和屬性導入到你自己的名字空間,——>可以直接訪問它們卻不需要引用它們所來源的模塊的原因。

 

23. 閉包(closure)

閉包(closure)是函數式編程的重要的語法結構,也是一種組織代碼的結構。它提高了代碼的可重用行。

創建一個閉包的三個條件

  1. 必須有內嵌函數
  2. 內嵌函數必須要引用外部函數中的變量
  3. 外部函數返回值必須是內嵌函數

重點是函數運行后並不會被撤銷,像單例模型中instance字典一樣,當函數運行完后,instance並不被銷毀,而是繼續留在內存空間里。這個功能類似類里的類變量,只不過遷移到了函數上。

閉包就像個空心球一樣,你知道外面和里面,但你不知道中間是什么樣。

 

24. 面向函數式編程

什么是面向函數式編程?——關注我們要干什么?而不是怎么干

1. lambda匿名函數

>>> sum = lambda x,y:x*y >>> sum(1,2)
2

匿名函數的命名規則,用lamdba 關鍵字標識,冒號(:)左側表示函數接收的參數(a,b) ,冒號(:)右側表示函數的返回值(a+b)。

2. map()

有兩個參數:函數名,作用對象(eg:list)。

r = map(func, seq)
  • 函數func作用於seq中每個元素
  • 輸出必須轉為list()
>>> m = map(lambda x:x*x, range(3))
>>> list(m) # 輸出必須轉化成list()
[0, 1, 4]
>>> m
<map object at 0x1022be828>

3. reduce()

  • 需要引入reduce()函數:from fuctools import reduce
  • 類似於map,但是作用於seq中每兩個元素
  • 直接輸出

例如:階乘的實現:

>>> from functools import reduce
>>> result = reduce(lambda x,y:x*y, range(1, 6))
>>> result
120

4. filter()

# 定義:通過函數func過濾掉seq中不滿足條件的元素
# func:返回值必須為boolean

filter(func, seq)

需要注意:

  • 必須要用list()才能返回結果
>>> numbers = [1, 2, -3, 8, -2, 4, 7]
>>> even_number = list(filter(lambda x:x>0 and x%2==0, numbers))
>>> even_number
[2, 8, 4]

 

25、Python中的設計模式

2.1  單例模式(Singleton)——必考!!絕對要記住1-2個方法

1. 定義

  • 單例模式:保證系統中一個類只有一個實例。
  • 目的:通過單例模式,可以保證系統中一個類只有一個實例,而且該實例易於外界訪問
  • 例如:一台計算機中可以有若干個打印機,但是只能有一個printer spooler(打印機后台處理服務),避免兩個打印作業同時輸出到打印機中。
  • 實現的要點:
    • 只提供私有的構造函數
    • 只含有一個該類的靜態私有對象
    • 提供了一個靜態的共用函數用於創建和獲取他本身的靜態私有對象

2. 四種實現方法:

1)使用__new__方法——將類變量綁定到_isinstance

# 方法一:用__new__實現,將類實例綁定到類變量_instance上面
# 如果cls._instance為None說明該類還沒有實例化過,實例化該類,並返回  
# 如果cls._instance不為None,直接返回cls._instance  
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not (hasattr(cls, '_instance')):
            orig = super(Singleton, cls) 
            cls._instance = orig.__new__(cls, *args, **kwargs)
        return cls._instance

實例:

class MyClass(Singleton):  
    a = 1  

one = MyClass()  
two = MyClass()  
  
two.a = 3  
one.a  #3  
#one和two完全相同,可以用id(), ==, is檢測  
id(one) #29097904 id(two) #29097904 one == two #True one is two #True

2)Borg——指向同一個__dict__

__dict__是一個字典,鍵是屬性名,值為屬性值

思想:共享屬性。所謂單例模式就是所有引用(實例、對象)擁有相同的屬性和方法;因為方法天然相同,所以主要保證屬性一致。——>將實例的__dict__屬性指向(引用)同一個字典

class Borg(object):  
    _state = {}  
    def __new__(cls, *args, **kw):  
        ob = super(Borg, cls).__new__(cls, *args, **kw)  
        ob.__dict__ = cls._state  
        return ob  
  
class MyClass2(Borg):  
    a = 1  
  
one = MyClass2()  
two = MyClass2()  
  
#one和two是兩個不同的對象,id, ==, is對比結果可看出  
two.a = 3  
one.a   #3  
id(one)  #28873680  
id(two)  #28873712  

one == two   #False  
one is two  #False  

#但是one和two具有相同的(同一個__dict__屬性),見:  
id(one.__dict__)  #30104000  
id(two.__dict__)  #30104000  

3) import——天然的的單例模式

因為在Python中,模塊只會出初始化一次,所有變量都歸屬於某個模塊,import機制是線程安全的

例如,我們創建一個函數,獲取匯率。在一般情況下,我們只需要第一次獲得這個匯率,以后對它進行操作就行了。

my_singleton.py:

class My_Singleton(object):
    def func(self):
        val = 1
 my_singleton = My_Singleton()
    

to use

from my_singleton import my_singleton
my_singleton.func()

4). 裝飾器 

def singleton(cls, *args, **kwargs):  
    instances = {}  
    def _singleton():  
        if cls not in instances:  
            instances[cls] = cls(*args, **kwargs)  
        return instances[cls]  
    return _singleton  
 
@singleton class MyClass4(object):  
    a = 1  
    def __init__(self, x=0):  
        self.x = x  
  
one = MyClass4()  
two = MyClass4()  
  
two.a = 3  
one.a  #3  
id(one)  #29660784  
id(two)  #29660784  
one == two  #True  
one is two  #True  

one.x = 1  
one.x  #1  
two.x  #1  

 

 2.2 工廠模式

例如,一個婚介所,存放了男男女女的基本信息、要求。你想要找一個男朋友,只需要提出你的要求:高富帥,這個婚介所就只通過這些信息自動給你匹配。

class Shape:
    pass

class Circle(Shape):
    pass

class Square(Shape):
    pass

for name in ['Circle', 'Sqaure']:
    cls = globals[name] # cls是一個類,它的類型是type
    obj = cls()  # 可以通過type()動態的創建類

 結果:

cls    #<class '__main__.Square'>
obj.  # <__main__.Square object at 0x1012c3390>

type(obj)  #<class '__main__.Square'> obj是一個實例,它的類型就是 class Square
type(cls).  # <class 'type'> cls是一個類,它的類型就是type 

 

**26. os庫和sys庫的區別——騰訊有考!

1. os模塊:——提供了一系列對操作系統進行操作的接口

 1 os.getcwd() #獲取當前工作目錄,即當前python腳本工作的目錄路徑
 2 os.chdir("dirname")  #改變當前腳本工作目錄;相當於shell下cd
 3 os.curdir  #返回當前目錄: ('.')
 4 os.pardir  #獲取當前目錄的父目錄字符串名:('..')
 5 os.makedirs('dirname1/dirname2')    #可生成多層遞歸目錄
 6 os.removedirs('dirname1')    #若目錄為空,則刪除,並遞歸到上一級目錄,如若也為空,則刪除,依此類推
 7 os.mkdir('dirname')    #生成單級目錄;相當於shell中mkdir dirname
 8 os.rmdir('dirname')    #刪除單級空目錄,若目錄不為空則無法刪除,報錯;相當於shell中rmdir dirname
 9 os.listdir('dirname')    #列出指定目錄下的所有文件和子目錄,包括隱藏文件,並以列表方式打印
10 os.remove()  #刪除一個文件
11 os.rename("oldname","newname")  #重命名文件/目錄
12 os.stat('path/filename')  #獲取文件/目錄信息
13 os.sep    #輸出操作系統特定的路徑分隔符,win下為"\\",Linux下為"/"
14 os.linesep    #輸出當前平台使用的行終止符,win下為"\t\n",Linux下為"\n"
15 os.pathsep    #輸出用於分割文件路徑的字符串
16 os.name    #輸出字符串指示當前使用平台。win->'nt'; Linux->'posix'
17 os.system("bash command")  #運行shell命令,直接顯示
18 os.environ  #獲取系統環境變量
19 os.path.abspath(path)  #返回path規范化的絕對路徑
20 os.path.split(path)  #將path分割成目錄和文件名二元組返回
21 os.path.dirname(path)  #返回path的目錄。其實就是os.path.split(path)的第一個元素
22 os.path.basename(path)  #返回path最后的文件名。如何path以/或\結尾,那么就會返回空值。即os.path.split(path)的第二個元素
23 os.path.exists(path)  #如果path存在,返回True;如果path不存在,返回False
24 os.path.isabs(path)  #如果path是絕對路徑,返回True
25 os.path.isfile(path)  #如果path是一個存在的文件,返回True。否則返回False
26 os.path.isdir(path)  #如果path是一個存在的目錄,則返回True。否則返回False
27 os.path.join(path1[, path2[, ...]])  #將多個路徑組合后返回,第一個絕對路徑之前的參數將被忽略
28 os.path.getatime(path)  #返回path所指向的文件或者目錄的最后存取時間
29 os.path.getmtime(path)  #返回path所指向的文件或者目錄的最后修改時間

 例如:如何判斷一個文件是否存在,如果存在統計她的行數;不存在則建立這個文件並輸入“hello”

>>> import os 
>>> if not(os.path.exists('test.txt')):
...     with open('test.txt', 'wt') as file:
...             file.write('hello\n')
... else:
...     lines = readlines()
...     print('the len of file is %d' %len(lines))
... 

在Linux下實現:

songbowendeMacBook-Pro:~ lesley$ ls *.txt|grep 'test'|wc 2       2      20

 注意:

  • read 讀取整個文件
  • readline 讀取下一行,使用生成器方法
  • readlines 讀取整個文件到一個迭代器以供我們遍歷

2. sys:來處理Python運行時配置以及資源,從而可以與當前程序之外的系統環境交互

例如:與解釋器交互

我們可以用dir(sys)來查看sys模塊下面的built-in函數

常見的命令有:

1 sys.argv           #命令行參數List,第一個元素是程序本身路徑
2 sys.exit(n)        #退出程序,正常退出時exit(0)
3 sys.version        #獲取Python解釋程序的版本信息
4 sys.maxint         #最大的Int值
5 sys.path           #返回模塊的搜索路徑,初始化時使用PYTHONPATH環境變量的值
6 sys.platform       #返回操作系統平台名稱
7 sys.stdout.write('please:')
8 val = sys.stdin.readline()[:-1]

補充:

3. Shutil模塊高級的文件、文件夾、壓縮包處理模塊

 1 shutil.copyfileobj(force, fdst[, lenght]). #將文件內容拷貝到另一個文件中,可以部分內容
 2 shutil.copyfile(src, dst).                 # 拷貝文件
 3 shutil.copymode(src, dst)                  # 僅拷貝權限。內容、組、用戶都不變
 4 shutil.copystat(src, dst)                  # 拷貝狀態信息,包括:包括:mode bits, atime, mtime, flags
 5 
 6 shutil.copy(src, dst)                       # 拷貝文件和權限
 7 shutil.copy2(src, dst)                      # 拷貝文件和狀態信息
 8 
 9 shutil.copytree(src, dst, symlinks=False, ignore=None) #遞歸的拷貝文件
10 shutil.rmtree(path[, ignore_errors[, onerror]])        # 遞歸的刪除文件
11 shutil.move(src, dst)                                  # 遞歸的移動文件
12 
13 shutil.make_archive(base_name, format,...)  # 創建壓縮包並且返回路徑

 

shutil 對壓縮包的處理是調用 ZipFile 和 TarFile 兩個模塊來進行的,詳細:

import tarfile

# 壓縮
tar = tarfile.open('your.tar','w')
tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip')
tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip')
tar.close()

# 解壓
tar = tarfile.open('your.tar','r')
tar.extractall()  # 可設置解壓地址
tar.close()

zipfile 壓縮解壓
import tarfile

# 壓縮
tar = tarfile.open('your.tar','w')
tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip')
tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip')
tar.close()

# 解壓
tar = tarfile.open('your.tar','r')
tar.extractall()  # 可設置解壓地址
tar.close()

tarfile 壓縮解壓

 

27. read,readline和readlines

  • read 讀取整個文件
  • readline 讀取下一行,使用生成器方法
  • readlines 讀取整個文件到一個迭代器以供我們遍歷

 

https://zhuanlan.zhihu.com/p/23526961?refer=passer

https://github.com/taizilongxu/interview_python/blob/master/Readme.md#11-面向切面編程aop和裝飾器

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM