python: атрибут класса / наследование переменных с полиморфизмом? - PullRequest
7 голосов
/ 20 декабря 2011

В моих усилиях в качестве ученика-питона я недавно застрял в каком-то странном (с моей точки зрения) поведении, если попытался работать с атрибутами класса.Я не жалуюсь, но был бы признателен за некоторые полезные комментарии, чтобы пролить свет на этот вопрос.

Чтобы свести сложный вопрос к более краткому вопросу, я бы сформулировал его так:

Чтоявляется "питоническим" способом гарантировать, что атрибут класса ведет себя больше как статическая переменная в дереве наследования?

Мне кажется, что атрибут класса ведет себя как значение по умолчанию "копировать при чтении" сполиморфные характеристики.Пока я выполняю операции «только для чтения», он остается «синглтоном», но как только я получаю доступ к атрибуту класса с помощью присваивания через производный класс или экземпляр, он превращается в новую ссылку, теряя отношение кунаследованная базовая ссылка.

(У этого есть определенный потенциал для некоторых интересных функций, но вы должны понять это, чтобы охватить его, поэтому некоторая проницательность высоко ценится.)

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.

Isэто вопрос сферы или что-то совсем другое.

Ждем ваших ответов и спасибо вперед.: -)


Редактировать: В течение очень короткого времени был дан ответ, предлагающий использовать дескрипторы классов, такие как: Как создать свойство класса? .

К сожалению, это не работает:

class Hotel(Bar):
    def __init__(self):        
        Hotel.bar += 1

hotel = Hotel()
assert hotel.bar == 51
assert hotel.bar == foo.bar

2-е утверждение не выполняется!hotel.bar не ссылается на тот же объект, что и foo.bar, а hotel.bar ссылается на что-то другое, чем Hotel.bar!


2-е редактирование: я прекрасно знаю, что синглтоны считаются "антипаттернами""и я не собирался их использовать (экстенсивно).Поэтому я не упомянул их в вопроснике.Несмотря на то, что существует множество решений, обсуждающих и предлагающих решения с и о синглетонах, мой вопрос остается: почему переменная класса может так легко отсоединить свою ссылку?Ruby ведет себя более естественно для меня: http://snippets.dzone.com/posts/show/6649

Ответы [ 2 ]

3 голосов
/ 20 декабря 2011
a1.classvar = 'aaa'

Это , а не"ссылка" на переменную класса.

Это новая переменная экземпляра в объекте 'a1'.

Выражение типа A.classvar является переменной класса. Все объекты класса (и его суперклассы) имеют словарь уровня класса (A.__dict__) с определенными в нем объектами уровня класса. Разрешение имен работает путем проверки класса, а затем всех суперклассов в порядке разрешения методов (MRO).

Выражение типа a.classvar разрешается путем поиска в пространстве имен объекта. Когда это ссылка для чтения, выполняется поиск объекта и класса (и суперклассов).

Когда это появляется в левой части присваивания, переменная экземпляра ("classvar") просто создается на объекте, на который делается ссылка ("a"). Нет поиска в родительских пространствах имен для разрешения имени, так как нет ничего для разрешения. Он создается.

1 голос
/ 21 декабря 2011

Если реализацию сложно объяснить, это плохая идея.

В данном случае я включаю реализацию для полноты картины, и потому что это своего родахитрая вещь, которая мне нравится в Python.

Таким образом, приведенный ниже фрагмент использует несколько замыканий для создания Class Decorator, который удовлетворяет потребности OP: переменная класса, которая остается «унифицированной» для чтения и записи внутрипроизводные классы.

Кроме того, в качестве бонуса я обертываю атрибут в дескрипторе, который делает атрибут неизменным также и в экземплярах - так что всякий раз, когда атрибут записывается - либо в подкласс, либо в экземпляр подклассаВ исходном классе атрибут class обновляется должным образом.

Как говорит дзен Python: «Если реализацию сложно объяснить, это плохая идея» - я не думаю, что смог бы прийти с чем-нибудьсложнее - здесь мы говорим о динамически генерируемых мета-классах.Он будет работать , но он теряет этот «непифонический» код, поскольку он очень загадочный из-за интенсивного использования механизмов класса, метакласса, замыканий и дескрипторов.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...