2017-04-19 4 views
1

@property은 게터를 정의하는 좋은 방법입니다. 속성이 변경 가능하면 반환 된 참조를 사용하여 클래스 정의로 제어되지 않는 방식으로 속성을 수정할 수 있습니다. 동기 부여 비유로 바나나 스탠드를 사용 하겠지만이 문제는 컨테이너를 래핑하는 모든 클래스에 적용됩니다.클래스 속성을 변경하지 못하게하려면 어떻게해야합니까?

class BananaStand: 
    def __init__(self): 
     self._money = 0 
     self._bananas = ['b1', 'b2'] 

    @property 
    def bananas(self): 
     return self._bananas 

    def buy_bananas(self, money): 
     change = money 
     basket = [] 
     while change >= 1 and self._bananas: 
      change -= 1 
      basket.append(self._bananas.pop()) 
      self._money += 1 
     return change, basket 

바나나 스탠드 방문객들이 바나나를 지불하고 싶습니다. 불행히도, 내 바나나 중 하나를 복용에서 원숭이를 (누가 더 잘 모르는) 멈추는 아무것도. 원숭이는 내부 특성 인 _banana을 사용할 필요가 없었으며, 지불하지 않고 바나나를 가져 왔습니다.

def take_banana(banana_stand): 
    return banana_stand.bananas.pop() 

>>> stand = BananaStand() 
>>> stand.bananas 
['b1', 'b2'] 
>>> take_banana(stand) 
'b2' 
>>> stand.bananas 
['b1'] 

이 비유가 좀 바보이지만, 변경 가능한 속성이있는 클래스는 우발적 인 파괴 행위로부터 보호되지 않습니다. 내 실제 경우에는 동일한 길이를 유지해야하는 두 개의 배열 특성이있는 클래스가 있습니다.

>>> from array import array 
>>> x = array('f', [1,2,3]) 
>>> x 
array('f', [1.0, 2.0, 3.0]) 
>>> x[1:2] = array('f', [4,5,6]) 
>>> x 
array('f', [1.0, 4.0, 5.0, 6.0, 3.0]) 

배열 속성 인 경우에 동일한 behavour가 발생 어레이와, 제 1 및 자동 불변 내 동일한 크기 파괴에 제 2 어레이를 배분하고에서 사용자를 막는 것도 없다.

나는 피하는 문제의 두 가지 방법을 생각할 수 있습니다

  1. 서브 클래스 배열과 __setitem__ 우선합니다. 나는이 배열 접합 동작을 내부적으로 사용할 수 있기를 원하기 때문에 이에 저항한다.
  2. 접근자를 변경하여 배열의 deepcopy를 반환하십시오. 반환 된 배열은 여전히 ​​변경할 수 있지만 변경된 배열은 부모 개체에 영향을주지 않습니다.

이 문제를 해결하려면 우아한 방법이 있습니까? 저는 속성을 하위 클래스 화하는 멋진 방법에 특히 관심이 있습니다.

답변

2

제안한 두 가지 방법 모두 좋은 아이디어입니다. 한 번만 더 보자. 튜플! 튜플은 변경 불가능합니다.

@property 
def bananas(self): 
    return tuple(self._bananas) 

이제 이러한 대안을 가지고, 다른 통해 하나 선택하는 동안 것들 몇 가지를 염두에두고있다 :

  • 목록이 작은을 당신은 O와 괜찮아 접근 자? 튜플을 선택하십시오. 대부분의 경우 소비자는 차이점을 보지 않을 것입니다. (물론 그가 돌연변이를 시도하지 않는 한)
  • 목록 바나나에는 일반 list이 부족한 몇 가지 특별한 능력이 필요합니까? 목록을 서브 클래스 화하고 변경 함수에 대한 예외를 발생시킵니다. [1]

[1] : jsbueno에는 O (n) 오버 헤드가없는 nice ReadOnlyList implementation가 있습니다.

1

오랜 시간이 걸렸지 만, answer에 제공된 조리법을 기반으로 매우 강력하고 유연한 솔루션을 만들었다 고 생각합니다.

from array import array 
from collections import MutableSequence 
from inspect import getmembers 

class Wrapper(type): 
    __wraps__ = None 
    __ignore__ = { 
     '__class__', '__mro__', '__new__', '__init__', '__dir__', 
     '__setattr__', '__getattr__', '__getattribute__',} 
    __hide__ = None 

    def __init__(cls, name, bases, dict_): 
     super().__init__(name, bases, dict_) 
     def __init__(self, obj): 
      if isinstance(obj, cls.__wraps__): 
       self._obj = obj 
       return 
      raise TypeError(
       'wrapped obj must be of type {}'.format(cls.__wraps__)) 
     setattr(cls, '__init__', __init__) 

     @property 
     def obj(self): 
      return self._obj 
     setattr(cls, 'obj', obj) 

     def __dir__(self): 
      return list(set(dir(self.obj)) - set(cls.__hide__)) 
     setattr(cls, '__dir__', __dir__) 

     def __getattr__(self, name): 
      if name in cls.__hide__: 
       return 
      return getattr(self.obj, name) 
     setattr(cls, '__getattr__', __getattr__) 

     for name, _ in getmembers(cls.__wraps__, callable): 
      if name not in cls.__ignore__ \ 
        and name not in cls.__hide__ \ 
        and name.startswith('__') \ 
        and name not in dict_: 
       cls.__add_method__(name) 

    def __add_method__(cls, name): 
     method_str = \ 
      'def {method}(self, *args, **kwargs):\n'    \ 
      '  return self.obj.{method}(*args, **kwargs)\n' \ 
      'setattr(cls, "{method}", {method})'.format(method=name) 
     exec(method_str) 


class FixLen(metaclass=Wrapper): 
    __wraps__ = MutableSequence 
    __hide__ = { 
     '__delitem__', '__iadd__', 'append', 'clear', 'extend', 'insert', 
     'pop', 'remove', 
    } 

    # def _slice_size(self, slice): 
    #  start, stop, stride = key.indices(len(self.obj)) 
    #  return (stop - start)//stride 

    def __setitem__(self, key, value): 
     if isinstance(key, int): 
      return self.obj.__setitem__(key, value) 
     #if self._slice_size(key) != len(value): 
     if (lambda a, b, c: (b - a)//c)(*key.indices(len(self.obj))) \ 
      != len(value): 
      raise ValueError('input sequences must have same length') 
     return self.obj.__setitem__(key, value) 

FixLen 당신이 생성자에 전달하고 블록에 대한 액세스 가변 시퀀스 내부 참조를 유지, 또는의 길이를 변경 방법의 대체 정의를 제공 자부심과 함께, 나는 FixLen 래퍼를 제공 목적. 이렇게하면 길이를 내부적으로 변경할 수는 있지만 속성으로 전달 될 때 시퀀스의 길이가 수정되지 않도록 보호 할 수 있습니다. 완벽하지는 않습니다 (FixLen 서브 클래스 Sequence해야한다고 생각합니다).

사용 예제 :

>>> import fixlen 
>>> x = [1,2,3,4,5] 
>>> y = fixlen.FixLen(x) 
>>> y 
[1, 2, 3, 4, 5] 
>>> y[1] 
2 
>>> y[1] = 100 
>>> y 
[1, 100, 3, 4, 5] 
>>> x 
[1, 100, 3, 4, 5] 
>>> y.pop() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'NoneType' object is not callable 
관련 문제