Len*_*bro 165 python immutability python-3.x
虽然我从来没有需要这个,但让我感到震惊的是在Python中创建一个不可变对象可能会有点棘手.你不能只是覆盖__setattr__
,因为那时你甚至不能设置属性__init__
.对元组进行子类化是一种有效的技巧:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Run Code Online (Sandbox Code Playgroud)
但是你可以通过和访问a
和b
变量,这很烦人.self[0]
self[1]
这在纯Python中是否可行?如果没有,我将如何使用C扩展?
(只能在Python 3中使用的答案是可以接受的).
更新:
因此,子类的元组是做纯Python,效果很好,除了通过访问数据的另一种可能性的方式[0]
,[1]
等等.所以,要完成这个问题,所有这一切都缺少的是HOWTO在C,做"正确的",这我怀疑是非常简单,只是没有实现任何geititem
或setattribute
等等.但我不是自己做,我为此提供赏金,因为我很懒.:)
Sve*_*ach 105
我刚想到的另一个解决方案:获得与原始代码相同的行为的最简单方法是
Immutable = collections.namedtuple("Immutable", ["a", "b"])
Run Code Online (Sandbox Code Playgroud)
它没有解决可以通过[0]
等方式访问属性的问题,但至少它要短得多,并且提供了兼容pickle
和的额外优势copy
.
namedtuple
创建一个类似于我在本回答中描述的类型,即派生自tuple
和使用__slots__
.它在Python 2.6或更高版本中可用.
Sve*_*ach 74
最简单的方法是使用__slots__
:
class A(object):
__slots__ = []
Run Code Online (Sandbox Code Playgroud)
实例A
现在是不可变的,因为您无法在它们上设置任何属性.
如果希望类实例包含数据,可以将其与派生自tuple
:
from operator import itemgetter
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
x = property(itemgetter(0))
y = property(itemgetter(1))
p = Point(2, 3)
p.x
# 2
p.y
# 3
Run Code Online (Sandbox Code Playgroud)
编辑:如果你想摆脱索引,你可以覆盖__getitem__()
:
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
@property
def x(self):
return tuple.__getitem__(self, 0)
@property
def y(self):
return tuple.__getitem__(self, 1)
def __getitem__(self, item):
raise TypeError
Run Code Online (Sandbox Code Playgroud)
请注意,operator.itemgetter
在这种情况下,您不能使用属性,因为这将依赖于Point.__getitem__()
而不是tuple.__getitem__()
.此外,这不会阻止使用tuple.__getitem__(p, 0)
,但我很难想象这应该如何构成一个问题.
我不认为创建不可变对象的"正确"方法是编写C扩展.Python通常依赖于库实现者和库用户同意成人,而不是真正强制执行接口,应该在文档中明确说明接口.这就是为什么我不考虑__setattr__()
通过调用object.__setattr__()
问题来绕过被覆盖的可能性.如果有人这样做,那就是她自己的风险.
jfs*_*jfs 50
..如何在C中"正确"地做到
您可以使用Cython为Python创建扩展类型:
cdef class Immutable:
cdef readonly object a, b
cdef object __weakref__ # enable weak referencing support
def __init__(self, a, b):
self.a, self.b = a, b
Run Code Online (Sandbox Code Playgroud)
它适用于Python 2.x和3.
# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable
o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2
try: o.a = 3
except AttributeError:
pass
else:
assert 0, 'attribute must be readonly'
try: o[1]
except TypeError:
pass
else:
assert 0, 'indexing must not be supported'
try: o.c = 1
except AttributeError:
pass
else:
assert 0, 'no new attributes are allowed'
o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []
o.b.append(3) # attribute may contain mutable object
assert o.b == [3]
try: o.c
except AttributeError:
pass
else:
assert 0, 'no c attribute'
o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3
try: del o.b
except AttributeError:
pass
else:
assert 0, "can't delete attribute"
d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']
o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3
try: object.__setattr__(o, 'a', 1)
except AttributeError:
pass
else:
assert 0, 'attributes are readonly'
try: object.__setattr__(o, 'c', 1)
except AttributeError:
pass
else:
assert 0, 'no new attributes'
try: Immutable(1,c=3)
except TypeError:
pass
else:
assert 0, 'accept only a,b keywords'
for kwd in [dict(a=1), dict(b=2)]:
try: Immutable(**kwd)
except TypeError:
pass
else:
assert 0, 'Immutable requires exactly 2 arguments'
Run Code Online (Sandbox Code Playgroud)
如果您不介意索引支持,那么@Sven Marnachcollections.namedtuple
建议您更喜欢:
Immutable = collections.namedtuple("Immutable", "a b")
Run Code Online (Sandbox Code Playgroud)
Jun*_*ius 40
对于 Python 3.7+,您可以使用带有选项的数据类frozen=True
,这是一种非常pythonic 且可维护的方式来做您想做的事情。
它看起来像这样:
from dataclasses import dataclass
@dataclass(frozen=True)
class Immutable:
a: Any
b: Any
Run Code Online (Sandbox Code Playgroud)
由于数据类的字段需要类型提示,我使用了typing
模块中的Any。
在 Python 3.7 之前,经常看到命名元组被用作不可变对象。这在很多方面都很棘手,其中之一是__eq__
命名元组之间的方法不考虑对象的类。例如:
from collections import namedtuple
ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])
obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)
obj1 == obj2 # will be True
Run Code Online (Sandbox Code Playgroud)
如您所见,即使obj1
和的类型obj2
不同,即使它们的字段名称不同,obj1 == obj2
仍然会给出True
. 那是因为使用的__eq__
方法是元组的方法,它只比较给定位置的字段的值。这可能是一个巨大的错误来源,特别是如果您要对这些类进行子类化。
Sve*_*ach 38
另一个想法是在构造函数中完全禁止__setattr__
和使用object.__setattr__
:
class Point(object):
def __init__(self, x, y):
object.__setattr__(self, "x", x)
object.__setattr__(self, "y", y)
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
Run Code Online (Sandbox Code Playgroud)
当然,你可以使用object.__setattr__(p, "x", 3)
修改Point
的实例p
,但你原来实行从同一问题的困扰(尝试tuple.__setattr__(i, "x", 42)
在一个Immutable
实例).
您可以在原始实现中应用相同的技巧:摆脱__getitem__()
,并tuple.__getitem__()
在您的属性函数中使用.
Pao*_*tor 18
您可以创建一个@immutable
装饰器,它覆盖__setattr__
并更改__slots__
为空列表,然后__init__
用它装饰方法.
编辑:正如OP所指出的,更改__slots__
属性只会阻止创建新属性,而不是修改.
Edit2:这是一个实现:
Edit3:使用__slots__
中断此代码,因为如果停止创建对象的话__dict__
.我正在寻找另一种选择.
编辑4:嗯,就是这样.这是一个但很强硬,但作为一个练习:-)
class immutable(object):
def __init__(self, immutable_params):
self.immutable_params = immutable_params
def __call__(self, new):
params = self.immutable_params
def __set_if_unset__(self, name, value):
if name in self.__dict__:
raise Exception("Attribute %s has already been set" % name)
if not name in params:
raise Exception("Cannot create atribute %s" % name)
self.__dict__[name] = value;
def __new__(cls, *args, **kws):
cls.__setattr__ = __set_if_unset__
return super(cls.__class__, cls).__new__(cls, *args, **kws)
return __new__
class Point(object):
@immutable(['x', 'y'])
def __new__(): pass
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z
Run Code Online (Sandbox Code Playgroud)
Dun*_*can 10
我不认为除了使用元组或命名元组之外完全可能.无论如何,如果你覆盖__setattr__()
用户总是可以通过object.__setattr__()
直接调用来绕过它.任何依赖的解决方案__setattr__
都保证不起作用.
以下是关于你可以在不使用某种元组的情况下获得的最近值:
class Immutable:
__slots__ = ['a', 'b']
def __init__(self, a, b):
object.__setattr__(self, 'a', a)
object.__setattr__(self, 'b', b)
def __setattr__(self, *ignored):
raise NotImplementedError
__delattr__ = __setattr__
Run Code Online (Sandbox Code Playgroud)
但如果你努力尝试就会破裂:
>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2
Run Code Online (Sandbox Code Playgroud)
但斯文的使用namedtuple
真的是不变的.
更新
既然问题已经更新,请问如何在C中正确地完成它,这里是我在Cython中如何正确完成它的答案:
第一immutable.pyx
:
cdef class Immutable:
cdef object _a, _b
def __init__(self, a, b):
self._a = a
self._b = b
property a:
def __get__(self):
return self._a
property b:
def __get__(self):
return self._b
def __repr__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
Run Code Online (Sandbox Code Playgroud)
和a setup.py
编译它(使用命令setup.py build_ext --inplace
:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("immutable", ["immutable.pyx"])]
setup(
name = 'Immutable object',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
Run Code Online (Sandbox Code Playgroud)
然后尝试一下:
>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>
Run Code Online (Sandbox Code Playgroud)
我通过覆盖创建了不可变的类__setattr__
,如果调用者是,则允许设置__init__
:
import inspect
class Immutable(object):
def __setattr__(self, name, value):
if inspect.stack()[2][3] != "__init__":
raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
object.__setattr__(self, name, value)
Run Code Online (Sandbox Code Playgroud)
这还不够,因为它允许任何人___init__
更改对象,但是您明白了。
除了出色的其他答案之外,我还喜欢为 python 3.4(或者可能是 3.3)添加一个方法。这个答案建立在这个问题的几个先前的答案之上。
在 python 3.4 中,您可以使用没有 setter 的属性来创建无法修改的类成员。(在早期版本中,可以在没有 setter 的情况下分配给属性。)
class A:
__slots__=['_A__a']
def __init__(self, aValue):
self.__a=aValue
@property
def a(self):
return self.__a
Run Code Online (Sandbox Code Playgroud)
你可以这样使用它:
instance=A("constant")
print (instance.a)
Run Code Online (Sandbox Code Playgroud)
这将打印 "constant"
但是调用instance.a=10
会导致:
AttributeError: can't set attribute
Run Code Online (Sandbox Code Playgroud)
解释:没有setter的属性是python 3.4(我认为是3.3)的最新特性。如果您尝试分配给这样的属性,则会引发错误。使用插槽我将成员变量限制为__A_a
(即__a
)。
问题:_A__a
仍然可以分配给( instance._A__a=2
)。但是如果你分配给一个私有变量,那是你自己的错......
然而,这个答案不鼓励使用__slots__
. 使用其他方法来阻止属性创建可能是更可取的。
这是一个优雅的解决方案:
class Immutable(object):
def __setattr__(self, key, value):
if not hasattr(self, key):
super().__setattr__(key, value)
else:
raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))
Run Code Online (Sandbox Code Playgroud)
从此类继承,初始化构造函数中的字段,一切就绪。
小智 5
所以,我正在编写 python 3 的相应内容:
I)在数据类装饰器的帮助下并设置 freeze=True。我们可以在 python 中创建不可变对象。
为此需要从数据类库导入数据类并需要设置 freeze=True
前任。
从数据类导入数据类
@dataclass(frozen=True)
class Location:
name: str
longitude: float = 0.0
latitude: float = 0.0
Run Code Online (Sandbox Code Playgroud)
输出:
>>> l = Location("Delhi", 112.345, 234.788)
>>> l.name
'Delhi'
>>> l.longitude
112.345
>>> l.latitude
234.788
>>> l.name = "Kolkata"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
>>>
Run Code Online (Sandbox Code Playgroud)
来源: https: //realpython.com/python-data-classes/
归档时间: |
|
查看次数: |
69132 次 |
最近记录: |