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 dict
và 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.pop
và 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 super
vớ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à values
miễ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 haskey
khô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
và __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 collections
mô-đ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 dict
lớ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 MutableMapping
thực hiện một số điều trong Python dict
thực hiện trong C - vì vậy tôi mong đợi một dict
lớ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 dict
lớ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
dict
nhanh 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.