为什么Python描述符会复制?

时间:2014-10-26 04:26:40

标签: python python-3.x lambda copy descriptor

(我编辑了这个问题,因为我认为这仍然是基本相同的问题,虽然我从评论中得到了一些了解。我不知道是否允许这样做,或者我应该问一个新问题。)

以下代码

class A: c = lambda:0
a = A()
print(a.c is a.c)

打印错误。我已经学会了它,因为Python认为A.c是一种方法,因为c在类级别被赋予了一个函数。我有两个问题:

  • (不太重要)Python如何判断某些东西是否是函数?如果它成为一种方法,我认为必须明确定义。 “任意可调用”显然不是标准:例如,不接受内置函数。

  • (更重要)我了解到“每当你通过class.name或instance.name查找方法时,方法对象就会被创建为a-new”。是否有任何与实现无关的原因为什么会这样?也就是说,如果没有制作副本,是否有任何语言功能无法正常工作? (当然,我知道a1.c不是a2.c,但是对于同一个对象a,ac可能总是同一个对象吗?或者至少,Ac总是可以是同一个对象吗?)

3 个答案:

答案 0 :(得分:1)

默认function.__get__ method执行“复制”(创建新方法实例):

/* Bind a function to an object */
static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
    if (obj == Py_None || obj == NULL) {
        Py_INCREF(func);
        return func;
    }
    return PyMethod_New(func, obj);
}

但您可以定义不复制的描述符:

from functools import partial

class D:
    def __init__(self, function, cached=False):
        self.function = function
        if cached:
            self.cache = {}
        else:
            self.cache = None

    def __get__(self, instance, klass):
        if instance is None: # C.m
            return self.function
        if self.cache is None: # no cache
            m = partial(self.function, instance)
        else:
            m = self.cache.get(instance)
            if m is None:
                m = self.cache[instance] = partial(self.function, instance)
        m.__self__ = instance
        return m # C().m

class C:
    m = D(print)
    cached = D(print, cached=True)

assert C.m is C.m
assert C.cached is C.cached
c = C()
assert c.m is not c.m
assert c.cached is c.cached

每次调用.__get__()时重新创建方法可能比保持(可能weakref)映射(实例 - >方法)和打破周期(由于{{ {1}}),以避免浪费记忆。

答案 1 :(得分:0)

我没有电脑方便。如果您输入print(id(a.b), id(a.b), id(a.c), id(a.c))会怎样?如果第二对不同,则创建单独的对象,我们没有错误。

答案 2 :(得分:0)

以下是关于实例上属性的Python 2.x语言参考says(也适用于3.x)(向下滚动到“类实例”):

  

当在那里找不到属性时,实例的类有一个   通过该名称的属性,搜索继续该类   属性。如果找到属性是用户定义的类属性   函数对象或未绑定的用户定义的方法对象   关联类是实例的类(称之为C)   属性引用已启动或其基础之一,它是   转换为绑定的用户定义的方法对象,其im_class   attribute是C,其im_self属性是实例。

对于类上的属性(仅在2.x中,但不在3.x中)(滚动到“类”):

  

当一个类属性引用(比如C类)会产生一个   用户定义的函数对象或未绑定的用户定义的方法对象   它的关联类是C或它的一个基类,它是   转换为未绑定的用户定义方法对象,其im_class   属性是C。

所以回答你的问题:

  1. 如何决定?规范明确指出“用户定义的函数对象”或“未绑定的用户定义的方法对象”。所以那些是这条规则适用的。这不是所有可调用的类型。如果在同一页面上向上滚动到可调用类型的部分,则有许多类型的可调用类型,其中“用户定义的函数”只是一种类型。

  2. (首先,在此进行修正:对于实例上的属性访问,是,创建方法对象;对于类的属性访问,方法对象仅在Python 2.x上创建 - 在Python 3中。 x你只需得到你放在那里的任何内容,而不需要任何包装。)为了回答你的问题,推测Python实现可能会返回相同的方法对象。它需要某种缓存或实习来执行此操作,这在存储方面存在开销。规范没有说明这一点。并且当前的CPython实现不会返回相同的对象。你不应该依赖它。