栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

Python元类:为什么在类定义期间不调用__setattr__来设置属性?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python元类:为什么在类定义期间不调用__setattr__来设置属性?

类块大致是语法糖,用于构建字典,然后调用元类来构建类对象。

这个:

class Foo(object):    __metaclass__ = Foometa    FOO = 123    def a(self):        pass

几乎就像您写的那样出来:

d = {}d['__metaclass__'] = Foometad['FOO'] = 123def a(self):    passd['a'] = aFoo = d.get('__metaclass__', type)('Foo', (object,), d)

只有在没有名称空间污染的情况下(实际上,还可以通过所有基础进行搜索以确定元类,或者是否存在元类冲突,但是我在这里忽略了这一点)。

元类

__setattr__
可以控制当您尝试在其实例之一(类对象)上设置属性时发生的情况,但是 在类块内部
您没有这样做,而是在插入字典对象,因此
dict
类控件这是怎么回事,而不是您的元类。所以你很不走运。


除非您使用的是Python 3.x!在Python
3.x中,您可以

__prepare__
在元类上定义一个类方法(或静态方法),该方法控制在将对象集传递给元类构造函数之前,使用哪个对象累积在类块中设置的属性。默认值
__prepare__
只是返回一个普通的字典,但是您可以构建一个自定义类似dict的类,该类不允许重新定义键,并使用该类来累积您的属性:

from collections import MutableMappingclass SingleAssignDict(MutableMapping):    def __init__(self, *args, **kwargs):        self._d = dict(*args, **kwargs)    def __getitem__(self, key):        return self._d[key]    def __setitem__(self, key, value):        if key in self._d: raise ValueError(     'Key {!r} already exists in SingleAssignDict'.format(key) )        else: self._d[key] = value    def __delitem__(self, key):        del self._d[key]    def __iter__(self):        return iter(self._d)    def __len__(self):        return len(self._d)    def __contains__(self, key):        return key in self._d    def __repr__(self):        return '{}({!r})'.format(type(self).__name__, self._d)class RedefBlocker(type):    @classmethod    def __prepare__(metacls, name, bases, **kwargs):        return SingleAssignDict()    def __new__(metacls, name, bases, sad):        return super().__new__(metacls, name, bases, dict(sad))class Okay(metaclass=RedefBlocker):    a = 1    b = 2class Boom(metaclass=RedefBlocker):    a = 1    b = 2    a = 3

运行这个给我:

Traceback (most recent call last):  File "/tmp/redef.py", line 50, in <module>    class Boom(metaclass=RedefBlocker):  File "/tmp/redef.py", line 53, in Boom    a = 3  File "/tmp/redef.py", line 15, in __setitem__    'Key {!r} already exists in SingleAssignDict'.format(key)ValueError: Key 'a' already exists in SingleAssignDict

一些注意事项:

  1. __prepare__
    必须为
    classmethod
    staticmethod
    ,因为它是在元类实例(您的类)存在之前被调用的。
  2. type
    仍然需要其第三个参数为实数
    dict
    ,因此您必须具有一种
    __new__
    将转换
    SingleAssignDict
    为普通参数的方法
  3. 我本可以进行子类化
    dict
    ,这本可以避免(2),但是我真的不喜欢这样做,因为这样的非基本方法
    update
    不尊重您对诸如此类的基本方法的覆盖
    __setitem__
    。所以我更喜欢继承
    collections.MutableMapping
    并包装字典。
  4. 实际的
    Okay.__dict__
    对象是普通词典,因为它是由它设置的,
    type
    并且
    type
    对所需的词典类型有所挑剔。这意味着在创建类之后覆盖类属性不会引发异常。如果要保持类对象的字典强制执行的不覆盖
    __dict__
    __new__
    则可以在超类调用之后覆盖属性。

遗憾的是,该技术在Python 2.x中不可用(我检查过)。该

__prepare__
方法不会被调用,这在Python
2.x中是有意义的,元类是由
__metaclass__
magic属性而不是classblock中的特殊关键字确定的;这意味着在知道元类时,用于为类块累积属性的dict对象已经存在。

比较Python 2:

class Foo(object):    __metaclass__ = Foometa    FOO = 123    def a(self):        pass

大致相当于:

d = {}d['__metaclass__'] = Foometad['FOO'] = 123def a(self):    passd['a'] = aFoo = d.get('__metaclass__', type)('Foo', (object,), d)

与字典3相比,从字典中确定要调用的元类的位置:

class Foo(metaclass=Foometa):    FOO = 123    def a(self):        pass

大致相当于:

d = Foometa.__prepare__('Foo', ())d['Foo'] = 123def a(self):    passd['a'] = aFoo = Foometa('Foo', (), d)

使用字典的位置由元类确定。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/516862.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号