亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Python中屬性和描述符的正確使用

系統(tǒng) 1728 0

關(guān)于@property裝飾器

在Python中我們使用@property裝飾器來把對函數(shù)的調(diào)用偽裝成對屬性的訪問。

那么為什么要這樣做呢?因為@property讓我們將自定義的代碼同變量的訪問/設(shè)定聯(lián)系在了一起,同時為你的類保持一個簡單的訪問屬性的接口。

舉個栗子,假如我們有一個需要表示電影的類:

            
class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = scroe
 self.ticket = ticket
          

你開始在項目的其他地方使用這個類,但是之后你意識到:如果不小心給電影打了負(fù)分怎么辦?你覺得這是錯誤的行為,希望Movie類可以阻止這個錯誤。 你首先想到的辦法是將Movie類修改為這樣:

            
class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
     self.ticket = ticket
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.score = scroe
          

但這行不通。因為其他部分的代碼都是直接通過 Movie.score 來賦值的。這個新修改的類只會在 __init__ 方法中捕獲錯誤的數(shù)據(jù),但對于已經(jīng)存在的類實例就無能為力了。如果有人試著運行 m.scrore= -100 ,那么誰也沒法阻止。那該怎么辦?

Python的property解決了這個問題。

我們可以這樣做

            
class Movie(object):
 def __init__(self, title, description, score):
 self.title = title
 self.description = description
 self.score = score
     self.ticket = ticket
 
 @property
 def score(self):
 return self.__score
 
 
 @score.setter
 def score(self, score):
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.__score = score
 
 @score.deleter
 def score(self):
 raise AttributeError("Can not delete score")
          

這樣在任何地方修改 score 都會檢測它是否小于0。

property的不足

對property來說,最大的缺點就是它們不能重復(fù)使用。舉個例子,假設(shè)你想為 ticket 字段也添加非負(fù)檢查。

下面是修改過的新類:

            
class Movie(object):
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = score
 self.ticket = ticket
 
 @property
 def score(self):
 return self.__score
 
 
 @score.setter
 def score(self, score):
 if score < 0:
  raise ValueError("Negative value not allowed:{}".format(score))
 self.__score = score
 
 @score.deleter
 def score(self):
 raise AttributeError("Can not delete score")
 
 
 @property
 def ticket(self):
 return self.__ticket
 
 @ticket.setter
 def ticket(self, ticket):
 if ticket < 0:
  raise ValueError("Negative value not allowed:{}".format(ticket))
 self.__ticket = ticket
 
 
 @ticket.deleter
 def ticket(self):
 raise AttributeError("Can not delete ticket")
          

可以看到代碼增加了不少,但重復(fù)的邏輯也出現(xiàn)了不少。雖然property可以讓類從外部看起來接口整潔漂亮,但是卻做不到內(nèi)部同樣整潔漂亮。

描述符登場

什么是描述符?

一般來說,描述符是一個具有綁定行為的對象屬性,其屬性的訪問被描述符協(xié)議方法覆寫。這些方法是 __get__()? __set__() __delete__() ,一個對象中只要包含了這三個方法中的至少一個就稱它為描述符。

描述符有什么作用?

The default behavior for attribute access is to get, set, or delete the attribute from an object's dictionary. For instance, a.x has a lookup chain starting witha.__dict__[‘x'], then type(a).__dict__[‘x'], and continuing through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.―?C摘自官方文檔

簡單的說 描述符會改變一個屬性的基本的獲取、設(shè)置和刪除方式

先看如何用描述符來解決上面 property邏輯重復(fù)的問題。

            
class Integer(object):
 def __init__(self, name):
 self.name = name
 
 def __get__(self, instance, owner):
 return instance.__dict__[self.name]
 
 def __set__(self, instance, value):
 if value < 0:
  raise ValueError("Negative value not allowed")
 instance.__dict__[self.name] = value
 
class Movie(object):
 score = Integer('score')
 ticket = Integer('ticket')
          

因為描述符優(yōu)先級高并且會改變默認(rèn)的 get set 行為,這樣一來,當(dāng)我們訪問或者設(shè)置 Movie().score 的時候都會受到描述符 Integer 的限制。

不過我們也總不能用下面這樣的方式來創(chuàng)建實例。

            
a = Movie()
a.score = 1
a.ticket = 2
a.title = ‘test'
a.descript = ‘…'
          

這樣太生硬了,所以我們還缺一個構(gòu)造函數(shù)。

            
class Integer(object):
 def __init__(self, name):
 self.name = name
 
 def __get__(self, instance, owner):
 if instance is None:
  return self
 return instance.__dict__[self.name]
 
 def __set__(self, instance, value):
 if value < 0:
  raise ValueError('Negative value not allowed')
 instance.__dict__[self.name] = value
 
 
class Movie(object):
 score = Integer('score')
 ticket = Integer('ticket')
 
 def __init__(self, title, description, score, ticket):
 self.title = title
 self.description = description
 self.score = score
 self.ticket = ticket
          

這樣在獲取、設(shè)置和刪除 score ticket 的時候都會進(jìn)入 Integer __get__ __set__ ,從而減少了重復(fù)的邏輯。

現(xiàn)在雖然問題得到了解決,但是你可能會好奇這個描述符到底是如何工作的。具體來說,在 __init__ 函數(shù)里訪問的是自己的 self.score self.ticket ,怎么和類屬性 score ticket 關(guān)聯(lián)起來的?

描述符如何工作

看官方的說明

If an object defines both __get__() and __set__(), it is considered a data descriptor. Descriptors that only define __get__() are called non-data descriptors (they are typically used for methods but other uses are possible).

Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance's dictionary. If an instance's dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.

The important points to remember are:

descriptors are invoked by the __getattribute__() method
overriding __getattribute__() prevents automatic descriptor calls
object.__getattribute__() and type.__getattribute__() make different calls to __get__().
data descriptors always override instance dictionaries.
non-data descriptors may be overridden by instance dictionaries.

類調(diào)用 __getattribute__() 的時候大概是下面這樣子:

            
def __getattribute__(self, key):
 "Emulate type_getattro() in Objects/typeobject.c"
 v = object.__getattribute__(self, key)
 if hasattr(v, '__get__'):
 return v.__get__(None, self)
 return v
          

下面是摘自國外一篇博客上的內(nèi)容。

Given a Class “C” and an Instance “c” where “c = C(…)”, calling “c.name” means looking up an Attribute “name” on the Instance “c” like this:

Get the Class from Instance
Call the Class's special method getattribute__. All objects have a default __getattribute
Inside getattribute

Get the Class's mro as ClassParents
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a data descriptor
Return the result from calling the data descriptor's special method __get__()
Break the for each (do not continue searching the same Attribute any further)
If the Attribute is in Instance's dict
Return the value as it is (even if the value is a data descriptor)
For each ClassParent in ClassParents
If the Attribute is in the ClassParent's dict
If is a non-data descriptor
Return the result from calling the non-data descriptor's special method __get__()
If it is NOT a descriptor
Return the value
If Class has the special method getattr
Return the result from calling the Class's special method__getattr__.

我對上面的理解是,訪問一個實例的屬性的時候是先遍歷它和它的父類,尋找它們的 __dict__ 里是否有同名的 data descriptor 如果有,就用這個 data descriptor 代理該屬性,如果沒有再尋找該實例自身的 __dict__ ,如果有就返回。任然沒有再查找它和它父類里的 non-data descriptor ,最后查找是否有 __getattr__

描述符的應(yīng)用場景

python的property、classmethod修飾器本身也是一個描述符,甚至普通的函數(shù)也是描述符(non-data discriptor)

django model和SQLAlchemy里也有描述符的應(yīng)用

            
class User(db.Model):
 id = db.Column(db.Integer, primary_key=True)
 username = db.Column(db.String(80), unique=True)
 email = db.Column(db.String(120), unique=True)
 
 def __init__(self, username, email):
 self.username = username
 self.email = email
 
 def __repr__(self):
 return '
            
              ' % self.username
            
          

總結(jié)

只有當(dāng)確實需要在訪問屬性的時候完成一些額外的處理任務(wù)時,才應(yīng)該使用property。不然代碼反而會變得更加??嗦,而且這樣會讓程序變慢很多。以上就是本文的全部內(nèi)容,由于個人能力有限,文中如有筆誤、邏輯錯誤甚至概念性錯誤,還請?zhí)岢霾⒅刚?


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 色老头在线观看精品 | 国产毛片久久久久久国产毛片 | 久久91精品久久久久久水蜜桃 | 夜夜爽天天狠狠九月婷婷 | 激情五月宗合网 | 免费一级特黄特色黄大任片 | 欧美老妇69交 | 国产色产综合色产在线观看视频 | 久久一二 | 一级毛片秋霞特色大片 | 天天拍夜夜添久久精品免费 | 伊人久久成人成综合网222 | 女人l8毛片a一级毛片免费 | 99影视在线视频免费观看 | 国产粉嫩白浆在线观看 | 免费看国产一级特黄aa大片 | 亚洲一级黄色 | 日韩在线一区二区 | 久久伦理 | 奇米影视亚洲狠狠色777不卡 | 国产成人亚洲综合一区 | 久久亚洲精品人成综合网 | 亚洲aa | 男人天堂视频在线观看 | 国产精品综合一区二区 | 天天综合天天看夜夜添狠狠玩 | 日本一级特黄毛片免费视频9 | 色婷婷网 | 激情五月开心婷婷 | 婷婷在线五月 | 亚洲精品久久久久中文字小说 | 成熟日本语热亚洲人 | 可以看美女隐私的网站 | 日韩精品福利视频一区二区三区 | 精品视频在线观看一区二区 | 国产亚洲精品中文带字幕21页 | 久久青草18免费观看网站 | 一级毛片aaa片免费观看 | 日韩在线网 | 亚洲欧美高清视频 | 看免费5xxaaa毛片30厘米 |