1. 程式人生 > >python學習之延遲初始化

python學習之延遲初始化

概述

Python的延遲初始化主要是指物件第一次被建立時才進行初始化,其主要是為了提高計算效能,避免計算浪費並減少,減少程式的記憶體需求, 總結起來如下:

類屬性的延遲計算就是將類的屬性定義成一個property,只在訪問的時候才會計算,而且一旦被訪問後,結果將會被快取起來,不用每次都計算

下面一步步瞭解一下python的這一功能

原始程式

class my_class(object):
    def __init__(self,name):
        self.name = name

    def get_name(self):
        print '獲取姓名'
return self.name # 原始 obj = my_class('xiaoming') print obj.name print obj.get_name() print obj.get_name() # 結果 xiaoming 獲取姓名 xiaoming 獲取姓名 xiaoming

上面程式中,my_class的屬性有name,而get_name主要是類獲取屬性值的方法,具體方法如上述程式碼所示

注意到,獲取類的屬性值,必須先例項化一個物件,再通過物件呼叫類內的方法,這樣不免不好,若是將方法轉為屬性,直接訪問不是更好,因此就有了property

property

property主要將方法的訪問轉變成屬性的方法

class my_class(object):
    def __init__(self,name):
        self.name = name

    @property
    def get_name(self):
        print '獲取姓名'
        return self.name

obj = my_class('xiaoming')
print obj.name
print obj.get_name
print obj.get_name
print obj.get_name


# 結果
xiaoming
獲取姓名
xiaoming
獲取姓名
xiaoming
獲取姓名
xiaoming

注意到,get_name雖然定義成一個方法的形式,但是加上@property後,便可直接執行obj.get_name當成屬性訪問。

但注意到,每次呼叫obj.get_name時,都會計算一次,浪費CPU,但是怎樣只計算一次,已減少CPU資源呢,這就用到了lazy延遲初始化

lazy

from lazy import lazy

class my_class(object):
    def __init__(self, name):
        self.name = name

    @lazy
    def get_name(self):
        print '獲取姓名'
        return self.name

obj = my_class('xiaoming')
print obj.name
print obj.get_name
print obj.get_name
print obj.get_name

# 結果
xiaoming
獲取姓名
xiaoming
xiaoming
xiaoming

注意到,輸出結果時,只有第一次進行計算,而並不是每次都進行計算,減少了CPU資源, 那麼是如何做到的,我們細細來看

lazy原始碼

class lazy(object):
    """lazy descriptor

    Used as a decorator to create lazy attributes. Lazy attributes
    are evaluated on first use.
    """

    def __init__(self, func):
        self.__func = func
        functools.wraps(self.__func)(self)

    def __get__(self, inst, inst_cls):
        if inst is None:
            return self

        if not hasattr(inst, '__dict__'):
            raise AttributeError("'%s' object has no attribute '__dict__'" % (inst_cls.__name__,))

        name = self.__name__
        if name.startswith('__') and not name.endswith('__'):
            name = '_%s%s' % (inst_cls.__name__, name)

        value = self.__func(inst)
        inst.__dict__[name] = value
        return value
    ...
  • lazy描述符:在lazy類中,定義了__get__()方法,因此它是一個描述符(任何實現了__get()__, __set()__, __delete()__方法的物件就是描述符,而一個class的屬性是一個描述符的時候,對這個屬性的訪問都會觸發特定的繫結行為)。
  • my_class:類內部的方法get_name用@lazy裝飾,說白了就是呼叫了lazy(get_name),這時候就會對lazy類初始化,其中def __init__(self, func)的func便是get_name
  • obj.get_name:從第一條,我們已經知道lazy是一個描述符,第二條中用lazy裝飾get_name,當執行obj.get_name時,python直譯器會先從obj.__dict__中查詢,若沒找到就從my_class.__dict__中進行查詢,由於lazy為描述符,便會呼叫__get()__方法
  • __get()__: 直接看程式碼如下,注意到,比如呼叫get_name()方法返回的值為value,返回後設置物件例項的__dict__,因此這樣便實現了延遲化,然下次呼叫是直接就從__dict__中便可查詢到想要的資料
value = self.__func(inst)
inst.__dict__[name] = value

我的lazy

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

    def __get__(self, instance, owner):
        value = self._func(instance)
        setattr(instance, self._func.__name__, value)
        return value

class my_class(object):
    def __init__(self, name):
        self.name = name

    @lazy
    def get_name(self):
        print '獲取姓名'
        return self.name

obj = my_class('xiaoming')
print obj.name
print obj.__dict__
print obj.get_name
print obj.__dict__
print obj.get_name
print obj.get_name

# 結果
xiaoming
{'name': 'xiaoming'}
獲取姓名
xiaoming
{'name': 'xiaoming', 'get_name': 'xiaoming'}
xiaoming
xiaoming