- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我正在研究 Python 如何实现 property descriptor内部。根据文档 property()
是根据描述符协议(protocol)实现的,为方便起见,在此处复制它:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
我的问题是:为什么最后三个方法没有实现如下:
def getter(self, fget):
self.fget = fget
return self
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel= fdel
return self
是否有理由返回新的属性实例,内部指向基本相同的 get 和 set 函数?
最佳答案
让我们从一些历史开始,因为原始实现与您的替代方案等效(等效因为 property
在 CPython 中用 C 实现,因此 getter
等. 是用 C 而不是“普通 Python”编写的)。
但它被报告为 issue (1620) on the Python bug tracker早在 2007 年:
As reported by Duncan Booth at http://permalink.gmane.org/gmane.comp.python.general/551183 the new @spam.getter syntax modifies the property in place but it should create a new one.
The patch is the first draft of a fix. I've to write unit tests to verify the patch. It copies the property and as a bonus grabs the
__doc__
string from the getter if the doc string initially came from the getter as well.
不幸的是,链接没有去任何地方(我真的不知道为什么它被称为“永久链接”......)。它被归类为错误并更改为当前形式(参见 this patch 或相应的 Github commit (but it's a combination of several patches) )。如果您不想点击链接,更改是:
PyObject *
property_getter(PyObject *self, PyObject *getter)
{
- Py_XDECREF(((propertyobject *)self)->prop_get);
- if (getter == Py_None)
- getter = NULL;
- Py_XINCREF(getter);
- ((propertyobject *)self)->prop_get = getter;
- Py_INCREF(self);
- return self;
+ return property_copy(self, getter, NULL, NULL, NULL);
}
setter
和 deleter
也类似。如果您不知道 C,重要的几行是:
((propertyobject *)self)->prop_get = getter;
和
return self;
其余的主要是“Python C API 样板”。然而,这两行相当于你的:
self.fget = fget
return self
又改成了:
return property_copy(self, getter, NULL, NULL, NULL);
本质上是这样的:
return type(self)(fget, self.fset, self.fdel, self.__doc__)
由于链接断开,我不知道确切原因,但是我可以根据添加的 test-cases in that commit 进行推测。 :
import unittest
class PropertyBase(Exception):
pass
class PropertyGet(PropertyBase):
pass
class PropertySet(PropertyBase):
pass
class PropertyDel(PropertyBase):
pass
class BaseClass(object):
def __init__(self):
self._spam = 5
@property
def spam(self):
"""BaseClass.getter"""
return self._spam
@spam.setter
def spam(self, value):
self._spam = value
@spam.deleter
def spam(self):
del self._spam
class SubClass(BaseClass):
@BaseClass.spam.getter
def spam(self):
"""SubClass.getter"""
raise PropertyGet(self._spam)
@spam.setter
def spam(self, value):
raise PropertySet(self._spam)
@spam.deleter
def spam(self):
raise PropertyDel(self._spam)
class PropertyTests(unittest.TestCase):
def test_property_decorator_baseclass(self):
# see #1620
base = BaseClass()
self.assertEqual(base.spam, 5)
self.assertEqual(base._spam, 5)
base.spam = 10
self.assertEqual(base.spam, 10)
self.assertEqual(base._spam, 10)
delattr(base, "spam")
self.assert_(not hasattr(base, "spam"))
self.assert_(not hasattr(base, "_spam"))
base.spam = 20
self.assertEqual(base.spam, 20)
self.assertEqual(base._spam, 20)
self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
def test_property_decorator_subclass(self):
# see #1620
sub = SubClass()
self.assertRaises(PropertyGet, getattr, sub, "spam")
self.assertRaises(PropertySet, setattr, sub, "spam", None)
self.assertRaises(PropertyDel, delattr, sub, "spam")
self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
这与其他答案已经提供的示例类似。问题是您希望能够在不影响父类的情况下更改子类中的行为:
>>> b = BaseClass()
>>> b.spam
5
但是对于您的属性(property),它会导致:
>>> b = BaseClass()
>>> b.spam
---------------------------------------------------------------------------
PropertyGet Traceback (most recent call last)
PropertyGet: 5
这是因为 BaseClass.spam.getter
(在 SubClass
中使用)实际上修改并返回了 BaseClass.spam
属性!
所以是的,它已经被改变了(很可能),因为它允许修改子类中属性的行为而不改变父类的行为。
请注意,还有一个额外的原因,这有点愚蠢但实际上值得一提(在我看来):
让我们简要回顾一下:装饰器只是分配的语法糖,所以:
@decorator
def decoratee():
pass
相当于:
def func():
pass
decoratee = decorator(func)
del func
这里的重点是装饰器的结果被分配给被装饰函数的名称。因此,虽然您通常对 getter/setter/deleter 使用相同的“函数名称” - 但您不必这样做!
例如:
class Fun(object):
@property
def a(self):
return self._a
@a.setter
def b(self, value):
self._a = value
>>> o = Fun()
>>> o.b = 100
>>> o.a
100
>>> o.b
100
>>> o.a = 100
AttributeError: can't set attribute
在本例中,您使用 a
的描述符为 b
创建另一个描述符,该描述符的行为类似于 a
,只是它获得了一个 setter
.
这是一个相当奇怪的例子,可能不经常(或根本不)使用。但即使它很奇怪并且(对我来说)不是很好的风格 - 它应该说明仅仅因为你使用 property_name.setter
(或 getter
/deleter
),它必须绑定(bind)到 property_name
。它可以绑定(bind)到任何名称!而且我不希望它传播回原始属性(尽管我不确定我会在这里期待什么)。
getter
、setter
和deleter
中使用了一次“修改并返回self
”的方式.return self
始终有效的假设可能是有问题的(对于通用装饰器)。<关于Python属性描述符设计: why copy rather than mutate?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49081819/
你能比较一下属性吗 我想禁用文本框“txtName”。有两种方式 使用javascript,txtName.disabled = true 使用 ASP.NET, 哪种方法更好,为什么? 最佳答案 我
Count 属性 返回一个集合或 Dictionary 对象包含的项目数。只读。 object.Count object 可以是“应用于”列表中列出的任何集合或对
CompareMode 属性 设置并返回在 Dictionary 对象中比较字符串关键字的比较模式。 object.CompareMode[ = compare] 参数
Column 属性 只读属性,返回 TextStream 文件中当前字符位置的列号。 object.Column object 通常是 TextStream 对象的名称。
AvailableSpace 属性 返回指定的驱动器或网络共享对于用户的可用空间大小。 object.AvailableSpace object 应为 Drive 
Attributes 属性 设置或返回文件或文件夹的属性。可读写或只读(与属性有关)。 object.Attributes [= newattributes] 参数 object
AtEndOfStream 属性 如果文件指针位于 TextStream 文件末,则返回 True;否则如果不为只读则返回 False。 object.A
AtEndOfLine 属性 TextStream 文件中,如果文件指针指向行末标记,就返回 True;否则如果不是只读则返回 False。 object.AtEn
RootFolder 属性 返回一个 Folder 对象,表示指定驱动器的根文件夹。只读。 object.RootFolder object 应为 Dr
Path 属性 返回指定文件、文件夹或驱动器的路径。 object.Path object 应为 File、Folder 或 Drive 对象的名称。 说明 对于驱动器,路径不包含根目录。
ParentFolder 属性 返回指定文件或文件夹的父文件夹。只读。 object.ParentFolder object 应为 File 或 Folder 对象的名称。 说明 以下代码
Name 属性 设置或返回指定的文件或文件夹的名称。可读写。 object.Name [= newname] 参数 object 必选项。应为 File 或&
Line 属性 只读属性,返回 TextStream 文件中的当前行号。 object.Line object 通常是 TextStream 对象的名称。 说明 文件刚
Key 属性 在 Dictionary 对象中设置 key。 object.Key(key) = newkey 参数 object 必选项。通常是 Dictionary 
Item 属性 设置或返回 Dictionary 对象中指定的 key 对应的 item,或返回集合中基于指定的 key 的&
IsRootFolder 属性 如果指定的文件夹是根文件夹,返回 True;否则返回 False。 object.IsRootFolder object 应为&n
IsReady 属性 如果指定的驱动器就绪,返回 True;否则返回 False。 object.IsReady object 应为 Drive&nbs
FreeSpace 属性 返回指定的驱动器或网络共享对于用户的可用空间大小。只读。 object.FreeSpace object 应为 Drive 对象的名称。
FileSystem 属性 返回指定的驱动器使用的文件系统的类型。 object.FileSystem object 应为 Drive 对象的名称。 说明 可
Files 属性 返回由指定文件夹中所有 File 对象(包括隐藏文件和系统文件)组成的 Files 集合。 object.Files object&n
我是一名优秀的程序员,十分优秀!