詳解Python中的動態屬性和特性_python

不言
發布: 2018-04-08 10:45:25
原創
1458 人瀏覽過

本篇主要介紹了詳解Python中的動態屬性和特性,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟著小編過來看看吧

導語:這篇文章記錄了本人在學習Python基礎之元程式設計篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習並交流。

一、利用動態屬性處理JSON資料來源

#屬性:在Python中,資料的屬性與處理資料的方法統稱屬性。

元編程:用元類別編程,元類別→類別→對象,元類別比類別更抽象,產生類別的類別。

1、使用動態屬性存取JSON類別資料

第一版:利用json.load(fp)審查資料

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__方法。

2、處理無效屬性名

在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
登入後複製

#3、使用特殊方法__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])
登入後複製

二、特性

1、類別屬性、實例屬性、私有屬性與特性

類別屬性:類別屬性在__init__()外初始化,屬於類別所有,所有實例共用一個屬性。
呼叫方法:類別屬性在內部用classname.類別屬性名調用,外部既可以用classname.類別屬性名稱又可以用instancename.類別屬性名稱來呼叫。

實例屬性:實例屬性屬於各個實例所有,互不干擾。

私有屬性:

  1. 單下劃線_開頭:只是告訴別人這是私有屬性,外部仍然可以存取變更。

  2. 雙重下劃線__開頭:外部不可透過instancename.propertyname來存取或更改,實際將其轉化為了_classname__propertyname。

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

注意:

  1. 不要對實例屬性和類別屬性使用相同的名字。否則實例屬性會遮蓋類別屬性,發生難以發現的錯誤。

  2. 實例屬性不會遮蓋類別特性,但類別特性會遮蓋實例屬性。

這是因為obj.attr不會從實例obj開始尋找attr,而是從obj.__class__開始;而且僅當類別中沒有名為attr的特性時,Python才會在實例中尋找attr。

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

2、使用特性驗證屬性

使用特性可以驗證實例屬性的有效性,同時能夠根據已知屬性與屬性之間的關係式調整其他屬性,避免硬編碼。
案例:假設某商店經營堅果、雜糧等多種有機食物,每位顧客的訂單會包含店中的一系列商品,我們需要根據顧客的訂單計算出總價。

分析:我們不希望顧客訂單的商品重量為非正數,需要藉助@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)
登入後複製

3、特性工廠函數

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

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 # 此处利用特性获取实例中存储的值。
登入後複製

4、使用特性刪除屬性

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

三、處理屬性的重要屬性與函數

1、特殊屬性

  • __class__:物件所屬類別的參考(即obj.__class__和type(obj)的作用相同)。 Python中的某些特殊方法例如 __getattr__,只在物件的類別中尋找,而不是在實例中尋找。

  • __dict__:一個映射,儲存物件或類別的可寫屬性。

  • __slots__:類別可以定義這個屬性,限制實例有哪些屬性。

2、內建函數

  • #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__屬性。

3、特殊方法

  • #__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內建函數

  • 觸發這個方法。

相關推薦:

詳解Python中for迴圈的使用_python

實例詳解python高階函數的使用


以上是詳解Python中的動態屬性和特性_python的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板