详解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中的super()方法使用简介
Aug 14 Python
python 打印对象的所有属性值的方法
Sep 11 Python
Python 2与Python 3版本和编码的对比
Feb 14 Python
Python 关于反射和类的特殊成员方法
Sep 14 Python
Django 实现下载文件功能的示例
Mar 06 Python
Windows系统下PhantomJS的安装和基本用法
Oct 21 Python
Python判断一个list中是否包含另一个list全部元素的方法分析
Dec 24 Python
Python爬虫爬取Bilibili弹幕过程解析
Oct 10 Python
django连接mysql数据库及建表操作实例详解
Dec 10 Python
使用OpenCV circle函数图像上画圆的示例代码
Dec 27 Python
TensorFlow实现打印每一层的输出
Jan 21 Python
Python2 与Python3的版本区别实例分析
Mar 30 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
PHPlet在Windows下的安装
2006/10/09 PHP
Adodb的十个实例(清晰版)
2006/12/31 PHP
php配置php-fpm启动参数及配置详解
2013/11/04 PHP
php中json_encode处理gbk与gb2312中文乱码问题的解决方法
2014/07/10 PHP
php+mysqli使用面向对象方式更新数据库实例
2015/01/29 PHP
JS实现本地存储信息的方法(基于localStorage与userData)
2017/02/18 Javascript
jQuery实现在HTML文档加载完毕后自动执行某个事件的方法
2017/05/08 jQuery
jQuery实现frame之间互通的方法
2017/06/26 jQuery
Vue.js项目部署到服务器的详细步骤
2017/07/17 Javascript
vue proxyTable 接口跨域请求调试的示例
2017/09/12 Javascript
Node.js + express基本用法教程
2019/03/14 Javascript
JavaScript图像放大镜效果实现方法详解
2020/06/28 Javascript
Vue使用Element实现增删改查+打包的步骤
2020/11/25 Vue.js
[02:32]DOTA2亚洲邀请赛 VG战队巡礼
2015/02/03 DOTA
python引用(import)某个模块提示没找到对应模块的解决方法
2019/01/19 Python
python实现网页自动签到功能
2019/01/21 Python
Python实现字符串匹配的KMP算法
2019/04/04 Python
python 读取数据库并绘图的实例
2019/12/03 Python
PyTorch加载预训练模型实例(pretrained)
2020/01/17 Python
python获取响应某个字段值的3种实现方法
2020/04/30 Python
Python是什么 Python的用处
2020/05/26 Python
美国室内和室外装饰花盆购物网站:ePlanters
2019/03/22 全球购物
大一新生军训时的自我评价分享
2013/12/05 职场文书
运动会表扬稿大全
2014/01/16 职场文书
《鸟的天堂》教学反思
2014/02/27 职场文书
集团公司党的群众路线教育实践活动工作总结
2014/03/03 职场文书
广告创意求职信
2014/03/17 职场文书
中学生寄语大全
2014/04/03 职场文书
党委班子剖析材料
2014/08/21 职场文书
经验交流材料格式
2014/12/30 职场文书
旷课检讨书
2015/01/26 职场文书
车间主任岗位职责范本
2015/04/08 职场文书
魂断蓝桥观后感
2015/06/10 职场文书
欢送会主持词
2015/07/01 职场文书
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis
Netty分布式客户端处理接入事件handle源码解析
2022/03/25 Java/Android