使用实例级别而非类级别定义的属性

2 投票
4 回答
704 浏览
提问于 2025-04-15 15:25

我想实现的功能大概是这样的:

class object:
    def __init__(self):
        WidthVariable(self)

        print self.width
        #Imagine I did this 60frames/1second later
        print self.width

#output:
>>0
>>25

我希望发生的事情(如上所述):当创建一个名为 WidthVariable 的类时,它会将一个变量 width 添加到对象 instance 中。这个变量就像普通的属性,但在这个特定的情况下,它是只读的(只有 fget 变量被设置)。此外,fget 会调用一个在 WidthVariable 中定义的函数,这个函数决定返回什么样的 width

不过,我不知道该怎么做!我尝试使用普通属性,但发现它们只在类上有效,而不是在每个实例上——请注意,我使用的代码应该尽可能与上面的代码相似(也就是说,只有 WidthVariable__init__ 方法中的代码应该设置 width 变量,其他地方都不可以)。另外,self.width 不能是一个函数,因为我不想像 self.width() 这样调用它,我想要的是 self.width(因为这与我设计的其他部分保持一致)。

为了更清楚,完整的代码大概是这样的:

class MyObject:
    def __init__(self)
        WidthVariable(self)
        print self.width

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

    def get_width(self):
        value = #do_stuff
        return value #The Value

#output:
>>25 #Whatever the Value was

4 个回答

0

我不太明白你这个例子的构造方式,也不太理解你说的“普通属性”只能在“类”上工作的意思。下面是如何创建一个只读属性的示例:

class Foo(object):
    # create read-only property "rop"
    rop = property(lambda self: self._x)

    def __init__(self):
        self._x = 0

    def tick(self):
        self._x += 1    

f = Foo()
print f.rop # prints 0
f.tick()
f.tick()
print f.rop # prints 2
f.rop = 4 # this will raise AtributeError
1

我觉得我现在明白你的问题了,不过我也觉得你可能有点不幸。

对于新式类,特殊方法的隐式调用只有在定义在对象的类型上时才能正确工作,而不能在对象的实例字典里。

描述符(用来实现属性的东西)必须出现在类的 __dict__中,而不能出现在实例的 __dict__中。换句话说,Python和Ruby是不一样的!

我很期待有大神的Python元编程高手来纠正我,但我觉得我说的没错。

6

正如@Jonathan所说,描述符(包括属性)是针对类的,而不是针对实例的。所以,要让每个实例有不同的描述符,唯一的方法就是让每个实例自己定义自己的类。这其实很简单,算是元编程中的小事儿;-)...:

class Individualistic(object_or_whatever_bases):
  def __init__(self, whatever_args):
    self.__class__ = type('GottaBeMe', (self.__class__, object), {})
    # keep rocking...!-)

我还特别提到要使用object,因为在Python 2.*中,这是必要的,你也说你在用这个版本!!! 以后不要再用旧式类了,它们在属性等方面的表现不太正常(而且为了向后兼容,它们也做不到这一点——在Python 3中,旧式类终于被淘汰了,所以现在每个类都是新式类,不再需要显式地继承object了!)。

现在,放在self.__class__.__dict__中的任何描述符只会影响这个特定的实例,不会影响其他实例。虽然会有一点额外开销(每个GottaBeMe类和每个实例都有自己的__dict__等),但也没什么太糟糕的。

现在,要满足最初的要求,只需要改动一下:

class WidthVariable:
    def __init__(self, object)
        object.width = property(self.get_width)

(同时合理地重命名object参数,以免覆盖内置名称,并且让类变成新式类,因为你应该总是让每个类都是新式的;-),改成:

class WidthVariable(object):
    def __init__(self, obj)
        obj.__class__.width = property(self.get_width)

看起来并没有什么神秘的东西,对吧!-)

撰写回答