ฉันจะทำให้คลาสย่อยของ "dict" สมบูรณ์แบบที่สุดได้อย่างไร
เป้าหมายสุดท้ายคือการใช้ dict แบบง่าย ๆ ซึ่งมีปุ่มเล็ก ๆ
ถ้าฉันลบล้าง__getitem__
/ __setitem__
ดังนั้นรับ / ตั้งไม่ทำงาน ฉันจะทำให้พวกเขาทำงานได้อย่างไร แน่นอนฉันไม่จำเป็นต้องใช้พวกเขาทีละรายการ?
ฉันจะป้องกันการดองจากการทำงานและฉันจำเป็นต้องติดตั้ง
__setstate__
ฯลฯ หรือไม่?
ฉันจำเป็นต้องพิมพ์อัปเดตและ__init__
หรือไม่
ฉันควรใช้mutablemapping
(ดูเหมือนว่าไม่ควรใช้UserDict
หรือDictMixin
) ถ้าเป็นเช่นนั้นได้อย่างไร เอกสารไม่ได้ตรัสรู้อย่างแน่นอน
คำตอบที่ได้รับการยอมรับจะเป็นแนวทางแรกของฉัน แต่เนื่องจากมันมีปัญหาบางอย่างและเนื่องจากไม่มีใครพูดถึงทางเลือกอื่นจริง ๆ แล้วการทำคลาสย่อย a dict
ฉันจะทำที่นี่
เกิดอะไรขึ้นกับคำตอบที่ยอมรับ?
ดูเหมือนจะเป็นคำขอที่ค่อนข้างง่ายสำหรับฉัน:
ฉันจะทำให้คลาสย่อยของ "dict" สมบูรณ์แบบที่สุดได้อย่างไร เป้าหมายสุดท้ายคือการใช้ dict แบบง่าย ๆ ซึ่งมีปุ่มเล็ก ๆ
คำตอบที่ยอมรับไม่ได้เป็นคลาสย่อยจริง ๆdict
และการทดสอบนี้ล้มเหลว:
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
ตามหลักการแล้วรหัสการตรวจสอบประเภทใด ๆ จะทำการทดสอบสำหรับอินเทอร์เฟซที่เราคาดหวังหรือคลาสเบสที่เป็นนามธรรม แต่ถ้าวัตถุข้อมูลของเราถูกส่งผ่านไปยังฟังก์ชั่นที่กำลังทดสอบสำหรับdict
และเราไม่สามารถ "แก้ไข" ฟังก์ชันเหล่านั้น จะล้มเหลว
อื่น ๆ ที่อาจทำให้เกิดการเล่นแร่แปรธาตุ:
- คำตอบที่ได้รับการยอมรับก็ยังหายไป classmethod
fromkeys
นี้:
คำตอบที่ได้รับการยอมรับมีซ้ำซ้อน__dict__
- ดังนั้นการเพิ่มพื้นที่ในหน่วยความจำ:
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
จริงคลาสย่อย dict
เราสามารถใช้วิธี dict ผ่านการรับมรดก สิ่งที่เราต้องทำคือสร้างเลเยอร์อินเตอร์เฟสที่ทำให้มั่นใจว่าคีย์ถูกส่งผ่านไปยัง dict ในรูปแบบตัวพิมพ์เล็กถ้ามันเป็นสตริง
ถ้าฉันลบล้าง__getitem__
/ __setitem__
ดังนั้นรับ / ตั้งไม่ทำงาน ฉันจะทำให้พวกเขาทำงานได้อย่างไร แน่นอนฉันไม่จำเป็นต้องใช้พวกเขาทีละรายการ?
การใช้งานพวกมันทีละข้อคือข้อเสียของวิธีนี้และการใช้กลับหัวกลับหางMutableMapping
(ดูคำตอบที่ยอมรับได้) แต่มันก็ไม่ได้ผลมากนัก
อันดับแรกให้แยกความแตกต่างระหว่าง Python 2 และ 3 ออกจากกันสร้างซิงเกิลตัน ( _RaiseKeyError
) เพื่อให้แน่ใจว่าเรารู้ว่าเราได้โต้แย้งกันdict.pop
แล้วและสร้างฟังก์ชั่นเพื่อให้แน่ใจว่าคีย์สตริงของเราเป็นตัวพิมพ์เล็ก:
from itertools import chain
try: # Python 2
str_base = basestring
items = 'iteritems'
except NameError: # Python 3
str_base = str, bytes, bytearray
items = 'items'
_RaiseKeyError = object() # singleton for no-default behavior
def ensure_lower(maybe_str):
"""dict keys can be any hashable object - only call lower if str"""
return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str
ตอนนี้เรานำไปใช้ - ฉันใช้super
กับอาร์กิวเมนต์แบบเต็มเพื่อให้โค้ดนี้ใช้ได้กับ Python 2 และ 3:
class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument
__slots__ = () # no __dict__ - that would be redundant
@staticmethod # because this doesn't make sense as a global function.
def _process_args(mapping=(), **kwargs):
if hasattr(mapping, items):
mapping = getattr(mapping, items)()
return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
def __init__(self, mapping=(), **kwargs):
super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
def __getitem__(self, k):
return super(LowerDict, self).__getitem__(ensure_lower(k))
def __setitem__(self, k, v):
return super(LowerDict, self).__setitem__(ensure_lower(k), v)
def __delitem__(self, k):
return super(LowerDict, self).__delitem__(ensure_lower(k))
def get(self, k, default=None):
return super(LowerDict, self).get(ensure_lower(k), default)
def setdefault(self, k, default=None):
return super(LowerDict, self).setdefault(ensure_lower(k), default)
def pop(self, k, v=_RaiseKeyError):
if v is _RaiseKeyError:
return super(LowerDict, self).pop(ensure_lower(k))
return super(LowerDict, self).pop(ensure_lower(k), v)
def update(self, mapping=(), **kwargs):
super(LowerDict, self).update(self._process_args(mapping, **kwargs))
def __contains__(self, k):
return super(LowerDict, self).__contains__(ensure_lower(k))
def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
return type(self)(self)
@classmethod
def fromkeys(cls, keys, v=None):
return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
def __repr__(self):
return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())
เราใช้วิธีการเกือบหม้อไอน้ำแผ่นสำหรับวิธีการใด ๆ หรือวิธีการพิเศษที่อ้างอิงที่สำคัญ แต่อย่างอื่นโดยการรับมรดกที่เราได้รับวิธีการ: len
, clear
, items
, keys
, popitem
และvalues
ฟรี ในขณะนี้จำเป็นต้องใช้ความคิดอย่างรอบคอบเพื่อให้ถูกต้องมันเป็นเรื่องเล็กน้อยที่จะเห็นว่างานนี้
(โปรดทราบว่าhaskey
เลิกใช้แล้วใน Python 2 ถูกลบใน Python 3)
นี่คือการใช้งานบางส่วน:
>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)
ฉันจะป้องกันการดองจากการทำงานและฉันจำเป็นต้องติดตั้ง
__setstate__
ฯลฯ หรือไม่?
ดอง
และผักดอง subclass ของ dict ก็ใช้ได้:
>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>
__repr__
ฉันจำเป็นต้องพิมพ์อัปเดตและ__init__
หรือไม่
เรากำหนดupdate
และ__init__
คุณมี__repr__
ค่าเริ่มต้นที่สวยงาม:
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
อย่างไรก็ตามคุณควรเขียน a __repr__
เพื่อปรับปรุงการดีบักของรหัสของคุณ eval(repr(obj)) == obj
การทดสอบที่เหมาะคือ หากรหัสของคุณเป็นเรื่องง่ายฉันขอแนะนำอย่างยิ่ง:
>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True
คุณเห็นมันเป็นสิ่งที่เราต้องการในการสร้างวัตถุที่เทียบเท่า - นี่คือสิ่งที่อาจปรากฏในบันทึกของเราหรือใน backtraces:
>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
ข้อสรุป
ฉันควรใช้mutablemapping
(ดูเหมือนว่าไม่ควรใช้UserDict
หรือDictMixin
) ถ้าเป็นเช่นนั้นได้อย่างไร เอกสารไม่ได้ตรัสรู้อย่างแน่นอน
ใช่พวกนี้เป็นโค้ดอีกสองสามบรรทัด แต่พวกเขาตั้งใจจะให้ครอบคลุม ความชอบแรกของฉันคือการใช้คำตอบที่ยอมรับและถ้ามีปัญหากับมันฉันก็จะดูคำตอบของฉัน - เพราะมันซับซ้อนกว่านี้เล็กน้อยและไม่มี ABC ที่จะช่วยให้อินเทอร์เฟซของฉันถูกต้อง
การเพิ่มประสิทธิภาพก่อนวัยอันควรช่วยเพิ่มความซับซ้อนในการค้นหาประสิทธิภาพ
MutableMapping
ง่ายกว่า - ดังนั้นจึงได้รับความได้เปรียบทันทีทุกอย่างเท่าเทียมกัน อย่างไรก็ตามเพื่อให้เห็นความแตกต่างทั้งหมดเรามาเปรียบเทียบและเปรียบเทียบกัน
ฉันควรเพิ่มว่ามีการผลักดันที่จะนำพจนานุกรมที่คล้ายกันเข้าไปในcollections
โมดูล แต่ถูกปฏิเสธ คุณน่าจะทำเช่นนี้แทน:
my_dict[transform(key)]
มันควรจะ debugable ได้ง่ายขึ้น
เปรียบเทียบและความคมชัด
มีฟังก์ชั่นอินเทอร์เฟซ 6 ตัวที่ใช้กับMutableMapping
(ซึ่งหายไปfromkeys
) และ 11 กับdict
คลาสย่อย ผมไม่จำเป็นต้องใช้__iter__
หรือ__len__
แต่ฉันมีการดำเนินการget
, setdefault
, pop
, update
, copy
, __contains__
และfromkeys
- แต่เหล่านี้เป็นธรรมที่น่ารำคาญเพราะผมสามารถใช้สิ่งที่สืบทอดมากที่สุดของการใช้งานเหล่านั้น
การMutableMapping
ดำเนินการบางอย่างใน Python ที่dict
ใช้ C - ดังนั้นฉันคาดว่าdict
subclass จะมีประสิทธิภาพมากกว่าในบางกรณี
เราได้รับฟรี__eq__
ทั้งสองวิธี - ซึ่งทั้งสองวิธีถือว่าเท่าเทียมกันถ้า dict อื่นเป็นตัวพิมพ์เล็กทั้งหมด - แต่อีกครั้งฉันคิดว่าdict
คลาสย่อยจะเปรียบเทียบเร็วกว่า
สรุป:
- การแบ่งคลาสย่อย
MutableMapping
นั้นง่ายกว่าโดยมีโอกาสน้อยลงสำหรับข้อบกพร่อง แต่ช้ากว่าใช้หน่วยความจำมากขึ้น (ดู dict ซ้ำซ้อน) และล้มเหลวisinstance(x, dict)
- การแบ่งคลาสย่อย
dict
เร็วกว่าใช้หน่วยความจำน้อยกว่าและผ่านisinstance(x, dict)
แต่มีความซับซ้อนมากขึ้นในการนำไปใช้
อันไหนสมบูรณ์แบบกว่ากัน? ขึ้นอยู่กับคำนิยามของคุณสมบูรณ์แบบ