详解Python中的动态属性和特性


Posted in Python onApril 07, 2018

导语:本文章记录了本人在学习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 相关文章推荐
深入解析Python中的urllib2模块
Nov 13 Python
python中星号变量的几种特殊用法
Sep 07 Python
Python中eval带来的潜在风险代码分析
Dec 11 Python
Python实现二维数组输出为图片
Apr 03 Python
python增加矩阵维度的实例讲解
Apr 04 Python
使用pandas模块读取csv文件和excel表格,并用matplotlib画图的方法
Jun 22 Python
pytorch中的自定义反向传播,求导实例
Jan 06 Python
Tensorflow训练模型越来越慢的2种解决方案
Feb 07 Python
超全Python图像处理讲解(多模块实现)
Apr 13 Python
Python classmethod装饰器原理及用法解析
Oct 17 Python
Python基于tkinter canvas实现图片裁剪功能
Nov 05 Python
Python合并多张图片成PDF
Jun 09 Python
简单谈谈Python的pycurl模块
Apr 07 #Python
VSCode下好用的Python插件及配置
Apr 06 #Python
VScode编写第一个Python程序HelloWorld步骤
Apr 06 #Python
在VS Code上搭建Python开发环境的方法
Apr 06 #Python
python装饰器深入学习
Apr 06 #Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
Apr 06 #Python
python中pylint使用方法(pylint代码检查)
Apr 06 #Python
You might like
yii实现图片上传及缩略图生成的方法
2014/12/04 PHP
php实现MySQL数据库备份与还原类实例
2014/12/09 PHP
php编程中echo用逗号和用点号连接的区别
2016/03/26 PHP
微信公众号OAuth2.0网页授权问题浅析
2017/01/21 PHP
JavaScript去掉数组中的重复元素
2011/01/13 Javascript
JS限制上传图片大小不使用控件在本地实现
2012/12/19 Javascript
jquery滚动组件(vticker.js)实现页面动态数据的滚动效果
2013/07/03 Javascript
JavaScript转换二进制编码为ASCII码的方法
2015/04/16 Javascript
纯javascript移动优先的幻灯片效果
2015/11/02 Javascript
JSON+Jquery省市区三级联动
2016/01/13 Javascript
深入解析JavaScript中的arguments对象
2016/06/12 Javascript
javascript回到顶部特效
2016/07/30 Javascript
js实现四舍五入完全保留两位小数的方法
2016/08/02 Javascript
BootStrap栅格系统、表单样式与按钮样式源码解析
2017/01/20 Javascript
js获取元素的偏移量offset简单方法(必看)
2017/07/05 Javascript
mescroll.js上拉加载下拉刷新组件使用详解
2017/11/13 Javascript
Layui带搜索的下拉框的使用以及动态数据绑定方法
2019/09/28 Javascript
原生JavaScript实现弹幕组件的示例代码
2020/10/12 Javascript
[58:58]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第二场
2018/04/05 DOTA
Python中暂存上传图片的方法
2015/02/18 Python
Python实现输出程序执行进度百分比的方法
2017/09/16 Python
Django视图和URL配置详解
2018/01/31 Python
python:pandas合并csv文件的方法(图书数据集成)
2018/04/12 Python
python计算无向图节点度的实例代码
2019/11/22 Python
Django 再谈一谈json序列化
2020/03/16 Python
英国儿童图书网站:Scholastic
2017/03/26 全球购物
Prototype中如何为一个元素添加一个方法
2014/12/08 面试题
J2EE中的容器都包括哪些
2013/08/21 面试题
优秀学生干部推荐材料
2014/02/03 职场文书
教师校本培训方案
2014/02/26 职场文书
厉行勤俭节约倡议书
2014/05/16 职场文书
创先争优活动承诺书
2014/08/30 职场文书
党员个人整改方案及措施
2014/10/25 职场文书
2015年个人工作总结报告
2015/04/25 职场文书
python 模拟在天空中放风筝的示例代码
2021/04/21 Python
用Python爬虫破解滑动验证码的案例解析
2021/05/06 Python