python面试题~反射,元类,单例
1 什么是反射?以及應(yīng)用場(chǎng)景?
test.py
def f1():print('f1') def f2():print('f2') def f3():print('f3') def f4():print('f4') a = 1 復(fù)制代碼import test as ss ss.f1() ss.f2() print(ss.a) 復(fù)制代碼我們要導(dǎo)入另外一個(gè)模塊,可以使用import.現(xiàn)在有這樣的需求,我動(dòng)態(tài)輸入一個(gè)模塊名,可以隨時(shí)訪問(wèn)到導(dǎo)入模塊中的方法或者變量,怎么做呢?
imp = input(“請(qǐng)輸入你想導(dǎo)入的模塊名:”) CC = __import__(imp) 這種方式就是通過(guò)輸入字符串導(dǎo)入你所想導(dǎo)入的模塊 CC.f1() # 執(zhí)行模塊中的f1方法#或者用importlib模塊 import importlib importlib.import_module(name="",package="") 復(fù)制代碼上面我們實(shí)現(xiàn)了動(dòng)態(tài)輸入模塊名,從而使我們能夠輸入模塊名并且執(zhí)行里面的函數(shù)。但是上面有一個(gè)缺點(diǎn),那就是執(zhí)行的函數(shù)被固定了。那么,我們能不能改進(jìn)一下,動(dòng)態(tài)輸入函數(shù)名,并且來(lái)執(zhí)行呢?
#dynamic.py imp = input("請(qǐng)輸入模塊:") dd = __import__(imp) # 等價(jià)于import imp inp_func = input("請(qǐng)輸入要執(zhí)行的函數(shù):") f = getattr(dd,inp_func,None)#作用:從導(dǎo)入模塊中找到你需要調(diào)用的函數(shù)inp_func,然后返回一個(gè)該函數(shù)的引用.沒(méi)有找到就煩會(huì)None f() # 執(zhí)行該函數(shù) 復(fù)制代碼反射機(jī)制 上面說(shuō)了那么多,到底什么是反射機(jī)制呢?
??其實(shí),反射就是通過(guò)字符串的形式,導(dǎo)入模塊;通過(guò)字符串的形式,去模塊尋找指定函數(shù),并執(zhí)行。利用字符串的形式去對(duì)象(模塊)中操作(查找/獲取/刪除/添加)成員,一種基于字符串的事件驅(qū)動(dòng)!
2 metaclass作用?以及應(yīng)用場(chǎng)景?
元類(lèi)(metaclass)的理解和簡(jiǎn)單運(yùn)用 首先這里討論的python類(lèi),都基于繼承于object的新式類(lèi)進(jìn)行討論。
首先在python中,所有東西都是對(duì)象。這句話非常重要要理解元類(lèi)我要重新來(lái)理解一下python中的類(lèi)
class Trick(object):pass 復(fù)制代碼當(dāng)python在執(zhí)行帶class語(yǔ)句的時(shí)候,會(huì)初始化一個(gè)類(lèi)對(duì)象放在內(nèi)存里面。例如這里會(huì)初始化一個(gè)Trick對(duì)象
這個(gè)對(duì)象(類(lèi))自身?yè)碛袆?chuàng)建對(duì)象(通常我們說(shuō)的實(shí)例,但是在python中還是對(duì)象)的能力。
為了方便后續(xù)理解,我們可以先嘗試一下在新式類(lèi)中最古老厲害的關(guān)鍵字type。
input: class Trick(object):pass print type('123') print type(123) print type(Trick) output: <type 'str'> <type 'int'> <class '__main__.Trick'> 復(fù)制代碼可以看到能得到我們平時(shí)使用的 str, int, 以及我們初始化的一個(gè)實(shí)例對(duì)象Trick()
但是下面的方法你可能沒(méi)有見(jiàn)過(guò),type同樣可以用來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)
type(類(lèi)名, 父類(lèi)的元組(針對(duì)繼承的情況,可以為空),包含屬性的字典(名稱(chēng)和值))
這個(gè)怎么用呢,我要用這個(gè)方法創(chuàng)建一個(gè)類(lèi) 讓我們看下下面的代碼
input: print type('Trick', (), {})output: <class '__main__.Trick'> 同樣我們可以實(shí)例化這個(gè)類(lèi)對(duì)象input: print type('trick', (), {})()output: <__main__.trick object at 0x109283450> 可以看到,這里就是一個(gè)trick的實(shí)例對(duì)象了。 復(fù)制代碼同樣的這個(gè)方法還可以初始化創(chuàng)建類(lèi)的父類(lèi),同時(shí)也可以初始化類(lèi)屬性:
input: class FlyToSky(object):passpw = 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'> 復(fù)制代碼下面我將依次理一下上面的內(nèi)容,在此之前我必須先介紹兩個(gè)魔法方法:
__class__這個(gè)方法用于查看對(duì)象屬于是哪個(gè)生成的,這樣理解在python中的所有東西都是對(duì)象,類(lèi)對(duì)象也是對(duì)象。如果按照以前的思維來(lái)想的話就是類(lèi)是元類(lèi)的實(shí)例,而實(shí)例對(duì)象是類(lèi)的實(shí)例。__bases__這個(gè)方法用于得到一個(gè)對(duì)象的父類(lèi)是誰(shuí),特別注意一下__base__返回單個(gè)父類(lèi),__bases__以tuple形式返回所有父類(lèi)。 復(fù)制代碼好了知道了這兩個(gè)方法我來(lái)依次說(shuō)一下每行什么意思。
使用type創(chuàng)建一個(gè)類(lèi)賦值給pw type的接受的三個(gè)參數(shù)的意思分辨是(類(lèi)的名稱(chēng), 類(lèi)是否有父類(lèi)(), 類(lèi)的屬性字典{})
這里初始化一個(gè)類(lèi)的實(shí)例,然后嘗試去獲得類(lèi)屬性 的laugh_at 屬性值,然后得到結(jié)果hahahaha
取一個(gè)pw的也就是我們常見(jiàn)類(lèi)的類(lèi)字典數(shù)據(jù)
拿到pw的父類(lèi),結(jié)果是我們指定的 FlyToSky
pw的實(shí)例pw()屬于哪個(gè)類(lèi)初始化的,可以看到是class Trick
我們?cè)倏碿lass trick是誰(shuí)初始化的? 就是元類(lèi)type了
什么是元類(lèi)以及簡(jiǎn)單運(yùn)用
這一切介紹完之后我們總算可以進(jìn)入正題
到底什么是元類(lèi)?通俗的就是說(shuō),元類(lèi)就是創(chuàng)建類(lèi)的類(lèi)。。。這樣聽(tīng)起來(lái)是不是超級(jí)抽象?
來(lái)看看這個(gè)
Trick = MetaClass() MyObject = Trick() 復(fù)制代碼上面我們已經(jīng)介紹了,搞一個(gè)Trick可以直接這樣
Trick = type('Trick', (), {}) 可以這樣其實(shí)就是因?yàn)?#xff0c;Type實(shí)際上是一個(gè)元類(lèi),用他可以去創(chuàng)建類(lèi)。什么是元類(lèi)剛才說(shuō)了,元類(lèi)就是創(chuàng)建類(lèi)的類(lèi)。也可以說(shuō)他就是一個(gè)類(lèi)的創(chuàng)建工廠。
類(lèi)上面的__metaclass__屬性,相信愿意了解元類(lèi)細(xì)節(jié)的盆友,都肯定見(jiàn)過(guò)這個(gè)東西,而且為之好奇。不然我不知道是什么支撐你看到這里的?。使用了__metaclass__這個(gè)魔法方法就意味著就會(huì)用__metaclass__指定的元類(lèi)來(lái)創(chuàng)建類(lèi)了。
class Trick(FlyToSky):pass 復(fù)制代碼當(dāng)我們?cè)趧?chuàng)建上面的類(lèi)的時(shí)候,python做了如下的操作:
Trick中有__metaclass__這個(gè)屬性嗎?如果有,那么Python會(huì)在內(nèi)存中通過(guò)__metaclass__創(chuàng)建一個(gè)名字為T(mén)rick的類(lèi)對(duì)象,也就是Trick這個(gè)東西。如果Python沒(méi)有找到__metaclass__,它會(huì)繼續(xù)在自己的父類(lèi)FlyToSky中尋找__metaclass__屬性,并且嘗試以__metaclass__指定的方法創(chuàng)建一個(gè)Trick類(lèi)對(duì)象。如果Python在任何一個(gè)父類(lèi)中都找不到__metaclass__,它也不會(huì)就此放棄,而是去模塊中搜尋是否有__metaclass__的指定。如果還是找不到,好吧那就是使用默認(rèn)的type來(lái)創(chuàng)建Trick。
那么問(wèn)題來(lái)了,我們要在__metaclass__中放置什么呢?答案是可以創(chuàng)建一個(gè)類(lèi)的東西,type,或者任何用到type或子類(lèi)化type的東西都行。
自定義元類(lèi)
自定義類(lèi)的的目的,我總結(jié)了一下就是攔截類(lèi)的創(chuàng)建,然后修改一些特性,然后返回該類(lèi)。是不是有點(diǎn)熟悉?沒(méi)錯(cuò),就是感覺(jué)是裝飾器干的事情,只是裝飾器是修飾一個(gè)函數(shù),同樣是一個(gè)東西進(jìn)去,然后被額外加了一些東西,最后被返回。
其實(shí)除了上面談到的制定一個(gè)__metaclass__并不需要賦值給它的不一定要是正式類(lèi),是一個(gè)函數(shù)也可以。要?jiǎng)?chuàng)建一個(gè)使所有模塊級(jí)別都是用這個(gè)元類(lèi)創(chuàng)建類(lèi)的話,在模塊級(jí)別設(shè)定__metaclass__就可以了。先寫(xiě)一個(gè)來(lái)試試看
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__ = UpperAttrMetaClassbar = 12money = 'unlimited'print(Trick.bar) print(Trick.money) 復(fù)制代碼3 用盡量多的方法實(shí)現(xiàn)單例模式。
單例模式 單例模式(Singleton Pattern)是一種常用的軟件設(shè)計(jì)模式,該模式的主要目的是確保某一個(gè)類(lèi)只有一個(gè)實(shí)例存在。當(dāng)你希望在整個(gè)系統(tǒng)中,某個(gè)類(lèi)只能出現(xiàn)一個(gè)實(shí)例時(shí),單例對(duì)象就能派上用場(chǎng)。
比如,某個(gè)服務(wù)器程序的配置信息存放在一個(gè)文件中,客戶(hù)端通過(guò)一個(gè) AppConfig 的類(lèi)來(lái)讀取配置文件的信息。如果在程序運(yùn)行期間,有很多地方都需要使用配置文件的內(nèi)容,也就是說(shuō),很多地方都需要?jiǎng)?chuàng)建 AppConfig 對(duì)象的實(shí)例,這就導(dǎo)致系統(tǒng)中存在多個(gè) AppConfig 的實(shí)例對(duì)象,而這樣會(huì)嚴(yán)重浪費(fèi)內(nèi)存資源,尤其是在配置文件內(nèi)容很多的情況下。事實(shí)上,類(lèi)似 AppConfig 這樣的類(lèi),我們希望在程序運(yùn)行期間只存在一個(gè)實(shí)例對(duì)象。
在 Python 中,我們可以用多種方法來(lái)實(shí)現(xiàn)單例模式
實(shí)現(xiàn)單例模式的幾種方式 使用模塊 其實(shí),Python 的模塊就是天然的單例模式,因?yàn)槟K在第一次導(dǎo)入時(shí),會(huì)生成 .pyc 文件,當(dāng)?shù)诙螌?dǎo)入時(shí),就會(huì)直接加載 .pyc 文件,而不會(huì)再次執(zhí)行模塊代碼。因此,我們只需把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個(gè)模塊中,就可以獲得一個(gè)單例對(duì)象了。如果我們真的想要一個(gè)單例類(lèi),可以考慮這樣做:
class Singleton(object):def foo(self):pass singleton = Singleton() 復(fù)制代碼將上面的代碼保存在文件 mysingleton.py 中,要使用時(shí),直接在其他文件中導(dǎo)入此文件中的對(duì)象,這個(gè)對(duì)象即是單例模式的對(duì)象
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 = 1def __init__(self, x=0):self.x = xa1 = A(2) a2 = A(3) 復(fù)制代碼使用類(lèi)
class Singleton(object):def __init__(self):pass@classmethoddef instance(cls, *args, **kwargs):if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instance 復(fù)制代碼一般情況,大家以為這樣就完成了單例模式,但是這樣當(dāng)使用多線程時(shí)會(huì)存在問(wèn)題
class Singleton(object):def __init__(self):pass@classmethoddef 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() 程序執(zhí)行后,打印結(jié)果如下:<__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> 復(fù)制代碼看起來(lái)也沒(méi)有問(wèn)題,那是因?yàn)閳?zhí)行速度過(guò)快,如果在init方法中有一些IO操作,就會(huì)發(fā)現(xiàn)問(wèn)題了,下面我們通過(guò)time.sleep模擬
我們?cè)谏厦鎋_init__方法中加入以下代碼:
def __init__(self):import timetime.sleep(1) 復(fù)制代碼重新執(zhí)行程序后,結(jié)果如下
<__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> 復(fù)制代碼問(wèn)題出現(xiàn)了!按照以上方式創(chuàng)建的單例,無(wú)法支持多線程
解決辦法:加鎖!未加鎖部分并發(fā)執(zhí)行,加鎖部分串行執(zhí)行,速度降低,但是保證了數(shù)據(jù)安全
import time import threading class Singleton(object):_instance_lock = threading.Lock()def __init__(self):time.sleep(1)@classmethoddef instance(cls, *args, **kwargs):with Singleton._instance_lock:if not hasattr(Singleton, "_instance"):Singleton._instance = Singleton(*args, **kwargs)return Singleton._instancedef 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) 打印結(jié)果如下:<__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> 復(fù)制代碼這樣就差不多了,但是還是有一點(diǎn)小問(wèn)題,就是當(dāng)程序執(zhí)行時(shí),執(zhí)行了time.sleep(20)后,下面實(shí)例化對(duì)象時(shí),此時(shí)已經(jīng)是單例模式了,但我們還是加了鎖,這樣不太好,再進(jìn)行一些優(yōu)化,把intance方法,改成下面的這樣就行:
@classmethoddef 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 復(fù)制代碼這種方式實(shí)現(xiàn)的單例模式,使用時(shí)會(huì)有限制,以后實(shí)例化必須通過(guò) obj = Singleton.instance()
如果用 obj=Singleton() ,這種方式得到的不是單例
基于__new__方法實(shí)現(xiàn)(推薦使用,方便) 通過(guò)上面例子,我們可以知道,當(dāng)我們實(shí)現(xiàn)單例時(shí),為了保證線程安全需要在內(nèi)部加入鎖
我們知道,當(dāng)我們實(shí)例化一個(gè)對(duì)象時(shí),是先執(zhí)行了類(lèi)的__new__方法(我們沒(méi)寫(xiě)時(shí),默認(rèn)調(diào)用object.new),實(shí)例化對(duì)象;然后再執(zhí)行類(lèi)的__init__方法,對(duì)這個(gè)對(duì)象進(jìn)行初始化,所有我們可以基于這個(gè),實(shí)現(xiàn)單例模式
import threading class Singleton(object):_instance_lock = threading.Lock()def __init__(self):passdef __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._instanceobj1 = 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() 復(fù)制代碼采用這種方式的單例模式,以后實(shí)例化對(duì)象時(shí),和平時(shí)實(shí)例化對(duì)象的方法一樣 obj = Singleton()
基于metaclass方式實(shí)現(xiàn) """ 1.類(lèi)由type創(chuàng)建,創(chuàng)建類(lèi)時(shí),type的__init__方法自動(dòng)執(zhí)行,類(lèi)() 執(zhí)行type的 __call__方法(類(lèi)的__new__方法,類(lèi)的__init__方法) 2.對(duì)象由類(lèi)創(chuàng)建,創(chuàng)建對(duì)象時(shí),類(lèi)的__init__方法自動(dòng)執(zhí)行,對(duì)象()執(zhí)行類(lèi)的 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._instanceclass Foo(metaclass=SingletonType):def __init__(self,name):self.name = name obj1 = Foo('name') obj2 = Foo('name') print(obj1,obj2) 復(fù)制代碼 識(shí)別圖中二維碼,領(lǐng)取python全套視頻資料總結(jié)
以上是生活随笔為你收集整理的python面试题~反射,元类,单例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 构建Squid代理服务器-传统代理、透明
- 下一篇: 点击回退按钮刷新页面