Làm thế nào để thành công hoàn hảo, ghi đè lên một dict?


217

Làm thế nào tôi có thể làm cho "hoàn hảo" một lớp con của dict càng tốt? Mục tiêu cuối cùng là phải có một đơn giản dict trong đó các phím được thành chữ thường.

Dường như cần có một số nguyên thủy nhỏ mà tôi có thể ghi đè để thực hiện công việc này, nhưng theo tất cả các nghiên cứu và cố gắng của tôi thì có vẻ như đây không phải là trường hợp:

  • Nếu tôi ghi đè __getitem__/__setitem__ , thì get/ setkhông hoạt động. Làm thế nào tôi có thể làm cho họ làm việc? Chắc chắn tôi không cần phải thực hiện chúng riêng lẻ?

  • Tôi có đang ngăn cản việc làm việc không, và tôi có cần phải thực hiện __setstate__không?

  • Tôi có cần repr, update__init__ ?

  • Tôi chỉ nên sử dụng mutablemicking (có vẻ như người ta không nên sử dụng UserDict hoặc DictMixin)? Nếu vậy thì thế nào? Các tài liệu không chính xác khai sáng.

Đây là lần đầu tiên tôi get()làm việc đó, không hoạt động và chắc chắn có nhiều vấn đề nhỏ khác:

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

Tôi nghĩ __keytransform __ () nên tĩnh. Cách tiếp cận tốt đẹp mặc dù. (trả trước @staticmethod)
Aiyion.Prime

Câu trả lời:


229

Bạn có thể viết một đối tượng hoạt động như một cách dictkhá dễ dàng với ABC (Các lớp cơ sở trừu tượng) từ collections.abcmô-đun. Nó thậm chí còn cho bạn biết nếu bạn bỏ lỡ một phương thức, vì vậy bên dưới là phiên bản tối thiểu tắt 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

Bạn nhận được một vài phương thức miễn phí từ 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

Tôi sẽ không dicttrực tiếp phân lớp (hoặc nội dung khác). Nó thường không có ý nghĩa, bởi vì những gì bạn thực sự muốn làm là thực hiện giao diện của adict . Và đó chính xác là những gì ABC dành cho.


46
Tôi sẽ đề nghị đổi tên __keytransform__()vì nó vi phạm hướng dẫn kiểu PEP 8 khuyên "Không bao giờ phát minh ra tên đó; chỉ sử dụng chúng như tài liệu" ở cuối phần Mô tả: Đặt tên kiểu .
martineau

1
Câu hỏi mặc dù - sẽ không triển khai giao diện này với loại do người dùng xác định thường dẫn đến các hoạt động giống như chính tả chậm hơn sử dụng loại tích hợp?
twneale

2
Có cách nào để làm điều này sao cho isinstance (_, dict) == True? Hay bạn chỉ sử dụng Mutable Mapping để xây dựng lớp con sau đó?
Andy Hayden

5
@AndyHayden: Bạn nên viết if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Đừng kiểm tra loại đối tượng, kiểm tra giao diện.
Jochen Ritzel

2
@NeilG Điều này không may bao gồm bộ mã hóa trong bộ thư viện chuẩn python - github.com/python-git/python/blob/ Kẻ
Andy Smith

97

Làm thế nào tôi có thể làm cho "hoàn hảo" một lớp con của dict càng tốt?

Mục tiêu cuối cùng là có một lệnh đơn giản trong đó các phím là chữ thường.

  • Nếu tôi ghi đè __getitem__/ __setitem__, thì get / set không hoạt động. Làm thế nào để tôi làm cho họ làm việc? Chắc chắn tôi không cần phải thực hiện chúng riêng lẻ?

  • Tôi có đang ngăn cản việc làm việc không, và tôi có cần phải thực hiện __setstate__không?

  • Tôi có cần repr, cập nhật và __init__?

  • Tôi chỉ nên sử dụng mutablemapping(có vẻ như người ta không nên sử dụng UserDict hoặc DictMixin)? Nếu vậy thì thế nào? Các tài liệu không chính xác khai sáng.

Câu trả lời được chấp nhận sẽ là cách tiếp cận đầu tiên của tôi, nhưng vì nó có một số vấn đề và vì không ai giải quyết được phương án thay thế, nên thực sự phân lớp a dict, tôi sẽ làm điều đó ở đây.

Có gì sai với câu trả lời được chấp nhận?

Đây có vẻ là một yêu cầu khá đơn giản với tôi:

Làm thế nào tôi có thể làm cho "hoàn hảo" một lớp con của dict càng tốt? Mục tiêu cuối cùng là có một lệnh đơn giản trong đó các phím là chữ thường.

Câu trả lời được chấp nhận không thực sự là lớp con dictvà một bài kiểm tra cho điều này không thành công:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False

Lý tưởng nhất là bất kỳ mã kiểm tra loại nào sẽ kiểm tra giao diện mà chúng ta mong đợi hoặc một lớp cơ sở trừu tượng, nhưng nếu các đối tượng dữ liệu của chúng ta đang được chuyển vào các hàm đang kiểm tra dict- và chúng ta không thể "sửa" các hàm đó, mã này sẽ thất bại.

Người khác có thể phân biệt:

  • Câu trả lời được chấp nhận cũng thiếu phân loại: fromkeys .
  • Câu trả lời được chấp nhận cũng có một phần thừa __dict__- do đó chiếm nhiều dung lượng hơn trong bộ nhớ:

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}

Thực tế phân lớp dict

Chúng ta có thể sử dụng lại các phương thức dict thông qua kế thừa. Tất cả những gì chúng ta cần làm là tạo một lớp giao diện để đảm bảo các khóa được truyền vào dict ở dạng chữ thường nếu chúng là các chuỗi.

Nếu tôi ghi đè __getitem__/ __setitem__, thì get / set không hoạt động. Làm thế nào để tôi làm cho họ làm việc? Chắc chắn tôi không cần phải thực hiện chúng riêng lẻ?

Chà, thực hiện từng cái riêng lẻ là nhược điểm của phương pháp này và mặt trái của việc sử dụng MutableMapping(xem câu trả lời được chấp nhận), nhưng nó thực sự không hiệu quả hơn nhiều.

Trước tiên, hãy xác định sự khác biệt giữa Python 2 và 3, tạo một singleton ( _RaiseKeyError) để đảm bảo chúng ta biết nếu chúng ta thực sự có một đối số dict.popvà tạo một hàm để đảm bảo các khóa chuỗi của chúng ta là chữ thường:

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

Bây giờ chúng tôi triển khai - Tôi đang sử dụng supervới các đối số đầy đủ để mã này hoạt động cho Python 2 và 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__())

Chúng tôi sử dụng một cách tiếp cận gần như nồi hơi kéo cho bất kỳ phương pháp hay phương pháp đặc biệt mà tài liệu tham khảo một chìa khóa, nhưng mặt khác, bằng cách thừa kế, chúng tôi nhận phương pháp: len, clear, items, keys, popitem, và valuesmiễn phí. Trong khi điều này đòi hỏi một số suy nghĩ cẩn thận để có được đúng, thật tầm thường khi thấy rằng điều này hoạt động.

(Lưu ý rằng haskeykhông được dùng trong Python 2, đã bị xóa trong Python 3.)

Đây là một số cách sử dụng:

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

Tôi có đang ngăn cản việc làm việc không, và tôi có cần phải thực hiện __setstate__không?

dưa chua

Và dưa chua lớp con dict tốt:

>>> 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__

Tôi có cần repr, cập nhật và __init__?

Chúng tôi đã xác định update__init__, nhưng bạn có một __repr__mặc định đẹp :

>>> ld # without __repr__ defined for the class, we get this
{'foo': None}

Tuy nhiên, thật tốt khi viết một __repr__để cải thiện khả năng gỡ lỗi của mã của bạn. Bài kiểm tra lý tưởng là eval(repr(obj)) == obj. Nếu nó dễ thực hiện cho mã của bạn, tôi thực sự khuyên bạn nên:

>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True

Bạn thấy đấy, đó chính xác là những gì chúng ta cần để tạo lại một đối tượng tương đương - đây là thứ có thể hiển thị trong nhật ký của chúng tôi hoặc trong backtraces:

>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})

Phần kết luận

Tôi chỉ nên sử dụng mutablemapping(có vẻ như người ta không nên sử dụng UserDict hoặc DictMixin)? Nếu vậy thì thế nào? Các tài liệu không chính xác khai sáng.

Vâng, đây là một vài dòng mã, nhưng chúng được dự định là toàn diện. Xu hướng đầu tiên của tôi là sử dụng câu trả lời được chấp nhận và nếu có vấn đề với nó, tôi sẽ xem câu trả lời của mình - vì nó phức tạp hơn một chút và không có ABC để giúp tôi giao diện đúng.

Tối ưu hóa sớm sẽ phức tạp hơn trong tìm kiếm hiệu suất. MutableMappingđơn giản hơn - vì vậy nó có lợi thế ngay lập tức, tất cả những thứ khác đều bằng nhau. Tuy nhiên, để đặt ra tất cả sự khác biệt, hãy so sánh và đối chiếu.

Tôi nên thêm rằng có một sự thúc đẩy để đưa một từ điển tương tự vào collectionsmô-đun, nhưng nó đã bị từ chối . Có lẽ bạn chỉ nên làm điều này thay vào đó:

my_dict[transform(key)]

Nó sẽ dễ dàng gỡ lỗi hơn nhiều.

So sánh và đối chiếu

Có 6 chức năng giao diện được triển khai với MutableMapping(thiếu fromkeys) và 11 với dictlớp con. Tôi không cần phải thực hiện __iter__hoặc __len__, nhưng thay vào đó tôi phải thực hiện get, setdefault, pop, update, copy, __contains__, vàfromkeys - nhưng đây là khá tầm thường, vì tôi có thể sử dụng thừa kế đối với hầu hết những hiện thực.

Việc MutableMappingthực hiện một số điều trong Python dictthực hiện trong C - vì vậy tôi mong đợi một dictlớp con sẽ hoạt động tốt hơn trong một số trường hợp.

Chúng tôi có được miễn phí __eq__trong cả hai cách tiếp cận - cả hai đều giả sử bình đẳng nếu một chính tả khác là chữ thường - nhưng một lần nữa, tôi nghĩ rằng dictlớp con sẽ so sánh nhanh hơn.

Tóm lược:

  • phân lớp MutableMapping đơn giản hơn với ít cơ hội hơn cho các lỗi, nhưng chậm hơn, chiếm nhiều bộ nhớ hơn (xem dict thừa) và thất bạiisinstance(x, dict)
  • phân lớp dictnhanh hơn, sử dụng ít bộ nhớ hơn và vượt qua isinstance(x, dict), nhưng nó có độ phức tạp cao hơn để thực hiện.

Cái nào hoàn hảo hơn? Điều đó phụ thuộc vào định nghĩa của bạn về sự hoàn hảo.


Làm thế nào câu trả lời được chấp nhận sẽ loại bỏ dict thừa?
Seanny123

1
Hai cách xuất hiện ngay lập tức là khai báo thuộc tính cửa hàng __slots__hoặc có thể sử dụng lại __dict__làm cửa hàng, nhưng điều đó trộn lẫn ngữ nghĩa, một điểm tiềm năng khác của sự chỉ trích.
Aaron Hall

1
Sẽ không dễ dàng hơn để viết một trình trang trí có một phương thức và sử dụng của bạn ensure_lowertrên arguemtn đầu tiên (luôn luôn là chìa khóa)? Sau đó, nó sẽ là cùng số lượng ghi đè, nhưng tất cả chúng sẽ có dạng __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__).
Graodes 17/03/2017

1
Cảm ơn vì điều này - nhận được cảnh báo cho pop và fromkey rằng chúng không khớp với chữ ký của phương thức lớp cơ sở.
Mr_and_Mrs_D

1
@Mr_and_Mrs_D Tôi đã thêm một triển khai copy- Tôi nghĩ rằng nên làm điều đó, phải không? Tôi nghĩ rằng nó nên kiểm tra giao diện - ví dụ: đối tượng DataFrame của gấu trúc không phải là một đối tượng Ánh xạ (ở lần kiểm tra cuối cùng) nhưng nó có các mục / iteritems.
Aaron Hall

4

Yêu cầu của tôi nghiêm ngặt hơn một chút:

  • Tôi đã phải giữ lại thông tin trường hợp (các chuỗi là đường dẫn đến các tệp được hiển thị cho người dùng, nhưng đó là một ứng dụng windows nên bên trong tất cả các hoạt động phải không phân biệt chữ hoa chữ thường)
  • Tôi cần các phím càng nhỏ càng tốt (nó đã tạo ra sự khác biệt về hiệu suất bộ nhớ, cắt giảm 110 mb trong số 370). Điều này có nghĩa là bộ nhớ đệm phiên bản chữ thường không phải là một tùy chọn.
  • Tôi cần tạo ra các cấu trúc dữ liệu nhanh nhất có thể (một lần nữa tạo ra sự khác biệt về hiệu suất, tốc độ lần này). Tôi đã phải đi với một nội dung

Suy nghĩ ban đầu của tôi là thay thế lớp Path khó hiểu của chúng tôi cho một lớp con unicode không nhạy cảm trường hợp - nhưng:

  • tỏ ra khó khăn để có được quyền đó - xem: Một lớp chuỗi không nhạy cảm trong python
  • Hóa ra việc xử lý các khóa chính tả rõ ràng làm cho mã dài dòng và lộn xộn - và dễ bị lỗi (các cấu trúc được truyền qua và cả hai, và không rõ liệu chúng có các trường hợp CIStr như các khóa / phần tử hay không, dễ quên cộng some_dict[CIstr(path)]là xấu)

Vì vậy, cuối cùng tôi đã phải viết ra trường hợp không nhạy cảm đó. Nhờ bởi @AaronHall mà đã được thực hiện dễ dàng hơn 10 lần.

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__())

Ngẫu nhiên và rõ ràng vẫn còn là một vấn đề, nhưng một khi bụi đã lắng xuống, việc đổi tên các thuộc tính / biến để bắt đầu bằng ci (và một nhận xét tài liệu lớn về giải thích rằng ci là viết tắt của trường hợp không nhạy cảm) Tôi nghĩ là một giải pháp hoàn hảo - vì người đọc mã phải lưu ý đầy đủ rằng chúng tôi đang xử lý các cấu trúc dữ liệu cơ bản không nhạy cảm. Điều này hy vọng sẽ khắc phục một số lỗi khó tái tạo, mà tôi nghi ngờ là đã làm giảm độ nhạy của vỏ máy.

Bình luận / chỉnh sửa chào mừng :)


CIstr __repr__nên sử dụng lớp cha mẹ __repr__để vượt qua eval (repr (obj)) == obj test (Tôi không nghĩ rằng nó làm ngay bây giờ) và không dựa vào __str__.
Aaron Hall

Ngoài ra hãy kiểm tra trình total_orderingtrang trí lớp - sẽ loại bỏ 4 phương thức khỏi lớp con unicode của bạn. Nhưng lớp con dict trông rất thông minh được thực hiện. : P
Aaron Hall

Cảm ơn @AaronHall - chính bạn là người đã thực hiện điều đó: P Re: tổng đơn hàng - Tôi cố tình viết các phương thức được đưa ra theo lời khuyên của Raymond Hettinger tại đây: stackoverflow.com/a/43122305/281545 . Re: repr: Tôi nhớ đã đọc một bình luận (bởi một số nhà phát triển cốt lõi IIRC), thật không đáng để thử và làm cho repr vượt qua bài kiểm tra đó (thật rắc rối) - tập trung tốt hơn vào nó càng nhiều thông tin càng tốt ( nhưng không nhiều hơn)
Mr_and_Mrs_D

Tôi sẽ cho phép bạn các phương pháp so sánh dự phòng của bạn (bạn nên ghi chú về nó trong câu trả lời của bạn), nhưng CIstr.__repr__, trong trường hợp của bạn , có thể vượt qua bài kiểm tra repr với rất ít rắc rối và nó sẽ giúp gỡ lỗi dễ dàng hơn rất nhiều. Tôi cũng sẽ thêm một __repr__cho dict của bạn. Tôi sẽ làm điều đó trong câu trả lời của tôi để chứng minh.
Aaron Hall

@AaronHall: Tôi đã thêm __slots__vào CIstr - không tạo ra sự khác biệt về hiệu năng (CIstr không có nghĩa là được phân lớp hoặc thực sự được sử dụng bên ngoài LowerDict, nên là lớp cuối cùng được lồng tĩnh). Vẫn không chắc chắn làm thế nào để giải quyết vấn đề repr một cách thanh lịch (sting có thể chứa sự kết hợp của '"trích dẫn)
Mr_and_Mrs_D

4

Tất cả bạn sẽ phải làm là

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

HOẶC LÀ

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

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

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

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


3

Sau khi thử cả hai gợi ý hàng đầu , tôi đã giải quyết một tuyến giữa có vẻ mờ ám cho Python 2.7. Có lẽ 3 là saner, nhưng đối với tôi:

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

điều tôi thực sự ghét, nhưng dường như phù hợp với nhu cầu của tôi, đó là:

  • có thể ghi đè **my_dict
    • nếu bạn thừa kế từ dict, điều này bỏ qua mã của bạn . Hãy thử nó.
    • điều này làm cho số 2 không thể chấp nhận được đối với tôi mọi lúc , vì điều này khá phổ biến trong mã python
  • giả dạng như isinstance(my_dict, dict)
    • loại trừ MutableMapping một mình, vì vậy # 1 là không đủ
    • Tôi chân thành khuyên bạn nên số 1 nếu bạn không cần điều này, nó đơn giản và dễ đoán
  • hành vi kiểm soát đầy đủ
    • vì vậy tôi không thể thừa kế từ dict

Nếu bạn cần phân biệt mình với những người khác, cá nhân tôi sử dụng một cái gì đó như thế này (mặc dù tôi muốn giới thiệu tên tốt hơn):

def __am_i_me(self):
  return True

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

Miễn là bạn chỉ cần nhận ra bản thân mình bên trong, theo cách này, việc gọi vô tình sẽ khó hơn __am_i_medo tên của con trăn (cái này được đổi tên thành _MyDict__am_i_metừ bất cứ thứ gì gọi bên ngoài lớp này). Hơi riêng tư hơn_method s, cả trong thực tế và văn hóa.

Cho đến nay tôi không có khiếu nại, ngoài việc __class__ghi đè trông nghiêm túc . Tôi rất vui khi nghe về bất kỳ vấn đề nào mà người khác gặp phải với vấn đề này, tôi không hiểu hết hậu quả. Nhưng cho đến nay tôi không gặp vấn đề gì và điều này cho phép tôi di chuyển rất nhiều mã chất lượng trung bình ở nhiều vị trí mà không cần bất kỳ thay đổi nào.


Làm bằng chứng: https://repl.it/repls/TraumaticToughCockatoo

Về cơ bản: sao chép tùy chọn # 2 hiện tại , thêm print 'method_name'dòng vào mọi phương thức, sau đó thử điều này và xem đầu ra:

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

Bạn sẽ thấy hành vi tương tự cho các kịch bản khác. Nói giả của bạn-dict là một trình bao bọc xung quanh một số kiểu dữ liệu khác, vì vậy không có cách nào hợp lý để lưu trữ dữ liệu trong bản sao lưu;**your_dictsẽ trống rỗng, bất kể mọi phương pháp khác làm gì.

Điều này hoạt động chính xác cho MutableMapping, nhưng ngay khi bạn thừa kế từdict nó trở nên không thể kiểm soát.


Chỉnh sửa: như một bản cập nhật, nó đã chạy mà không gặp sự cố nào trong gần hai năm nay, trên vài trăm nghìn (eh, có thể là vài triệu) dòng trăn phức tạp, có di sản. Vì vậy, tôi khá hài lòng với nó :)

Chỉnh sửa 2: hình như tôi đã sao chép sai cái này hoặc cái gì đó từ lâu. @classmethod __class__không hoạt động đối với isinstanceséc - @property __class__hiện: https://repl.it/repls/UnitedSellectificSequence


Chính xác ý bạn là gì bởi " **your_dictsẽ trống rỗng" (nếu bạn phân lớp từ dict)? Tôi chưa thấy bất kỳ vấn đề nào với việc giải nén dict ...
Matt P

Nếu bạn thực sự đưa dữ liệu vào dict cha (giống như LowerDict), nó sẽ hoạt động - bạn sẽ nhận được dữ liệu được lưu trữ chính tả đó. Nếu bạn không (giả sử bạn muốn tạo dữ liệu một cách nhanh chóng, như {access_count: "stack track of access"} sẽ lấp đầy mỗi lần nó đọc), bạn sẽ nhận thấy rằng **your_dictnó không thực thi mã của bạn, vì vậy nó không thể xuất bất cứ thứ gì "đặc biệt". Ví dụ: bạn không thể đếm "đọc" vì nó không thực thi mã đếm đọc của bạn. MutableMapping không hoạt động cho việc này (sử dụng nó nếu bạn có thể!), Nhưng nó không thành công isinstance(..., dict)nên tôi không thể sử dụng nó. phần mềm di sản yay.
Groxx

Ok, tôi hiểu ý của bạn bây giờ. Tôi cho rằng tôi đã không mong đợi việc thực thi mã **your_dict, nhưng tôi thấy nó rất thú vị MutableMappingsẽ làm điều đó.
Matt P

Vâng Nó cần thiết cho một số thứ (ví dụ: tôi đã làm mờ các cuộc gọi RPC vào những gì đã từng là đọc chính tả cục bộ và phải thực hiện theo yêu cầu cho R Reason ™), và dường như rất ít người biết về nó, thậm chí tho **some_dictlà khá phổ biến. Ít nhất nó xảy ra rất thường xuyên trong các nhà trang trí, vì vậy nếu bạn có bất kỳ điều gì , bạn ngay lập tức có nguy cơ bị hành vi sai trái dường như không thể nếu bạn không tính đến nó.
Groxx

Có lẽ tôi đang thiếu một cái gì đó, nhưng def __class__()thủ thuật dường như không hoạt động với Python 2 hoặc 3, ít nhất là đối với mã ví dụ trong câu hỏi Làm thế nào để đăng ký thực hiện abc.MutableMapping như một lớp con chính tả? (sửa đổi để làm việc trong hai phiên bản). Tôi muốn isinstance(SpreadSheet(), dict)trở về True.
martineau
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.