程序问答   发布时间:2022-06-01  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了定义 __slots__ 的 Python 元类使 __slots__ 只读大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

如何解决定义 __slots__ 的 Python 元类使 __slots__ 只读?

开发过程中遇到定义 __slots__ 的 Python 元类使 __slots__ 只读的问题如何解决?下面主要结合日常开发的经验,给出你关于定义 __slots__ 的 Python 元类使 __slots__ 只读的解决方法建议,希望对你解决定义 __slots__ 的 Python 元类使 __slots__ 只读有所启发或帮助;

在下面的示例中,我尝试创建一个 python 元类,它是我的类,具有 __slots__ 和默认值。

class Meta(typE):
    def __new__(cls,name,bases,Dictionary,defaults):
        DictionarY['__slots__'] = List(defaults.keys())
        obj = super().__new__(cls,Dictionary)
        return obj
    def __init__(self,defaults):
        for s in defaults:
            setattr(self,s,defaults[s])

                        
class A(Metaclass = Meta,defaults = {'a':123,'b':987}):
    pass

实例化类A,得到如下结果:

a = A()
>>> dir (a)
['__class__','__delattr__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__module__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__slots__','__str__','__subclasshook__','a','b']

-> 好的

>>> a.c = 500
TraceBACk (most recent call last):
  file "<pysHell#87>",line 1,in <module>
    a.c = 500
AttributeError: 'A' object has no attribute 'c'

-> 好的

>>> a.b = 40
TraceBACk (most recent call last):
  file "<pysHell#88>",in <module>
    a.b = 40
AttributeError: 'A' object attribute 'b' is read-only

-> 不行,预计 a.b 是可读写的

您可以看到元类 @H_239_3@meta 正确创建了 __slots__ 并正确设置了默认值,但不幸的是,由于某些我不明白的原因,插槽属性被设为只读。 是否可以从元类 @H_239_3@meta 中获取槽读/写属性?

解决方法

问题是在 @H_239_3@meta.__init__ 中设置属性的代码在类本身中更改了它。问题在于类中的默认变量(在本例中为“A”和“B”默认值)是特殊的描述符,用于处理创建的类的实例中的槽值分配(您的示例中的对象“a”)。描述符被覆盖并且无法再工作。 (它们变成“只读类属性”确实是一个奇特的副作用——我会调查这是记录在案还是故意的,或者只是未定义的行为)

尽管如此,您需要的是一种设置方法,以便在对象实例化后使槽变量中的值可用。

一种显而易见的方法是将 @H_239_3@meta.__init__ 中的逻辑转移到基类 __init__ 方法,并在那里设置值(将 defaults Dict 附加到类本身)。然后任何调用 super().__init__() 的子类都会拥有它。

如果您不想或不能这样做,您可以将代码放入元类中以在每个类中注入一个 __init__,如果有原始 __init__(和处理所有可能的情况,例如:没有 __init__ ,在父类中已经有一个包裹的 __init__ 等......) - 这可以完成,如果你选择这个,我可以提供一些示例代码。

(更新:再想一想,可以在元类 __c@R_801_10636@_ 方法上设置代码,而不是所有这些恶意代码,并完全覆盖默认的 type.__c@R_801_10636@_,所以默认值赋值发生在类'__init__被调用之前)




class Meta(typE):
    def __new__(mcls,name,bases,Dictionary,defaults):
        DictionarY['__slots__'] = list(defaults)
        DictionarY["_defaults"] = defaults
        return super().__new__(mcls,Dictionary)
        
    def __c@R_801_10636@_(cls,*args,**kw):
        """replaces completly the mechanism that makes  `__new__` and 
        `__init__` being called,adding a new step between the two calls
        """
        instance = cls.__new__(cls,**kw)
        for k,v in instance._defaults.items():
            setattr(instance,k,v)
        instance.__init__(*args,**kw)
        return instance
                        
class A(metaclass = Meta,defaults = {'a':123,'b':987}):
    def __init__(self):
        print (f"I can see the default values of a and b: {(self.a,self.b)}")
        

它的工作原理:


In [51]: A()                                                                                                                              
I can see the default values of a and b: (123,987)
Out[51]: <__main__.A at 0x7f093cfeb820>

In [52]: a = A()                                                                                                                          
I can see the default values of a and b: (123,987)

In [53]: a.c = 500                                                                                                                        
---------------------------------------------------------------------------
AttributeError                            TraceBACk (most recent call last)
<ipython-input-53-ce3d946a718e> in <module>
----> 1 a.c = 500

AttributeError: 'A' object has no attribute 'c'

In [54]: a.b                                                                                                                              
Out[54]: 987

In [55]: a.b = 1000                                                                                                                       

In [56]: a.b                                                                                                                              
Out[56]: 1000

另一种方法是创建知道默认值的特殊描述符。添加前缀(例如“_”)更改带槽变量名称,并使用这些描述符访问它们。这有点直截了当,然它比编写元类 __c@R_801_10636@_ 更复杂,但你的优势是能够在描述符本身上放置额外的保护代码(例如:拒绝分配类型不同的值默认值)

PREFIX = "_"

class DefaultDescriptor:

    def __init__(self,default):
        self.name = name
        self.default = default
    def __get__(self,instance,owner):
        if instance is None: 
            return self
            # or,if you want the default value to be visible as a class attribute:
            # return self.default 
        return getattr(instance,PREFIX + self.name,self.default)
    
    def __set__(self,value):
        setattr(instance,value)
        


class Meta(typE):
    def __new__(mcls,defaults):
        DictionarY['__slots__'] = [PREFIX + key for key in defaults]
        cls = super().__new__(mcls,Dictionary)
        for key,value in defaults.items():
            setattr(cls,key,DefaultDescriptor(key,value))
        return cls
    
                        
class A(metaclass = Meta,'b':987}):
    pass

在 REPL 上:

In [37]: a = A()                                                                                                                          

In [38]: a.c = 500                                                                                                                        
---------------------------------------------------------------------------
AttributeError                            TraceBACk (most recent call last)
<ipython-input-38-ce3d946a718e> in <module>
----> 1 a.c = 500

AttributeError: 'A' object has no attribute 'c'

In [39]: a.b                                                                                                                              
Out[39]: 987

In [40]: a.b = 1000                                                                                                                       

In [41]: a.b                                                                                                                              
Out[41]: 1000

大佬总结

以上是大佬教程为你收集整理的定义 __slots__ 的 Python 元类使 __slots__ 只读全部内容,希望文章能够帮你解决定义 __slots__ 的 Python 元类使 __slots__ 只读所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。