Python中的動態屬性和特性

導語:本文章記錄了本人在學習Python基礎之元編程篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、瞭解如何利用動態屬性處理數據;
二、掌握Python中的特性概念以及@property裝飾器;
三、瞭解Python中處理屬性的重要屬性和函數。

1、利用動態屬性處理JSON數據源

屬性:在Python中,數據的屬性和處理數據的方法統稱屬性。
元編程:用元類進行編程,元類→類→對象,元類比類更抽象,生成類的類。編程

一、使用動態屬性訪問JSON類數據

初版:利用json.load(fp)審查數據json

from urllib.request import urlopen
import warnings
import os
import json

URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON = 'data/osconfeed.json'

def load():
    if not os.path.exists(JSON):
        msg = 'downloading {} to {}'.format(URL, JSON)
        warnings.warn(msg) #若是須要下載就發出提醒。
        with urlopen(URL) as remote, open(JSON, 'wb') as local: #在with語句中使用兩個上下文管理器分別用於讀取和保存遠程文件。
            local.write(remote.read())
    with open(JSON) as fp:
        return json.load(fp)#json.load函數解析JSON文件,返回Python原生對象。

第二版:使用動態屬性訪問JSON類數據
初版查閱深層數據的格式比較冗長,例如feed'Schedule'40,咱們但願在讀取屬性上採用feed.Schedule.events[40].name這類方式來改進。而且第二版的類能遞歸,自動處理嵌套的映射和列表。閉包

from collections import abc

class FronenJSON:
    def __init__(self,mapping):
        self.__data=dict(mapping)#建立副本,同時確保處理的是字典。
        
    def __getattr__(self, name):#僅當沒有指定名稱的屬性才調用__getattr__方法。
        if hasattr(self,name):
            return getattr(self.__data,name)
        else:
            return FronenJSON.build(self.__data[name])
   
    @classmethod    
    def __build__(cls,obj):
        if isinstance(obj,abc.Mapping):#判斷obj是不是映射。
            return cls(obj)#建立FrozenJSON對象。
        elif isinstance(obj,abc.MutableSequence):
            return [cls.build(item) for item in obj]#遞歸調用.build()方法,構建一個列表。
        else:#既不是字典也不是列表,則返回元素自己。
            return obj

分析: FronenJSON類的關鍵是__getattr__方法。僅當沒法使用常規的方式獲取屬性(即在實例、類或超類中找不到指定的屬性),解釋器纔會調用特殊的__getattr__方法。app

二、處理無效屬性名

在Python中,因爲關鍵字被保留,名稱爲關鍵字的屬性是無效的。所以須要對第二版中的__init__進行改進:函數

def __init__(self,mapping):
        self.__data={}
        for key,value in mapping.items():
            if keyword.iskeyword(key):
                key+='_'#與Python關鍵字重複的key在尾部加上下劃線。
            self.__data[key]=value

三、使用特殊方法__new__

第三版:使用__new__構造方法把一個類轉換成一個靈活的對象工廠函數。學習

from collections import abc

class FronenJSON:
    def __new__(cls, arg):  # __new__是類方法,第一個參數是類自己cls。
        if isinstance(arg, abc.Mapping):
            return super().__new__(cls)  #委託給超類object基類的__new__方法處理。
        elif isinstance(arg, abc.MutableSequence):  # 餘下方法與原先的build方法一致。
            return [cls(item) for item in arg]
        else:
            return arg
 
     def __init__(self,mapping):
        self.__data={}
        for key,value in mapping.items():
            if keyword.iskeyword(key):
                key+='_'
            self.__data[key]=value 

    def __getattr__(self, name):
        if hasattr(self,name):
            return getattr(self.__data,name)
        else:
            return FronenJSON(self.__data[name])

2、特性

一、類屬性、實例屬性、私有屬性與特性

類屬性:類屬性在__init__()外初始化,屬於類全部,全部實例共享一個屬性。
調用方法:類屬性在內部用classname.類屬性名調用,外部既能夠用classname.類屬性名又能夠用instancename.類屬性名來調用。ui

實例屬性:實例屬性屬於各個實例全部,互不干擾。編碼

私有屬性url

  • 單下劃線_開頭:只是告訴別人這是私有屬性,外部依然能夠訪問更改。
  • 雙下劃線__開頭:外部不可經過instancename.propertyname來訪問或者更改,實際將其轉化爲了_classname__propertyname。

特性:是用於管理實例屬性的類屬性。
特性用途:常常用於把公開的屬性變成使用讀值方法和設值方法管理的屬性,且在不影響客戶端代碼的前提下實施業務規則。

注意

  • 不要對實例屬性和類屬性使用相同的名字。不然實例屬性會遮蓋類屬性,發生難以發現的錯誤。
  • 實例屬性不會遮蓋類特性,但類特性會遮蓋實例屬性。
    這是由於obj.attr不會從實例obj開始尋找attr,而是從obj.__class__開始;並且僅當類中沒有名爲attr的特性時,Python纔會在實例中尋找attr。

簡言之,就遮蓋層級而言,類特性>實例屬性>類屬性。

二、使用特性驗證屬性

使用特性能夠驗證明例屬性的有效性,同時可以根據已知屬性和屬性之間的關係式調整其餘屬性,避免硬編碼。
案例:假設某商店經營堅果、雜糧等多種有機食物,每位顧客的訂單會包含店中的一系列商品,咱們須要根據客戶的訂單計算出總價。
分析:咱們不但願顧客訂單的商品重量爲非正數,須要藉助@property裝飾器實現值的獲取與設置,從而驗證明例屬性的有效性。代碼以下:

class LineItem:
    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

    @property#讀值。
    def weight(self):
        return self.__weight#真正的值存儲在私有屬性中。

    @weight.setter
    def weight(self,value):
        if value >0:
            self.__weight=value#有效值存入私有屬性中。
        else:
            raise ValueError('Value must be > 0')#對於無效的值拋出ValueError。

Tips:當咱們須要設置只讀屬性時,只使用@property,無需使用@func.setter。

原理解析:爲了更好地理解@property裝飾器的原理,咱們寫一版效果相同但沒使用裝飾器的代碼。

class LineItem:
    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight * self.price

    def get_weight(self): #普通讀值方法。
        return self.__weight

    def set_weight(self, value): #普通設值方法。
        if value > 0:
            self.__weight = value
        else:
            raise ValueError('value must be > 0')
    weight = property(get_weight, set_weight) #構建property對象,賦值給公開的類特性。

property 構造方法的完整簽名:

property(fget=None, fset=None, fdel=None, doc=None)

三、特性工廠函數

抽象定義特性的方式有兩種,一是使用特性工廠函數,二是使用描述符類。
下面咱們用特性工廠函數來完成上文中提到的訂單結算案例:

def quantity(storage_name):  

    def qty_getter(instance):  # instance指的是要把屬性存儲其中的LineItem實例。
        return instance.__dict__[storage_name]  # 引用閉包中的自由變量storage_name,值直接從instance.__dict__中獲取,以便跳過特性,防止無限遞歸。

    def qty_setter(instance, value):  
        if value > 0:
            instance.__dict__[storage_name] = value  # 同理存儲,跳過特性。
        else:
            raise ValueError('value must be > 0')

    return property(qty_getter, qty_setter)  # 構建自定義特性對象並返回。

class LineItem:
    weight = quantity('weight')  # 將自定義特性weight定義爲類屬性。
    price = quantity('price')  # 同上。

    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight  # 此處特性已經激活,可驗證值的有效性。
        self.price = price

    def subtotal(self):
        return self.weight * self.price  # 此處利用特性獲取實例中存儲的值。

四、使用特性刪除屬性

class BlackKnight:
  def __init__(self):
      self.members = ['an arm', 'another arm',
                      'a leg', 'another leg']
      self.phrases = ["'Tis but a scratch.",
                      "It's just a flesh wound.",
                      "I'm invincible!",
                      "All right, we'll call it a draw."]

  @property
  def member(self):
      print('next member is:')
      return self.members[0]

  @member.deleter
  def member(self):
      text = 'BLACK KNIGHT (loses {})\n-- {}'
      print(text.format(self.members.pop(0), self.phrases.pop(0)))

刪除屬性只需在主程序中發出指令:del obj.attr

3、處理屬性的重要屬性和函數

一、特殊屬性

  • __class__:對象所屬類的引用(即obj.__class__和type(obj)的做用相同)。Python中的某些特殊方法好比 __getattr__,只在對象的類中尋找,而不在實例中尋找。
  • __dict__:一個映射,存儲對象或類的可寫屬性。
  • __slots__:類能夠定義這個屬性,限制實例有哪些屬性。

二、內置函數

  • dir([object]):列出對象的大多數屬性。
  • getattr(object,name[,default]):從object對象中獲取name字符串對應的屬性。獲取的屬性可能來自對象所屬的類或超類。
  • hasattr(object,name):若object對象中存在指定的屬性,或者能以某種方式(如繼承)經過object對象獲取指定的屬性,返回True。
  • setattr(object,name,value):把object對象指定屬性的值設爲value,前提是object對象能接受那個值。這個函數可能會建立一個新屬性,或者覆蓋現有的屬性。
  • var([object]):返回object對象的__dict__屬性。

三、特殊方法

  • __delattr__(self,name):只要使用del語句刪除屬性,就會調用這個方法。
  • __dir__(self):把對象傳給dir函數時調用,列出屬性。
  • __getattr__(self,name):僅當獲取指定的屬性失敗,搜索過obj,Class和超類以後調用。
  • __getattribute__(self,name):嘗試獲取指定的屬性時總會調用這個方法。不過尋找的屬性是特殊屬性或特殊方法時除外。爲了防止無限遞歸,__getattribute__方法的實現要使用super().__getattribute__(obj,name)。
  • __setattr__(self,name,value):嘗試設置指定的屬性時總會調用這個方法。點號和setattr內置函數會觸發這個方法。
相關文章
相關標籤/搜索