创建一流的对象,它的所有实例属性都是只读的,就像切片一样?

Posted

技术标签:

【中文标题】创建一流的对象,它的所有实例属性都是只读的,就像切片一样?【英文标题】:Create first-class object all of it's instance attributes are readonly like slice? 【发布时间】:2018-09-30 21:07:29 【问题描述】:

我的问题是如何创建像slice 这样的类?

slice(内置类型)没有__dict__ 属性 即使这个slicemetaclasstype

并且它没有使用__slots__,并且它的所有属性都是只读的并且它没有覆盖 __setattr__(这个我不确定,但是看看我的代码,看看我是不是对的)。

检查此代码:

# how slice is removing the __dict__ from the class object
# and the metaclass is type!!

class sliceS(object):
    pass

class sliceS0(object):

    def __setattr__(self, name, value):
        pass

# this means that both have the same
# metaclass type.
print type(slice) == type(sliceS) # prints True

# from what i understand the metaclass is the one
# that is responsible for making the class object
sliceS2 = type('sliceS2', (object,), )
# witch is the same
# sliceS2 = type.__new__(type, 'sliceS2', (object,), )
print type(sliceS2) # prints type

# but when i check the list of attribute using dir
print '__dict__' in dir(slice)  # prints False
print '__dict__' in dir(sliceS) # prints True

# now when i try to set an attribute on slice
obj_slice = slice(10)
# there is no __dict__  here
print '__dict__' in dir(obj_slice) # prints False
obj_sliceS = sliceS()
try:
    obj_slice.x = 1
except AttributeError as e:
    # you get AttributeError
    # mean you cannot add new properties
    print "'slice' object has no attribute 'x'"

obj_sliceS.x = 1 # Ok: x is added to __dict__ of obj_sliceS
print 'x' in obj_sliceS.__dict__ # prints True

# and slice is not using __slots__ because as you see it's not here
print '__slots__' in dir(slice) # print False

# and this why i'm saying it's not overriding the __settattr__
print id(obj_slice.__setattr__) == id(obj_sliceS.__setattr__) # True: it's the same object
obj_sliceS0 = sliceS0()
print id(obj_slice.__setattr__) == id(obj_sliceS0.__setattr__) # False: it's the same object

# so slice have only start, stop, step and are all readonly attribute and it's not overriding the __setattr__
# what technique it's using?!!!!

如何使这种一流的对象所有的属性都是只读的,你不能 添加新属性。

【问题讨论】:

...“一流的对象”是什么意思? 我猜你的意思是内置类型。 但是可以创建一个具有相同行为的类 如果您的意思是不覆盖setattr 或使用__slots__?写一个 C 扩展。或者使用property 或其他一些描述符,而不实现__set__ @juanpa.arrivillaga 感谢您的评论我编辑了我的问题以消除冲突。问题是我如何创建一个行为类似于切片的类 【参考方案1】:

问题是 Python 的内置 slice 类是用 C 编程的。当您使用 C-Python API 进行编码时,您可以编写与 __slots__ 可访问的属性等效的代码,而无需使用任何可见的机制蟒蛇方面。 (您甚至可以拥有“真正的”私有属性,这在纯 Python 代码中几乎是不可能的)。

用于 Python 代码的机制能够防止 __dict__ 用于类的实例以及随后的“可以设置任何属性”是 __slots__ 正是该属性。 但是,与实际使用类时必须存在的魔术方法不同,__slots__ 上的信息仅在创建类时使用,并且仅在那时使用。因此,如果您担心在最后一堂课中有一个可见的__slots__,您可以在公开之前将其从课程中删除:

In [8]: class A:
   ...:     __slots__ = "b"
   ...:     

In [9]: del A.__slots__

In [10]: a = A()

In [11]: a.b = 5

In [12]: a.c = 5
------------------------
AttributeError   
...

In [13]: A.__slots__
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-68a69c802e74> in <module>()
----> 1 A.__slots__

AttributeError: type object 'A' has no attribute '__slots__'

如果您不希望 del MyClass.__slots__ 行在您声明类的任何位置都可见,则它是单行类装饰器:

def slotless(cls):
   del cls.__slots__
   return cls

@slotless
class MyClass:
   __slots__ = "x y".split()

或者,您可以使用元类自动创建并自动销毁 Python 可见 __slots__,以便您可以在类主体中声明您的描述符和属性,并保护该类免受额外属性的影响:

class AttrOnly(type):
   def __new__(metacls, name, bases, namespace, **kw):
        namespace["__slots__"] = list(namespace.keys())  # not sure if "list(" is needed
        cls = super().__new__(metacls, name, bases, namespace, **kw)
        del cls.__slots__
        return cls

class MyClass(metaclass=AttrOnly):
    x = int
    y = int

如果您想要在实例本身中没有可见对应项的纯 Python 只读属性(例如,property 描述符使用 ._x 来保持 x 属性的值),直接方法是自定义 __setattr__ 。另一种方法是让您的元类在类创建阶段为每个属性自动添加只读属性。下面的元类会这样做并使用__slots__ 类属性来创建所需的描述符:

class ReadOnlyAttrs(type):
    def __new__(metacls, name, bases, namespace, **kw):
        def get_setter(attr):
            def setter(self, value):
                if getattr(self, "_initialized", False): 
                    raise ValueError("Can't set "  + attr)
                setattr(self, "_" + attr, value)
            return setter

        slots = namespace.get("__slots__", [])
        slots.append("initialized")
        def __new__(cls, *args, **kw):
            self = object.__new__(cls)  # for production code that could have an arbitrary hierarchy, this needs to be done more carefully
            for attr, value in kw.items():
                setattr(self, attr, value)
            self.initialized = True
            return self

        namespace["__new__"] = __new__
        real_slots = []
        for attr in slots:
            real_slots.append("_" + attr)
            namespace[attr] = property(
                (lambda attr: lambda self: getattr(self, "_" + attr))(attr), # Getter. Extra lambda needed to create an extra closure containing each attr
                get_setter(attr)
            )
        namespace["__slots__"] = real_slots
        cls = super().__new__(metacls, name, bases, namespace, **kw)
        del cls.__slots__
        return cls

请记住,如果您愿意,您还可以自定义类的 __dir__ 方法,以便不会看到 _x 阴影属性。

【讨论】:

它实际上什么都不做,只是让__slots__不可见。我添加它是因为 __slots__ 在创建的类中的“不存在”是问题中的一个问题。 对不起 - 刚才我看到你那里有“python 2.7”标签。然而,上面唯一应该改变的是如何利用元类,(我猜,元类的“kw”属性将是无用的);不管怎样,现在研究python 2中的高级Python特性没什么意义,我建议你更新到3.6。 @jsbueno 我不知道我可以从 calss 对象中删除 slots 所以基本上如果类对象没有 dict 属性并且没有子类,您不能向其添加新属性。我说的对吗? @jsbueno 这里缺少一件事如何使这个属性只读。我不认为有办法不覆盖__setattr__

以上是关于创建一流的对象,它的所有实例属性都是只读的,就像切片一样?的主要内容,如果未能解决你的问题,请参考以下文章

js04---object1

什么是“一流”对象?

Django Admin中的动态只读字段

创建任何对象的只读/不可变副本(包括深层属性)

javascript数组的实例属性(方法)

Vue.js入门学习