改变data属性_19.1 使用动态属性转换数据

1f8b7251590cce4d84a1d148d38716cf.png
特性至关重要的地方在于,特性的存在使得开发者可以非常安全并且确定可行地将公共数据属性作为类的公共接口的一部分开放出来。

在 Python 中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调用的属性。除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性。

除了特性,Python 还提供了丰富的 API,用于控制属性的访问权限,以 及实现动态属性。使用点号访问属性时(如 obj.attr),Python 解释 器会调用特殊的方法(如 __getattr__ 和 __setattr__)计算属性。 用户自己定义的类可以通过 __getattr__ 方法实现“虚拟属性”,当访 问不存在的属性时(如obj.no_such_attribute),即时计算属性的值。

仅当无法使用常规的方式获取属性(即在实例、类或超类中找不到指定的属性),解释器才会调用特殊的 __getattr__ 方法。

例子,使用动态属性访问JSON类数据

from urllib.request import urlopen
import warnings
import os
import json
from collections import abc
import keyword
from pprint import pprint


class FronzenJSON:
    def __init__(self, mapping):
        # 生成一个新的副本
        self.__data = {}
        for key, value in mapping.items():
            if keyword.iskeyword(key):
                key += '_'
            if not key.isidentifier():
                raise Exception('{}不是有效的python标识符'.format(key))
            self.__data[key] = value

    def __getattr__(self, name):
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            return FronzenJSON.build(self.__data[name])

    @classmethod
    def build(cls, obj):
        if isinstance(obj, abc.Mapping):
            return cls(obj)
        elif isinstance(obj, abc.MutableSequence):
            return [cls.build(item) for item in obj]
        else:
            return obj

grad = FronzenJSON({'2be': 'or not'})
# print(grad.2be)

注意事项: 1. 属性不能为关键字 2. 属性需要是有效的标识符

使用__new__方法创建对象

我们通常把 __init__ 称为构造方法,这是从其他语言借鉴过来的术 语。其实,用于构建实例的是特殊方法 __new__:这是个类方法(使用 特殊方式处理,因此不必使用 @classmethod 装饰器),必须返回一个 实例。返回的实例会作为第一个参数(即 self)传给 __init__ 方 法。因为调用 __init__ 方法时要传入实例,而且禁止返回任何值,所 以 __init__ 方法其实是“初始化方法”。真正的构造方法是 __new__。 我们几乎不需要自己编写 __new__ 方法,因为从 object 类继承的实现 已经足够了。

new方法也可以返回其他类的实例,此时,解释器不会调用 init方法。

伪代码表示如下

def object_maker(the_class, some_arg):
    new_object = the_class.__new__(some_arg)
    if isinstance(new_object, the_class):
        the_class.__init__(new_object, some_arg)
    return new_object

# 下述两个语句的作用基本等效
x = Foo('bar')
x = object_maker(Foo, 'bar')

shelve模块用来存放数据的

schedule1.py(包含一个重要的小技巧)

import warnings
import osconfeed
DB_NAME = 'data/schedule1_db'
CONFERENCE = 'conference.115'

class Record:
    def __init__(self, **kwargs):
        """
        Record.__init__ 方法展示了一个流行的 Python 技巧。我们知道,对
        象的 __dict__ 属性中存储着对象的属性——前提是类中没有声明
        __slots__ 属性,如 9.8 节所述。因此,更新实例的 __dict__ 属性,
        把值设为一个映射,能快速地在那个实例中创建一堆属性。
        """
        self.__dict__.update(kwargs)

def load_db(db):
    # 改变了原始数据的结构
    # 将每一条数据添加到db中
    raw_data = osconfeed.load()
    warnings.warn('loading ' + DB_NAME)
    for collection, rec_list in raw_data['Schedule'].items():
        record_type = collection[:-1]
        for record in rec_list:
            key = '{}.{}'.format(record_type, record['serial'])
            record['serial'] = key
            db[key] = Record(**record)

schedule2.py

import warnings
import inspect

import osconfeed

DB_NAME = 'data/schedule2_db'
CONFERENCE = 'conference.115'

class Record:
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __eq__(self, other):
        if isinstance(other, Record):
            return self.__dict__ == other.__dict__
        else:
            return NotImplemented

class MissingDatabaseError(RuntimeError):
    """需要数据库但没有指定数据库时抛出。"""

class DbRecord(Record):
    __db = None

    @staticmethod
    def set_db(db):
        DbRecord.__db = db

    @staticmethod
    def get_db():
        return DbRecord.__db

    @classmethod
    def fetch(cls, ident):
        db = cls.get_db()
        try:
            return db[ident]
        except TypeError:
            if db is None:
                msg = "database not set; call '{}.set_db(my_db)'"
                raise MissingDatabaseError(msg.format(cls.__name__))
            else:
                raise

    def __repr__(self):
        if hasattr(self, 'serial'):
            cls_name = self.__class__.__name__
            return '<{} serial={!r}>'.format(cls_name, self.serial)
        else:
            return super().__repr__()

class Event(DbRecord):

    @property
    def venue(self):
        key = 'venue.{}'.format(self.venue_serial)
        return self.__class__.fetch(key)

    @property
    def speakers(self):
        if not hasattr(self, '_speaker_objs'):
            spkr_serials = self.__dict__['speakers']
            fetch = self.__class__.fetch
            self._speaker_objs = [fetch('speaker.{}'.format(key)) for key in spkr_serials]
        return self._speaker_objs

    def __repr__(self):
        if hasattr(self, 'name'):
            cls_name = self.__class__.__name__
            return '<{} {!r}>'.format(cls_name, self.name)
        else:
            return super().__repr__()

def load_db(db):
    raw_data = osconfeed.load()
    warnings.warn('loading ' + DB_NAME)
    for collection, rec_list in raw_data['Schedule'].items():
        record_type = collection[:-1]
        cls_name = record_type.capitalize()
        cls = globals().get(cls_name, DbRecord)
        if inspect.isclass(cls) and issubclass(cls, DbRecord):
            factory = cls
        else:
            factory = DbRecord
        for record in rec_list:
            key = '{}.{}'.format(record_type, record['serial'])
            record['serial'] = key
            db[key] = factory(**record)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值