python面試題~反射,元類,單例

北京-IT拾荒者發表於2018-07-31

1 什麼是反射?以及應用場景?

test.py

def f1():
   print('f1')
def f2():
   print('f2')
def f3():
   print('f3')
def f4():
   print('f4')
a = 1
複製程式碼
import test as ss
ss.f1()
ss.f2()
print(ss.a)
複製程式碼

我們要匯入另外一個模組,可以使用import.現在有這樣的需求,我動態輸入一個模組名,可以隨時訪問到匯入模組中的方法或者變數,怎麼做呢?

imp = input(“請輸入你想匯入的模組名:”)
CC = __import__(imp) 這種方式就是通過輸入字串匯入你所想匯入的模組 
CC.f1()  # 執行模組中的f1方法

#或者用importlib模組
import importlib
importlib.import_module(name="",package="")
複製程式碼

上面我們實現了動態輸入模組名,從而使我們能夠輸入模組名並且執行裡面的函式。但是上面有一個缺點,那就是執行的函式被固定了。那麼,我們能不能改進一下,動態輸入函式名,並且來執行呢?

#dynamic.py
imp = input("請輸入模組:")
dd = __import__(imp)
# 等價於import imp
inp_func = input("請輸入要執行的函式:")
f = getattr(dd,inp_func,None)#作用:從匯入模組中找到你需要呼叫的函式inp_func,然後返回一個該函式的引用.沒有找到就煩會None
f() # 執行該函式
複製程式碼

反射機制 上面說了那麼多,到底什麼是反射機制呢?

  其實,反射就是通過字串的形式,匯入模組;通過字串的形式,去模組尋找指定函式,並執行。利用字串的形式去物件(模組)中操作(查詢/獲取/刪除/新增)成員,一種基於字串的事件驅動!

  1. getattr()函式是Python自省的核心函式,具體使用大體如下:
class A: 
def __init__(self): 
  self.name = 'zhangjing'
  #self.age='24'
def method(self): 
  print"method print"
Instance = A() 
print getattr(Instance , 'name, 'not find') #如果Instance 物件中有屬性
name則列印self.name的值,否則列印'not find'
print getattr(Instance , 'age', 'not find') #如果Instance 物件中有屬性
age則列印self.age的值,否則列印'not find'
print getattr(a, 'method', 'default') #如果有方法method,否則列印其地址,
否則列印default 
print getattr(a, 'method', 'default')() #如果有方法method,執行函式並
列印None否則列印default 
複製程式碼
  1. hasattr(object, name)
說明:判斷物件object是否包含名為name的特性(hasattr是通過呼叫getattr(ojbect, name)
是否丟擲異常來實現的)
複製程式碼
  1. setattr(object, name, value)
這是相對應的getattr()。引數是一個物件,一個字串和一個任意值。字串可能
會列出一個現有的屬性或一個新的屬性。這個函式將值賦給屬性的。該物件允許它提供
。例如,setattr(x,“foobar”,123)相當於x.foobar = 123。
複製程式碼
  1. delattr(object, name)
與setattr()相關的一組函式。引數是由一個物件(記住python中一切皆是物件)和一
個字串組成的。string引數必須是物件屬性名之一。該函式刪除該obj的一個由string
指定的屬性。delattr(x, 'foobar')=del x.foobar


我們可以利用上述的四個函式,來對模組進行一系列操作.

r = hasattr(commons,xxx)判斷某個函式或者變數是否存在
print(r)  
setattr(commons,'age',18)  給commons模組增加一個全域性變數age = 18,建立成功返回none
setattr(config,'age',lambda  a:a+1)  //給模組新增一個函式
delattr(commons,'age')//刪除模組中某個變數或者函式
注:getattr,hasattr,setattr,delattr對模組的修改都在記憶體中進行,並不會影響檔案中真實內容。
複製程式碼

2 metaclass作用?以及應用場景?

元類(metaclass)的理解和簡單運用 首先這裡討論的python類,都基於繼承於object的新式類進行討論。

首先在python中,所有東西都是物件。這句話非常重要要理解元類我要重新來理解一下python中的類

class Trick(object):
   pass
複製程式碼

當python在執行帶class語句的時候,會初始化一個類物件放在記憶體裡面。例如這裡會初始化一個Trick物件

這個物件(類)自身擁有建立物件(通常我們說的例項,但是在python中還是物件)的能力。

為了方便後續理解,我們可以先嚐試一下在新式類中最古老厲害的關鍵字type。

input:
class Trick(object):
   pass
print type('123')
print type(123)
print type(Trick)
output:
<type 'str'>
<type 'int'>
<class '__main__.Trick'>
複製程式碼

可以看到能得到我們平時使用的 str, int, 以及我們初始化的一個例項物件Trick()

但是下面的方法你可能沒有見過,type同樣可以用來動態建立一個類

type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))

這個怎麼用呢,我要用這個方法建立一個類 讓我們看下下面的程式碼

input:
print type('Trick', (), {})

output:
<class '__main__.Trick'>
同樣我們可以例項化這個類物件

input:
print type('trick', (), {})()

output:
<__main__.trick object at 0x109283450>
可以看到,這裡就是一個trick的例項物件了。
複製程式碼

同樣的這個方法還可以初始化建立類的父類,同時也可以初始化類屬性:

input:
class FlyToSky(object):
   pass

pw = type('Trick', (FlyToSky, ), {'laugh_at': 'hahahaha'})
print pw().laugh_at
print pw.__dict__
print pw.__bases__
print pw().__class__
print pw().__class__.__class__

output:
hahahaha
{'__module__': '__main__', 'laugh_at': 'hahahaha', '__doc__': None}
(<class '__main__.FlyToSky'>,)
<class '__main__.Trick'>
<type 'type'>
複製程式碼

下面我將依次理一下上面的內容,在此之前我必須先介紹兩個魔法方法:

__class__這個方法用於檢視物件屬於是哪個生成的,這樣理解在python中的所有東西都是物件,類物件也是物件。如果按照以前的思維來想的話就是類是元類的例項,而例項物件是類的例項。

__bases__這個方法用於得到一個物件的父類是誰,特別注意一下__base__返回單個父類,__bases__以tuple形式返回所有父類。
複製程式碼

好了知道了這兩個方法我來依次說一下每行什麼意思。

使用type建立一個類賦值給pw type的接受的三個引數的意思分辨是(類的名稱, 類是否有父類(), 類的屬性字典{})

這裡初始化一個類的例項,然後嘗試去獲得類屬性 的laugh_at 屬性值,然後得到結果hahahaha

取一個pw的也就是我們常見類的類字典資料

拿到pw的父類,結果是我們指定的 FlyToSky

pw的例項pw()屬於哪個類初始化的,可以看到是class Trick

我們再看class trick是誰初始化的? 就是元類type了

什麼是元類以及簡單運用

這一切介紹完之後我們總算可以進入正題

到底什麼是元類?通俗的就是說,元類就是建立類的類。。。這樣聽起來是不是超級抽象?

來看看這個

Trick = MetaClass()
MyObject = Trick()
複製程式碼

上面我們已經介紹了,搞一個Trick可以直接這樣

Trick = type('Trick', (), {}) 可以這樣其實就是因為,Type實際上是一個元類,用他可以去建立類。什麼是元類剛才說了,元類就是建立類的類。也可以說他就是一個類的建立工廠。

類上面的__metaclass__屬性,相信願意瞭解元類細節的盆友,都肯定見過這個東西,而且為之好奇。不然我不知道是什麼支撐你看到這裡的?。使用了__metaclass__這個魔法方法就意味著就會用__metaclass__指定的元類來建立類了。

class Trick(FlyToSky):
   pass
複製程式碼

當我們在建立上面的類的時候,python做了如下的操作:

Trick中有__metaclass__這個屬性嗎?如果有,那麼Python會在記憶體中通過__metaclass__建立一個名字為Trick的類物件,也就是Trick這個東西。如果Python沒有找到__metaclass__,它會繼續在自己的父類FlyToSky中尋找__metaclass__屬性,並且嘗試以__metaclass__指定的方法建立一個Trick類物件。如果Python在任何一個父類中都找不到__metaclass__,它也不會就此放棄,而是去模組中搜尋是否有__metaclass__的指定。如果還是找不到,好吧那就是使用預設的type來建立Trick。

那麼問題來了,我們要在__metaclass__中放置什麼呢?答案是可以建立一個類的東西,type,或者任何用到type或子類化type的東西都行。

自定義元類

自定義類的的目的,我總結了一下就是攔截類的建立,然後修改一些特性,然後返回該類。是不是有點熟悉?沒錯,就是感覺是裝飾器乾的事情,只是裝飾器是修飾一個函式,同樣是一個東西進去,然後被額外加了一些東西,最後被返回。

其實除了上面談到的制定一個__metaclass__並不需要賦值給它的不一定要是正式類,是一個函式也可以。要建立一個使所有模組級別都是用這個元類建立類的話,在模組級別設定__metaclass__就可以了。先寫一個來試試看

class UpperAttrMetaClass(type):
   def __new__(mcs, class_name, class_parents, class_attr):
       attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__'))
       uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
       return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, class_parents, uppercase_attrs)


class Trick(object):
   __metaclass__ = UpperAttrMetaClass
   bar = 12
   money = 'unlimited'

print(Trick.bar)
print(Trick.money)
複製程式碼

3 用盡量多的方法實現單例模式。

單例模式 單例模式(Singleton Pattern)是一種常用的軟體設計模式,該模式的主要目的是確保某一個類只有一個例項存在。當你希望在整個系統中,某個類只能出現一個例項時,單例物件就能派上用場。

比如,某個伺服器程式的配置資訊存放在一個檔案中,客戶端通過一個 AppConfig 的類來讀取配置檔案的資訊。如果在程式執行期間,有很多地方都需要使用配置檔案的內容,也就是說,很多地方都需要建立 AppConfig 物件的例項,這就導致系統中存在多個 AppConfig 的例項物件,而這樣會嚴重浪費記憶體資源,尤其是在配置檔案內容很多的情況下。事實上,類似 AppConfig 這樣的類,我們希望在程式執行期間只存在一個例項物件。

在 Python 中,我們可以用多種方法來實現單例模式

實現單例模式的幾種方式 使用模組 其實,Python 的模組就是天然的單例模式,因為模組在第一次匯入時,會生成 .pyc 檔案,當第二次匯入時,就會直接載入 .pyc 檔案,而不會再次執行模組程式碼。因此,我們只需把相關的函式和資料定義在一個模組中,就可以獲得一個單例物件了。如果我們真的想要一個單例類,可以考慮這樣做:

class Singleton(object):
   def foo(self):
       pass
singleton = Singleton()
複製程式碼

將上面的程式碼儲存在檔案 mysingleton.py 中,要使用時,直接在其他檔案中匯入此檔案中的物件,這個物件即是單例模式的物件

from a import singleton

使用裝飾器

def Singleton(cls):
   _instance = {}
   def _singleton(*args, **kargs):
       if cls not in _instance:
           _instance[cls] = cls(*args, **kargs)
       return _instance[cls]
   return _singleton

@Singleton
class A(object):
   a = 1
   def __init__(self, x=0):
       self.x = x

a1 = A(2)
a2 = A(3)
複製程式碼

使用類

class Singleton(object):
   def __init__(self):
       pass
   @classmethod
   def instance(cls, *args, **kwargs):
       if not hasattr(Singleton, "_instance"):
           Singleton._instance = Singleton(*args, **kwargs)
       return Singleton._instance
複製程式碼

一般情況,大家以為這樣就完成了單例模式,但是這樣當使用多執行緒時會存在問題

class Singleton(object):

   def __init__(self):
       pass

   @classmethod
   def instance(cls, *args, **kwargs):
       if not hasattr(Singleton, "_instance"):
           Singleton._instance = Singleton(*args, **kwargs)
       return Singleton._instance
import threading
def task(arg):
   obj = Singleton.instance()
   print(obj)

for i in range(10):
   t = threading.Thread(target=task,args=[i,])
   t.start()
程式執行後,列印結果如下:

<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
複製程式碼

看起來也沒有問題,那是因為執行速度過快,如果在init方法中有一些IO操作,就會發現問題了,下面我們通過time.sleep模擬

我們在上面__init__方法中加入以下程式碼:

def __init__(self):
       import time
       time.sleep(1)
複製程式碼

重新執行程式後,結果如下

<__main__.Singleton object at 0x034A3410>
<__main__.Singleton object at 0x034BB990>
<__main__.Singleton object at 0x034BB910>
<__main__.Singleton object at 0x034ADED0>
<__main__.Singleton object at 0x034E6BD0>
<__main__.Singleton object at 0x034E6C10>
<__main__.Singleton object at 0x034E6B90>
<__main__.Singleton object at 0x034BBA30>
<__main__.Singleton object at 0x034F6B90>
<__main__.Singleton object at 0x034E6A90>
複製程式碼

問題出現了!按照以上方式建立的單例,無法支援多執行緒

解決辦法:加鎖!未加鎖部分併發執行,加鎖部分序列執行,速度降低,但是保證了資料安全

import time
import threading
class Singleton(object):
   _instance_lock = threading.Lock()

   def __init__(self):
       time.sleep(1)

   @classmethod
   def instance(cls, *args, **kwargs):
       with Singleton._instance_lock:
           if not hasattr(Singleton, "_instance"):
               Singleton._instance = Singleton(*args, **kwargs)
       return Singleton._instance

def task(arg):
   obj = Singleton.instance()
   print(obj)
for i in range(10):
   t = threading.Thread(target=task,args=[i,])
   t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)
列印結果如下:

<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
複製程式碼

這樣就差不多了,但是還是有一點小問題,就是當程式執行時,執行了time.sleep(20)後,下面例項化物件時,此時已經是單例模式了,但我們還是加了鎖,這樣不太好,再進行一些優化,把intance方法,改成下面的這樣就行:

@classmethod
   def instance(cls, *args, **kwargs):
       if not hasattr(Singleton, "_instance"):
           with Singleton._instance_lock:
               if not hasattr(Singleton, "_instance"):
                   Singleton._instance = Singleton(*args, **kwargs)
       return Singleton._instance
複製程式碼

這種方式實現的單例模式,使用時會有限制,以後例項化必須通過 obj = Singleton.instance()

如果用 obj=Singleton() ,這種方式得到的不是單例

基於__new__方法實現(推薦使用,方便) 通過上面例子,我們可以知道,當我們實現單例時,為了保證執行緒安全需要在內部加入鎖

我們知道,當我們例項化一個物件時,是先執行了類的__new__方法(我們沒寫時,預設呼叫object.new),例項化物件;然後再執行類的__init__方法,對這個物件進行初始化,所有我們可以基於這個,實現單例模式

import threading
class Singleton(object):
   _instance_lock = threading.Lock()

   def __init__(self):
       pass


   def __new__(cls, *args, **kwargs):
       if not hasattr(Singleton, "_instance"):
           with Singleton._instance_lock:
               if not hasattr(Singleton, "_instance"):
                   Singleton._instance = object.__new__(cls)  
       return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

def task(arg):
   obj = Singleton()
   print(obj)

for i in range(10):
   t = threading.Thread(target=task,args=[i,])
   t.start()
複製程式碼

採用這種方式的單例模式,以後例項化物件時,和平時例項化物件的方法一樣 obj = Singleton()

基於metaclass方式實現 """ 1.類由type建立,建立類時,type的__init__方法自動執行,類() 執行type的 __call__方法(類的__new__方法,類的__init__方法) 2.物件由類建立,建立物件時,類的__init__方法自動執行,物件()執行類的 call 方法 """

import threading
class SingletonType(type):
   _instance_lock = threading.Lock()
   def __call__(cls, *args, **kwargs):
       if not hasattr(cls, "_instance"):
           with SingletonType._instance_lock:
               if not hasattr(cls, "_instance"):
                   cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
       return cls._instance

class Foo(metaclass=SingletonType):
   def __init__(self,name):
       self.name = name
obj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)
複製程式碼

python面試題~反射,元類,單例

識別圖中二維碼,領取python全套視訊資料

相關文章