详解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内置函数会触发这个方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

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

    导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流. 一.利用动态属性处理JSON数据源 属性:在Python中,数据的属性和处理数据的方法统称属性. 元编程:用元类进行编程,元类→类→对象,元类比类更抽象,生成类的类. 1.使用动态属性访问JSON类数据 第一版:利用json.load(fp)审查数据 from urllib.request import urlopen import warnings import os

  • 详解Python中元组的三个不常用特性

    目录 1. 引言 2. 举个栗子 3. 创建包含单一元素的元组 4. 使用下划线和*来unpack元组 5. 使用命名元组 6. 总结 1. 引言 元组是Python中一种重要的内置数据类型.与列表一样,我们经常使用元组将多个对象保存为相应的数据容器.然而,与列表不同的是元组的不变性——一个不可改变的数据序列. 2. 举个栗子 下面的代码片段向我们展示了元组的一些常见用法. response = (404, "Can't access website") response_code =

  • 详解Python中string模块除去Str还剩下什么

    string模块可以追溯到早期版本的Python. 以前在本模块中实现的许多功能已经转移到str物品. 这个string模块保留了几个有用的常量和类来处理str物品. 字符串-文本常量和模板 目的:包含用于处理文本的常量和类. 功能 功能capwords()将字符串中的所有单词大写. 字符串capwords.py import string s = 'The quick brown fox jumped over the lazy dog.' print(s) print(string.capw

  • 详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案

    先看一道GIL面试题: 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题.它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中. 通过

  • 详解Python中while无限迭代循环方法

    目录 前言 while循环 break语句 和 continue语句 else 子句 无限循环 嵌套while循环 单行 while 循环 前言 Python 有 while 语句和 for 语句作为循环处理.虽然 for 语句具有一定数量的进程,但 while 语句是『直到满足条件』类型的循环进程. 对于无限迭代 while,循环执行的次数没有事先明确指定.相反,只要满足某些条件指定的块就会重复执行. 使用定义迭代 for,指定块将被执行的次数在循环开始时已经倍明确指定. 除了 while 语

  • 详解Python中迭代器和生成器的原理与使用

    目录 1.可迭代对象.迭代器 1.1概念简介 1.2可迭代对象 1.3迭代器 1.4区分可迭代对象和迭代器 1.5可迭代对象和迭代器的关系 1.6可迭代对象和迭代器的工作机制 1.7自己动手创建可迭代对象和迭代器 1.8迭代器的优势 1.9迭代器的缺点和误区 1.10python自带的迭代器工具itertools 2.生成器 2.1生成器的创建方法 2.2生成器方法 2.3生成器的优势 2.4生成器应用场景 3.生成器节省内存.迭代器不节省内存 3.1可迭代对象 3.2迭代器 3.3生成器 3.

  • 详解Python中enumerate函数的使用

    Python 的 enumerate() 函数就像是一个神秘的黑箱,你无法简单地用一句话来概括这个函数的作用与用法. enumerate() 函数属于非常有用的高级用法,而对于这一点,很多初学者甚至中级学者都没有意识到.这个函数的基本应用就是用来遍历一个集合对象,它在遍历的同时还可以得到当前元素的索引位置. 我们看一个例子: names = ["Alice","Bob","Carl"] for index,value in enumerate(n

  • 一文详解Python中实现单例模式的几种常见方式

    目录 Python 中实现单例模式的几种常见方式 元类(Metaclass): 装饰器(Decorator): 模块(Module): new 方法: Python 中实现单例模式的几种常见方式 元类(Metaclass): class SingletonType(type): """ 单例元类.用于将普通类转换为单例类. """ _instances = {} # 存储单例实例的字典 def __call__(cls, *args, **kwa

  • 详解Python中神奇的字符串驻留机制

    目录 1 什么是字符串驻留机制 2 如何使用字符串驻留机制 3 简单拼接驻留, 运行时不驻留 4 总结 5 全部代码 今天有一个初学者在学习Python的时候又整不会了. 原因是以下代码: a = [1, 2, 3] b = [1, 2, 3] if a is b: print("a and b point to the same object") else: print("a and b point to different objects") 运行结果是a an

  • 详解python中executemany和序列的使用方法

    详解python中executemany和序列的使用方法 一 代码 import sqlite3 persons=[ ("Jim","Green"), ("Hu","jie") ] conn=sqlite3.connect(":memory:") conn.execute("CREATE TABLE person(firstname,lastname)") conn.executeman

随机推荐