2015-07-22 4 views
2

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

from __future__ import print_function # For lambda print() 

class CallList(list): 
    def __call__(self, *args, **kwargs): 
     for f in self: 
      f(*args, **kwargs) 

class State:  
    on_enter = CallList() 
    def enter(self): 
     self.on_enter() 

class Opening(State): pass 
class Closing(State): pass 

Opening.on_enter.append(lambda: print('Opening state entered')) 
Closing.on_enter.append(lambda: print('Closing state entered')) 

но поведение подклассов и экземпляров подкласса, чтобы ссылаться на переменном класс базовых классов, что дает мне следующее:

opening = Opening() 
closing = Closing() 

opening.enter() 
# Actual output: Opening state entered 
#     Closing state entered 
# Desired output: Opening state entered 

opening.on_enter.append(lambda: print('Additional instance callback')) 
opening.enter() 
# Actual output: Opening state entered 
#     Closing state entered 
#     Additional instance callback 
# Desired output: Opening state entered 
#     Additional instance callback 

closing.enter() 
# Actual output: Opening state entered 
#     Closing state entered 
#     Additional instance callback 
# Desired output: Closing state entered 

Я понимаю, почему это (я ожидал, что что-то отличное от опыта - это другие языки, но это прекрасно).

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

+2

Я не понимаю, почему открытия и закрытие подклассов, и почему on_enter должны быть переменным класс. Если on_enter - это переменная экземпляра, а открытие и закрытие - это экземпляры состояния, все будет работать нормально. – RemcoGerlich

+0

Возможно, вы можете написать метакласс для базового класса, который гарантирует, что подклассы не «наследуют» определенные переменные. – doublep

+0

Вы можете сделать каждый * класс * отдельным атрибутом класса с метаклассом (см., Например, http://stackoverflow.com/q/100003/3001761), но если каждый * экземпляр * должен иметь отдельный атрибут, почему бы не сделать его атрибут экземпляра? – jonrsharpe

ответ

1

Прежде всего, начать использовать new-style classes унаследовав от объекта:

class State(object):  
    on_enter = CallList() 
    def enter(self): 
     self.on_enter() 

классов старого стиля старые, и то, что я собираюсь предложить не будет работать там.

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

import weakref 

class CallListDescriptor(object): 
    def __init__(self): 
     self._class_level_values = weakref.WeakKeyDictionary() 

    def __get__(self, instance, type): 
     if instance is None: 
      # class-level access 
      try: 
       return self._class_level_values[type] 
      except KeyError: 
       default = CallList() 
       self._class_level_values[type] = default 
       return default 
     # instance-level access 
     return instance._call_lists.setdefault(self, CallList()) 


class State(object): 
    def __init__(self): 
     # for the instance-level values 
     self._call_lists = {} 

    on_enter = CallListDescriptor() 

Мы используем weakref для уровня класса атрибутов, чтобы гарантировать, что подклассы могут получить мусора должным образом, пока суперкласс еще находится в области видимости.

Мы можем проверить, что это работает:

class SubState(State): 
    pass 

class OtherSubState(State): 
    pass 

assert State.on_enter is State.on_enter 
assert State.on_enter is not SubState.on_enter 
assert State.on_enter is not OtherSubState.on_enter 
assert SubState.on_enter is SubState.on_enter 

instance = SubState() 
assert instance.on_enter is not SubState.on_enter 

Я бы, однако, рекомендую, чтобы избавиться от функции подкласса и просто обеспечить экземпляр отдельных значений, а затем представлять состояние как экземпляров из State вместо подклассов (за исключение если у вас есть хорошая причина, чтобы не, что там вполне может быть):

class CallListDescriptor(object): 
    def __get__(self, instance, type): 
     if instance is None: 
      return self 

     return instance._call_lists.setdefault(self, CallList()) 

class State(object): 
    def __init__(self): 
     # for the instance-level values 
     self._call_lists = {} 
+0

Это выглядит очень красиво - единственное, что он не делает, однако, имеет экземпляр Closing, наследующий действия, которые я назначил классу Closing. Я хотел (в моем первоначальном примере) для открытия.введите() 'для печати« Введенное состояние открытия », потому что я привязал это действие к открытию. –

+0

Что касается подклассов класса State design: это упрощенный пример того, над чем я работаю - это эксперимент, но до сих пор я получаю хорошее средство от подклассов/примеров, поэтому я думаю, что я На этот раз я буду придерживаться этого. –

+0

Я не читал требование наследования от вопроса, это еще один блок работы. Вероятно, вы можете использовать ['' __mro__''] (https://docs.python.org/2/library/stdtypes.html#class.__mro__) (который содержит все базовые классы в порядке их порядка поиска) '' type'' внутри '' __get__''. Не уверен, можете ли вы понять это здесь, если нет, я предлагаю вам задать новый вопрос. В верхней части моей головы я не уверен, что это можно решить без специального метакласса, хотя, возможно, потребуется узнать имя атрибута. –

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