中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

聽(tīng)說(shuō)你會(huì) Python ?

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

最近覺(jué)得 Python 太“簡(jiǎn)單了”,于是在師父川爺面前放肆了一把:“我覺(jué)得 Python 是世界上最簡(jiǎn)單的語(yǔ)言!”。于是川爺嘴角閃過(guò)了一絲輕蔑的微笑(內(nèi)心 OS:Naive!,作為一個(gè) Python 開(kāi)發(fā)者,我必須要給你一點(diǎn)人生經(jīng)驗(yàn),不然你不知道天高地厚。┯谑谴斀o我了一份滿分 100 分的題,然后這篇文章就是記錄下做這套題所踩過(guò)的坑。

1.列表生成器

描述

下面的代碼會(huì)報(bào)錯(cuò),為什么?

class A(object):
    x = 1
    gen = (x for _ in xrange(10))  # gen=(x for _ in range(10))


if __name__ == "__main__":
    print(list(A.gen))

答案

這個(gè)問(wèn)題是變量作用域問(wèn)題,在 gen=(x for _ in xrange(10)) 中 gen 是一個(gè) generator ,在 generator 中變量有自己的一套作用域,與其余作用域空間相互隔離。因此,將會(huì)出現(xiàn)這樣的 NameError: name 'x' is not defined 的問(wèn)題,那么解決方案是什么呢?答案是:用 lambda 。

class A(object):
    x = 1
    gen = (lambda x: (x for _ in xrange(10)))(x)  # gen=(x for _ in range(10))


if __name__ == "__main__":
    print(list(A.gen))

2.裝飾器

描述

我想寫一個(gè)類裝飾器用來(lái)度量函數(shù)/方法運(yùn)行時(shí)間

import time

class Timeit(object):
    def __init__(self, func):
        self._wrapped = func

    def __call__(self, *args, **kws):
        start_time = time.time()
        result = self._wrapped(*args, **kws)
        print("elapsed time is %s " % (time.time() - start_time))
        return result

這個(gè)裝飾器能夠運(yùn)行在普通函數(shù)上:

@Timeit
def func():
    time.sleep(1)
    return "invoking function func"


if __name__ == '__main__':
    func()  # output: elapsed time is 1.00044410133

但是運(yùn)行在方法上會(huì)報(bào)錯(cuò),為什么?

class A(object):
    @Timeit
    def func(self):
        time.sleep(1)
        return 'invoking method func'


if __name__ == '__main__':
    a = A()
    a.func()  # Boom!

如果我堅(jiān)持使用類裝飾器,應(yīng)該如何修改?

答案

使用類裝飾器后,在調(diào)用 func 函數(shù)的過(guò)程中其對(duì)應(yīng)的 instance 并不會(huì)傳遞給 __call__ 方法,造成其 mehtod unbound ,那么解決方法是什么呢?描述符賽高

class Timeit(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('invoking Timer')

    def __get__(self, instance, owner):
        return lambda *args, **kwargs: self.func(instance, *args, **kwargs)

3.Python 調(diào)用機(jī)制

描述

我們知道 __call__ 方法可以用來(lái)重載圓括號(hào)調(diào)用,好的,以為問(wèn)題就這么簡(jiǎn)單?Naive!

class A(object):
    def __call__(self):
        print("invoking __call__ from A!")


if __name__ == "__main__":
    a = A()
    a()  # output: invoking __call__ from A

現(xiàn)在我們可以看到 a() 似乎等價(jià)于 a.__call__() ,看起來(lái)很 Easy 對(duì)吧,好的,我現(xiàn)在想作死,又寫出了如下的代碼,

a.__call__ = lambda: "invoking __call__ from lambda"
a.__call__()
# output:invoking __call__ from lambda
a()


# output:invoking __call__ from A!

請(qǐng)大佬們解釋下,為什么 a() 沒(méi)有調(diào)用出 a.__call__() (此題由 USTC 王子博前輩提出)

答案

原因在于,在 Python 中,新式類( new class )的內(nèi)建特殊方法,和實(shí)例的屬性字典是相互隔離的,具體可以看看 Python 官方 文檔 對(duì)于這一情況的說(shuō)明

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception (unlike the equivalent example with old-style classes):

同時(shí)官方也給出了一個(gè)例子:

class C(object):
    pass


c = C()
c.__len__ = lambda: 5
len(c)


# Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
# TypeError: object of type 'C' has no len()

回到我們的例子上來(lái),當(dāng)我們?cè)趫?zhí)行 a.__call__=lambda:"invoking __call__ from lambda" 時(shí),的確在我們?cè)?a.__dict__ 中新增加了一個(gè) key 為 __call__ 的 item,但是當(dāng)我們執(zhí)行 a() 時(shí),因?yàn)樯婕疤厥夥椒ǖ恼{(diào)用,因此我們的調(diào)用過(guò)程不會(huì)從 a.__dict__ 中尋找屬性,而是從 tyee(a).__dict__ 中尋找屬性。因此,就會(huì)出現(xiàn)如上所述的情況。

4.描述符

描述

我想寫一個(gè) Exam 類,其屬性 math 為 [0,100] 的整數(shù),若賦值時(shí)不在此范圍內(nèi)則拋出異常,我決定用描述符來(lái)實(shí)現(xiàn)這個(gè)需求。

class Grade(object):
    def __init__(self):
        self._score = 0

    def __get__(self, instance, owner):
        return self._score

    def __set__(self, instance, value):
        if 0 <= value <= 100:
            self._score = value
        else:
            raise ValueError('grade must be between 0 and 100')


class Exam(object):
    math = Grade()

    def __init__(self, math):
        self.math = math


if __name__ == '__main__':
    niche = Exam(math=90)
    print(niche.math)
    # output : 90
    snake = Exam(math=75)
    print(snake.math)
    # output : 75
    snake.math = 120
    # output: ValueError:grade must be between 0 and 100!

看起來(lái)一切正常。不過(guò)這里面有個(gè)巨大的問(wèn)題,嘗試說(shuō)明是什么問(wèn)題

為了解決這個(gè)問(wèn)題,我改寫了 Grade 描述符如下:

class Grad(object):
    def __init__(self):
        self._grade_pool = {}

    def __get__(self, instance, owner):
        return self._grade_pool.get(instance, None)

    def __set__(self, instance, value):
        if 0 <= value <= 100:
            _grade_pool = self.__dict__.setdefault('_grade_pool', {})
            _grade_pool[instance] = value
        else:
            raise ValueError("fuck")

不過(guò)這樣會(huì)導(dǎo)致更大的問(wèn)題,請(qǐng)問(wèn)該怎么解決這個(gè)問(wèn)題?

答案

1.第一個(gè)問(wèn)題的其實(shí)很簡(jiǎn)單,如果你再運(yùn)行一次 print(niche.math) 你就會(huì)發(fā)現(xiàn),輸出值是 120 ,那么這是為什么呢?這就要先從 Python 的調(diào)用機(jī)制說(shuō)起了。我們?nèi)绻{(diào)用一個(gè)屬性,那么其順序是優(yōu)先從實(shí)例的 __dict__ 里查找,然后如果沒(méi)有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。好的,現(xiàn)在回到我們的問(wèn)題,我們發(fā)現(xiàn),在我們的類 Exam 中,其 self.math 的調(diào)用過(guò)程是,首先在實(shí)例化后的實(shí)例的 __dict__ 中進(jìn)行查找,沒(méi)有找到,接著往上一級(jí),在我們的類 Exam 中進(jìn)行查找,好的找到了,返回。那么這意味著,我們對(duì)于 self.math 的所有操作都是對(duì)于類變量 math 的操作。因此造成變量污染的問(wèn)題。那么該則怎么解決呢?很多同志可能會(huì)說(shuō),恩,在 __set__ 函數(shù)中將值設(shè)置到具體的實(shí)例字典不就行了。

那么這樣可不可以呢?答案是,很明顯不得行啊,至于為什么,就涉及到我們 Python 描述符的機(jī)制了,描述符指的是實(shí)現(xiàn)了描述符協(xié)議的特殊的類,三個(gè)描述符協(xié)議指的是 __get__ , ‘ set ‘ , __delete__ 以及 Python 3.6 中新增的 __set_name__ 方法,其中實(shí)現(xiàn)了 __get__ 以及 __set__ / __delete__ / __set_name__ 的是 Data descriptors ,而只實(shí)現(xiàn)了 __get__ 的是 Non-Data descriptor 。那么有什么區(qū)別呢,前面說(shuō)了, 我們?nèi)绻{(diào)用一個(gè)屬性,那么其順序是優(yōu)先從實(shí)例的 __dict__ 里查找,然后如果沒(méi)有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。 但是,這里沒(méi)有考慮描述符的因素進(jìn)去,如果將描述符因素考慮進(jìn)去,那么正確的表述應(yīng)該是 我們?nèi)绻{(diào)用一個(gè)屬性,那么其順序是優(yōu)先從實(shí)例的 __dict__ 里查找,然后如果沒(méi)有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。其中如果在類實(shí)例字典中的該屬性是一個(gè) Data descriptors ,那么無(wú)論實(shí)例字典中存在該屬性與否,無(wú)條件走描述符協(xié)議進(jìn)行調(diào)用,在類實(shí)例字典中的該屬性是一個(gè) Non-Data descriptors ,那么優(yōu)先調(diào)用實(shí)例字典中的屬性值而不觸發(fā)描述符協(xié)議,如果實(shí)例字典中不存在該屬性值,那么觸發(fā) Non-Data descriptor 的描述符協(xié)議 。回到之前的問(wèn)題,我們即使在 __set__ 將具體的屬性寫入實(shí)例字典中,但是由于類字典中存在著 Data descriptors ,因此,我們?cè)谡{(diào)用 math 屬性時(shí),依舊會(huì)觸發(fā)描述符協(xié)議。

2.經(jīng)過(guò)改良的做法,利用 dict 的 key 唯一性,將具體的值與實(shí)例進(jìn)行綁定,但是同時(shí)帶來(lái)了內(nèi)存泄露的問(wèn)題。那么為什么會(huì)造成內(nèi)存泄露呢,首先復(fù)習(xí)下我們的 dict 的特性, dict 最重要的一個(gè)特性,就是凡可 hash 的對(duì)象皆可為 key , dict 通過(guò)利用的 hash 值的唯一性(嚴(yán)格意義上來(lái)講并不是唯一,而是其 hash 值碰撞幾率極小,近似認(rèn)定其唯一)來(lái)保證 key 的不重復(fù)性,同時(shí)(敲黑板,重點(diǎn)來(lái)了), dict 中的 key 引用是強(qiáng)引用類型,會(huì)造成對(duì)應(yīng)對(duì)象的引用計(jì)數(shù)的增加,可能造成對(duì)象無(wú)法被 gc ,從而產(chǎn)生內(nèi)存泄露。那么這里該怎么解決呢??jī)煞N方法

第一種:

class Grad(object):
    def __init__(self):
        import weakref
        self._grade_pool = weakref.WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self._grade_pool.get(instance, None)

    def __set__(self, instance, value):
        if 0 <= value <= 100:
            _grade_pool = self.__dict__.setdefault('_grade_pool', {})
            _grade_pool[instance] = value
        else:
            raise ValueError("fuck")

weakref 庫(kù)中的 WeakKeyDictionary 所產(chǎn)生的字典的 key 對(duì)于對(duì)象的引用是弱引用類型,其不會(huì)造成內(nèi)存引用計(jì)數(shù)的增加,因此不會(huì)造成內(nèi)存泄露。同理,如果我們?yōu)榱吮苊?value 對(duì)于對(duì)象的強(qiáng)引用,我們可以使用 WeakValueDictionary 。

第二種:在 Python 3.6 中,實(shí)現(xiàn)的 PEP 487 提案,為描述符新增加了一個(gè)協(xié)議,我們可以用其來(lái)綁定對(duì)應(yīng)的對(duì)象:

class Grad(object):
    def __get__(self, instance, owner):
        return instance.__dict__[self.key]

    def __set__(self, instance, value):
        if 0 <= value <= 100:
            instance.__dict__[self.key] = value
        else:
            raise ValueError("fuck")

    def __set_name__(self, owner, name):
        self.key = name

這道題涉及的東西比較多,這里給出一點(diǎn)參考鏈接, invoking-descriptors , Descriptor HowTo Guide , PEP 487 , what`s new in Python 3.6 。

5.Python 繼承機(jī)制

描述

試求出以下代碼的輸出結(jié)果。

class Init(object):
    def __init__(self, value):
        self.val = value


class Add2(Init):
    def __init__(self, val):
        super(Add2, self).__init__(val)
        self.val += 2


class Mul5(Init):
    def __init__(self, val):
        super(Mul5, self).__init__(val)
        self.val *= 5


class Pro(Mul5, Add2):
    pass


class Incr(Pro):
    csup = super(Pro)

    def __init__(self, val):
        self.csup.__init__(val)
        self.val += 1


p = Incr(5)
print(p.val)

答案

輸出是 36 ,具體可以參考 New-style Classes , multiple-inheritance

6. Python 特殊方法

描述

我寫了一個(gè)通過(guò)重載 new 方法來(lái)實(shí)現(xiàn)單例模式的類。

class Singleton(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance:
            return cls._instance
        cls._isntance = cv = object.__new__(cls, *args, **kwargs)
        return cv


sin1 = Singleton()
sin2 = Singleton()
print(sin1 is sin2)
# output: True

現(xiàn)在我有一堆類要實(shí)現(xiàn)為單例模式,所以我打算照葫蘆畫瓢寫一個(gè)元類,這樣可以讓代碼復(fù)用:

class SingleMeta(type):
    def __init__(cls, name, bases, dict):
        cls._instance = None
        __new__o = cls.__new__

        def __new__(cls, *args, **kwargs):
            if cls._instance:
                return cls._instance
            cls._instance = cv = __new__o(cls, *args, **kwargs)
            return cv

        cls.__new__ = __new__o


class A(object):
    __metaclass__ = SingleMeta


a1 = A()  # what`s the fuck

哎呀,好氣啊,為啥這會(huì)報(bào)錯(cuò)啊,我明明之前用這種方法給 __getattribute__ 打補(bǔ)丁的,下面這段代碼能夠捕獲一切屬性調(diào)用并打印參數(shù)

class TraceAttribute(type):
    def __init__(cls, name, bases, dict):
        __getattribute__o = cls.__getattribute__

        def __getattribute__(self, *args, **kwargs):
            print('__getattribute__:', args, kwargs)
            return __getattribute__o(self, *args, **kwargs)

        cls.__getattribute__ = __getattribute__


class A(object):  # Python 3 是 class A(object,metaclass=TraceAttribute):
    __metaclass__ = TraceAttribute
    a = 1
    b = 2


a = A()
a.a
# output: __getattribute__:('a',){}
a.b

試解釋為什么給 getattribute 打補(bǔ)丁成功,而 new 打補(bǔ)丁失敗。

如果我堅(jiān)持使用元類給 new 打補(bǔ)丁來(lái)實(shí)現(xiàn)單例模式,應(yīng)該怎么修改?

答案

其實(shí)這是最氣人的一點(diǎn),類里的 __new__ 是一個(gè) staticmethod 因此替換的時(shí)候必須以 staticmethod 進(jìn)行替換。答案如下:

class SingleMeta(type):
    def __init__(cls, name, bases, dict):
        cls._instance = None
        __new__o = cls.__new__

        @staticmethod
        def __new__(cls, *args, **kwargs):
            if cls._instance:
                return cls._instance
            cls._instance = cv = __new__o(cls, *args, **kwargs)
            return cv

        cls.__new__ = __new__o


class A(object):
    __metaclass__ = SingleMeta


print(A() is A())  # output: True

結(jié)語(yǔ)

感謝師父大人的一套題讓我開(kāi)啟新世界的大門,恩,博客上沒(méi)法艾特,只能傳遞心意了。說(shuō)實(shí)話 Python 的動(dòng)態(tài)特性可以讓其用眾多 black magic 去實(shí)現(xiàn)一些很舒服的功能,當(dāng)然這也對(duì)我們對(duì)語(yǔ)言特性及坑的掌握也變得更嚴(yán)格了,愿各位 Pythoner 沒(méi)事閱讀官方文檔,早日達(dá)到 裝逼如風(fēng),常伴吾身 的境界。

 

來(lái)自:http://manjusaka.itscoder.com/2016/11/18/Someone-tell-me-that-you-think-Python-is-simple/

 

標(biāo)簽: 代碼 開(kāi)發(fā)者

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:對(duì)于Java Web中的Filter和Interceptor的理解

下一篇:Android安全問(wèn)題-網(wǎng)絡(luò)傳輸