Một lớp python hoạt động giống như dict


113

Tôi muốn viết một lớp tùy chỉnh hoạt động như dict - vì vậy, tôi đang kế thừa từ dict.

Tuy nhiên, câu hỏi của tôi là: Tôi có cần tạo một dictthành viên riêng trong __init__()phương thức của mình không ?. Tôi không hiểu vấn đề này, vì tôi đã códict hành vi nếu tôi thừa kế từ đó dict.

Bất cứ ai có thể chỉ ra lý do tại sao hầu hết các đoạn mã kế thừa trông giống như hình dưới đây?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Thay vì đơn giản hơn ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

Trên thực tế, tôi nghĩ rằng tôi nghi ngờ câu trả lời cho câu hỏi là người dùng không thể truy cập trực tiếp từ điển của bạn (tức là họ phải sử dụng các phương pháp truy cập mà bạn đã cung cấp).

Tuy nhiên, còn toán tử truy cập mảng []thì sao? Làm thế nào một người sẽ thực hiện điều đó? Cho đến nay, tôi chưa thấy một ví dụ nào cho thấy cách ghi đè []toán tử.

Vì vậy, nếu một []chức năng truy cập không được cung cấp trong lớp tùy chỉnh, các phương thức cơ sở kế thừa sẽ hoạt động trên một từ điển khác?

Tôi đã thử đoạn mã sau để kiểm tra hiểu biết của mình về tính kế thừa Python:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Tôi gặp lỗi sau:

KeyError: <id hàm tích hợp sẵn>

Có gì sai với đoạn mã trên?

Làm cách nào để sửa lớp myDictđể tôi có thể viết mã như thế này?

md = myDict()
md['id'] = 123

[Biên tập]

Tôi đã chỉnh sửa mẫu mã ở trên để loại bỏ lỗi ngớ ngẩn mà tôi đã mắc phải trước khi rời khỏi bàn làm việc. Đó là lỗi đánh máy (đáng lẽ tôi phải phát hiện ra nó từ thông báo lỗi).

Câu trả lời:



104
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}

37
Nếu bạn định chuyển sang lớp con dict, thì bạn nên sử dụng chính đối tượng đó (sử dụng super) thay vì chỉ ủy quyền cho cá thể __dict__đó - về cơ bản có nghĩa là bạn đang tạo hai phái cho mỗi cá thể.
Aaron Hall

8
bản thân .__ dict__ không giống với nội dung từ điển thực tế. Mọi đối tượng python, bất kể loại của nó, đều có một _dict__chứa tất cả các thuộc tính đối tượng (phương thức, trường, v.v.). Bạn không muốn mess xung quanh với điều này trừ khi bạn muốn ghi mã được sửa đổi bản thân ...
Raik

85

Như thế này

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Bây giờ bạn có thể sử dụng các chức năng được tích hợp sẵn, chẳng hạn dict.get()như self.get().

Bạn không cần phải bọc một ẩn self._dict. Lớp học của bạn đã một chính tả.


3
Điều này. Không có ích gì khi kế thừa from dictmà không gọi hàm tạo của nó trước.
sykora

1
Lưu ý rằng dictthực tế được kế thừa của bạn chứa 2 phiên bản dict: Phiên bản thứ nhất là vùng chứa được kế thừa và phiên bản thứ 2 là dict chứa các thuộc tính lớp - bạn có thể tránh điều đó bằng cách sử dụng các vị trí .
ankostis

1
Thực __dict__tế chỉ được tạo khi nó được truy cập lần đầu tiên, vì vậy, miễn là người dùng không cố gắng sử dụng nó là được. __slots__sẽ rất tốt.
Aaron Hall

9
Sử dụng dấu cách sau dấu phẩy bạn dã man! ;-)
James Burke

10

Để đầy đủ, đây là liên kết đến tài liệu được @ björn-pollex đề cập cho Python 2.x mới nhất (2.7.7 tính đến thời điểm viết bài):

Mô phỏng các loại vùng chứa

(Xin lỗi vì không sử dụng chức năng nhận xét, tôi chỉ không được phép làm như vậy bởi stackoverflow.)


3

Vấn đề với đoạn mã này:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... là phương thức 'add' của bạn (... và bất kỳ phương thức nào bạn muốn trở thành thành viên của một lớp) cần phải khai báo rõ ràng 'self' làm đối số đầu tiên của nó, như:

def add(self, 'id', 23):

Để thực hiện nạp chồng toán tử để truy cập các mục theo khóa, hãy tìm trong tài liệu để biết các phương pháp ma thuật __getitem____setitem__ .

Lưu ý rằng vì Python sử dụng Duck Typing, thực sự có thể không có lý do gì để lấy lớp dict tùy chỉnh của bạn từ lớp dict của ngôn ngữ - mà không cần biết thêm về những gì bạn đang cố gắng thực hiện (ví dụ: nếu bạn cần chuyển một trường hợp của điều này lớp thành một số mã ở nơi nào đó sẽ bị hỏng trừ khi isinstance(MyDict(), dict) == True), bạn có thể tốt hơn chỉ nên triển khai API làm cho lớp của bạn đủ giống và dừng lại ở đó.


3

Đây là một giải pháp thay thế:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2

Điều này thật tệ vì bạn không xác định bất kỳ phương pháp tùy chỉnh nào và cũng có những vấn đề khác như câu trả lời khác đã nói.
Shital Shah

đây chính xác là những gì tôi cần. Cảm ơn bạn!
jakebrinkmann

2

Tôi thực sự không thấy câu trả lời thích hợp cho điều này ở bất cứ đâu

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Tất cả những gì bạn thực sự phải làm là xác định cái riêng của bạn __init__- đó thực sự là tất cả những gì có quá.

Một ví dụ khác (phức tạp hơn một chút):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Ví dụ cuối cùng này rất hữu ích khi bạn muốn nhúng một số loại logic.

Dù sao thì ... tóm lại class GiveYourClassAName(dict)là đủ để khiến lớp học của bạn hành động như một chế tài. Bất kỳ thao tác dict nào bạn thực hiện selfsẽ giống như một lệnh dict thông thường.


1

Đây là giải pháp tốt nhất của tôi. Tôi đã sử dụng điều này nhiều lần.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

Bạn có thể sử dụng như:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])

0

Đừng kế thừa từ dict tích hợp sẵn trong Python, bao giờ hết! ví dụ như updatephương pháp không sử dụng __setitem__, chúng làm rất nhiều để tối ưu hóa. Sử dụng UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass

1
Dựa trên điều gì mà ai đó không bao giờ nên kế thừa từ dict tích hợp sẵn? Từ tài liệu ( docs.python.org/3/library/collections.html#collections.UserDict ): "Nhu cầu về 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ễ dàng hơn làm việc với vì từ điển bên dưới có thể truy cập được dưới dạng một thuộc tính. " Cũng trên cùng một trang: Mô-đun bộ sưu tập là "Không được dùng nữa kể từ phiên bản 3.3, sẽ bị xóa trong phiên bản 3.9: Các lớp cơ sở trừu tượng đã chuyển sang bộ sưu tập sang mô-đun collection.abc." ... mà không có UserDict.
NumesSanguis

Tôi nghi ngờ điều này được truyền cảm hứng từ, hoặc ít nhất là cộng hưởng với, treyhunner.com/2019/04/…
tripleee
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.