首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >定义__slots__的使__slots__成为只读

定义__slots__的使__slots__成为只读
EN

Stack Overflow用户
提问于 2021-02-03 11:26:54
回答 1查看 431关注 0票数 1

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

代码语言:javascript
复制
class Meta(type):
    def __new__(cls, name, bases, dictionary, defaults):
        dictionary['__slots__'] = list(defaults.keys())
        obj = super().__new__(cls, name, bases, dictionary)
        return obj
    def __init__(self, name, bases, dictionary, defaults):
        for s in defaults:
            setattr(self, s, defaults[s])

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

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

代码语言:javascript
复制
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']

->正常

代码语言:javascript
复制
>>> 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'

->正常

代码语言:javascript
复制
>>> a.b = 40
Traceback (most recent call last):
  File "<pyshell#88>", line 1, in <module>
    a.b = 40
AttributeError: 'A' object attribute 'b' is read-only

->不好,预期a.b是可读的和可写的

A您可以看到元类Meta正确地创建了__slots__并正确设置了默认值,但是不幸的是,由于一些我不理解的原因,设置了时隙属性是只读的。是否可以从元类Meta获取时隙读/写属性?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-02-03 14:19:18

问题是,在Meta.__init__中设置属性的代码更改了类本身中的属性。问题是类中的默认变量(在本例中为"a“和"b”默认值)是特殊的描述符,用于处理所创建类的实例中的槽值分配(示例中的对象"a“)。描述符被覆盖,不能再工作了。(它们成为“只读类属性”确实是一种特殊的副作用-我将调查这是有文档记录的,还是故意的,还是仅仅是一种未定义的行为)

尽管如此,您需要的是一种方法来设置在您的插槽变量中可用的值,一旦对象被实例化。

这样做的一个明显方法是将Meta.__init__中的逻辑传递给baseclass __init__方法,并在那里设置值(将defaults dict附加到类本身)。然后,任何调用super().__init__()的子类都会拥有它。

如果您不需要,或者不能这样做,您应该将代码放入元类中,在每个类中注入一个__init__,如果有的话包装原始的__init__ (并处理所有可能的情况,例如:没有__init__,在父类中已经有一个包装的__init__,等等)--这是可以做到的,如果您选择这样做,我可以提供一些示例代码。

(更新:再考虑一下,代码可以设置在元类__call__方法上,完全覆盖默认的type.__call__,因此缺省值赋值在类‘__init__被调用之前进行。)

代码语言:javascript
复制
class Meta(type):
    def __new__(mcls, name, bases, dictionary, defaults):
        dictionary['__slots__'] = list(defaults)
        dictionary["_defaults"] = defaults
        return super().__new__(mcls, name, bases, dictionary)
        
    def __call__(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, *args, **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)}")

它起作用了:

代码语言:javascript
复制
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

另一种方法是创建知道默认值的特殊描述符。更改添加前缀(例如"_")的时隙变量名,并使用这些描述符访问它们。这有点简单,虽然它比写元类__call__更复杂,但ou的优点是能够在描述符本身上添加额外的保护代码(例如:拒绝分配不同类型的默认值)。

代码语言:javascript
复制
PREFIX = "_"

class DefaultDescriptor:

    def __init__(self, name, 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, instance, value):
        setattr(instance, PREFIX + self.name, value)
        


class Meta(type):
    def __new__(mcls, name, bases, dictionary, defaults):
        dictionary['__slots__'] = [PREFIX + key for key in defaults]
        cls = super().__new__(mcls, name, bases, dictionary)
        for key, value in defaults.items():
            setattr(cls, key, DefaultDescriptor(key, value))
        return cls
    
                        
class A(metaclass = Meta, defaults = {'a':123, 'b':987}):
    pass

在REPL上:

代码语言:javascript
复制
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
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66026576

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档