Cách ghi đè lớp con dict và ghi đè __getitem__ & __setitem__ một cách chính xác


84

Tôi đang gỡ lỗi một số mã và tôi muốn tìm hiểu khi nào một từ điển cụ thể được truy cập. Chà, nó thực sự là một lớp phân lớp dictvà triển khai một vài tính năng bổ sung. Dù sao, những gì tôi muốn làm là dicttự phân lớp và thêm ghi đè __getitem____setitem__tạo ra một số đầu ra gỡ lỗi. Ngay bây giờ, tôi có

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

' name_label'là một khóa cuối cùng sẽ được đặt mà tôi muốn sử dụng để xác định đầu ra. Sau đó, tôi đã thay đổi lớp mà tôi đang thiết lập thành lớp con DictWatchthay vì dictvà thay đổi lời gọi thành siêu khối lệnh. Tuy nhiên, dường như không có gì đang xảy ra. Tôi nghĩ rằng tôi đã thông minh, nhưng tôi tự hỏi liệu tôi có nên đi theo một hướng khác.

Cảm ơn đã giúp đỡ!


Bạn đã cố gắng sử dụng in thay vì nhật ký? Ngoài ra, bạn có thể giải thích cách bạn tạo / cấu hình nhật ký của bạn không?
pajton

2
Không dict.__init__mất *args?
Tom Russell

4
Trông hơi giống một ứng cử viên sáng giá cho một người trang trí.
Tom Russell

Câu trả lời:


39

Những gì bạn đang làm hoàn toàn nên hiệu quả. Tôi đã kiểm tra lớp của bạn, và ngoài việc thiếu dấu ngoặc đơn mở trong báo cáo nhật ký của bạn, nó hoạt động tốt. Chỉ có hai điều tôi có thể nghĩ đến. Đầu tiên, đầu ra của câu lệnh nhật ký của bạn có được đặt chính xác không? Bạn có thể cần phải đặt logging.basicConfig(level=logging.DEBUG)ở đầu tập lệnh của mình.

Thứ hai, __getitem____setitem__chỉ được gọi khi []truy cập. Vì vậy, hãy đảm bảo rằng bạn chỉ truy cập DictWatchqua d[key], thay vì d.get()d.set()


Trên thực tế, đó không phải là dấu ngoặc kép, mà là dấu mở đầu bị thiếu xung quanh(str(dict.get(self, 'name_label')), str(key), str(val)))
cobbal

3
Thật. Đối với OP: Để tham khảo trong tương lai, bạn chỉ cần thực hiện log.info ('% s% s% s', a, b, c), thay vì toán tử định dạng chuỗi Python.
BrainCore

Mức độ ghi nhật ký cuối cùng là vấn đề. Tôi đang gỡ lỗi mã của người khác và ban đầu tôi đang thử nghiệm trong một tệp khác, tệp này đứng đầu một cấp độ khác của bộ gỡ lỗi. Cảm ơn!
Michael Mior

73

Một vấn đề khác khi phân lớp con dictlà phần tích hợp __init__không gọi updatevà phần tích hợp updatekhông gọi __setitem__. Vì vậy, nếu bạn muốn tất cả các hoạt động setitem đi qua __setitem__hàm của mình , bạn nên đảm bảo rằng nó được gọi cho chính bạn:

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print 'GET', key
        return val

    def __setitem__(self, key, val):
        print 'SET', key, val
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        print 'update', args, kwargs
        for k, v in dict(*args, **kwargs).iteritems():
            self[k] = v

9
Nếu bạn đang sử dụng Python 3, bạn sẽ muốn thay đổi ví dụ này để đó printprint()hàm và update()phương thức sử dụng items()thay vì iteritems().
Al Sweigart

Tôi đã cố gắng sol của bạn, nhưng có vẻ như nó chỉ hoạt động cho chỉ có một mức độ lập chỉ mục (ví dụ, dict [key] và không dict [key1] [khóa2] ...) *
Andrew Naguib

d [key1] trả về một thứ gì đó, có lẽ là một từ điển. Chìa khóa thứ hai chỉ số đó. Kỹ thuật này không thể hoạt động trừ khi thứ được trả lại đó cũng hỗ trợ hoạt động của đồng hồ.
Matt Anderson

1
@AndrewNaguib: Tại sao nó phải hoạt động với các mảng lồng nhau? Mảng lồng nhau cũng không hoạt động với python dict bình thường (nếu bạn không tự triển khai)
Igor Chubin

1
@AndrewNaguib: __getitem__sẽ cần phải kiểm tra valvà chỉ làm điều đó có điều kiện - tứcif isinstance(val, dict): ...
Martineau

14

Xem xét phân lớp UserDicthoặc UserList. Các lớp này được thiết kế để được phân lớp trong khi lớp bình thường dictlistkhông phải, và chứa các tối ưu hóa.


9
Để tham khảo, tài liệu bằng Python 3.6 cho biết "Sự cần thiết của lớp này đã được thay thế một phần nhờ khả năng phân lớp trực tiếp từ dict; tuy nhiên, lớp này có thể dễ làm việc hơn vì từ điển bên dưới có thể truy cập dưới dạng thuộc tính".
Sean

@andrew một ví dụ có thể hữu ích.
Vasantha Ganesh K


9

Điều đó sẽ không thực sự thay đổi kết quả (sẽ hoạt động, đối với các giá trị ngưỡng ghi nhật ký tốt): init của bạn phải là:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

thay vào đó, vì nếu bạn gọi phương thức của mình bằng DictWatch ([(1,2), (2,3)]) hoặc DictWatch (a = 1, b = 2) thì điều này sẽ không thành công.

(hoặc tốt hơn, không xác định hàm tạo cho việc này)


Tôi chỉ lo lắng về dict[key]hình thức truy cập, vì vậy đây không phải là vấn đề.
Michael Mior

1

Tất cả những gì bạn sẽ phải làm là

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Một cách sử dụng mẫu cho mục đích sử dụng cá nhân của tôi

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Lưu ý : chỉ được thử nghiệm trong python3


0

Để hoàn thành câu trả lời andrew pate, đây là một ví dụ cho thấy sự khác biệt giữa dictUserDict:

Rất khó để ghi đè chính xác dict:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDictkế thừa từ collections.abc.MutableMapping, do đó dễ dàng tùy chỉnh hơn nhiều:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

Tương tự như vậy, bạn chỉ cần thực hiện __getitem__để tự động phù hợp với key in my_dict, my_dict.get...

Lưu ý: UserDictkhông phải là một lớp con của dict, vì vậy isinstance(UserDict(), dict)sẽ thất bại (nhưng isinstance(UserDict(), collections.abc.MutableMapping)sẽ hoạt động)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.