2013-07-31 43 views
8

Có rất nhiều getattr tốt() - chức năng như để phân tích cấu trúc điển lồng nhau, chẳng hạn như:Python setattr đệ quy() - chức năng như để làm việc với các từ điển lồng nhau

Tôi muốn tạo một setattr song song(). Về cơ bản, đưa ra:

cmd = 'f[0].a' 
val = 'whatever' 
x = {"a":"stuff"} 

Tôi muốn tạo ra một chức năng như vậy mà tôi có thể gán:

x['f'][0]['a'] = val 

Nhiều hơn hoặc ít hơn, điều này sẽ làm việc theo cách tương tự như:

setattr(x,'f[0].a',val) 

để mang lại:

>>> x 
{"a":"stuff","f":[{"a":"whatever"}]} 

Tôi là c urrently gọi đó là setByDot():

setByDot(x,'f[0].a',val) 

Một vấn đề với điều này là nếu một phím ở giữa không tồn tại, bạn cần phải kiểm tra và thực hiện một chính trung gian nếu nó không tồn tại --- tức là, cho ở trên:

>>> x = {"a":"stuff"} 
>>> x['f'][0]['a'] = val 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
KeyError: 'f' 

vì vậy, trước tiên bạn phải thực hiện:

>>> x['f']=[{}] 
>>> x 
{'a': 'stuff', 'f': [{}]} 
>>> x['f'][0]['a']=val 
>>> x 
{'a': 'stuff', 'f': [{'a': 'whatever'}]} 

khác là keying cho khi mục tiếp theo là một danh sách sẽ khác so với keying khi mục tiếp theo là một chuỗi, tức là:

>>> x = {"a":"stuff"} 
>>> x['f']=[''] 
>>> x['f'][0]['a']=val 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'str' object does not support item assignment 

... không thành công vì gán cho chuỗi rỗng thay vì một giá trị rỗng. Các dict null sẽ được chuyển nhượng quyền cho mỗi phi danh sách trong dict cho đến khi người cuối cùng --- mà có thể là một danh sách, hoặc một giá trị.

Vấn đề thứ hai, được nêu trong phần bình luận bên dưới bởi @TokenMacGuy, là khi bạn phải tạo danh sách không tồn tại, bạn có thể phải tạo ra rất nhiều giá trị trống. Vì vậy,

setattr(x,'f[10].a',val) 

--- có nghĩa là thuật toán sẽ phải thực hiện một như trung gian:

>>> x['f']=[{},{},{},{},{},{},{},{},{},{},{}] 
>>> x['f'][10]['a']=val 

để mang

>>> x 
{"a":"stuff","f":[{},{},{},{},{},{},{},{},{},{},{"a":"whatever"}]} 

như rằng đây là setter gắn liền với getter ...

>>> getByDot(x,"f[10].a") 
"whatever" 

Thêm quan trọng, các trung gian nên/không/ghi đè các giá trị đã tồn tại.

Dưới đây là ý tưởng junky tôi có cho đến nay --- Tôi có thể xác định danh sách so với dicts và các loại dữ liệu khác và tạo chúng ở nơi chúng không tồn tại. Tuy nhiên, tôi không thấy (a) nơi đặt cuộc gọi đệ quy, hoặc (b) cách 'xây dựng' đối tượng sâu khi tôi lặp qua danh sách, và (c) cách phân biệt/thăm dò/tôi làm như tôi xây dựng các đối tượng sâu từ/thiết lập/tôi phải làm gì khi tôi đạt đến cuối của ngăn xếp.

def setByDot(obj,ref,newval): 
    ref = ref.replace("[",".[") 
    cmd = ref.split('.') 
    numkeys = len(cmd) 
    count = 0 
    for c in cmd: 
     count = count+1 
     while count < numkeys: 
      if c.find("["): 
       idstart = c.find("[") 
       numend = c.find("]") 
       try: 
        deep = obj[int(idstart+1:numend-1)] 
       except: 
        obj[int(idstart+1:numend-1)] = [] 
        deep = obj[int(idstart+1:numend-1)] 
      else: 
       try: 
        deep = obj[c] 
       except: 
        if obj[c] isinstance(dict): 
         obj[c] = {} 
        else: 
         obj[c] = '' 
        deep = obj[c] 
     setByDot(deep,c,newval) 

Điều này có vẻ rất phức tạp vì bạn cần phải kiểm tra loại/tiếp theo/đối tượng nếu bạn đang tạo địa điểm và bạn phải nhìn sau để xây dựng đường dẫn lên như bạn đi.

CẬP NHẬT

thời gian gần đây tôi đã this question trả lời, quá, mà có thể có liên quan hoặc hữu ích.

Trả lời

2

Tôi đã tách riêng phần này thành hai bước. Trong bước đầu tiên, chuỗi truy vấn được chia nhỏ thành một loạt các hướng dẫn. Bằng cách này, vấn đề được tách riêng, chúng ta có thể xem hướng dẫn trước khi chạy chúng, và không cần phải gọi đệ quy.

def build_instructions(obj, q): 
    """ 
    Breaks down a query string into a series of actionable instructions. 

    Each instruction is a (_type, arg) tuple. 
    arg -- The key used for the __getitem__ or __setitem__ call on 
      the current object. 
    _type -- Used to determine the data type for the value of 
      obj.__getitem__(arg) 

    If a key/index is missing, _type is used to initialize an empty value. 
    In this way _type provides the ability to 
    """ 
    arg = [] 
    _type = None 
    instructions = [] 
    for i, ch in enumerate(q): 
     if ch == "[": 
      # Begin list query 
      if _type is not None: 
       arg = "".join(arg) 
       if _type == list and arg.isalpha(): 
        _type = dict 
       instructions.append((_type, arg)) 
       _type, arg = None, [] 
      _type = list 
     elif ch == ".": 
      # Begin dict query 
      if _type is not None: 
       arg = "".join(arg) 
       if _type == list and arg.isalpha(): 
        _type = dict 
       instructions.append((_type, arg)) 
       _type, arg = None, [] 

      _type = dict 
     elif ch.isalnum(): 
      if i == 0: 
       # Query begins with alphanum, assume dict access 
       _type = type(obj) 

      # Fill out args 
      arg.append(ch) 
     else: 
      TypeError("Unrecognized character: {}".format(ch)) 

    if _type is not None: 
     # Finish up last query 
     instructions.append((_type, "".join(arg))) 

    return instructions 

Ví dụ bạn

>>> x = {"a": "stuff"} 
>>> print(build_instructions(x, "f[0].a")) 
[(<type 'dict'>, 'f'), (<type 'list'>, '0'), (<type 'dict'>, 'a')] 

Giá trị lợi nhuận kỳ vọng chỉ đơn giản là (mục đầu tiên) _type của tuple tiếp theo trong hướng dẫn. Điều này rất quan trọng vì nó cho phép chúng ta khởi tạo lại chính xác/tái tạo lại các phím bị thiếu.

Điều này có nghĩa là lệnh đầu tiên của chúng tôi hoạt động trên dict, hoặc đặt hoặc nhận khóa 'f' và dự kiến ​​sẽ trả lại list. Tương tự như vậy, lệnh thứ hai của chúng tôi hoạt động trên list, hoặc đặt hoặc nhận chỉ mục 0 và dự kiến ​​trả về dict.

Bây giờ, hãy tạo chức năng _setattr của chúng tôi. Điều này có được hướng dẫn thích hợp và đi qua chúng, tạo ra các cặp khóa-giá trị khi cần thiết. Cuối cùng, nó cũng thiết lập các val chúng tôi cung cấp cho nó.

def _setattr(obj, query, val): 
    """ 
    This is a special setattr function that will take in a string query, 
    interpret it, add the appropriate data structure to obj, and set val. 

    We only define two actions that are available in our query string: 
    .x -- dict.__setitem__(x, ...) 
    [x] -- list.__setitem__(x, ...) OR dict.__setitem__(x, ...) 
      the calling context determines how this is interpreted. 
    """ 
    instructions = build_instructions(obj, query) 
    for i, (_, arg) in enumerate(instructions[:-1]): 
     _type = instructions[i + 1][0] 
     obj = _set(obj, _type, arg) 

    _type, arg = instructions[-1] 
    _set(obj, _type, arg, val) 

def _set(obj, _type, arg, val=None): 
    """ 
    Helper function for calling obj.__setitem__(arg, val or _type()). 
    """ 
    if val is not None: 
     # Time to set our value 
     _type = type(val) 

    if isinstance(obj, dict): 
     if arg not in obj: 
      # If key isn't in obj, initialize it with _type() 
      # or set it with val 
      obj[arg] = (_type() if val is None else val) 
     obj = obj[arg] 
    elif isinstance(obj, list): 
     n = len(obj) 
     arg = int(arg) 
     if n > arg: 
      obj[arg] = (_type() if val is None else val) 
     else: 
      # Need to amplify our list, initialize empty values with _type() 
      obj.extend([_type() for x in range(arg - n + 1)]) 
     obj = obj[arg] 
    return obj 

Và chỉ vì chúng tôi có thể, đây là chức năng _getattr.

def _getattr(obj, query): 
    """ 
    Very similar to _setattr. Instead of setting attributes they will be 
    returned. As expected, an error will be raised if a __getitem__ call 
    fails. 
    """ 
    instructions = build_instructions(obj, query) 
    for i, (_, arg) in enumerate(instructions[:-1]): 
     _type = instructions[i + 1][0] 
     obj = _get(obj, _type, arg) 

    _type, arg = instructions[-1] 
    return _get(obj, _type, arg) 


def _get(obj, _type, arg): 
    """ 
    Helper function for calling obj.__getitem__(arg). 
    """ 
    if isinstance(obj, dict): 
     obj = obj[arg] 
    elif isinstance(obj, list): 
     arg = int(arg) 
     obj = obj[arg] 
    return obj 

Trong hành động:

>>> x = {"a": "stuff"} 
>>> _setattr(x, "f[0].a", "test") 
>>> print x 
{'a': 'stuff', 'f': [{'a': 'test'}]} 
>>> print _getattr(x, "f[0].a") 
"test" 

>>> x = ["one", "two"] 
>>> _setattr(x, "3[0].a", "test") 
>>> print x 
['one', 'two', [], [{'a': 'test'}]] 
>>> print _getattr(x, "3[0].a") 
"test" 

Bây giờ cho một số nội dung mát mẻ. Không giống như python, chức năng _setattr của chúng tôi có thể đặt các phím dict không thể sửa được.

x = [] 
_setattr(x, "1.4", "asdf") 
print x 
[{}, {'4': 'asdf'}] # A list, which isn't hashable 

>>> y = {"a": "stuff"} 
>>> _setattr(y, "f[1.4]", "test") # We're indexing f with 1.4, which is a list! 
>>> print y 
{'a': 'stuff', 'f': [{}, {'4': 'test'}]} 
>>> print _getattr(y, "f[1.4]") # Works for _getattr too 
"test" 

Chúng tôi không thực sự sử dụng unhashable dict phím, nhưng có vẻ như chúng tôi đang trong ngôn ngữ truy vấn của chúng tôi để những người quan tâm, phải không!

Cuối cùng, bạn có thể chạy nhiều cuộc gọi _setattr cùng một đối tượng, chỉ cần dùng thử.

+0

Điều này trông rất tuyệt, nhưng dường như không hiệu quả với tôi. '>>> x = {" a ":" công cụ "} >>> _setattr (x," f [0] .a "," test ") >>> x {'a': 'stuff' } ' – Mittenchops

+0

@Mittenchops Vì lý do nào đó' build_instructions' mà tôi đã lưu khác với những gì trên SO. Tôi đã cập nhật nó nên nó sẽ hoạt động ngay bây giờ. – FastTurtle

+0

@Mittenchops Haha, hóa ra tôi đã chuyển các đối số trong 'build_instructions' một cách tình cờ. Hãy cho tôi biết nếu vẫn còn bất kỳ vấn đề! – FastTurtle

1
>>> class D(dict): 
...  def __missing__(self, k): 
...   ret = self[k] = D() 
...   return ret 
... 
>>> x=D() 
>>> x['f'][0]['a'] = 'whatever' 
>>> x 
{'f': {0: {'a': 'whatever'}}} 
+0

Hmm, tôi thích điều đó/rất đơn giản, và nó gần, nhưng x sẽ cần phải trả về '{" f ": [{" a ":" bất kỳ "}]}' thay vì '{' f ': {0: {' a ':' bất cứ điều gì '}}} 'trong đó trung gian là 0 của danh sách, thay vì giá trị được đánh số là 0.Tôi nghĩ rằng tôi có thể làm việc với điều này, hoặc có lẽ cách tiếp cận chung của việc dùng một trình phân tích cú pháp dict-to-object hoặc một cái gì đó ... – Mittenchops

+1

những gì nên 'x = D(); x ['f'] [100] ['a' ] = 'whatevs'' làm gì, nếu danh sách của nó thay vì một dict? – SingleNegationElimination

+0

Vâng, điểm tốt, nhưng tôi đoán nó sẽ cần phải tạo núm vú giả cho các hạng mục 0 đến 99. Ngoài ra điểm tốt mà có thể có nghĩa là nếu bạn chỉ định chúng theo thứ tự x ['f'] [100] ['a'] = 'whatevs', x ['f'] [99] ['a'] = 'more', bạn có thể ghi đè lên bất kỳ giá trị nào bạn đã thực hiện cho 99, nếu bạn điền vào một chuỗi rỗng hoặc danh sách trống hoặc dict trống. – Mittenchops

2

Bạn có thể hack một cái gì đó với nhau bằng cách sửa chữa hai vấn đề:

  1. Danh sách tự động tăng lên khi truy cập ngoài giới hạn (PaddedList)
  2. Một cách để trì hoãn việc quyết định những gì để tạo ra (danh sách của dict) cho đến khi bạn truy cập nó lần đầu tiên (DictOrList)

Vì vậy, mã sẽ như sau:

import collections 

class PaddedList(list): 
    """ List that grows automatically up to the max index ever passed""" 
    def __init__(self, padding): 
     self.padding = padding 

    def __getitem__(self, key): 
     if isinstance(key, int) and len(self) <= key: 
      self.extend(self.padding() for i in xrange(key + 1 - len(self))) 
     return super(PaddedList, self).__getitem__(key) 

class DictOrList(object): 
    """ Object proxy that delays the decision of being a List or Dict """ 
    def __init__(self, parent): 
     self.parent = parent 

    def __getitem__(self, key): 
     # Type of the structure depends on the type of the key 
     if isinstance(key, int): 
      obj = PaddedList(MyDict) 
     else: 
      obj = MyDict() 

     # Update parent references with the selected object 
     parent_seq = (self.parent if isinstance(self.parent, dict) 
         else xrange(len(self.parent))) 
     for i in parent_seq: 
      if self == parent_seq[i]: 
       parent_seq[i] = obj 
       break 

     return obj[key] 


class MyDict(collections.defaultdict): 
    def __missing__(self, key): 
     ret = self[key] = DictOrList(self) 
     return ret 

def pprint_mydict(d): 
    """ Helper to print MyDict as dicts """ 
    print d.__str__().replace('defaultdict(None, {', '{').replace('})', '}') 

x = MyDict() 
x['f'][0]['a'] = 'whatever' 

y = MyDict() 
y['f'][10]['a'] = 'whatever' 

pprint_mydict(x) 
pprint_mydict(y) 

Và đầu ra của x và y sẽ là:

{'f': [{'a': 'whatever'}]} 
{'f': [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {'a': 'whatever'}]} 

Bí quyết bao gồm việc tạo ra một defaultdict của các đối tượng đó có thể là một dict hoặc một danh sách tuỳ theo cách bạn truy cập vào nó. Vì vậy, khi bạn có assigment x['f'][10]['a'] = 'whatever' nó sẽ hoạt động theo cách sau:

  1. Nhận X ['f']. Nó sẽ không tồn tại để nó sẽ trả về một đối tượng DictOrList cho chỉ mục 'f'
  2. Nhận X ['f'] [10]. DictOrList. getitem sẽ được gọi với chỉ số nguyên. Đối tượng DictOrList sẽ thay thế chính nó trong bộ sưu tập cha bằng một phần tử PaddedList
  3. Truy cập phần tử thứ 11 trong danh sách PaddedList sẽ phát triển thành phần 11 thành phần và sẽ trả về phần tử MyDict ở vị trí đó
  4. Gán "bất cứ điều gì" cho x ['f '] [10] [' a ']

Cả hai danh sách PaddedList và DictOrList đều không có nhiều ma thuật, bạn có cấu trúc dicts và danh sách.

+0

Xin lỗi, tôi không hiểu --- bạn có thể chỉ cho tôi cách làm việc này như một setter, đi từ 'function (x, 'f [10] .a', val)' thành 'x ['f'] [ 10] ['a'] = val = 'bất cứ điều gì''? – Mittenchops

+0

Bạn cũng có thể triển khai danh sách đệm dưới dạng default_dict, với giả định rằng các chỉ mục sẽ là int và rằng \ _ \ _ iter \ _ \ _ sẽ trả về itervalues ​​(). – dbn

+0

@dbw Tôi đã nghĩ về nó, nhưng thích sử dụng danh sách hơn vì không chắc chắn cách sử dụng danh sách (ví dụ: cắt, phân loại ...) – barracel

1

Có thể tổng hợp đệ quy các mục/thuộc tính bằng cách ghi đè __getitem__ để trả lại một proxy có thể đặt giá trị trong hàm ban đầu.

Tôi tình cờ làm việc trên một thư viện thực hiện một vài điều tương tự như vậy, vì vậy tôi đã làm việc trên một lớp có thể tự động gán các lớp con của riêng mình vào lúc khởi tạo. Nó làm cho việc này trở nên dễ dàng hơn, nhưng nếu kiểu tấn công đó làm bạn bối rối, bạn có thể có được hành vi tương tự bằng cách tạo một ProxyObject tương tự với cái tôi tạo và bằng cách tạo các lớp riêng được sử dụng bởi ProxyObject động trong một hàm . Một cái gì đó như

class ProxyObject(object): 
    ... #see below 

def instanciateProxyObjcet(val): 
    class ProxyClassForVal(ProxyObject,val.__class__): 
     pass 
    return ProxyClassForVal(val) 

Bạn có thể sử dụng từ điển như tôi đã sử dụng trong FlexibleObject dưới đây sẽ làm cho việc triển khai đó hiệu quả hơn đáng kể nếu bạn thực hiện nó. Mã mà tôi sẽ cung cấp sử dụng FlexibleObject. Ngay bây giờ nó chỉ hỗ trợ các lớp học, giống như hầu như tất cả các lớp nội trang của Python đều có khả năng được tạo ra bằng cách lấy một cá thể của chính họ làm đối số duy nhất của chúng cho __init__/__new__ của chúng. Trong một hoặc hai tuần tới, tôi sẽ thêm hỗ trợ cho bất kỳ thứ gì có thể chọn và liên kết đến kho lưu trữ github có chứa nó.Dưới đây là các mã:

class FlexibleObject(object): 
    """ A FlexibleObject is a baseclass for allowing type to be declared 
     at instantiation rather than in the declaration of the class. 

     Usage: 
     class DoubleAppender(FlexibleObject): 
      def append(self,x): 
       super(self.__class__,self).append(x) 
       super(self.__class__,self).append(x) 

     instance1 = DoubleAppender(list) 
     instance2 = DoubleAppender(bytearray) 
    """ 
    classes = {} 
    def __new__(cls,supercls,*args,**kws): 
     if isinstance(supercls,type): 
      supercls = (supercls,) 
     else: 
      supercls = tuple(supercls) 
     if (cls,supercls) in FlexibleObject.classes: 
      return FlexibleObject.classes[(cls,supercls)](*args,**kws) 
     superclsnames = tuple([c.__name__ for c in supercls]) 
     name = '%s%s' % (cls.__name__,superclsnames) 
     d = dict(cls.__dict__) 
     d['__class__'] = cls 
     if cls == FlexibleObject: 
      d.pop('__new__') 
     try: 
      d.pop('__weakref__') 
     except: 
      pass 
     d['__dict__'] = {} 
     newcls = type(name,supercls,d) 
     FlexibleObject.classes[(cls,supercls)] = newcls 
     return newcls(*args,**kws) 

Sau đó sử dụng điều này để sử dụng để tổng hợp nhìn lên các thuộc tính và các mặt hàng của một đối tượng từ điển giống như bạn có thể làm một cái gì đó như thế này:

class ProxyObject(FlexibleObject): 
    @classmethod 
    def new(cls,obj,quickrecdict,path,attribute_marker): 
     self = ProxyObject(obj.__class__,obj) 
     self.__dict__['reference'] = quickrecdict 
     self.__dict__['path'] = path 
     self.__dict__['attr_mark'] = attribute_marker 
     return self 
    def __getitem__(self,item): 
     path = self.__dict__['path'] + [item] 
     ref = self.__dict__['reference'] 
     return ref[tuple(path)] 
    def __setitem__(self,item,val): 
     path = self.__dict__['path'] + [item] 
     ref = self.__dict__['reference'] 
     ref.dict[tuple(path)] = ProxyObject.new(val,ref, 
       path,self.__dict__['attr_mark']) 
    def __getattribute__(self,attr): 
     if attr == '__dict__': 
      return object.__getattribute__(self,'__dict__') 
     path = self.__dict__['path'] + [self.__dict__['attr_mark'],attr] 
     ref = self.__dict__['reference'] 
     return ref[tuple(path)] 
    def __setattr__(self,attr,val): 
     path = self.__dict__['path'] + [self.__dict__['attr_mark'],attr] 
     ref = self.__dict__['reference'] 
     ref.dict[tuple(path)] = ProxyObject.new(val,ref, 
       path,self.__dict__['attr_mark']) 

class UniqueValue(object): 
    pass 

class QuickRecursiveDict(object): 
    def __init__(self,dictionary={}): 
     self.dict = dictionary 
     self.internal_id = UniqueValue() 
     self.attr_marker = UniqueValue() 
    def __getitem__(self,item): 
     if item in self.dict: 
      val = self.dict[item] 
      try: 
       if val.__dict__['path'][0] == self.internal_id: 
        return val 
       else: 
        raise TypeError 
      except: 
       return ProxyObject.new(val,self,[self.internal_id,item], 
         self.attr_marker) 
     try: 
      if item[0] == self.internal_id: 
       return ProxyObject.new(KeyError(),self,list(item), 
         self.attr_marker) 
     except TypeError: 
      pass #Item isn't iterable 
     return ProxyObject.new(KeyError(),self,[self.internal_id,item], 
        self.attr_marker) 
    def __setitem__(self,item,val): 
     self.dict[item] = val 

Các cụ thể của việc thực hiện sẽ khác nhau tùy thuộc vào những gì bạn muốn. Rõ ràng là dễ dàng ghi đè hơn __getitem__ trong proxy hơn là ghi đè cả hai số __getitem____getattribute__ hoặc __getattr__. Cú pháp bạn đang sử dụng trong setbydot làm cho nó trông giống như bạn sẽ hạnh phúc nhất với một số giải pháp ghi đè một hỗn hợp của cả hai.

Nếu bạn chỉ sử dụng từ điển để so sánh giá trị, sử dụng =, < =,> = v.v. Ghi đè __getattribute__ hoạt động thực sự độc đáo. Nếu bạn muốn làm điều gì đó tinh vi hơn, bạn có thể sẽ làm tốt hơn việc ghi đè __getattr__ và thực hiện một số kiểm tra trong __setattr__ để xác định xem bạn có muốn tổng hợp cài đặt thuộc tính hay không bằng cách đặt giá trị trong từ điển hoặc bạn có muốn thực sự cài đặt hay không thuộc tính trên mục bạn đã thu được. Hoặc bạn có thể muốn xử lý nó để nếu đối tượng của bạn có thuộc tính, __getattribute__ trả về proxy cho thuộc tính đó và __setattr__ luôn chỉ đặt thuộc tính trong đối tượng (trong trường hợp này, bạn hoàn toàn có thể bỏ qua nó). Tất cả những điều này phụ thuộc vào chính xác những gì bạn đang cố gắng sử dụng từ điển cho.

Bạn cũng có thể muốn tạo __iter__ và các tùy chọn tương tự. Phải mất một chút nỗ lực để làm cho chúng, nhưng các chi tiết phải tuân theo từ việc thực hiện __getitem____setitem__.

Cuối cùng, tôi sẽ tóm tắt ngắn gọn hành vi của QuickRecursiveDict trong trường hợp không rõ ràng ngay lập tức khi kiểm tra. Các thử/excepts chỉ là viết tắt để kiểm tra xem liệu if s có thể được thực hiện hay không. Một khiếm khuyết lớn của việc tổng hợp các thiết lập đệ quy hơn là tìm cách để làm điều đó là bạn không còn có thể nâng KeyErrors khi bạn cố gắng truy cập một khóa chưa được thiết lập. Tuy nhiên, bạn có thể đến khá gần bằng cách trả về một lớp con của KeyError, đó là những gì tôi làm trong ví dụ. Tôi đã không thử nghiệm nó vì vậy tôi sẽ không thêm nó vào mã, nhưng bạn có thể muốn chuyển một số biểu diễn có thể đọc được của con người của khóa tới KeyError.

Nhưng ngoài tất cả, nó hoạt động khá độc đáo.

>>> qrd = QuickRecursiveDict 
>>> qrd[0][13] # returns an instance of a subclass of KeyError 
>>> qrd[0][13] = 9 
>>> qrd[0][13] # 9 
>>> qrd[0][13]['forever'] = 'young' 
>>> qrd[0][13] # 9 
>>> qrd[0][13]['forever'] # 'young' 
>>> qrd[0] # returns an instance of a subclass of KeyError 
>>> qrd[0] = 0 
>>> qrd[0] # 0 
>>> qrd[0][13]['forever'] # 'young' 

Một lưu ý nữa, những thứ được trả lại không hoàn toàn giống như vậy. Đó là một proxy cho những gì nó trông giống như. Nếu bạn muốn số int 9, bạn cần int(qrd[0][13]) không phải qrd[0][13]. Đối với ints điều này không quan trọng nhiều kể từ khi, +, -, = và tất cả những gì bỏ qua __getattribute__ nhưng đối với danh sách, bạn sẽ mất các thuộc tính như append nếu bạn không recast chúng. (Bạn sẽ giữ len và các phương pháp nội trang khác, không phải thuộc tính của list. Bạn bị mất __len__.)

Vậy là xong. Mã này hơi phức tạp, vì vậy hãy cho tôi biết nếu bạn có bất kỳ câu hỏi nào. Tôi có lẽ không thể trả lời chúng cho đến tối nay trừ khi câu trả lời thực sự ngắn gọn. Tôi muốn tôi thấy câu hỏi này sớm hơn, đó là một câu hỏi thực sự thú vị, và tôi sẽ cố gắng cập nhật một giải pháp sạch hơn sớm. Tôi đã vui vẻ cố gắng để mã một giải pháp vào giờ wee của đêm qua. :)

Các vấn đề liên quan