คลาส python ที่ทำหน้าที่เหมือน dict


114

ผมอยากจะเขียนชั้นเองว่าพฤติกรรมเช่นdict- dictดังนั้นฉันกำลังสืบทอดจาก

คำถามของฉันคือ: ฉันต้องสร้างdictสมาชิกส่วนตัวด้วย__init__()วิธีของฉันหรือไม่? ฉันไม่เห็นจุดนี้ตั้งแต่ผมอยู่แล้วมีพฤติกรรมถ้าฉันก็สืบทอดมาจากdictdict

ใครสามารถชี้ให้เห็นว่าทำไมส่วนย่อยของมรดกส่วนใหญ่จึงมีลักษณะเหมือนด้านล่างนี้?

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

   # other methods follow

แทนที่จะง่ายกว่า ...

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

   # other methods follow

อันที่จริงฉันคิดว่าฉันสงสัยว่าคำตอบของคำถามคือเพื่อให้ผู้ใช้ไม่สามารถเข้าถึงพจนานุกรมของคุณได้โดยตรง (กล่าวคือพวกเขาต้องใช้วิธีการเข้าถึงที่คุณให้ไว้)

อย่างไรก็ตามสิ่งที่เกี่ยวกับตัวดำเนินการการเข้าถึงอาร์เรย์[]? เราจะดำเนินการอย่างไร จนถึงตอนนี้ฉันยังไม่เห็นตัวอย่างที่แสดงวิธีการแทนที่ตัว[]ดำเนินการ

ดังนั้นหากไม่มี[]ฟังก์ชันการเข้าถึงในคลาสที่กำหนดเองเมธอดพื้นฐานที่สืบทอดมาจะทำงานบนพจนานุกรมอื่น?

ฉันลองใช้ตัวอย่างต่อไปนี้เพื่อทดสอบความเข้าใจเกี่ยวกับการสืบทอด 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]

ฉันได้รับข้อผิดพลาดต่อไปนี้:

KeyError: <รหัสฟังก์ชันในตัว>

โค้ดด้านบนผิดอะไร

ฉันจะแก้ไขคลาสอย่างไรmyDictให้สามารถเขียนโค้ดแบบนี้ได้

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

[แก้ไข]

ฉันได้แก้ไขตัวอย่างโค้ดด้านบนเพื่อกำจัดข้อผิดพลาดโง่ ๆ ที่ฉันทำก่อนที่จะหลบออกจากโต๊ะทำงาน มันเป็นการพิมพ์ผิด (ฉันควรจะเห็นมันจากข้อความแสดงข้อผิดพลาด)

คำตอบ:


43

ตรวจสอบเอกสารเกี่ยวกับการลอกเลียนแบบประเภทภาชนะ ในกรณีของคุณพารามิเตอร์แรกที่ควรจะเป็นaddself


106
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
หากคุณกำลังจะเข้าสู่คลาสย่อยdictคุณควรใช้ออบเจ็กต์นั้นเอง (โดยใช้super) แทนที่จะเพียงแค่มอบหมายให้อินสแตนซ์__dict__ซึ่งโดยพื้นฐานแล้วหมายความว่าคุณกำลังสร้างสองคำสั่งสำหรับทุกอินสแตนซ์
Aaron Hall

8
self .__ dict__ ไม่เหมือนกับเนื้อหาในพจนานุกรมจริง ทุกออบเจ็กต์ python ไม่ว่าจะเป็นประเภทใดก็ตามจะ_dict__มีแอตทริบิวต์ของอ็อบเจ็กต์ทั้งหมด (วิธีการฟิลด์ ฯลฯ ) คุณไม่ต้องการยุ่งกับเรื่องนี้เว้นแต่คุณจะต้องการเขียนโค้ดที่แก้ไขตัวเอง ...
Raik

86

แบบนี้

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

ตอนนี้คุณสามารถใช้ฟังก์ชั่นในตัวเช่นdict.get()as self.get().

self._dictคุณไม่จำเป็นต้องห่อซ่อน ชั้นเรียนของคุณเป็นเผด็จการแล้ว


3
นี้. ไม่มีจุดใดในการสืบทอดdictโดยไม่ต้องเรียกตัวสร้างก่อน
sykora

1
ทราบว่าได้รับมรดกของคุณdictจริงมี 2 Dict อินสแตนซ์: 1 เป็นภาชนะสืบทอดและครั้งที่ 2 คือ Dict ถือคุณลักษณะชั้น - คุณอาจหลีกเลี่ยงการว่าด้วยการใช้ช่อง
ankostis

1
สิ่ง__dict__นี้ถูกสร้างขึ้นเมื่อมีการเข้าถึงครั้งแรกเท่านั้นตราบใดที่ผู้ใช้ไม่พยายามใช้มันก็ใช้ได้ __slots__คงจะดี
Aaron Hall

10
ใช้ช่องว่างหลังลูกน้ำคุณโหด! ;-)
James Burke

10

เพื่อความสมบูรณ์นี่คือลิงค์ไปยังเอกสารที่กล่าวถึงโดย @ björn-pollex สำหรับ Python ล่าสุด 2.x (2.7.7 ณ เวลาที่เขียน):

การจำลองประเภทคอนเทนเนอร์

(ขออภัยที่ไม่ได้ใช้ฟังก์ชั่นแสดงความคิดเห็นฉันไม่ได้รับอนุญาตให้ทำโดย stackoverflow)


3

ปัญหาเกี่ยวกับโค้ดส่วนนี้:

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

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


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

... คือเมธอด 'เพิ่ม' ของคุณ (... และวิธีการใด ๆ ที่คุณต้องการเป็นสมาชิกของคลาส) จำเป็นต้องมีการประกาศ 'ตัวเอง' อย่างชัดเจนเป็นอาร์กิวเมนต์แรกเช่น:

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

ที่จะใช้ประกอบการบรรทุกเกินพิกัดกับรายการการเข้าถึงโดยคีย์ดูในเอกสารสำหรับวิธีมายากลและ__getitem____setitem__

โปรดทราบว่าเนื่องจาก Python ใช้ Duck Typing จึงอาจไม่มีเหตุผลที่จะได้รับคลาส dict ที่กำหนดเองของคุณจากคลาส dict ของภาษาโดยไม่ทราบข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่คุณกำลังพยายามทำ (เช่นหากคุณต้องการส่งผ่านอินสแตนซ์นี้ ลงในโค้ดบางที่ซึ่งจะพังเว้นแต่isinstance(MyDict(), dict) == True) คุณอาจจะดีกว่าเพียงแค่ใช้ API ที่ทำให้คลาสของคุณมีความคล้ายคลึงและหยุดอยู่แค่นั้น


3

นี่คือทางเลือกอื่น:

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

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

สิ่งนี้ไม่ดีเนื่องจากคุณไม่ได้กำหนดวิธีการที่กำหนดเองใด ๆ และยังมีปัญหาอื่น ๆ ตามที่คำตอบอื่น ๆ ได้กล่าวไว้
Shital Shah

นี่คือสิ่งที่ฉันต้องการ ขอบคุณ!
jakebrinkmann

2

ฉันไม่เห็นคำตอบที่ถูกต้องสำหรับเรื่องนี้เลย

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

สิ่งที่คุณต้องทำจริงๆคือกำหนดของคุณเอง__init__นั่นคือทั้งหมดที่มีอยู่ด้วย

อีกตัวอย่างหนึ่ง (ซับซ้อนกว่าเล็กน้อย):

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"}

ตัวอย่างสุดท้ายนี้มีประโยชน์เมื่อคุณต้องการฝังตรรกะบางอย่าง

อย่างไรก็ตาม ... ในระยะสั้นclass GiveYourClassAName(dict)ก็เพียงพอที่จะทำให้ชั้นเรียนของคุณทำตัวเหมือนบงการ การดำเนินการตามคำสั่งใด ๆ ที่คุณทำselfจะเหมือนกับการเขียนตามปกติ


1

นี่คือทางออกที่ดีที่สุดของฉัน ฉันใช้สิ่งนี้หลายครั้ง

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

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

คุณสามารถใช้เช่น:

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

0

อย่าสืบทอดจาก Python built-in dict เลย! ตัวอย่างเช่นupdateวิธีการที่ไม่ได้ใช้__setitem__พวกเขาทำมากสำหรับการเพิ่มประสิทธิภาพ ใช้ UserDict

from collections import UserDict

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

1
ขึ้นอยู่กับสิ่งที่ใครบางคนไม่ควรได้รับมรดกจากคำสั่งในตัว? จากเอกสารประกอบ ( docs.python.org/3/library/collections.html#collections.UserDict ): "ความจำเป็นสำหรับคลาสนี้ถูกแทนที่บางส่วนด้วยความสามารถในคลาสย่อยโดยตรงจากการเขียนตามคำบอกอย่างไรก็ตามคลาสนี้สามารถทำได้ง่ายกว่า ใช้งานได้เนื่องจากพจนานุกรมที่อยู่ภายใต้สามารถเข้าถึงได้เป็นแอตทริบิวต์ " นอกจากนี้ในหน้าเดียวกัน: โมดูลคอลเลกชันคือ "เลิกใช้แล้วตั้งแต่เวอร์ชัน 3.3 จะถูกลบออกในเวอร์ชัน 3.9: ย้ายคลาสพื้นฐานของคอลเลกชันที่เป็นนามธรรมไปยังโมดูลคอลเลกชัน abc" ... ซึ่งไม่มี UserDict
NumesSanguis

ฉันสงสัยว่านี่เป็นแรงบันดาลใจหรืออย่างน้อยก็สะท้อนกับtreyhunner.com/2019/04/…
tripleee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.