知識點整理基於 Python3.
1. Python 魔法方法
在Python中用雙下劃線__包裹起來的方法被成為魔法方法,可以用來給類提供算術、邏輯運算等功能,讓這些類能夠像原生的對象一樣用更標準、簡潔的方式進行這些操作。
下面介紹常常被問到的幾個魔法方法。
1.1 __init__
__init__方法做的事情是在對象創建好之後初始化變量。很多人以為__init__是構造方法,其實不然,真正創建實例的是__new__方法,下面會講它,先來看看__init__方法。
class Person(object):
def __init__(self, name, age):
print("in __init__")
self.name = name
self.age = age
p = Person("TianCheng", 27)
print("p:", p)
輸出:
in __init__
p: <__main__.person object="" at="">
明白__init__負責初始化工作,平常也是我們經常用到的。。
1.2 __new__
構造方法: __new__(cls, […])
__new__是Python中對象實例化時所調用的第一個函數,在__init__之前被調用。__new__將class作為他的第一個參數, 並返回一個這個class的 instance。而__init__是將 instance 作為參數,並對這個 instance 進行初始化操作。每個實例創建時都會調用__new__函數。下面來看一個例子:
class Person(object):
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = super().__new__(cls)
return instance
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
p = Person("TianCheng", 27)
print("p:", p)
輸出結果:
in __new__
in __init__
p: <__main__.person object="" at="">
可以看到先執行 new 方法創建對象,然後 init 進行初始化。假設將__new__方法中不返還該對象,會有什麼結果了?
class Person(object):
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = super().__new__(cls)
#return instance
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
p = Person("TianCheng", 27)
print("p:", p)
# 輸出:
in __new__
p: None
發現如果 new 沒有返回實例化對象,init 就沒法初始化了。
如何使用 new 方法實現單例(高頻考點):
class SingleTon(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = cls.__new__(cls, *args, **kwargs)
return cls._instance
s1 = SingleTon()
s2 = SingleTon()
print(s1)
print(s2)
輸出結果:
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
s1, s2 內存地址一致,實現單例效果。
1.3 __call__
__call__ 方法,先需要明白什麼是可調用對象,平時自定義的函數、內置函數和類都屬於可調用對象,但凡是可以把一對括號()應用到某個對象身上都可稱之為可調用對象,判斷對象是否為可調用對象可以用函數 callable。舉例如下:
class A(object):
def __init__(self):
print("__init__ ")
super(A, self).__init__()
def __new__(cls):
print("__new__ ")
return super(A, cls).__new__(cls)
def __call__(self): # 可以定義任意參數
print('__call__ ')
a = A()
a()
print(callable(a)) # True
輸出:
__new__
__init__
__call__
True
執行 a() 才會打印出 __call__。 a是一個實例化對象,也是一個可調用對象。
1.4 __del__
__del__ 析構函數,當刪除一個對象時,則會執行此方法,對象在內存中銷燬時,自動會調用此方法。舉例:
class People:
def __init__(self,name,age):
self.name=name
self.age=age
def __del__(self): # 在對象被刪除的條件下,自動執行
print('__del__')
obj=People("Tiancheng", 27)
#del obj #obj.__del__() #先刪除的情況下,直接執行__del__
輸出結果:
__del__
2. 閉包 和 自省
2.1 閉包
2.1.1 什麼閉包
簡單的說,如果在一個內部函數里,對在外部作用域(但不是在全局作用域)的變量進行引用,那麼內部函數就被認為是閉包(closure)。來看一個簡單的例子:
>>>def addx(x):
>>> def adder(y): return x + y
>>> return adder
>>> c = addx(8)
>>> type(c)
<type>
>>> c.__name__
'adder'
>>> c(10)
18
/<type>
其中 adder(y) 函數就是閉包。
2.1.2 實現一個閉包並可以修改外部變量
def foo():
a = 1
def bar():
a = a + 1
return a
return bar
c = foo()
print(c())
有上面一個小例子,目的是每次執行一次,a 自增1,執行後是否正確了?顯示會報下面錯誤。
local variable 'a' referenced before assignment
原因是bar()函數中會把a作為局部變量,而bar中沒有對a進行聲明。
如果面試官問你,在 Python2 和 Python3 中如何修改 a 的值了。
Python3 中只需引入 nonlocal 關鍵字即可:
def foo():
a = 1
def bar():
nonlocal a
a = a + 1
return a
return bar
c = foo()
print(c()) # 2
而在 Python2 中沒有 nonlocal 關鍵字,該如何實現了:
def foo():
a = [1]
def bar():
a[0] = a[0] + 1
return a[0]
return bar
c = foo()
print(c()) # 2
需藉助可變變量實現,比如dict和list對象。
閉包的一個常用場景就是 裝飾器。 後面會講到。
2.2 自省(反射)
自省,也可以說是反射,自省在計算機編程中通常指這種能力:檢查某些事物以確定它是什麼、它知道什麼以及它能做什麼。
與其相關的主要方法:
- hasattr(object, name)
- 檢查對象是否具體 name 屬性。返回 bool.
- getattr(object, name, default)
- 獲取對象的name屬性。
- setattr(object, name, default)
- 給對象設置name屬性
- delattr(object, name)
- 給對象刪除name屬性
- dir([object])
- 獲取對象大部分的屬性
- isinstance(name, object)
- 檢查name是不是object對象
- type(object)
- 查看對象的類型
- callable(object)
- 判斷對象是否是可調用對象
>>> class A:
... a = 1
...
>>> hasattr(A, 'a')
True
>>> getattr(A, 'a')
1
>>> setattr(A, 'b', 1)
>>> getattr(A, 'b')
1
>>> delattr(A, 'b')
>>> hasattr(A, 'b')
False
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']
>>> isinstance(1, int)
True
>>> type(A)
<class>
>>> type(1)
<class>
>>> callable(A)
True
/<class>/<class>
3. 裝飾器 和 迭代器
3.1 裝飾器
裝飾器本質上是一個 Python 函數或類,它可以讓其他函數或類在不需要做任何代碼修改的前提下增加額外功能(設計模式中的裝飾器模式),裝飾器的返回值也是一個函數/類對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等場景。
3.1.1 簡單裝飾器
先來看一個之前閉包的例子:
def my_logging(func):
def wrapper():
print("{} is running.".format(func.__name__))
return func() # 把 foo 當做參數傳遞進來時,執行func()就相當於執行foo()
return wrapper
def foo():
print("this is foo function.")
foo = my_logging(foo) # 因為裝飾器 my_logging(foo) 返回的時函數對象 wrapper,這條語句相當於 foo = wrapper
foo() # 執行foo相當於執行wrapper
但在Python裡有@語法糖,則可以直接這樣做:
def my_logging(func):
def wrapper():
print("{} is running.".format(func.__name__))
return func()
return wrapper
@my_logging
def foo():
print("this is foo function.")
foo()
上面二者都會有如下打印結果:
foo is running.
this is foo function.
my_logging 就是一個裝飾器,它一個普通的函數,它把執行真正業務邏輯的函數 func 包裹在其中,看起來像 foo 被 my_logging 裝飾了一樣 my_logging 返回的也是一個函數,這個函數的名字叫 wrapper。在這個例子中,函數進入和退出時 ,被稱為一個橫切面,這種編程方式被稱為面向切面的編程(AOP)。
如果 foo 帶有參數,如何將參數帶到 wrapper 中了?
def my_logging(func):
def wrapper(*args, **kwargs):
print("{} is running.".format(func.__name__))
return func(*args, **kwargs)
return wrapper
@my_logging
def foo(x, y):
print("this is foo function.")
return x + y
print(foo(1, 2))
可以通過 *args, **kwargs 接收參數,然後帶入func中執行,上面執行結果為:
foo is running.
this is foo function.
3
3.1.2 帶參數的裝飾器
裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣就大大增加了靈活性,比如在日誌告警場景中,可以根據不同的告警定告警等級: info/warn等。
def my_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "info":
print("{} is running. level: ".format(func.__name__), level)
elif level == "warn":
print("{} is running. level: ".format(func.__name__), level)
return func(*args, **kwargs)
return wrapper
return decorator
@my_logging(level="info")
def foo(name="foo"):
print("{} is running".format(name))
@my_logging(level="warn")
def bar(name="bar"):
print("{} is running".format(name))
foo()
bar()
結果輸出:
foo is running. level: info
foo is running
bar is running. level: warn
bar is running
上面的 my_logging 是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解為一個含有參數的閉包。當使用@my_logging(level="info")調用的時候,Python 能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。
@my_logging(level="info")等價於@decorator
3.1.3 類裝飾器
裝飾器不僅可以是函數,還可以是類,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器主要依靠類的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class MyLogging(object):
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print("class decorator starting.")
a = self._func(*args, **kwargs)
print("class decorator end.")
return a
@MyLogging
def foo(x, y):
print("foo is running")
return x + y
print(foo(1, 2))
輸出結果:
class decorator starting.
foo is running
class decorator end.
3
3.1.4 functools.wraps
Python 中還有一個裝飾器的修飾函數 functools.wraps,先來看看它的作用是什麼?先來看看有一個問題存在,因為原函數被裝飾函數裝飾後,發生了一下變化:
def my_logging(func):
def wrapper(*args, **kwargs):
print("{} is running.".format(func.__name__))
return func(*args, **kwargs)
return wrapper
@my_logging
def foo(x, y):
"""
add function
"""
print("this is foo function.")
return x + y
print(foo(1, 2))
print("func name:", foo.__name__)
print("doc:", foo.__doc__)
打印結果:
foo is running.
this is foo function.
3
func name: wrapper
doc: None
問題出來了,func name 應該打印出 foo 才對,而且 doc 也不為None。由此發現原函數被裝飾函數裝飾之後,元信息發生了改變,這明顯不是我們想要的,Python裡可以通過functools.wraps來解決,保持原函數元信息。
from functools import wraps
def my_logging(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("{} is running.".format(func.__name__))
return func(*args, **kwargs)
return wrapper
@my_logging
def foo(x, y):
"""
add function
"""
print("this is foo function.")
return x + y
print(foo(1, 2))
print("func name:", foo.__name__)
print("doc:", foo.__doc__)
輸出結果:
foo is running.
this is foo function.
3
func name: foo
doc:
add function
3.1.5 多個裝飾器的執行順序
@a
@b
@c
def f ():
pass
執行順序為 f = a(b(c(f)))
3.2 迭代器 VS 生成器
先來看一個關係圖:
3.2.1 container(容器)
container 可以理解為把多個元素組織在一起的數據結構,container中的元素可以逐個地迭代獲取,可以用in, not in關鍵字判斷元素是否包含在容器中。在Python中,常見的container對象有:
list, deque, ....
set, frozensets, ....
dict, defaultdict, OrderedDict, Counter, ....
tuple, namedtuple, …
str
舉例:
>>> assert 1 in [1, 2, 3] # lists
>>> assert 4 not in [1, 2, 3]
>>> assert 1 in {1, 2, 3} # sets
>>> assert 4 not in {1, 2, 3}
>>> assert 1 in (1, 2, 3) # tuples
>>> assert 4 not in (1, 2, 3)
3.2.2 可迭代對象(iterables) vs 迭代器(iterator)
大部分的container都是可迭代對象,比如 list or set 都是可迭代對象,可以說只要是可以返回一個迭代器的都可以稱作可迭代對象。下面看一個例子:
>>> x = [1, 2, 3]
>>> y = iter(x)
>>> next(y)
1
>>> next(y)
2
>>> type(x)
<class>
>>> type(y)
<class>
/<class>/<class>
可見, x 是可迭代對象,這裡也叫container。y 則是迭代器,且實現了__iter__ 和 __next__ 方法。它們之間的關係是:
那什麼是迭代器了?上面例子中有2個方法 iter and next。可見通過iter方法後就是迭代器。
它是一個帶狀態的對象,調用next方法的時候返回容器中的下一個值,可以說任何實現了__iter__和next方法的對象都是迭代器,__iter__返回迭代器自身,next返回容器中的下一個值,如果容器中沒有更多元素了,則拋異常。
迭代器就像一個懶加載的工廠,等到有人需要的時候才給它生成值返回,沒調用的時候就處於休眠狀態等待下一次調用。
3.2.3 生成器(generator)
生成器一定是迭代器,是一種特殊的迭代器,特殊在於它不需要再像上面的__iter__()和next方法了,只需要一個yiled關鍵字。下面來看一個例子:
用生成器實現斐波拉契:
# content of test.py
def fib(n):
prev, curr = 0, 1
while n > 0:
yield curr
prev, curr = curr, curr + prev
n -= 1
到終端執行fib函數:
>>> from test import fib
>>> y = fib(10)
>>> next(y)
1
>>> type(y)
<class>
>>> next(y)
1
>>> next(y)
2
/<class>
fib就是一個普通的python函數,它特殊的地方在於函數體中沒有return關鍵字,函數的返回值是一個生成器對象(通過 yield 關鍵字)。當執行f=fib()返回的是一個生成器對象,此時函數體中的代碼並不會執行,只有顯示或隱示地調用next的時候才會真正執行裡面的代碼。
假設有千萬個對象,需要順序調取,如果一次性加載到內存,對內存是極大的壓力,有生成器之後,可以需要的時候去生成一個,不需要的則也不會佔用內存。
平常可能還會遇到一些生成器表達式,比如:
>>> a = (x*x for x in range(10))
>>> a
<generator> at 0x102d79a20>
>>> next(a)
0
>>> next(a)
1
>>> a.close()
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
/<module>/<stdin>/<generator>
這些小技巧也是非常有用的。close可以關閉生成器。生成器中還有一個send方法,其中send(None)與next是等價的。
>>> def double_inputs():
... while True:
... x = yield
... yield x * 2
...
>>> generator = double_inputs()
>>> generator.send(10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> generator.send(None)
>>> generator.send(10)
20
>>> next(generator)
>>> generator.send(20)
40
/<module>/<stdin>
從上面的例子中可以看出,生成器可以接收參數,通過send(value)方法,且第一次不能直接send(value),需要send(None)或者next()執行之後。也就是說調用send傳入非None值前,生成器必須處於掛起狀態,否則將拋出異常。
3.2.4 迭代器和生成器的區別
可能你看完上面的,有點好奇到底他們二者有什麼區別了?
- 迭代器是一個更抽象的概念,任何對象,如果它有next方法(next python3,python2 是 __next__方法)和__iter__方法,則可以稱作迭代器。
- 每個生成器都是一個迭代器,但是反過來不行。通常生成器是通過調用一個或多個yield表達式構成的函數s生成的。同時滿足迭代器的定義。
- 生成器能做到迭代器能做的所有事,而且因為自動創建了iter()和 next()方法,生成器顯得特別簡潔,而且生成器也是高效的。
剖析Python面試知識點完整內容請查看: https://gitbook.cn/gitchat/activity/5c8e364590020a6262806c8d
閱讀更多 天澄技術雜談 的文章