Python基礎(chǔ)

參考https://github.com/taizilongxu/interview_python#27-readreadline%E5%92%8Creadlines

1 Python的函數(shù)參數(shù)傳遞

看兩個例子

a = 1
def fun(a):
    a = 2
fun(a)
print a  # 1
a = []
def fun(a):
    a.append(1)
fun(a)
print a  # [1]

所有的變量都可以理解是內(nèi)存中一個對象的“引用”

通過id來看引用a的內(nèi)存地址可以比較理解:

a = 1
def fun(a):
    print "func_in",id(a)   # func_in 41322472
    a = 2
    print "re-point",id(a), id(2)   # re-point 41322448 41322448
print "func_out",id(a), id(1)  # func_out 41322472 41322472
fun(a)
print a  # 1

可以看到溯乒,在執(zhí)行完a = 2之后,a引用中保存的值产舞,即內(nèi)存地址發(fā)生變化,由原來1對象的所在的地址變成了2這個實體對象的內(nèi)存地址灿里。

而第2個例子a引用保存的內(nèi)存值就不會發(fā)生變化:

a = []
def fun(a):
    print "func_in",id(a)  # func_in 53629256
    a.append(1)
print "func_out",id(a)     # func_out 53629256
fun(a)
print a  # [1]

這里記住的是類型是屬于對象的忱详,而不是變量骑晶。而對象有兩種,“可更改”(mutable)與“不可更改”(immutable)對象拟赊。在python中刺桃,strings, tuples, 和numbers是不可更改的對象,而 list, dict, set 等則是可以修改的對象吸祟。

當(dāng)一個引用傳遞給函數(shù)的時候,函數(shù)自動復(fù)制一份引用,這個函數(shù)里的引用和外邊的引用沒有半毛關(guān)系了.所以第一個例子里函數(shù)把引用指向了一個不可變對象,當(dāng)函數(shù)返回的時候,外面的引用沒半毛感覺.而第二個例子就不一樣了,函數(shù)內(nèi)的引用指向的是可變對象,對它的操作就和定位了指針地址一樣,在內(nèi)存里進(jìn)行修改.

【注】第二個函數(shù)內(nèi)部調(diào)用的是可變對象的方法瑟慈,如果改為賦值語句的話,就會等同于第一個函數(shù)效果屋匕,具體如下:

a = []
def fun(a):
    print("func_in",id(a))  # func_in 53629256
    a = [1, 2, 3]
    print("func_in", id(a)) # func_in 53629300
print("func_out",id(a))     # func_out 53629256
fun(a)
print(a)  # []

當(dāng)然葛碧,很容易想到如果傳入不可變類型的字符串形參,然后在函數(shù)值調(diào)用字符串方法修改字符串呢炒瘟?放心吹埠,Python已經(jīng)幫你想好了阻止這種現(xiàn)象的出現(xiàn)而違反這種約定的方法第步,因為字符串的方法必須重新賦值才對原有串起到修改的作用疮装,如下,重新賦值就等同于上述函數(shù)一了粘都,所以Python函數(shù)參數(shù)傳遞嚴(yán)格遵循引用規(guī)則廓推。

a = "abc"
def fun(a):
    print("func_in",id(a))  # func_in 53629256
    a.replace("b", "d")     # a = a.replace("b", "d")
    print("func_in", id(a)) # func_in 53629256
print("func_out",id(a))     # func_out 53629256
fun(a)
print(a)  # abc

2 @staticmethod和@classmethod

Python其實有3個方法,即靜態(tài)方法(staticmethod),類方法(classmethod)和實例方法,如下:

def foo(x):
    print "executing foo(%s)"%(x)

class A(object):
    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)

    @classmethod
    def class_foo(cls,x):
        print "executing class_foo(%s,%s)"%(cls,x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x

a=A()

這里先理解下函數(shù)參數(shù)里面的self和cls.這個self和cls是對類或者實例的綁定,對于一般的函數(shù)來說我們可以這么調(diào)用foo(x),這個函數(shù)就是最常用的,它的工作跟任何東西(類,實例)無關(guān).
對于實例方法,我們知道在類里每次定義方法的時候都需要綁定這個實例,就是foo(self, x),為什么要這么做呢?因為實例方法的調(diào)用離不開實例,我們需要把實例自己傳給函數(shù),調(diào)用的時候是這樣的a.foo(x)(其實是foo(a, x)).類方法一樣,只不過它傳遞的是類而不是實例,A.class_foo(x)(其實是foo(A, x)).注意這里的self和cls可以替換別的參數(shù),但是python的約定是這倆,還是不要改的好.
對于靜態(tài)方法其實和普通的方法一樣,不需要對誰進(jìn)行綁定,唯一的區(qū)別是調(diào)用的時候需要使用a.static_foo(x)或者A.static_foo(x)來調(diào)用.

\ 實例方法 類方法 靜態(tài)方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

3 類變量和實例變量

類變量

是可在類的所有實例之間共享的值(也就是說,它們不是單獨分配給每個實例的)翩隧。例如下例中樊展,num_of_instance 就是類變量,用于跟蹤存在著多少個Test 的實例堆生。

實例變量

實例化之后专缠,每個實例單獨擁有的變量。

class Test(object):  
    num_of_instance = 0  
    def __init__(self, name):  
        self.name = name  
        Test.num_of_instance += 1  
  
if __name__ == '__main__':  
    print Test.num_of_instance   # 0
    t1 = Test('jack')  
    print Test.num_of_instance   # 1
    t2 = Test('lucy')  
    print t1.name , t1.num_of_instance  # jack 2
    print t2.name , t2.num_of_instance  # lucy 2

【補(bǔ)充】

class Person:
    name="aaa"

p1=Person()
p2=Person()
p1.name="bbb"
print p1.name  # bbb
print p2.name  # aaa
print Person.name  # aaa

這里p1.name="bbb"實際是p1對象為自己創(chuàng)建了一個實例變量淑仆,之后p1.name中的name變量便不再是類屬性涝婉,而是p1對象自己的實例屬性,這種不通過init函數(shù)初始化實例屬性的方法是不好的蔗怠,一般慎用墩弯!
再看下面的例子:

class Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1)
print p1.name  # [1]
print p2.name  # [1]
print Person.name  # [1]

類屬性為可變變量,對象.類屬性.方法()可以直接修改類屬性(慎用)寞射,這就回歸了我們問題一的函數(shù)參數(shù)傳遞渔工,一般修改類屬性還是類名.類屬性修改比較好。

4 Python自省

這個也是python彪悍的特性.

自省就是面向?qū)ο蟮恼Z言所寫的程序在運行時,所能知道對象的類型.簡單一句就是運行時能夠獲得對象的類型.比如:
type()---變量類型
dir()---返回對象的屬性列表
getattr()---返回一個對象屬性值(getattr(對象, 屬性))
hasattr()---判斷對象是否包含某種方法(hasattr(list, 'append'))
isinstance()---判斷一個對象是否是一個已知的類型(isinstance (a,int))

a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print type(a),type(b),type(c) # <type 'list'> <type 'dict'> <type 'bool'>
print isinstance(a,list)  # True

5 列表推導(dǎo)式和字典推導(dǎo)式

# 列表推導(dǎo)式
l= [squared(i) for i in range(30) if i % 3 is 0]
# 字典推導(dǎo)式
d = {key: value for (key, value) in iterable}

6 Python中單下劃線和雙下劃線

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

__foo__:一種約定,Python內(nèi)部的名字,用來區(qū)別其他用戶自定義的命名,以防沖突桥温,就是例如__init__(),__del__(),__call__()這些特殊方法
_foo:一種約定,用來指定變量私有.程序員用來指定私有變量的一種方式.不能用from module import * 導(dǎo)入引矩,其他方面和公有一樣訪問;
__foo:這個有真正的意義:解析器用_classname__foo來代替這個名字,以區(qū)別和其他類相同的命名,它無法直接像公有成員一樣隨便訪問,通過對象名._類名__xxx這樣的方式可以訪問.

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

.format在許多方面看起來更便利,對于%最煩人的是它無法同時傳遞一個變量和元組.你可能會想下面的代碼不會有什么問題:

"hi there %s" % name

但是,如果name恰好是(1,2,3),它將會拋出一個TypeError異常.為了保證它總是正確的,你必須這樣做:

"hi there %s" % (name,)   # 提供一個單元素的數(shù)組而不是一個參數(shù)

但是有點丑脓魏。.format就沒有這些問題兰吟。

"hi there {}".format(name)

8 迭代器和生成器

這里有個關(guān)于生成器的創(chuàng)建問題面試官有考: 問: 將列表生成式中[]改成() 之后數(shù)據(jù)結(jié)構(gòu)是否改變? 答案:是茂翔,從列表變?yōu)樯善?/p>

>>> 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 0x0000028F8B774200>

通過列表生成式混蔼,可以直接創(chuàng)建一個列表,將所有的值一次性加載到內(nèi)存中珊燎。但如果創(chuàng)建一個包含百萬元素的列表惭嚣,會占用很大的內(nèi)存空間,如:我們只需要訪問前面的幾個元素悔政,后面大部分元素所占的空間都是浪費的晚吞。因此,沒有必要創(chuàng)建完整的列表(節(jié)省大量內(nèi)存空間)谋国。
在Python中槽地,我們可以采用生成器:邊循環(huán),邊計算的機(jī)制—>generator芦瘾,生成器是將列表生成式的[]改成(),不會將所有的值一次性加載到內(nèi)存中,延遲計算,一次返回一個結(jié)果,它不會一次生成所有的結(jié)果,這對大數(shù)據(jù)量處理,非常有用捌蚊。如:

sum(x for x in range(10000000000))
sum([x for x in range(10000000000)])

第一個幾乎沒什么內(nèi)存占用,第二個內(nèi)存占有很多。

yield實現(xiàn)簡單的生成器

一般函數(shù)生成斐波那契數(shù)列

def create_num(n):
    a, b = 0, 1
    current_num = 0
    while current_num < n:
        print(a)
        a, b = b, a + b
        current_num += 1

create_num(5)
# 0
# 1
# 1
# 2
# 3

上述函數(shù)將數(shù)列直接打印近弟,但是我們需要將這個數(shù)列變?yōu)橐粋€可迭代對象缅糟,能夠用for循環(huán)去遍歷(當(dāng)然我們可以將上述函數(shù)進(jìn)行改造,使其生成一個列表祷愉,但如果是生成斐波那契數(shù)列前一百萬項窗宦,那么這個列表將占用很大內(nèi)存),可以使用yield將函數(shù)改為生成器二鳄,這樣需要的時候再計算赴涵,不會占用很大空間,實現(xiàn)很簡單订讼,只需將create_num函數(shù)中的print語句改為yield語句即可使create_num函數(shù)變?yōu)橐粋€生成器髓窜,代碼如下:

def create_num(n):
    a, b = 0, 1
    current_num = 0
    while current_num < n:
        # print(a)
        yield a  # 如果一個函數(shù)中有yield,那么這個函數(shù)就不再是函數(shù)躯嫉,而是一個生成器模板
        a, b = b, a + b
        current_num += 1

# 創(chuàng)建一個生成器對象
obj = create_num(5)
for num in obj:
    print(num)
# 0
# 1
# 1
# 2
# 3

9 *args 和 **kwargs

args和kwargs只是為了方便并沒有強(qiáng)制使用它們.
當(dāng)你不確定你的函數(shù)里將要傳遞多少參數(shù)時你可以用
args.例如,它可以傳遞任意數(shù)量的參數(shù):

>>> def print_everything(*args):
        for count, thing in enumerate(args):
...         print '{0}. {1}'.format(count, thing)
...
>>> print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage

相似的,**kwargs允許你使用沒有事先定義的參數(shù)名:

>>> 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

你也可以混著用.命名參數(shù)首先獲得參數(shù)值然后所有的其他參數(shù)都傳遞給args和*kwargs.命名參數(shù)在列表的最前端.例如:

def table_things(titlestring, **kwargs)

args和kwargs可以同時在函數(shù)的定義中,但是args必須在kwargs前面.
當(dāng)調(diào)用函數(shù)時你也可以用
*語法.例如:

>>> 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

就像你看到的一樣,它可以傳遞列表(或者元組)的每一項并把它們解包.注意必須與它們在函數(shù)里的參數(shù)相吻合.當(dāng)然,你也可以在函數(shù)定義或者函數(shù)調(diào)用時用*.
當(dāng)理解了這些纱烘,試試下面:

def print_three_things(name, *args, **kwargs):
    print(name)
    for elem in args:
        print(elem, end=" ")
    print()
    for k, v in kwargs.items():
        print('{0} = {1}'.format(k, v), end=",")
    print()

name = "xiaoming"
l = ['1', '2', '3']
d = {"age": 18, "height": "183cm"}
print_three_things(name, *l, **d)

# xiaoming
# 1 2 3 
# age = 18,height = 183cm,

10 面向切面編程AOP和裝飾器

AOP

簡言之、這種在運行時祈餐,編譯時擂啥,類和方法加載時,動態(tài)地將代碼切入到類的指定方法帆阳、指定位置上的編程思想就是面向切面的編程哺壶。

我們管切入到指定類指定方法的代碼片段稱為切面屋吨,而切入到哪些類、哪些方法則叫切入點山宾。有了AOP至扰,我們就可以把幾個類共有的代碼,抽取到一個切片中资锰,等到需要時再切入對象中去敢课,從而改變其原有的行為。

優(yōu)點是:這樣的做法绷杜,對原有代碼毫無入侵性

裝飾器

裝飾器是一個很著名的設(shè)計模式直秆,經(jīng)常被用于有切面需求的場景,較為經(jīng)典的有插入日志鞭盟、性能測試圾结、事務(wù)處理等。裝飾器是解決這類問題的絕佳設(shè)計齿诉,有了裝飾器筝野,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。

概括的講粤剧,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能

函數(shù)就是對象.因此,對象:

  • 可以賦值給一個變量
  • 可以在其他函數(shù)里定義
  • 函數(shù)可以返回另一個函數(shù)
  • 函數(shù)作為參數(shù)傳遞

簡單裝飾器

def decorator(fun):
    def wrapper():
        print("方法執(zhí)行前")
        fun()
        print("方法執(zhí)行后")
    return wrapper

@decorator  
# 相當(dāng)于my_fun = decorator(my_fun)
def my_fun():
    print("需要裝飾的函數(shù)")

# 執(zhí)行
my_fun()
# 輸出:
# 方法執(zhí)行前
# 需要裝飾的函數(shù)
# 方法執(zhí)行后

在裝飾器函數(shù)里傳入?yún)?shù)

def decorator(fun):
    print("裝飾器內(nèi)的語句")  # 此處會在裝飾的時候執(zhí)行
    def wrapper(arg1, arg2):
        print("接收到兩個參數(shù)", arg1, arg2)
        fun(arg1, arg2)
    return wrapper

@decorator
def my_fun(first_name, last_name):
    print("我的名字是", first_name, last_name)
# 輸出:
# 裝飾器內(nèi)的語句

my_fun("Peter", "Venkman")
# 輸出:
# 接收到兩個參數(shù) Peter Venkman
# 我的名字是 Peter Venkman

把參數(shù)傳給裝飾器

def decorator_out(decorator_arg1, decorator_arg2):
    print("我是外層裝飾器歇竟,接收參數(shù)", decorator_arg1, decorator_arg2)
    def decorator_in(fun):
        print("我是內(nèi)層裝飾器,接收到外層裝飾器參數(shù)", decorator_arg1, decorator_arg2)
        def wrapper(function_arg1, function_arg2) :
            print("我是wrapper俊扳,可以接收所有參數(shù)途蒋,裝飾器參數(shù)有{0} {1}猛遍,被裝飾函數(shù)參數(shù)有{2} {3}" .format(decorator_arg1, decorator_arg2, function_arg1, function_arg2))
            return fun(function_arg1, function_arg2)
        return wrapper
    return decorator_in

@decorator_out("Leonard", "Sheldon")
# 相當(dāng)于兩步
# temp = decorator_out("Leonard", "Sheldon")
# my_fun = temp(my_fun)
def my_fun(function_arg1, function_arg2):
    print("我是被裝飾的函數(shù)馋记,只知道自己的參數(shù): {0} {1}".format(function_arg1, function_arg2))
# 輸出:
# 我是外層裝飾器,接收參數(shù) Leonard Sheldon
# 我是內(nèi)層裝飾器懊烤,接收到外層裝飾器參數(shù) Leonard Sheldon

my_fun("Rajesh", "Howard")
#輸出:
#我是wrapper梯醒,可以接收所有參數(shù),裝飾器參數(shù)有Leonard Sheldon腌紧,被裝飾函數(shù)參數(shù)有Rajesh Howard
# 我是被裝飾的函數(shù)茸习,只知道自己的參數(shù): Rajesh Howard

裝飾器的知識點

  • 裝飾器使函數(shù)調(diào)用變慢了.一定要記住.
  • 裝飾器不能被取消(有些人把裝飾器做成可以移除的但是沒有人會用)所以一旦一個函數(shù)被裝飾了.所有的代碼都會被裝飾.
  • Python自身提供了幾個裝飾器,像property, staticmethod.
  • Django用裝飾器管理緩存和視圖的權(quán)限.

11 Python中重載

函數(shù)重載主要是為了解決兩個問題。

  • 可變參數(shù)類型壁肋。
  • 可變參數(shù)個數(shù)号胚。

另外,一個基本的設(shè)計原則是浸遗,僅僅當(dāng)兩個函數(shù)除了參數(shù)類型和參數(shù)個數(shù)不同以外猫胁,其功能是完全相同的,此時才使用函數(shù)重載跛锌,如果兩個函數(shù)的功能其實不同弃秆,那么不應(yīng)當(dāng)使用重載,而應(yīng)當(dāng)使用一個名字不同的函數(shù)。

好吧菠赚,那么對于情況 1 脑豹,函數(shù)功能相同,但是參數(shù)類型不同衡查,python 如何處理瘩欺?答案是根本不需要處理,因為 python 可以接受任何類型的參數(shù)拌牲,如果函數(shù)的功能相同击碗,那么不同的參數(shù)類型在 python 中很可能是相同的代碼,沒有必要做成兩個不同函數(shù)们拙。

那么對于情況 2 稍途,函數(shù)功能相同,但參數(shù)個數(shù)不同砚婆,python 如何處理械拍?大家知道,答案就是缺省參數(shù)装盯。對那些缺少的參數(shù)設(shè)定為缺省參數(shù)即可解決問題坷虑。因為你假設(shè)函數(shù)功能相同,那么那些缺少的參數(shù)終歸是需要用的埂奈。

好了迄损,鑒于情況 1 跟 情況 2 都有了解決方案,python 自然就不需要函數(shù)重載了账磺。

12 新式類和舊式類

新式類很早在2.2就出現(xiàn)了,所以舊式類完全是兼容的問題,Python3里的類全部都是新式類.這里有一個MRO(Method Resolution Order芹敌, 方法解析順序)問題可以了解下(新式類繼承是根據(jù)C3算法,舊式類是深度優(yōu)先),<Python核心編程>里講的也很多.

  • 一個舊式類的深度優(yōu)先的例子
class A():
    def foo1(self):
        print "A"
class B(A):
    def foo2(self):
        pass
class C(A):
    def foo1(self):
        print "C"
class D(B, C):
    pass

d = D()
d.foo1()

# A

按照經(jīng)典類的查找順序從左到右深度優(yōu)先的規(guī)則,在訪問d.foo1()的時候,D這個類是沒有的..那么往上查找,先找到B,里面沒有,深度優(yōu)先,訪問A,找到了foo1(),所以這時候調(diào)用的是A的foo1()垮抗,從而導(dǎo)致C重寫的foo1()被繞過.
而這個問題在新式類中得到修復(fù)氏捞,新的對象模型采用的是從左到右,廣度優(yōu)先的方式進(jìn)行查找冒版,在訪問d.foo1()的時候,D這個類是沒有的..那么往上查找,先找到B,里面沒有,廣度優(yōu)先,訪問C,找到了foo1(),所以這時候調(diào)用的是C的foo1()液茎,解決了C重寫的foo1()被繞過的問題.

13 __new____init__的區(qū)別

這個new確實很少見到,先做了解吧.

  • __new__是一個靜態(tài)方法,而__init__是一個實例方法.
  • __new__方法會返回一個創(chuàng)建的實例,而__init__什么都不返回.
  • 只有在__new__返回一個cls的實例時后面的__init__才能被調(diào)用.
  • 當(dāng)創(chuàng)建一個新實例時調(diào)用__new__,初始化一個實例時用__init__.

【注】__metaclass__是創(chuàng)建類時起作用.所以我們可以分別使用__metaclass__,__new____init__來分別在類創(chuàng)建,實例創(chuàng)建和實例初始化的時候做一些小手腳.

14 單例模式

單例模式是一種常用的軟件設(shè)計模式。在它的核心結(jié)構(gòu)中只包含一個被稱為單例類的特殊類辞嗡。通過單例模式可以保證系統(tǒng)中一個類只有一個實例而且該實例易于外界訪問捆等,從而方便對實例個數(shù)的控制并節(jié)約系統(tǒng)資源。如果希望在系統(tǒng)中某個類的對象只能存在一個续室,單例模式是最好的解決方案栋烤。
new()在init()之前被調(diào)用,用于生成實例對象猎贴。利用這個方法和類的屬性的特點可以實現(xiàn)設(shè)計模式的單例模式班缎。單例模式是指創(chuàng)建唯一對象蝴光,單例模式設(shè)計的類只能實例一個對象 這個絕對常考啊.絕對要記住1~2個方法,當(dāng)時面試官是讓手寫的.

使用__new__方法

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

singleton = Singleton()

共享屬性

創(chuàng)建實例時把所有實例的__dict__指向同一個字典,這樣它們具有相同的屬性和方法.

class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super().__new__(cls)
        ob.__dict__ = cls._state
        return ob

borg = Borg()

裝飾器版本

def singleton(cls):
    instance = None
    def getinstance(*args, **kw):
        nonlocal instance  # 修改外部裝飾器函數(shù)中變量instance达址,注意不能使用global
        if instance is None:
            instance = cls(*args, **kw)
        return instance
    return getinstance

@singleton
class MyClass:
  ...

import方法

作為python的模塊是天然的單例模式

# mysingleton.py
class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

# to use
from mysingleton import my_singleton

my_singleton.foo()

15 Python中的作用域

Python 中蔑祟,一個變量的作用域總是由在代碼中被賦值的地方所決定的。

當(dāng) Python 遇到一個變量的話他會按照這樣的順序進(jìn)行搜索:

本地作用域(Local)→當(dāng)前作用域被嵌入的本地作用域(Enclosing locals)→全局/模塊作用域(Global)→內(nèi)置作用域(Built-in)

16 GIL(Global Interpreter Lock)全局解釋器鎖

  • Python語言和GIL沒有半毛錢關(guān)系沉唠,僅僅是因為歷史原因在Cpython解釋器(C語言編寫的Python解釋器)中疆虚,難以移除GIL
  • 每個線程在執(zhí)行過程中都需要先獲取GIL,保證同一時刻只有一個線程可以執(zhí)行代碼
  • 對于io密集型任務(wù)满葛,python的多線程起到作用径簿,因為遇到IO阻塞會自動釋放GIL鎖,但對于cpu(計算)密集型任務(wù)嘀韧,python的多線程幾乎占不到任何優(yōu)勢篇亭,還有可能因為爭奪資源而變慢
  • Python使用多進(jìn)程可以利用多核CPU資源
  • 多線程爬取比單線程性能有所提升,因為遇到IO阻塞會自動釋放GIL鎖
  • 解決辦法(多線程利用多核CPU資源):
    (1)更換解釋器锄贷,如Jpython(JAVA語言編寫的Python解釋器)
    (2)在使用多線程的地方使用其他語言編寫程序

【注】Python中译蒂,對于io密集型任務(wù),使用線程或協(xié)程解決谊却,但對于cpu(計算)密集型任務(wù)柔昼,使用進(jìn)程解決

17 進(jìn)程、線程炎辨、協(xié)程

  • 進(jìn)程是資源分配的單位
  • 線程是操作系統(tǒng)調(diào)度的單位
  • 進(jìn)程切換需要的資源很大捕透,效率很低
  • 線程切換需要的資源一般,效率一般(不考慮GIL)
  • 協(xié)程切換任務(wù)資源很小碴萧,效率高
  • 多進(jìn)程乙嘀,多線程根據(jù)CPU核數(shù)不一樣可能是并行的,但協(xié)程是在一個線程中勿决,所以一定是并發(fā)的

簡單來說協(xié)程是進(jìn)程和線程的升級版乒躺,進(jìn)程和線程都面臨著內(nèi)核態(tài)和用戶態(tài)的切換問題而耗費許多切換時間招盲,而協(xié)程就是用戶自己控制切換的時機(jī)低缩,通過在線程中實現(xiàn)調(diào)度,避免了陷入內(nèi)核級別的上下文切換造成的性能損失曹货,進(jìn)而突破了線程在IO上的性能瓶頸咆繁。

Python里最常見的yield就是協(xié)程的思想!

18 lambda匿名函數(shù)

lambda函數(shù)不需要專門的名字來說明,通常來完成簡單的功能顶籽。

lambda x, y: x*y  # 函數(shù)輸入是x和y玩般,返回它們的積x*y

19 閉包

匿名函數(shù)、函數(shù)礼饱、閉包坏为、對象當(dāng)做實參時究驴,有什么區(qū)別?

  • 匿名函數(shù)能夠完成基本的簡單功能匀伏,傳遞的是這個函數(shù)的引用(只有功能)
  • 函數(shù)能夠完成較為復(fù)雜的功能洒忧,傳遞的是這個函數(shù)的引用(只有功能)
  • 閉包能夠完成較為復(fù)雜的功能,傳遞的是這個閉包中的函數(shù)以及數(shù)據(jù)(功能+數(shù)據(jù))够颠,相比對象占用極少空間
  • 對象封裝較為復(fù)雜的數(shù)據(jù)和功能熙侍,傳遞很多數(shù)據(jù)和很多功能(功能+數(shù)據(jù))

當(dāng)一個內(nèi)嵌函數(shù)引用其外部作作用域的變量,我們就會得到一個閉包. 總結(jié)一下,創(chuàng)建一個閉包必須滿足以下幾點:

  • 必須有一個內(nèi)嵌函數(shù)
  • 內(nèi)嵌函數(shù)必須引用外部函數(shù)中的變量
  • 外部函數(shù)的返回值必須是內(nèi)嵌函數(shù)
def line(k, b):
    def create_y(x):
        print(k * x + b)
    return create_y

# 第一條線 y = x + 2
line1 = line(1, 2)  # k = 1, b = 2會被保存在line1這個函數(shù)引用中,只需要賦值一次
line1(0)
line1(1)
line1(2)

# 第二條線 y = 2x + 3
line2 = line(2, 3) 
line2(0)
line2(1)
line2(2)

【注】如果閉包外層函數(shù)定義了臨時變量履磨,要在內(nèi)層函數(shù)中進(jìn)行修改蛉抓,需用nonlocal關(guān)鍵字,如下:

def test1():
    a = 1
    def test2():
        nonlocal a
        print(a)
        a = 2
        print(a)
    return test2

test1()()
# 1
# 2

如果去掉上述代碼中nonlocal a語句剃诅,那么程序在print(a)處就會報錯巷送,因為解釋器會把內(nèi)層test2函數(shù)中a = 2看成是定義的一個局部變量屯远,而在定義局部變量a前打印其值會報變量未定義的錯誤售滤,此處需留意,如問題14中使用裝飾器實現(xiàn)單例模式就用到此技巧防楷。

20 Python函數(shù)式編程

filter 函數(shù)

filter 函數(shù)的功能相當(dāng)于過濾器如筛。調(diào)用一個布爾函數(shù)bool_func來迭代遍歷每個seq中的元素堡牡;返回一個使bool_seq返回值為true的元素的序列。

>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6,7]

map函數(shù)

map函數(shù)是對一個序列的每個項依次執(zhí)行函數(shù)杨刨,下面是對一個序列每個項都乘以2:

>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]

reduce函數(shù)

reduce函數(shù)是對一個序列的每個項迭代調(diào)用函數(shù)晤柄,下面是求3的階乘:

>>> reduce(lambda x,y:x*y,range(1,4))
6

21 Python里的拷貝

引用、copy()妖胀、deepcopy()的區(qū)別

import copy
a = [1, 2, 3, 4, ['a', 'b']]  #原始對象

b = a  #賦值芥颈,傳對象的引用
c = copy.copy(a)  #對象拷貝,淺拷貝
d = copy.deepcopy(a)  #對象拷貝赚抡,深拷貝

a.append(5)  #修改對象a
a[4].append('c')  #修改對象a中的['a', 'b']數(shù)組對象

結(jié)果:
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
d =  [1, 2, 3, 4, ['a', 'b']]

上述語句可理解為下圖:


  • b = a爬坑,賦值,將b直接指向a指向的地址
  • c = copy.copy(a)涂臣,淺拷貝盾计,重新指向一塊地址,只拷貝a頂層對象赁遗,對子對象不拷貝署辉,繼續(xù)指向原來的引用
  • d = copy.deepcopy(a),深拷貝岩四,重新指向一塊地址哭尝,將a無論是頂層對象還是子對象都復(fù)制一份

【注】對于不可變類型,如:

import copy
a = (1, 2, (3, 4)) 

b = a
c = copy.copy(a) 
d = copy.deepcopy(a) 

id(a), id(b), id(c), id(d)相同剖煌,都是直接傳遞引用材鹦,因為a為不可變類型逝淹,并且子對象都是不可變類型
而對于:

import copy
a = (1, 2, [3, 4]) 

b = a
c = copy.copy(a) 
d = copy.deepcopy(a) 

id(a), id(b), id(c)相同,都是直接傳遞引用桶唐,id(d)不同创橄,因為雖然a為不可變類型,但子對象中有可變類型莽红,深拷貝會自動檢查子對象中是否有可變類型妥畏,若有,則重新指向一塊地址安吁,將所有對象復(fù)制一份

22 Python內(nèi)存管理機(jī)制

(1)Python的存儲問題

  • 由于python中萬物皆對象醉蚁,所以python的存儲問題是對象的存儲問題,并且對于每個對象鬼店,python會分配一塊內(nèi)存空間去存儲它网棍。在定義一個變量時,變量指向這一對象妇智,其實該變量只是該對象的一個引用滥玷,保存該對象地址
  • 對于整數(shù)和短小的字符等,python會執(zhí)行緩存機(jī)制巍棱,即將這些對象進(jìn)行緩存惑畴,不會為相同的對象分配多個內(nèi)存空間
  • 容器對象,如列表航徙、元組如贷、字典等,存儲的其他對象到踏,僅僅是其他對象的引用杠袱,即地址,并不是這些對象本身

(2)引用計數(shù)

當(dāng)一個對象有新的引用時窝稿,它的引用計數(shù)就會增加楣富,當(dāng)引用它的對象被刪除,它的引用計數(shù)就會減少伴榔,引用計數(shù)為0時纹蝴,該對象生命就結(jié)束了。

(3)垃圾回收

  • 當(dāng)內(nèi)存中有不再使用的部分時潮梯,垃圾收集器就會把他們清理掉骗灶。它會去檢查那些引用計數(shù)為0的對象,然后清除其在內(nèi)存的空間秉馏。
  • 垃圾回收機(jī)制還有一個循環(huán)垃圾回收器, 確保釋放循環(huán)引用對象(a引用b, b引用a, 導(dǎo)致其引用計數(shù)永遠(yuǎn)不為0)。

(4)分代技術(shù)

  • Python同時采用了分代(generation)回收的策略脱羡。這一策略的基本假設(shè)是萝究,存活時間越久的對象免都,越不可能在后面的程序中變成垃圾。我們的程序往往會產(chǎn)生大量的對象帆竹,許多對象很快產(chǎn)生和消失绕娘,但也有一些對象長期被使用。出于信任和效率栽连,對于這樣一些“長壽”對象险领,我們相信它們的用處,所以減少在垃圾回收中掃描它們的頻率秒紧。
  • Python將所有的對象分為0绢陌,1,2三代熔恢。所有的新建對象都是0代對象脐湾。當(dāng)某一代對象經(jīng)歷過垃圾回收,依然存活叙淌,那么它就被歸入下一代對象秤掌。垃圾回收啟動時,一定會掃描所有的0代對象鹰霍。如果0代經(jīng)過一定次數(shù)垃圾回收闻鉴,那么就啟動對0代和1代的掃描清理。當(dāng)1代也經(jīng)歷了一定次數(shù)的垃圾回收后茂洒,那么會啟動對0椒拗,1,2获黔,即對所有對象進(jìn)行掃描蚀苛。

23 read,readline和readlines

  • read():一次性讀取整個文件內(nèi)容。推薦使用read(size)方法玷氏,size越大運行時間越長
  • readline():每次讀取一行內(nèi)容堵未。內(nèi)存不夠時使用,一般不太用
  • readlines():一次性讀取整個文件內(nèi)容盏触,并按行返回到list渗蟹,方便我們遍歷

24 Python2和3的區(qū)別

(1)Unicode編碼

  • Python2默認(rèn)ASCII編碼方式,但是ASCII編碼無法對中文等字符進(jìn)行有效編碼
  • Python3默認(rèn)的編碼方式是UTF-8

(2)print

print語句沒有了赞辩,取而代之的是print()函數(shù)

(3)xrange

  • 在 Python2中有xrange()和range()雌芽,前者是創(chuàng)建一個生成器,后者則是直接生成一個列表
  • 在 Python3中辨嗽,只有range()世落,功能就是Python2中xrange() 那樣創(chuàng)建一個生成器,不再存在xrange()函數(shù)

(4)數(shù)據(jù)類型

Python3去除了long類型糟需,現(xiàn)在只有一種整型——int屉佳,但它的行為就像Python2中的long

25 常用正則表達(dá)式(匹配郵箱)

舉例:zhang_san-001@gmail.com

(1)分析郵件名稱部分

  • 26個大小寫英文字母表示為a-zA-Z
  • 數(shù)字表示為0-9
  • 下劃線表示為_
  • 中劃線表示為-
  • 由于名稱是由若干個字母谷朝、數(shù)字、下劃線和中劃線組成武花,所以需要用到+表示多次出現(xiàn)

根據(jù)以上條件得出郵件名稱表達(dá)式:[a-zA-Z0-9_-]+

(2)分析域名部分

一般域名的規(guī)律為“N級域名.三級域名.二級域名.頂級域名”圆凰,比如qq.comwww.qq.com体箕、mp.weixin.qq.com专钉、12-34.com.cn,分析可得域名類似** .** .** .**組成累铅。

  • **部分可以表示為[a-zA-Z0-9_-]+
  • .**部分可以表示為\.[a-zA-Z0-9_-]+
  • 多個.**可以表示為(\.[a-zA-Z0-9_-]+)+

綜上所述跃须,域名部分可以表示為[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+

(3)最終表達(dá)式:

由于郵箱的基本格式為“名稱@域名”,需要使用^匹配郵箱的開始部分争群,用$匹配郵箱結(jié)束部分以保證郵箱前后不能有其他字符回怜,所以最終郵箱的正則表達(dá)式為:^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市换薄,隨后出現(xiàn)的幾起案子玉雾,更是在濱河造成了極大的恐慌,老刑警劉巖轻要,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件复旬,死亡現(xiàn)場離奇詭異,居然都是意外死亡冲泥,警方通過查閱死者的電腦和手機(jī)驹碍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凡恍,“玉大人志秃,你說我怎么就攤上這事〗涝停” “怎么了浮还?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闽巩。 經(jīng)常有香客問我钧舌,道長,這世上最難降的妖魔是什么涎跨? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任洼冻,我火速辦了婚禮,結(jié)果婚禮上隅很,老公的妹妹穿的比我還像新娘撞牢。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布普泡。 她就那樣靜靜地躺著播掷,像睡著了一般审编。 火紅的嫁衣襯著肌膚如雪撼班。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天垒酬,我揣著相機(jī)與錄音砰嘁,去河邊找鬼。 笑死勘究,一個胖子當(dāng)著我的面吹牛矮湘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播口糕,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼缅阳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了景描?” 一聲冷哼從身側(cè)響起十办,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎超棺,沒想到半個月后向族,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡棠绘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年件相,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氧苍。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡夜矗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出让虐,到底是詐尸還是另有隱情紊撕,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布澄干,位于F島的核電站逛揩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏麸俘。R本人自食惡果不足惜辩稽,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望从媚。 院中可真熱鬧逞泄,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至到千,卻和暖如春昌渤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背憔四。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工膀息, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人了赵。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓潜支,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柿汛。 傳聞我的和親對象是個殘疾皇子冗酿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容