2011-12-20 3 views
6

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

Чтобы уменьшить сложный вопрос в более лаконичный вопрос я бы сформулировал это следующим образом:

Что такое «вещий» способ гарантировать, что класс-атрибут ведет себя больше как статические переменный в дереве наследования?

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

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

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.

Является ли это проблемой сферы или что-то совсем другое.

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


Edit: Был ответ в течение очень короткого промежутка времени предполагая использование класса-дескрипторов, как: How to make a class property?.

Unfortunatly, что не похоже на работу:

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 ссылки somethin other then Hotel.bar!


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

+0

Ищите Python Singleton. (а) на это был дан ответ, и (б) обычно очень сложно попытаться создать Синглтонов. http://stackoverflow.com/search?q=python+singleton –

+0

Возможный дубликат [Создание синглета в python] (http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) –

+0

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

ответ

1

Если внедрение трудно объяснить, это плохая идея.

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

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

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

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

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 
+0

У меня есть непрестанное чувство, что его можно добиться с помощью кода на порядок проще. Но это только что пришло мне в голову. – jsbueno

+0

спасибо. Отличный пример. Я многому научился, просто выполнив вашу реализацию. Так что просто «ради полноты», и для этого я представляю собой сложную вещь, которая мне нравится ». Действительно ли я в своем предположении, что ваше решение даже сборщик мусора -« безопасно »? –

2
a1.classvar = 'aaa' 

Это не «Ссылка» в переменную класса.

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

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

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

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

+0

Отлично! Это именно то, что я подозревал. Так что у них нет простого выхода? –

+0

Так что, если я хочу избежать использования BASECLASS.classvar все время, я бы либо имел a) для доступа к классу-словарю BASECLASS, или b) получить ссылку на исходный объект, прежде чем переназначить его? Подождите, я думаю, b) невозможно, потому что я бы создал новую переменную с новым объектом, связанным в более высоком пространстве имен, если не использовал BASECLASS .__ dict__ ?! так, может быть, c) определить свойство в BASECLASS, инкапсулирующем BASECLASS.classvar? –

+0

'class A (объект): _classvar = SOMEVALUE; ... classvar = свойство (getter, setter) ' –

Смежные вопросы