资讯专栏INFORMATION COLUMN

Python中的动态属性和特性

scola666 / 2950人阅读

摘要:一利用动态属性处理数据源属性在中,数据的属性和处理数据的方法统称属性。处理无效属性名在中,由于关键字被保留,名称为关键字的属性是无效的。内置函数列出对象的大多数属性。点号和内置函数会触发这个方法。

导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。

本文重点:

1、了解如何利用动态属性处理数据;
2、掌握Python中的特性概念以及@property装饰器;
3、了解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.类属性名来调用。

实例属性:实例属性属于各个实例所有,互不干扰。

私有属性

单下划线_开头:只是告诉别人这是私有属性,外部依然可以访问更改。

双下划线__开头:外部不可通过instancename.propertyname来访问或者更改,实际将其转化为了_classname__propertyname。

特性:是用于管理实例属性的类属性。
特性用途:经常用于把公开的属性变成使用读值方法和设值方法管理的属性,且在不影响客户端代码的前提下实施业务规则。

注意

不要对实例属性和类属性使用相同的名字。否则实例属性会遮盖类属性,发生难以发现的错误。

实例属性不会遮盖类特性,但类特性会遮盖实例属性。
这是因为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 {})
-- {}"
      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内置函数会触发这个方法。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41455.html

相关文章

  • Python中的属性描述符

    摘要:下面我们用描述符来实现中的动态属性和特性中提及的订单结算代码第四版使用描述符实现订单结算功能描述符基于协议实现,无需创建子类。特性是覆盖型描述符。非覆盖型描述符没有实现方法的描述符属于非覆盖型描述符。类中定义的方法是非覆盖型描述符。 导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、了解描述符...

    geekzhou 评论0 收藏0
  • Python动态特性应对动态情况

    摘要:有一些定制类的特殊方法,如,其中一些具有动态特性的方法可以用来很方便地处理某些动态状况。动态化属性和方法的调用,当调用不存在的属性时,如果存在方法,就会调用方法来尝试获得属性。这种完全动态的调用可以应对一些动态情况,例如实现。 Python有一些定制类的特殊方法,如__str__()、__iter__()、__getitem__(),其中一些具有动态特性的方法可以用来很方便地处理某些动...

    Vicky 评论0 收藏0
  • python3 学习笔记

    摘要:本人很少写代码一般都是用的去年时用写过一些收集系统信息的工具当时是边看手册边写的如今又要用来写一个生成的工具就又需要查看手册了至于为什么不用写那是因为的库不兼容永中在这里不得不说虽然很火但是一些库还是不如多不如兼容性好为了避免以后再出这种事 Python3 Study Notes 本人很少写 python 代码, 一般都是用 go 的, 去年时用 python 写过一些收集系统信息的工...

    tuomao 评论0 收藏0
  • Python 面试」第二次更新

    摘要:结果为对于迭代器和生成器你知道哪些,它们分别应用于什么场景先介绍什么是可迭代的任何可用于循环的都是可迭代的。示例结果为,迭代器任何可以使用函数的都是迭代器,也可使用函数将可迭代对象变为迭代器。未写完,下次更新补上。 showImg(https://segmentfault.com/img/bVbuN3P); 阅读本文大约需要 8 分钟。 7.说一下 Python 中的装饰器 原理:利用...

    Yu_Huang 评论0 收藏0
  • Python学习之路30-接口:从协议到抽象基类

    摘要:本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确能验证实现是否符合规定的抽象基类。抽象基类介绍完动态实现接口后,现在开始讨论抽象基类,它属于静态显示地实现接口。标准库中的抽象基类从开始,标准库提供了抽象基类。 《流畅的Python》笔记。本篇是面向对象惯用方法的第四篇,主要讨论接口。本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确、能验证实现是否符合规定的抽象基类(Abst...

    LucasTwilight 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<