python 随笔----__dict__

先上一段代码,来源是github

class Borg(object):
    __shared_state = {}

    def __init__(self):
        self.__dict__ = self.__shared_state
        self.state = 'Init'

    def __str__(self):
        return self.state

class YourBorg(Borg):
    pass

if __name__ == '__main__':
    rm1 = Borg()
    rm2 = Borg()

    rm1.state = 'Idle'
    rm2.state = 'Running'

    print('rm1: {0}'.format(rm1))
    print('rm2: {0}'.format(rm2))

    rm2.state = 'Zombie'

    print('rm1: {0}'.format(rm1))
    print('rm2: {0}'.format(rm2))

    print('rm1 id: {0}'.format(id(rm1)))
    print('rm2 id: {0}'.format(id(rm2)))

    rm3 = YourBorg()

    print('rm1: {0}'.format(rm1))
    print('rm2: {0}'.format(rm2))
    print('rm3: {0}'.format(rm3))

### OUTPUT ###
# rm1: Running
# rm2: Running
# rm1: Zombie
# rm2: Zombie
# rm1 id: 140732837899224
# rm2 id: 140732837899296
# rm1: Init
# rm2: Init
# rm3: Init

上面这一段代码,乍看挺神奇的,Borg 的各个实例共享了state。实现起来也很巧妙,利用了__dict__。 我们知道,python中__dict__存储了该对象的一些属性。类和实例分别拥有自己的__dict__,且实例会共享类的__dict__。

这里有一个我一直以来都搞混的知识点,在__init__ 中声明的变量 ,以及在方法体之外声明的变量分别是在哪里。很简单的测试就能得到,在__init__中,self.xxx = xxx会把变量存在实例的__dict__中,仅会在该实例中能获取到,而在方法体外声明的,会在class的__dict__中。下面的简单的测试。

class MyclassA():

    in_class = {}

    def __init__(self):
        self.in_func = {}

a = MyclassA()


print MyclassA.__dict__  # {'in_class': {}, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x10eb040c8>}
print a.__dict__ # {'in_func': {}}


有个有意思的问题,调用obj.key的搜索顺序是咋样的?

attr的搜索顺序

这个很有意思,比我想象的要复杂很多,因为涉及到了descriptor,不仅仅只是查询实例或者是类的__dict__这么简单,直接上个结论

1.如果attr是一个Python自动产生的属性,找到!(优先级非常高!)
2.查找obj.__class__.__dict__,如果attr存在并且是data descriptor,返回data descriptor的__get__方法的结果,如果没有继续在obj.__class__的父类以及祖先类中寻找data descriptor
3.在obj.__dict__中查找,这一步分两种情况,第一种情况是obj是一个普通实例,找到就直接返回,找不到进行下一步。第二种情况是obj是一个类,依次在obj和它的父类、祖先类的__dict__中查找,如果找到一个descriptor就返回descriptor的__get__方法的结果,否则直接返回attr。如果没有找到,进行下一步。
4.在obj.__class__.__dict__中查找,如果找到了一个descriptor(插一句:这里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的结果。如果找到一个普通属性,直接返回属性值。如果没找到,进行下一步。
5.很不幸,Python终于受不了。在这一步,它raise AttributeError

用代码解释上面的过程(这其中包含了descriptor相关概念)

作者:刘缙
链接:https://www.zhihu.com/question/25391709/answer/30634637
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

def object_getattr(obj, name):
    'Look for attribute /name/ in object /obj/.'
    # First look in class and base classes.
    v, cls = class_lookup(obj.__class__, name)
    if (v is not None) and hasattr(v, '__get__') and hasattr(v, '__set__'):
        # Data descriptor.  Overrides instance member.
        return v.__get__(obj, cls)
    w = obj.__dict__.get(name)
    if w is not None:
        # Found in object
        return w
    if v is not None:
        if hasattr(v, '__get__'):
            # Function-like descriptor.
            return v.__get__(obj, cls)
        else:
            # Normal data member in class
            return v
    raise AttributeError(obj.__class__, name)
    
 def class_lookup(cls, name): 
     'Look for attribute /name/ in class /cls/ and bases.' 
     v = cls.__dict__.get(name) 
     if v is not None: 
         # found in this class 
         return v, cls 
     # search in base classes 
     for i in cls.__bases__: 
         v, c = class_lookup(i, name) 
         if v is not None: 
             return v, c 
     # not found 
     return None

经常看到有人说obj.key = val 等价于obj.__dict__[key] = val,其实根据我们上面的那个搜索顺序,在有descriptor的情况下,会不太一样。

class DataDescriptor(object):
    def __init__(self, init_value):
        self.value = init_value

    def __get__(self, instance, owner):
        return "DataDescriptor __get__" + str(self.value)

    def __set__(self, instance, value):
        self.value = value
        print "DataDescriptor __set__"

class ClassA(object):
    val_descriptor = DataDescriptor(0)
    val_normal = 0

if __name__ == '__main__':
    a = ClassA()
    print a.val_descriptor # DataDescriptor __get__0
    print a.val_normal # 0
    a.val_descriptor = "has change"
    a.val_normal = "has change"
    print a.val_descriptor  # DataDescriptor __get__has change
    print a.val_normal # has change
    a.__dict__["val_descriptor"] = "change again"
    a.__dict__["val_normal"] = "change again"
    print a.val_descriptor # DataDescriptor __get__has change
    print a.val_normal #change again

重点是最后两行行,要修改的是一个descriptor 的话,obj.key = val 的方式是而已修改成功的,会调用descriptor 的__set__ 方法,而直接修改__dict__的方式则会有问题,因为在搜索attr的时候会先查找class的descriptor(对,即便a.__dict__["val_descriptor"] = DataDescriptor("change again")这样子也是没用的)。


关于__dict__,还有一个有趣的点。cls.__dict__ 是一个dictproxy,obj.__dict__ 是一个dict,前者是一个只读对象,后者是一般的dict。

这意味着,对于一个实例,可以使用obj.__dict__[key] = val的方式去赋值,也可以使用obj.key = value的方式赋值(这种方式会调用__setattr__方法,会按照attr的搜索顺序去搜索),而cls只能使用后者,即必须经过__setattr__ ,stackoverflow 上有人解释说是为了编译器优化。

个人认为这种设置还有另外一种原因,即保护了descriptor的使用。

看上面那长段的代码,我们发现,如果一个类中设置了一个descriptor,后续没有任何办法绕过descriptor的__set__方法。

  • 使用obj.key = val 的方式,毫无以为会调用descriptor的__set__方法。
  • obj.__dict__[key] = val的方式,会修改实例的__dict__,但是按照搜索的顺序会先搜索类的descriptor,所以相当于没有修改
  • cls.key = val,会调用descriptor的__set__方法
  • 于是只剩下直接修改cls.__dict__,但很可惜,这是一个只读对象无法修改。

后记

非常的乱,这些东西都是我在看python的设计模式的时候逐渐发现问题慢慢搜索的,主要是为了给自己做个记录,当做一个随笔,不太适合除了我以外的人看... 有空的时候会整理下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,715评论 0 9
  • 憨憨五十载,弹指一挥间。 淡忘青年血,蹉跎秀士欢。 人前常笑脸,夜半自忧烦。 愧教儿孙辈,羞言心意寒。 痴呆难混世...
    花屋主人萧寒阅读 734评论 0 6
  • 这是我2016年6月18号的一篇日记 大学时间已经½,岁月时间都很快 当年的单纯和青涩已然不在,取而代之的是成长和...
    JingErr阅读 213评论 0 0
  • 醉卧佳人侧 烽火连天雨 一怒为红颜 挂阵赴国难 问君何为故 乱世觅自由 随伊化作蝶 同赴杨柳岸
    基督山伯爵和他的城堡阅读 271评论 0 0