python:类属性/变量遗传多态性?

2024-10-01 11:23:02 发布

您现在位置:Python中文网/ 问答频道 /正文

作为一名python学徒,我最近遇到了一些奇怪的(从我的角度来看)行为,如果我试图使用类属性。我不是在抱怨,但希望你能给我一些有用的意见,让我们对这个问题有所了解。在

为了把一个复杂的问题简化成一个更简洁的问题,我可以这样表述:

确保类属性的行为更像继承树中的静态变量的“pythonic”方法是什么?在

在我看来,类属性的行为类似于具有多态特性的“读取时复制”默认值。只要我做“只读”操作,它就一直是“单例”, 但是,当我通过派生类或实例访问带有赋值的class属性时,它就变成了一个新的引用,失去了与继承的基引用的关系。在

(它确实有一些有趣的功能,但你必须理解它 欣然接受,因此我们高度赞赏一些洞察力。)

class A(object):
    classvar = 'A'
    def setclassvar(self, value):
        A.classvar = value                   
    def __str__(self):
        return "%s id(%s) " %(A.classvar, hex(id(A.classvar))[2:-1].upper())

class A1(A):
    pass

class B(object):
    classvar = 'B'
    def setclassvar(self, value):
        self.__class__.classvar = value            
    def __str__(self):
        cvar = self.__class__.classvar
        return "%s id(%s) " %(cvar, hex(id(cvar))[2:-1].upper())

class B1(B):
    def setclassvar(self, value):
        self.__class__.classvar = value

a, a1 = A(), A1()
a1.setclassvar('a')
print "new instance A: %s" %a
print "new instance A1: %s" %a

b, b1 = B(), B1()
b1.setclassvar('bb')
print "new instance B: %s" %b
print "new instance B1: %s" %b1

a1.setclassvar('aa')
print "new value a1: %s" %a
print "new value a: %s" %a

a1.classvar = 'aaa'
print "direct access a1: %s id(%s)" %(a1.classvar, hex(id(a1.classvar))[2:-1].upper())
print "method access a1: %s" %a1
print "direct access a: %s" %a

生成以下内容:

new instance A: a id(B73468A0) 
new instance A1: a id(B73468A0) 
new instance B: B id(B73551C0) 
new instance B1: bb id(AD1BFC) 
new value a1: aa id(AD1BE6) 
new value a: aa id(AD1BE6) 
direct access a1: aaa id(A3A494)
method access a1: aa id(AD1BE6) 
direct access a: aa id(AD1BE6)

因此,直接(分配)访问object.classvar或通过self.__class__.classvar进行中介的访问与BASECLASS.classvar不同。在

这是范围问题还是完全不同。在

期待您的回答和感谢。:-)


编辑:在很短的时间内,有人建议使用类描述符,比如: How to make a class property?。在

不幸的是,这似乎行不通:

^{pr2}$

第二个断言失败!酒店.bar与foo.bar引用的对象不同,hotel.bar引用了其他对象酒店.bar! 在


第二版编辑:我很清楚单例模式被认为是“反模式”,我并不打算(广泛地)使用它们。所以我在问题标题中没有提到它们。尽管有很多解决方案在讨论和提供关于单例的解决方案,但我的问题是:为什么类变量可以如此容易地分离它的引用?Ruby的行为方式在我看来更自然:http://snippets.dzone.com/posts/show/6649


Tags: instanceselfidnew属性accessvaluedef
2条回答

如果实现很难解释,这是个坏主意。

在本例中,为了完整起见,我包含了一个实现,这是我在Python中喜欢的一种技巧。在

因此,代码片段bellow在某种程度上滥用了闭包的功能,从而提出了一个满足O.p.需求的类装饰器:一个在派生类中读写保持“统一”的类变量。在

此外,作为一个额外的好处,我将属性包装在一个描述符中,这使得属性在实例中也是不可更改的——因此每当属性被写入原始类的子类或子类的实例中时,类属性都会被正确更新。在

正如Python的Zen所说:“如果实现很难解释,这是一个坏主意”—我不认为我可以用更难的方法来解释这里所说的范围动态生成的元类。它将工作,但它会使这是“非语法”的代码变得松散,因为大量使用类、元类、闭包和描述符机制,因此非常神秘。在

def SingletonAttrs(**names):
    keys = names.keys()
    def class_decorator(cls):
        class Meta(type):
            def __getattribute__(cls, attr):
                if attr in keys:
                    return type.__getattribute__(owner_cls,  attr)
                return type.__getattribute__(cls, attr)
            def __setattr__(cls, attr, value):
                if attr in keys:
                    class Wrapper(object):
                        def __init__(self, value):
                            self.__set__(None, value)
                        __set__ = lambda self, instance, value: setattr(owner_cls,"__" +  attr, value)
                        __get__ = lambda self, instance, owner: type.__getattribute__(owner_cls, "__" + attr)
                    return type.__setattr__(owner_cls,  attr, Wrapper(value))
                return type.__setattr__(cls, attr, value)
        owner_cls = Meta(cls.__name__, cls.__bases__, cls.__dict__.copy())
        for key in keys:
            setattr(owner_cls, key, names[key])
        return owner_cls
    return class_decorator

if __name__ == "__main__":

    @SingletonAttrs(a="value 1", b="value 2")
    class Test(object):
        pass

    class TestB(Test):
        pass

    t = Test()
    print t.a
    print t.b
    tb = TestB()
    tb.a = "value 3"
    print Test.a
a1.classvar = 'aaa'

这是对类变量的“引用”。在

这是对象“a1”中的新实例变量。在

A.classvar这样的表达式是类变量。类对象(及其超类)都有一个类级字典(A.__dict__),其中定义了类级对象。名称解析的工作方式是检查类,然后按方法解析顺序(MRO)检查所有超级类。在

a.classvar这样的表达式是通过搜索对象的命名空间来解析的。当这是一个“reading”引用时,将搜索对象和类(以及超类)。在

当它出现在赋值的左边时,实例变量(“classvar”)只是在被引用对象(“a”)上创建的。没有搜索父名称空间来解析名称,因为没有要解析的内容。它正在被创造。在

相关问题 更多 >