Python 属性描述符设计:为什么要复制而不是变异?

2024-03-04

我正在研究Python如何实现属性描述符 https://docs.python.org/2/howto/descriptor.html#properties内部。根据文档property()是根据描述符协议实现的,为了方便起见,在此处复制它:

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”)。

然而据报道称Python 错误跟踪器上的问题 (1620) https://bugs.python.org/issue1620早在 2007 年:

据邓肯·布斯 (Duncan Booth) 报道http://permalink.gmane.org/gmane.comp.python.general/551183 http://permalink.gmane.org/gmane.comp.python.general/551183新的 @spam.getter 语法会就地修改属性,但它应该创建 一个新的。

该补丁是修复程序的初稿。我必须编写单元测试 验证补丁。它复制该属性并作为奖励获取__doc__如果文档字符串最初来自 getter,则来自 getter 的字符串 吸气剂也是如此。

不幸的是,该链接没有去任何地方(我真的不知道为什么它被称为“永久链接”......)。它被归类为错误并更改为当前形式(请参阅这个补丁 https://bugs.python.org/file8947/py3k_copy_property.patch或相应的Github 提交(但它是几个补丁的组合) https://github.com/python/cpython/commit/0449f63f53fa0c2fcf779703d2db77d8a658cf7d#diff-a34ae826f869897e56e081e212b3d75a)。如果您不想点击链接,则更改为:

 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 and deleter。如果您不了解 C,重要的几行是:

((propertyobject *)self)->prop_get = getter;

and

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__)

为什么改变了?

由于链接已关闭,我不知道确切的原因,但我可以根据添加的内容进行推测该提交中的测试用例 https://github.com/python/cpython/commit/0449f63f53fa0c2fcf779703d2db77d8a658cf7d#diff-ea5875d237692b8c64af41a0dea6f3a9:

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

但是,对于您的财产,会导致以下结果:

>>> 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 (or getter/deleter)它必须绑定到property_name。它可以绑定到任何名称!而且我不希望它传播回原始属性(尽管我不太确定我在这里期望什么)。

Summary

  • CPython实际上使用了“修改并返回self” 中的方法getter, setter and deleter once.
  • 由于错误报告,它已被更改。
  • 当与覆盖父类属性的子类一起使用时,它的行为会出现“错误”。
  • 更一般地说:装饰器无法影响它们将绑定的名称,因此假设它始终有效return self在装饰器中可能是有问题的(对于通用装饰器)。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 属性描述符设计:为什么要复制而不是变异? 的相关文章

  • 从 Django 调用 Postgres SQL 存储过程

    我正在开发一个带有 Postgresql 数据库的 Django 项目 我编写了一个可以在 Postgres 上完美运行的存储过程 现在我想从 Django 1 5 调用该存储过程 我已经编写了代码 但它提示错误 CREATE FUNCTI
  • 检测到通过 ChromeDriver 启动的 Chrome 浏览器

    我正在尝试在 python 中使用 selenium chromedriver 来访问 www mouser co uk 网站 然而 从第一次拍摄开始 它就被检测为机器人 有人对此有解释吗 此后我使用的代码 options Options
  • TensorFlow:带有轴选项的 bincount

    在 TensorFlow 中 我可以使用 tf bincount 获取数组中每个元素的计数 x tf placeholder tf int32 None freq tf bincount x tf Session run freq feed
  • 在 python 3 中使用子进程

    我使用 subprocess 模块在 python 3 中运行 shell 命令 这是我的代码 import subprocess filename somename py in practical i m using a real fil
  • 无故运行测试时 PyCharm 抛出“AttributeError: 'module' object has no attribute”

    因此 我有一个 Django REST Framework 项目 有一天它无法在 PyCharm 中运行测试 从命令行我可以使用它们来运行它们paver or the manage py直接地 曾经有一段时间 当我们没有在文件顶部导入类的超
  • Pandas dataframe:每批行的操作

    我有一个熊猫数据框df我想计算每批行的一些统计信息 例如 假设我有一个batch size 200000 对于每批batch sizerows 我想要一列的唯一值的数量ID我的数据框 我怎样才能做这样的事情呢 这是我想要的一个例子 prin
  • “一旦获取切片就无法更新查询”。最佳实践?

    由于我的项目的性质 我发现自己不断地从查询集中取出切片 如下所示 Thread objects filter board requested board id order by updatedate 10 但这给我带来了实际对我选择的元素进
  • PyTorch 给出 cuda 运行时错误

    我对我的代码做了一些小小的修改 以便它不使用 DataParallel and DistributedDataParallel 代码如下 import argparse import os import shutil import time
  • 将二维数组放入 Pandas 系列中

    我有一个 2D Numpy 数组 我想将其放入 pandas 系列 而不是 DataFrame 中 gt gt gt import pandas as pd gt gt gt import numpy as np gt gt gt a np
  • 如何在 Django 中使用基于类的视图创建注册视图?

    当我开始使用 Django 时 我几乎使用 FBV 基于函数的视图 来处理所有事情 包括注册新用户 但当我更深入地研究项目时 我意识到基于类的视图通常更适合大型项目 因为它们更干净且可维护 但这并不是说 FBV 不是 无论如何 我将整个项目
  • Pandas 堆积条形图中元素的排序

    我正在尝试绘制有关某个地区 5 个地区的家庭在特定行业赚取的收入比例的信息 我使用 groupby 按地区对数据框中的信息进行排序 df df orig groupby District Portion of income value co
  • Python:我不明白 sum() 的完整用法

    当然 我明白你使用 sum 与几个数字 然后它总结所有 但我正在查看它的文档 我发现了这一点 sum iterable start 第二个参数 start 的作用是什么 这太尴尬了 但我似乎无法通过谷歌找到任何示例 并且对于尝试学习该语言的
  • C++派生模板类继承自模板基类,无法调用基类构造函数[重复]

    这个问题在这里已经有答案了 我试图从基类 模板 继承 派生类也是模板 它们具有相同的类型 T 我收到编译错误 非法成员初始化 Base 不是基类或成员 为什么 如何调用基类构造函数 include
  • 从 python 检测 macOS 中的暗模式

    我正在编写一个 PyQt 应用程序 我必须添加一个补丁 以便在启用暗模式的 Macos 上可以读取字体 app QApplication Fix for the font colours on macos when running dark
  • sqlite3从打印数据中删除括号

    我创建了一个脚本 用于查找数据库第一行中的最后一个值 import sqlite3 global SerialNum conn sqlite3 connect MyFirstDB db conn text factory str c con
  • Python对象初始化性能

    我只是做了一些快速的性能测试 我注意到一般情况下初始化列表比显式初始化列表慢大约四到六倍 这些可能是错误的术语 我不确定这里的行话 例如 gt gt gt import timeit gt gt gt print timeit timeit
  • OSX 上的 locale.getlocale() 问题

    我需要获取系统区域设置来执行许多操作 最终我想使用 gettext 翻译我的应用程序 我打算在 Linux 和 OSX 上分发它 但我在 OSX Snow Leopard 上遇到了问题 python Python 2 5 2 r252 60
  • 从列表python的单个列表中删除子列表

    我已经经历过从列表列表中删除子列表 https stackoverflow com questions 47209786 removing sublists from a list of lists 但当我为我的数据集扩展它时 它不适用于我
  • 操作错误:(sqlite3.OperationalError) SQL 变量太多,同时将 SQL 与数据帧一起使用

    我有一个熊猫数据框 如下所示 activity User Id 0 VIEWED MOVIE 158d292ec18a49 1 VIEWED MOVIE 158d292ec18a49 2 VIEWED MOVIE 158d292ec18a4
  • python 中的 after() 与 update()

    我是 python 新手 开始使用 tkinter 作为画布 到目前为止 我使用 update 来更新我的画布 但还有一个 after 方法 谁能给我解释一下这个函数 请举个例子 两者之间有什么区别 root after integer c

随机推荐

  • pthread_create 不带参数?

    我想创建一个没有函数参数的线程 但我不断收到严重困扰我的错误 因为我无法让一些超级简单的东西正常工作 这是我的代码 include
  • Spring Boot 2.1 中的 DataSource bean 重写

    我已经升级到 spring boot 2 1 版本 启动应用程序时出现奇怪的异常 无法注册在类路径资源 org springframework boot autoconfigure jdbc DataSourceConfiguration
  • Eclipse 类文件元数据

    在 Visual Studio 中 我可以通过按 F12 GoToDefinition 获取没有源代码 即捆绑在 DLL 内 的类中公开的公共方法 成员的简洁列表 同样 我正在学习 Android API 在 Eclipse 中 跳转到 A
  • 如何构建弹性搜索查询以使文档字段中的每个标记都匹配?

    我需要确保字段的每个标记与用户搜索中的至少一个标记相匹配 这是为了简化起见的通用示例 Let Store Name Square Steakhouse 当用户搜索 Square 或 Steakhouse 时 构建与此文档匹配的查询很简单 此
  • 尽管需要使用语句和扩展,但无法进行函数调用

    所以我希望能够在运行时选择我的环境dotnet 一个 net core mvc 项目 从终端 我发现这个帖子 https stackoverflow com questions 37322565 dotnet run or dotnet w
  • Azure DevOps - 无法运行已安装的 dotnet 工具

    我试着跑dotnet tool install在 Azure DevOps 中并测试该工具 Locally dotnet tool install dotnetsay g dotnetsay test 无缝工作 在 Azure DevOps
  • 在R中将多边形转换为sf

    使用此处的教程 https www r spatial org r 2018 10 25 ggplot2 sf 2 html https www r spatial org r 2018 10 25 ggplot2 sf 2 html 在名
  • 对文件的操作 |获取和放置指针

    我对操作文件有一些疑问 a 我对 C 中的 get 和 put 指针有点困惑 我是否显示获取指针和放置指针的正确位置 MyFile seekg 0 ios beg MyFile seekp 10 ios end index 0 1 2 3
  • Firebase:数据库引用“on”方法未运行回调(javascript)

    我正在调整对可能不存在的数据的查询 在这种情况下 回调不会运行 据我从文档中了解到 它应该运行并且 snapshot val 应该为 null 不是吗 这里有一个精简的示例 http surfmaps eu trombone case ht
  • 有效地将 {坐标+值} 集绘制到(numpy 数组)位图

    假设我有一组像素值 例如 gt S 42 6 2 0 1 0 0 这里第 42 个条目是像素位置 6 2 呈暗红色 如何高效绘图S进入一个新的 numpy 位图数组bitmap np zeros 1024 768 3 是否有一些矢量化解决方
  • Crypto-Js 与 mcrypt 的输出不同

    我有一个 js 脚本 它加密 Blade 数据 如果我加密它 它会返回输出 JS 脚本结果 uqnOrevjCc2YCvY3uKNjzA 现在 以此答案作为比较的基础 我编写了或者更确切地说 在 PHP 中搜索了与我的 JS 脚本类似的等效
  • 如何运行烧瓶应用程序?

    我想知道启动烧瓶应用程序的正确方法 该文档显示了两个不同的命令 flask a sample run and python3 4 sample py 产生相同的结果并正确运行应用程序 两者之间有什么区别 应该使用哪一个来运行 Flask 应
  • AWS EventBridge 使用自定义详细信息安排事件?

    我正在尝试构建一个架构 其中使用多个参数集按计划触发单个 Lambda 因此 例如 如果我有三组参数并将计划设置为十分钟 我希望每十分钟执行三次 有没有办法使用自定义属性触发 EventBridge 计划事件 以便我可以将参数传递给 Lam
  • 数据表中要删除的排序箭头

    需要帮助删除数据表标题行上的排序箭头 升序和降序 然后当用户单击标题列时 将出现升序箭头 当然数据将按升序排序 MyDataTable dataTable aoColumns bSortable false null
  • R通过R_LIBS设置库路径

    我已阅读 R 常见问题解答和其他帖子 但我有点困惑 并且很高兴知道我所做的一切是否正确 在Windows中 为了修改默认的库文件夹 我创建了一个文件Renviron site并放入里面E Programs R 3 3 0 etc 该文件只有
  • 如何在 VBA for Ms Access 中擦除或重新加载 InkPicture 笔画?

    我想显示之前在 Ms Access 中的 inkPicture 对象上保存的笔画 它位于通过 ActiveX 的表单上 笔划作为 ole 对象保存在表中的 笔划 字段中 现在 执行一次就足够简单了 一行代码 我可以毫无问题地使用此代码片段
  • 计算 pyspark 中的分组中位数

    使用 pyspark 时 我希望能够计算分组值与其中值之间的差异 这可能吗 这是我编写的一些代码 它可以执行我想要的操作 除了它计算平均值的分组差异之外 另外 如果您愿意提供帮助 请随时评论我如何做得更好 from pyspark impo
  • web.config 转换在构建服务器上不起作用

    我们正在与 Team City 建立持续集成 在签到的基础上进行构建 这工作正常 但是它总是使用默认的 web config 构建 它不会随着开发环境特定的 Web 配置进行转换 在 Visual Studio 中 我为开发创建了自定义构建
  • std::remove_if 和 std::isspace - 编译时错误

    我有以下代码 include
  • Python 属性描述符设计:为什么要复制而不是变异?

    我正在研究Python如何实现属性描述符 https docs python org 2 howto descriptor html properties内部 根据文档property 是根据描述符协议实现的 为了方便起见 在此处复制它 c