详解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实现自动获取IP并发送到邮箱
Dec 26 Python
django与小程序实现登录验证功能的示例代码
Feb 19 Python
python selenium firefox使用详解
Feb 26 Python
Python获取命令实时输出-原样彩色输出并返回输出结果的示例
Jul 11 Python
numpy.meshgrid()理解(小结)
Aug 01 Python
tensorflow 只恢复部分模型参数的实例
Jan 06 Python
Docker部署Python爬虫项目的方法步骤
Jan 19 Python
浅谈pytorch池化maxpool2D注意事项
Feb 18 Python
Python实现代码块儿折叠
Apr 15 Python
通过案例解析python鸭子类型相关原理
Oct 10 Python
python中random模块详解
Mar 01 Python
Python 解决空列表.append() 输出为None的问题
May 23 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
PHP和XSS跨站攻击的防范
2007/04/17 PHP
php 正则表达式小结
2009/08/31 PHP
获取php页面执行时间,数据库读写次数,函数调用次数等(THINKphp)
2013/06/03 PHP
PHP中Session引起的脚本阻塞问题解决办法
2014/04/08 PHP
通过php修改xml文档内容的方法
2015/01/23 PHP
PHP+Oracle本地开发环境搭建方法详解
2019/04/01 PHP
Dom 是什么的详细说明
2010/10/25 Javascript
基于jquery DOM写的类似微博发布的效果
2012/10/20 Javascript
Extjs 4.x 得到form CheckBox 复选框的值
2014/05/04 Javascript
使用jquery清空、复位整个输入域
2015/04/02 Javascript
javascript获取select值的方法分析
2015/07/02 Javascript
浏览器复制插件zeroclipboard使用指南
2016/03/26 Javascript
jQuery实现导航回弹效果
2017/02/27 Javascript
基于vue实现swipe分页组件实例
2017/05/25 Javascript
使用live-server快速搭建本地服务器+自动刷新的方法
2018/03/09 Javascript
Bootstrap-table使用footerFormatter做统计列功能
2018/09/07 Javascript
在小程序/mpvue中使用flyio发起网络请求的方法
2018/09/13 Javascript
js中值引用和地址引用实例分析
2019/06/21 Javascript
通过图带你深入了解vue的响应式原理
2019/06/21 Javascript
JavaScript中break、continue和return的用法区别实例分析
2020/03/02 Javascript
使用js获取身份证年龄的示例代码
2020/12/11 Javascript
进一步探究Python的装饰器的运用
2015/05/05 Python
python实现的用于搜索文件并进行内容替换的类实例
2015/06/28 Python
Scrapy使用的基本流程与实例讲解
2018/10/21 Python
python3.5安装python3-tk详解
2019/04/26 Python
python如何实现视频转代码视频
2019/06/17 Python
Python实现K折交叉验证法的方法步骤
2019/07/11 Python
Python入门Anaconda和Pycharm的安装和配置详解
2019/07/16 Python
Python3操作读写CSV文件使用包过程解析
2020/04/10 Python
CSS3实现跳动的动画效果
2016/09/12 HTML / CSS
美国领先的户外服装与装备用品店:Moosejaw
2016/08/25 全球购物
C++:局部变量能否和全局变量重名
2014/03/03 面试题
思想汇报格式
2014/01/05 职场文书
初中地理教学反思
2014/01/11 职场文书
浅谈Redis的几个过期策略
2021/05/27 Redis
JavaScript 事件捕获冒泡与捕获详情
2021/11/11 Javascript