1. 程式人生 > >Python新式類 單例模式與作用域(四)

Python新式類 單例模式與作用域(四)

如果 要求 pass 原型 參數 outer 語句 可選 被調用

1 新式類與舊式類

新式類擁有經典類的全部特性之外,還有一些新的特性,比如 __init__發生變化,新增了靜態方法__new__,python3目前都采用新式類,新式類是廣度優先,舊式類是深度優先

#新式類
class C(object):
    pass
#經典類
class B:
    pass
(1)內置的object對象

1. __new__,__init__方法
這兩個方法是用來創建object的子類對象,靜態方法__new__()用來創建類的實例,然後再調用
__init__()來初始化實例。
 
2. __delattr__, __getattribute__, __setattr__方法
對象使用這些方法來處理屬性的訪問
 
3
. __hash__, __repr__, __str__方法 print(someobj)會調用someobj.__str__(), 如果__str__沒有定義,則會調用someobj.__repr__(), __str__()和__repr__()的區別: 默認的實現是沒有任何作用的 __repr__的目標是對象信息唯一性 __str__的目標是對象信息的可讀性 容器對象的__str__一般使用的是對象元素的__repr__ 如果重新定義了__repr__,而沒有定義__str__,則默認調用__str__時,調用的是__repr__ 也就是說好的編程習慣是每一個類都需要重寫一個__repr__方法,用於提供對象的可讀信息, 而重寫__str__方法是可選的。實現__str__方法,一般是需要更加好看的打印效果,比如你要制作 一個報表的時候等。
(2)類的方法

靜態方法

靜態方法可以被類或者實例調用,它沒有常規方法的行為(比如綁定,非綁定,默認的第一個self參數),當有一
堆函數僅僅是為了一個類寫的時候,采用靜態方法聲明在類的內部,可以提供行為上的一致性。
使用裝飾符@staticmethod進行創建 

類方法

也是可以通過類和它的實例進行調用,不過它是有默認第一個參數,叫做是類對象,一般被
命名為cls,當然你也可以命名為其它名字,這樣就你可以調用類對象的一些操作,
代碼如下,使用裝飾符@classmethod創建:

新式類(new-style-class)

__init__方法: 類的初始化方法

__new__靜態方法

新式類都有一個__new__

的靜態方法,它的原型是object.__new__(cls[, ...])

cls是一個類對象,當你調用C(*args, **kargs)來創建一個類C的實例時,python的內部調用是

C.__new__(C, *args, **kargs),然後返回值是類C的實例c,在確認

c是C的實例後,python再調用C.__init__(c, *args, **kargs)來初始化實例c。

所以調用一個實例c = C(2),實際執行的代碼為:

c = C.__new__(C, 2)
if isinstance(c, C):
    C.__init__(c, 23)#__init__第一個參數要為實例對象
    
class Singleton(object):
    _singletons = {}
    def __new__(cls):
        if not cls._singletons.has_key(cls):            #若還沒有任何實例
            cls._singletons[cls] = object.__new__(cls)  #生成一個實例
        return cls._singletons[cls]                     #返回這個實例
    a = Singleton()
    b = Singleton()
    id(a)  #35966666
    id(b)  #35966666       
 #註:單例模式 ,兩個實例指向同一個內存地址 
(3)新式類實例

新式類的實例也具有新的特性。比如它擁有Property功能,該功能會對屬性的訪問方式產生影響;還有__slots__新屬性,該屬性會對生成子類實例產生影響;還添加了一個新的方法__getattribute__,比原有的__getattr__更加通用。

__slots__屬性

通常每一個實例x都會有一個__dict__屬性,用來記錄實例中所有的屬性和方法,也是通過這個字典,

可以讓實例綁定任意的屬性。而__slots__屬性作用就是,當類C有比較少的變量,而且擁有__slots__屬性時,

類C的實例 就沒有__dict__屬性,而是把變量的值存在一個固定的地方。如果試圖訪問一個__slots__中沒有

的屬性,實例就會報錯。這樣操作有什麽好處呢?__slots__屬性雖然令實例失去了綁定任意屬性的便利,

但是因為每一個實例沒有__dict__屬性,卻能有效節省每一個實例的內存消耗,有利於生成小而精

幹的實例。

定義__slots__屬性

class A(object):
    def __init__(self):
        self.x = 1
        self.y = 2
        __slots__ = (‘x‘,‘y‘)
a = A()
a.z = 3
a.u = 4  #都會報錯,不能對實例新增屬性,__dict__字典集沒有任何改變

class A(object):
    def __init__(self):
        self.x = 1
        self.y = 2
a = A()
a.x = 3  #不會報錯,在__dict__字典字典集會新增‘x‘屬性

使用時__slots__時需要註意的幾點:

1.  當一個類的父類沒有定義__slots__屬性,父類中的__dict__屬性總是可以訪問到的,所以只在子
類中定義__slots__屬性,而不在父類中定義是沒有意義的。 
2. 如果定義了__slots__屬性,還是想在之後添加新的變量,就需要把‘__dict__‘字符串添加到__slots__的
元組裏。
3. 定義了__slots__屬性,還會消失的一個屬性是__weakref__,這樣就不支持實例的weak reference,
如果還是想用這個功能,同樣,可以把‘__weakref__‘字符串添加到元組裏。
4. __slots__功能是通過descriptor實現的,會為每一個變量創建一個descriptor。
5. __slots__的功能只影響定義它的類,因此,子類需要重新定義__slots__才能有它的功能。
__getattribute__方法

對新式類的實例來說,所有屬性和方法的訪問操作都是通過__getattribute__完成,這是由object基類實現的。如果有特殊的要求,可以重載__getattribute__方法.

2 __init____new__區別

  1. __new__是一個靜態方法,而__init__是一個實例方法.
  2. __new__方法會返回一個創建的實例,而__init__什麽都不返回.
  3. 只有在__new__返回一個cls的實例時後面的__init__才能被調用.
  4. 當創建一個新實例時調用__new__,初始化一個實例時用__init__.

__metaclass__是創建類時起作用.所以我們可以分別使用__metaclass__,__new____init__來分別在類創建,實例創建和實例初始化的時候做一些操作

3 單例模式

? 單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源,系統中存在多個該類的實例對象,而這樣會嚴重浪費內存資源 如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。

__new__()__init__()之前被調用,用於生成實例對象。利用這個方法和類的屬性的特點可以實現設計模式的單例模式。單例模式是指創建唯一對象,單例模式設計的類只能實例一次

一般python中可以采用以下方法實現單例模式

  • 使用模塊(import導入)
  • 使用 __new__
  • 使用裝飾器(decorator)
  • 使用元類(metaclass)

(1)使用__new__方法

class Singleton(object):
    def __new__(cls, *args, **kw):
        if not hasattr(cls, ‘_instance‘): #如果該類是否含有實例
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance

class MyClass(Singleton):
    a = 1

#如果 cls._instance 為 None 則創建實例,否則直接返回 cls._instance。

(2)共享屬性(使用metaclass)

元類(metaclass)可以控制類的創建過程,它主要做三件事:

  • 攔截類的創建
  • 修改類的定義
  • 返回修改後的類

創建實例時把所有實例的__dict__指向同一個字典,這樣它們具有相同的屬性和方法.

class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state
        return ob

class MyClass2(Borg):
    a = 1

(3) 裝飾器版本

def singleton(cls):
    instances = {}
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

(4) import 方法(使用模塊)

作為python的模塊是天然的單例模式

# mysingleton.py
class My_Singleton(object):
    def foo(self):
        pass
my_singleton = My_Singleton()

# to use
from mysingleton import my_singleton #將類進行導入使用
my_singleton.foo()

4 Python作用域

python的作用域分全局和局部,python變量的作用域分為以下四類:

L(local) 局部作用域
E(Enclosing) 閉包函數外的函數中
G(Global) 全局作用域
B(Built-in) 內建作用域

(1) 局部作用域

在了解局部作用域之前,先了解下塊級作用域的概念

#塊級作用域
if 1 == 1:
    name = "lol"
print(name) 

for i in range(10):
    age = i
print(age)

#輸出:
C:/Users/L/PycharmProjects/s14/preview/Day8/作用域/test.py
lol
9

可以發現python代碼運行ok,為什麽外部可以調用內部的變量呢? 因為在python中沒有塊級作用域的概念,代碼塊裏的變量,外部可以調用,所以可運行成功, 也就是說,類似條件判斷(if…..else)、循環語句(for x in data)、異常捕捉(try…catch)等的變量是可以全局使用的 .但是不區分作用域明顯是不行的,因此python引入局部作用域

#局部作用域
def  func():
    name = "lol"
func()  #調用函數
print(name)
#輸出
Traceback (most recent call last):
  File "C:/Users/L/PycharmProjects/s14/preview/Day8/作用域/test.py", line 23, in <module>
    print(name)
NameError: name ‘name‘ is not defined

即使執行了一下函數,name的作用域也只是在函數內部,外部依然無法進行調用 ,因此函數可以產生局部作用域,在python中模塊(module),類(class)、函數(def、lambda)會產生新的作用域 .

(2) 作用域鏈

#作用域鏈
name = "張三"
def func1():
    name = "李四"
    def func2():
        name = "王五"
        print(name)
    func2()
func1()
#輸出
"王五"

func1()調用函數執行變量賦值操作,當調用func2()時 , print(name)中name屬性會先從局部作用域開始尋找,由內至外,最先找到的當然是 ‘王五‘.Python中有作用域鏈,變量會由內到外找,先去自己作用域去找,自己沒有再去上級去找,直到找不到報錯 .

(3) 全局作用域與內建作用域

x = int(2.9)  # 內建作用域
num = 0  # 全局作用域
def outer():
    num2 = 1  # 閉包函數外的函數中
    def inner():
        num3 = 2  # 局部作用域

(4) global關鍵字

全局變量是指在函數外的變量,可以在程序全局使用,局部變量是指定義在函數內的變量,只能在函數內被聲明使用若內部作用域的想要修改外部作用域的變量,就要使用global關鍵字

a = 100

def demo():
    global a
    a = 123
    print(a)
demo()
print(a)

#運行結果是
123
123

本來運行結果應該是123,100,但是因為global聲明a為全局作用域,因此在執行賦值後的a指向123,因此兩次打印的都是123.

總結下:

? Python 中,一個變量的作用域總是由在代碼中被賦值的地方所決定的。當 Python 遇到一個變量的話他會按照這樣的順序進行搜索:本地作用域(Local)→當前作用域被嵌入的本地作用域(Enclosing locals)→全局/模塊作用域(Global)→內置作用域(Built-in)

Python新式類 單例模式與作用域(四)