วิธี "สมบูรณ์แบบ" จะแทนที่คำสั่ง?


218

ฉันจะทำให้คลาสย่อยของ " dict " สมบูรณ์แบบที่สุดได้อย่างไร เป้าหมายสุดท้ายคือการใช้dictแบบง่าย ๆซึ่งมีปุ่มเล็ก ๆ

ดูเหมือนว่าควรมีชุดพื้นฐานเล็ก ๆ น้อย ๆ ที่ฉันสามารถแทนที่เพื่อให้งานนี้ แต่ตามการวิจัยของฉันทั้งหมดและความพยายามดูเหมือนว่านี่ไม่ใช่กรณี:

  • หากฉันแทนที่__getitem__/__setitem__ดังนั้นget/ setไม่ได้ผล ฉันจะทำให้พวกเขาทำงานได้อย่างไร แน่นอนฉันไม่จำเป็นต้องใช้พวกเขาทีละรายการ?

  • ฉันจะป้องกันการดองจากการทำงานและฉันจำเป็นต้องติดตั้ง__setstate__ฯลฯ หรือไม่?

  • ฉันต้องrepr, updateและ__init__ ?

  • ฉันควรใช้การทำแผนที่ที่ไม่แน่นอน (ดูเหมือนว่าไม่ควรใช้UserDict หรือDictMixin) ถ้าเป็นเช่นนั้นได้อย่างไร เอกสารไม่ได้ตรัสรู้อย่างแน่นอน

นี่เป็นครั้งแรกของฉันไปที่มันget()ไม่ทำงานและไม่ต้องสงสัยเลยว่ามีปัญหาเล็ก ๆ น้อย ๆ อื่น ๆ อีกมากมาย:

class arbitrary_dict(dict):
    """A dictionary that applies an arbitrary key-altering function
       before accessing the keys."""

    def __keytransform__(self, key):
        return key

    # Overridden methods. List from 
    # /programming/2390827/how-to-properly-subclass-dict

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

    # Note: I'm using dict directly, since super(dict, self) doesn't work.
    # I'm not sure why, perhaps dict is not a new-style class.

    def __getitem__(self, key):
        return dict.__getitem__(self, self.__keytransform__(key))

    def __setitem__(self, key, value):
        return dict.__setitem__(self, self.__keytransform__(key), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, self.__keytransform__(key))

    def __contains__(self, key):
        return dict.__contains__(self, self.__keytransform__(key))


class lcdict(arbitrary_dict):
    def __keytransform__(self, key):
        return str(key).lower()

ฉันคิดว่า __keytransform __ () ควรจะคงที่ วิธีการที่ดีแม้ว่า (การเติม @staticmethod)
Aiyion.Prime

คำตอบ:


229

คุณสามารถเขียนวัตถุที่ทำงานdictได้ค่อนข้างง่ายด้วยABC s (Abstract Base Classes) จากcollections.abcโมดูล มันยังบอกคุณว่าคุณพลาดวิธีการด้านล่างนี้เป็นเวอร์ชั่นขั้นต่ำที่ปิดการทำงานของ ABC

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

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

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

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

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

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

    def __keytransform__(self, key):
        return key

คุณได้รับวิธีการฟรีจาก ABC:

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

ฉันจะไม่ subclass dict(หรือ builtins อื่น ๆ ) โดยตรง บ่อยครั้งที่มันไม่สมเหตุสมผลเพราะสิ่งที่คุณต้องการทำจริง ๆ ก็คือการนำอินเตอร์เฟสของ adictไปใช้ และนั่นคือสิ่งที่ ABCs มีไว้สำหรับ


46
ฉันขอแนะนำให้เปลี่ยนชื่อ__keytransform__()เพราะละเมิดคู่มือสไตล์ PEP 8 ซึ่งให้คำแนะนำแก่ "ไม่เคยคิดค้นชื่อดังกล่าวเพียง แต่ใช้พวกเขาเป็นเอกสาร" ในตอนท้ายของคำอธิบาย: การตั้งชื่อรูปแบบส่วน
martineau

1
คำถามแม้ว่า - จะไม่ใช้อินเทอร์เฟซนี้กับประเภทที่ผู้ใช้กำหนดเองโดยทั่วไปส่งผลให้การดำเนินการคล้าย dict ช้าลงซึ่งใช้ประเภทบิวด์อิน
twneale

2
มีวิธีการทำเช่นนี้เพื่อที่ isinstance (_, dict) == จริงหรือไม่ หรือคุณเพียงแค่ใช้ Mutable Mapping เพื่อสร้างคลาสย่อย?
Andy Hayden

5
@AndyHayden: if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"คุณควรเขียน อย่าตรวจสอบประเภทของวัตถุตรวจสอบส่วนต่อประสาน
Jochen Ritzel

2
@NeilG สิ่งนี้โชคไม่ดีที่มี JSONEncoder ในไลบรารีมาตรฐานของไพ ธ อน - github.com/python-git/python/blob/…
Andy Smith

97

ฉันจะทำให้คลาสย่อยของ "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 - ดังนั้นฉันคาดว่าdictsubclass จะมีประสิทธิภาพมากกว่าในบางกรณี

เราได้รับฟรี__eq__ทั้งสองวิธี - ซึ่งทั้งสองวิธีถือว่าเท่าเทียมกันถ้า dict อื่นเป็นตัวพิมพ์เล็กทั้งหมด - แต่อีกครั้งฉันคิดว่าdictคลาสย่อยจะเปรียบเทียบเร็วกว่า

สรุป:

  • การแบ่งคลาสย่อยMutableMappingนั้นง่ายกว่าโดยมีโอกาสน้อยลงสำหรับข้อบกพร่อง แต่ช้ากว่าใช้หน่วยความจำมากขึ้น (ดู dict ซ้ำซ้อน) และล้มเหลวisinstance(x, dict)
  • การแบ่งคลาสย่อยdictเร็วกว่าใช้หน่วยความจำน้อยกว่าและผ่านisinstance(x, dict)แต่มีความซับซ้อนมากขึ้นในการนำไปใช้

อันไหนสมบูรณ์แบบกว่ากัน? ขึ้นอยู่กับคำนิยามของคุณสมบูรณ์แบบ


คำตอบที่ยอมรับจะลบ dict ซ้ำซ้อนได้อย่างไร
Seanny123

1
สองวิธีที่มาในใจทันทีคือการประกาศแอตทริบิวต์ร้านค้าใน__slots__หรืออาจนำมาใช้ซ้ำ__dict__เป็นร้านค้า แต่ที่ผสมความหมายซึ่งเป็นอีกจุดที่มีศักยภาพของการวิจารณ์
Aaron Hall

1
มันจะง่ายกว่าหรือถ้าจะเขียนมัณฑนากรที่ใช้วิธีการและใช้ของคุณensure_lowerใน arguemtn แรก (ซึ่งเป็นกุญแจสำคัญเสมอ)? แล้วมันจะเป็นหมายเลขเดียวกันของการแทนที่ __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)แต่พวกเขาทั้งหมดจะอยู่ในรูปแบบ
Graipher

1
ขอบคุณสำหรับสิ่งนี้ - รับคำเตือนสำหรับป๊อปและคีย์ที่ไม่ตรงกับลายเซ็นของเมธอดคลาสพื้นฐาน
Mr_and_Mrs_D

1
@Mr_and_Mrs_D ฉันได้เพิ่มการใช้งานcopy- ฉันคิดว่าควรทำใช่ไหม? ฉันคิดว่ามันควรทดสอบสำหรับอินเทอร์เฟซ - เช่นวัตถุแพนด้า DataFrame ไม่ใช่อินสแตนซ์ Mapping (ที่ตรวจสอบล่าสุด) แต่มันมีรายการ / iteritems
Aaron Hall

4

ความต้องการของฉันเข้มงวดขึ้นเล็กน้อย:

  • ฉันต้องเก็บข้อมูลเคส (สตริงเป็นพา ธ ไปยังไฟล์ที่แสดงให้ผู้ใช้เห็น แต่เป็นแอพพลิเคชั่น windows ดังนั้นการดำเนินการทั้งหมดจะต้องไม่คำนึงถึงขนาดตัวพิมพ์ภายใน)
  • ฉันต้องการกุญแจให้เล็กที่สุดเท่าที่จะทำได้สร้างความแตกต่างในการทำงานของหน่วยความจำสับปิด 110 MB จาก 370) นี่หมายความว่าการแคชรุ่นคีย์ตัวพิมพ์เล็กไม่ใช่ตัวเลือก
  • ฉันต้องการสร้างโครงสร้างข้อมูลให้เร็วที่สุดเท่าที่จะเป็นไปได้ (สร้างความแตกต่างด้านประสิทธิภาพความเร็วในครั้งนี้) ฉันต้องไปกับ builtin

ความคิดเริ่มต้นของฉันคือการแทนที่คลาส Path clunky ของเราสำหรับคลาสย่อย unicode ที่ไม่รู้สึกตัว - แต่:

  • พิสูจน์ได้ยากว่าจะได้รับสิทธินั้น - ดู: คลาสสตริงที่ไม่ต้องพิมพ์ตัวเล็กใน python
  • ปรากฎว่าการจัดการคีย์ dict อย่างชัดเจนทำให้โค้ด verbose และยุ่งเหยิง - และเกิดข้อผิดพลาดได้ง่าย (โครงสร้างถูกส่งผ่านที่นี่และที่นั่นและไม่ชัดเจนหากพวกเขามีอินสแตนซ์ CIStr เป็นคีย์ / องค์ประกอบง่ายต่อการลืมบวก some_dict[CIstr(path)]น่าเกลียด)

ดังนั้นในที่สุดฉันก็ต้องเขียนทบนั้นกรณีที่ไม่มีความรู้สึก ขอบคุณรหัสโดย @AaronHall ที่ทำง่ายขึ้น 10 เท่า

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __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, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(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__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

โดยปริยาย vs ชัดเจนยังคงเป็นปัญหา แต่เมื่อฝุ่นตกตะกอนเปลี่ยนชื่อแอตทริบิวต์ / ตัวแปรเริ่มต้นด้วย ci (และความคิดเห็น doc ไขมันใหญ่อธิบายว่า ci ย่อมาจากกรณีตาย) ฉันคิดว่าเป็นโซลูชั่นที่สมบูรณ์แบบ - เป็นผู้อ่านรหัสจะต้อง ระวังให้ดีว่าเรากำลังจัดการกับโครงสร้างข้อมูลที่ไม่คำนึงถึงขนาดตัวพิมพ์ หวังว่าจะแก้ไขยากที่จะทำซ้ำข้อบกพร่องซึ่งฉันสงสัยว่าต้มลงไปจนถึงระดับความลับ

ความเห็น / การแก้ไขยินดีต้อนรับ :)


CIstr ของ__repr__ควรใช้ระดับผู้ปกครองเป็น__repr__ที่จะผ่าน EVAL (ที่ Repr (obj)) == obj ทดสอบ (ผมไม่คิดว่ามันไม่ตอนนี้) __str__และไม่ต้องพึ่งพา
Aaron Hall

ตรวจสอบtotal_orderingมัณฑนากรเรียน - ซึ่งจะกำจัด 4 วิธีจากคลาสย่อยของ Unicode ของคุณ แต่คลาสย่อย dict นั้นมีการใช้งานอย่างชาญฉลาด : P
Aaron Hall

ขอบคุณ @AaronHall - เป็นผู้ที่ดำเนินการที่: P Re: สั่งซื้อทั้งหมด - ฉันจงใจเขียนวิธีการ inlined ตามคำแนะนำโดยเรย์มอนด์ Hettinger ที่นี่: stackoverflow.com/a/43122305/281545 Re: repr: ฉันจำการอ่านความคิดเห็น (โดย core dev IIRC บางตัว) ได้ดีมันไม่คุ้มค่ากับความยุ่งยากในการลองและทำการทดสอบซ้ำเพื่อผ่านการทดสอบนั้น (มันเป็นเรื่องยุ่งยาก) - ให้ความสำคัญกับการให้ข้อมูลมากที่สุด แต่ไม่เกิน)
Mr_and_Mrs_D

ฉันจะอนุญาตให้คุณเปรียบเทียบวิธีที่ซ้ำซ้อนของคุณ (คุณควรจดบันทึกไว้ในคำตอบของคุณ) แต่CIstr.__repr__ในกรณีของคุณสามารถผ่านการทดสอบซ้ำด้วยความยุ่งยากน้อยมากและควรทำการดีบักที่ดีกว่ามาก ฉันยังจะเพิ่ม__repr__สำหรับ dict ของคุณ ฉันจะทำมันในคำตอบของฉันเพื่อแสดงให้เห็น
Aaron Hall

@AaronHall: ฉันเพิ่ม__slots__ใน CIstr - สร้างความแตกต่างในประสิทธิภาพ (CIstr ไม่ได้หมายถึง subclassed หรือใช้นอก LowerDict จริง ๆ ควรเป็นคลาสสุดท้ายแบบคงที่ซ้อนกัน) ยังไม่แน่ใจว่าจะแก้ปัญหาการพิมพ์อย่างหรูหราได้อย่างไร (การต่อยอาจมีการรวมกันของ'และ"คำพูด)
Mr_and_Mrs_D

4

สิ่งที่คุณต้องทำคือ

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

หรือ

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

ตัวอย่างการใช้งานสำหรับการใช้งานส่วนตัวของฉัน

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

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

หมายเหตุ : ทดสอบใน python3 เท่านั้น


3

หลังจากที่พยายามออกมาทั้งสองด้านบน ทั้งสองข้อเสนอแนะที่ผมเคยนั่งอยู่บนเส้นทางที่ร่มรื่นกลาง-มองหางูหลาม 2.7 อาจเป็น 3 saner แต่สำหรับฉัน:

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return dict

ซึ่งฉันเกลียดจริง ๆ แต่ดูเหมือนว่าจะพอดีกับความต้องการของฉันซึ่งก็คือ:

  • สามารถแทนที่ **my_dict
    • ถ้าคุณได้รับมรดกจากdict, นี้ทะลุรหัสของคุณ ลองดู
    • นี่ทำให้# 2ไม่เป็นที่ยอมรับสำหรับฉันตลอดเวลาเนื่องจากเป็นเรื่องธรรมดาในรหัสไพ ธ อน
  • สวมหน้ากากเป็น isinstance(my_dict, dict)
    • ออกกฎ MutableMapping เพียงอย่างเดียวดังนั้น# 1ไม่เพียงพอ
    • ฉันแนะนำอย่างใจจดใจจ่อ# 1หากคุณไม่ต้องการมันง่ายและคาดการณ์ได้
  • พฤติกรรมที่สามารถควบคุมได้อย่างเต็มที่
    • ดังนั้นฉันไม่สามารถสืบทอดจาก dict

หากคุณต้องการบอกตัวเองให้แตกต่างจากคนอื่นโดยส่วนตัวฉันใช้สิ่งนี้ (แม้ว่าฉันจะแนะนำชื่อที่ดีกว่า):

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

ตราบใดที่คุณต้องจำตัวเองภายในเท่านั้นวิธีนี้ยากที่จะโทรหาโดยไม่ตั้งใจ__am_i_meเนื่องจากชื่องูเหลือมของงู_MyDict__am_i_meใหญ่ มีความเป็นส่วนตัวมากกว่า_methods เล็กน้อยทั้งในทางปฏิบัติและทางวัฒนธรรม

จนถึงตอนนี้ฉันไม่มีข้อร้องเรียนนอกเหนือจากการ__class__แทนที่อย่างจริงจังที่ดูร่มรื่น ฉันยินดีที่จะได้ยินปัญหาใด ๆ ที่คนอื่นเผชิญกับเรื่องนี้แม้ว่าฉันจะไม่เข้าใจผลที่ตามมาอย่างเต็มที่ แต่จนถึงตอนนี้ฉันก็ไม่มีปัญหาอะไรเลยและสิ่งนี้ทำให้ฉันสามารถโยกย้ายรหัสคุณภาพปานกลางในสถานที่มากมายโดยไม่จำเป็นต้องมีการเปลี่ยนแปลงใด ๆ


เป็นหลักฐาน: https://repl.it/repls/TraumaticToughCockatoo

โดยทั่วไป: คัดลอกตัวเลือก # 2 ปัจจุบันเพิ่มprint 'method_name'บรรทัดลงในทุกวิธีแล้วลองทำเช่นนี้และดูผลลัพธ์:

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

คุณจะเห็นพฤติกรรมที่คล้ายกันสำหรับสถานการณ์อื่น ๆ สมมติว่าตัวปลอมของคุณ - dictเป็นเสื้อคลุมรอบ ๆ ประเภทข้อมูลอื่น ๆ ดังนั้นจึงไม่มีวิธีที่เหมาะสมในการจัดเก็บข้อมูลไว้ใน backup-dict; **your_dictจะว่างเปล่าโดยไม่คำนึงถึงวิธีการอื่น ๆ ที่ทำ

สิ่งนี้ทำงานได้อย่างถูกต้องMutableMappingแต่ทันทีที่คุณรับช่วงต่อจากdictนั้นจะไม่สามารถควบคุมได้


แก้ไข: เป็นการอัปเดตสิ่งนี้ดำเนินการโดยไม่มีปัญหาเดียวมาเกือบสองปีแล้วในหลายแสนบรรทัด (eh, อาจเป็นสองล้านล้าน) บรรทัดของงูหลามที่ซับซ้อนและต้องห้าม ดังนั้นฉันจึงค่อนข้างมีความสุขกับมัน :)

แก้ไข 2: เห็นได้ชัดว่าฉันคัดลอกสิ่งนี้ผิดหรือสิ่งที่นานมาแล้ว @classmethod __class__ไม่ทำงานสำหรับการisinstanceตรวจสอบ - @property __class__ไม่: https://repl.it/repls/UnitedScientificSequence


คุณหมายถึงอะไรโดย" **your_dictจะว่างเปล่า" (ถ้าคุณ subclass จากdict) ฉันไม่ได้เห็นปัญหาใด ๆ กับ Dict เอาออก ...
แมตต์ P

หากคุณใส่ข้อมูลลงใน parent dict (เช่น LowerDict ทำ) มันใช้งานได้ - คุณจะได้รับข้อมูลที่เก็บ dict นั้น หากคุณไม่ต้องการ (บอกว่าคุณต้องการสร้างข้อมูลได้อย่างรวดเร็วเช่น {access_count: "การติดตามสแต็กของการเข้าถึง"} ที่กรอกข้อมูลในแต่ละครั้งที่อ่าน) คุณจะสังเกตเห็น**your_dictว่าไม่ได้รันรหัสของคุณ ไม่สามารถแสดงผลอะไรเป็น "พิเศษ" เช่นคุณไม่สามารถนับ "อ่าน" เพราะมันไม่ได้รันรหัสการอ่านของคุณ MutableMapping ใช้งานได้กับสิ่งนี้ (ใช้ถ้าคุณสามารถ!) แต่มันล้มเหลวisinstance(..., dict)ดังนั้นฉันจึงไม่สามารถใช้งานได้ ซอฟต์แวร์รุ่นเก่า
Groxx

ตกลงฉันเห็นสิ่งที่คุณหมายถึงตอนนี้ ฉันคิดว่าฉันไม่ได้คาดหวังการใช้รหัส**your_dictแต่ฉันคิดว่ามันน่าสนใจมากที่MutableMappingจะทำเช่นนั้น
Matt P

ใช่. มันจำเป็นสำหรับหลายสิ่ง (เช่นฉันกำลังปรับ RPC ให้โทรหาสิ่งที่เคยเป็นแบบอ่านในท้องที่และต้องทำตามความต้องการสำหรับเหตุผล™) และดูเหมือนว่ามีคนไม่กี่คนที่รู้เรื่องนี้แม้กระทั่ง**some_dictเป็นเรื่องธรรมดา อย่างน้อยที่สุดมันก็เกิดขึ้นบ่อยมากในนักตกแต่งดังนั้นหากคุณมีสิ่งใดสิ่งหนึ่งคุณจะเสี่ยงต่อการกระทำผิดที่ดูเหมือนจะเป็นไปไม่ได้ทันทีหากคุณไม่ได้คำนึงถึงมัน
Groxx

บางทีฉันอาจขาดอะไรบางอย่าง แต่def __class__()เคล็ดลับดูเหมือนจะไม่ทำงานกับ Python 2 หรือ 3 อย่างน้อยสำหรับโค้ดตัวอย่างในคำถามวิธีการลงทะเบียนการใช้งาน abc.MutableMapping เป็น subclass dict? (ดัดแปลงเป็นอย่างอื่นสามารถทำงานได้ในสองรุ่น) ฉันต้องการที่จะกลับมาisinstance(SpreadSheet(), dict) True
martineau
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.