1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc. All rights reserved.
3#
4# Use of this source code is governed by a BSD-style
5# license that can be found in the LICENSE file or at
6# https://developers.google.com/open-source/licenses/bsd
7"""Contains container classes to represent different protocol buffer types.
8
9This file defines container classes which represent categories of protocol
10buffer field types which need extra maintenance. Currently these categories
11are:
12
13- Repeated scalar fields - These are all repeated fields which aren't
14 composite (e.g. they are of simple types like int32, string, etc).
15- Repeated composite fields - Repeated fields which are composite. This
16 includes groups and nested messages.
17"""
18
19import collections.abc
20import copy
21import pickle
22import warnings
23from typing import (
24 Any,
25 Iterable,
26 Iterator,
27 List,
28 MutableMapping,
29 MutableSequence,
30 NoReturn,
31 Optional,
32 Sequence,
33 TypeVar,
34 Union,
35 overload,
36)
37
38_T = TypeVar('_T')
39_K = TypeVar('_K')
40_V = TypeVar('_V')
41
42from google.protobuf.descriptor import FieldDescriptor
43from google.protobuf import message
44
45
46def _CheckFrozen(is_frozen: bool, msg: str) -> None:
47 if is_frozen:
48 warnings.warn(
49 'Mutating messages or containers returned by GetOptions() is'
50 ' deprecated and will raise an exception in a future release.',
51 category=FutureWarning,
52 stacklevel=3,
53 )
54
55
56class BaseContainer(Sequence[_T]):
57 """Base container class."""
58
59 # Minimizes memory usage and disallows assignment to other attributes.
60 __slots__ = ['_message_listener', '_values', '_frozen']
61
62 def __init__(self, message_listener: Any) -> None:
63 """Args:
64
65 message_listener: A MessageListener implementation.
66 The RepeatedScalarFieldContainer will call this object's
67 Modified() method when it is modified.
68 """
69 self._message_listener = message_listener
70 self._values = []
71 self._frozen = False
72
73 @overload
74 def __getitem__(self, key: int) -> _T:
75 ...
76
77 @overload
78 def __getitem__(self, key: slice) -> List[_T]:
79 ...
80
81 def __getitem__(self, key):
82 """Retrieves item by the specified key."""
83 return self._values[key]
84
85 def __len__(self) -> int:
86 """Returns the number of elements in the container."""
87 return len(self._values)
88
89 def __ne__(self, other: Any) -> bool:
90 """Checks if another instance isn't equal to this one."""
91 # The concrete classes should define __eq__.
92 return not self == other
93
94 __hash__ = None
95
96 def __repr__(self) -> str:
97 return repr(self._values)
98
99 def _SetFrozen(self) -> None:
100 self._frozen = True
101
102 def _AssureWritable(self) -> 'BaseContainer[_T]':
103 _CheckFrozen(self._frozen, 'Container is immutable')
104 return self
105
106 def sort(self, *args, **kwargs) -> None:
107 self._AssureWritable()
108 # Continue to support the old sort_function keyword argument.
109 # This is expected to be a rare occurrence, so use LBYL to avoid
110 # the overhead of actually catching KeyError.
111 if 'sort_function' in kwargs:
112 kwargs['cmp'] = kwargs.pop('sort_function')
113 self._values.sort(*args, **kwargs)
114
115 def reverse(self) -> None:
116 self._AssureWritable()
117 self._values.reverse()
118
119
120# TODO: Remove this. BaseContainer does *not* conform to
121# MutableSequence, only its subclasses do.
122collections.abc.MutableSequence.register(BaseContainer)
123
124
125class RepeatedScalarFieldContainer(BaseContainer[_T], MutableSequence[_T]):
126 """Simple, type-checked, list-like container for holding repeated scalars."""
127
128 # Disallows assignment to other attributes.
129 __slots__ = ['_type_checker', '_field']
130
131 def __init__(
132 self,
133 message_listener: Any,
134 type_checker: Any,
135 field: Any = None,
136 ) -> None:
137 """Args:
138
139 message_listener: A MessageListener implementation. The
140 RepeatedScalarFieldContainer will call this object's Modified() method
141 when it is modified.
142 type_checker: A type_checkers.ValueChecker instance to run on elements
143 inserted into this container.
144 """
145 super().__init__(message_listener)
146 self._type_checker = type_checker
147 self._field = field
148
149 def append(self, value: _T) -> None:
150 """Appends an item to the list. Similar to list.append()."""
151 self._AssureWritable()
152 self._values.append(self._type_checker.CheckValue(value))
153 if not self._message_listener.dirty:
154 self._message_listener.Modified()
155
156 def insert(self, key: int, value: _T) -> None:
157 """Inserts the item at the specified position. Similar to list.insert()."""
158 self._AssureWritable()
159 self._values.insert(key, self._type_checker.CheckValue(value))
160 if not self._message_listener.dirty:
161 self._message_listener.Modified()
162
163 def extend(self, elem_seq: Iterable[_T]) -> None:
164 """Extends by appending the given iterable. Similar to list.extend()."""
165 self._AssureWritable()
166 elem_seq_iter = iter(elem_seq)
167 new_values = [self._type_checker.CheckValue(elem) for elem in elem_seq_iter]
168 if new_values:
169 self._values.extend(new_values)
170 self._message_listener.Modified()
171
172 def MergeFrom(
173 self,
174 other: Union['RepeatedScalarFieldContainer[_T]', Iterable[_T]],
175 ) -> None:
176 """Appends the contents of another repeated field of the same type to this
177
178 one. We do not check the types of the individual fields.
179 """
180 self._AssureWritable()
181 self._values.extend(other)
182 self._message_listener.Modified()
183
184 def remove(self, elem: _T):
185 """Removes an item from the list. Similar to list.remove()."""
186 self._AssureWritable()
187 self._values.remove(elem)
188 self._message_listener.Modified()
189
190 def pop(self, key: Optional[int] = -1) -> _T:
191 """Removes and returns an item at a given index. Similar to list.pop()."""
192 self._AssureWritable()
193 value = self._values[key]
194 self.__delitem__(key)
195 return value
196
197 @overload
198 def __setitem__(self, key: int, value: _T) -> None:
199 ...
200
201 @overload
202 def __setitem__(self, key: slice, value: Iterable[_T]) -> None:
203 ...
204
205 def __setitem__(self, key, value) -> None:
206 """Sets the item on the specified position."""
207 self._AssureWritable()
208 if isinstance(key, slice):
209 if key.step is not None:
210 raise ValueError('Extended slices not supported')
211 self._values[key] = map(self._type_checker.CheckValue, value)
212 self._message_listener.Modified()
213 else:
214 self._values[key] = self._type_checker.CheckValue(value)
215 self._message_listener.Modified()
216
217 def __delitem__(self, key: Union[int, slice]) -> None:
218 """Deletes the item at the specified position."""
219 self._AssureWritable()
220 del self._values[key]
221 self._message_listener.Modified()
222
223 def __eq__(self, other: Any) -> bool:
224 """Compares the current instance with another one."""
225 if self is other:
226 return True
227 # Special case for the same type which should be common and fast.
228 if isinstance(other, self.__class__):
229 return other._values == self._values
230 # We are presumably comparing against some other sequence type.
231 return other == self._values
232
233 def __deepcopy__(
234 self,
235 unused_memo: Any = None,
236 ) -> 'RepeatedScalarFieldContainer[_T]':
237 clone = RepeatedScalarFieldContainer(
238 copy.deepcopy(self._message_listener), self._type_checker, self._field
239 )
240 clone.MergeFrom(self)
241 return clone
242
243 def __reduce__(self, **kwargs) -> NoReturn:
244 raise pickle.PickleError(
245 "Can't pickle repeated scalar fields, convert to list first"
246 )
247
248 def __array__(self, dtype=None, copy=None):
249 import numpy as np
250
251 if dtype is None:
252 cpp_type = self._field.cpp_type
253 if cpp_type == FieldDescriptor.CPPTYPE_INT32:
254 dtype = np.int32
255 elif cpp_type == FieldDescriptor.CPPTYPE_INT64:
256 dtype = np.int64
257 elif cpp_type == FieldDescriptor.CPPTYPE_UINT32:
258 dtype = np.uint32
259 elif cpp_type == FieldDescriptor.CPPTYPE_UINT64:
260 dtype = np.uint64
261 elif cpp_type == FieldDescriptor.CPPTYPE_DOUBLE:
262 dtype = np.float64
263 elif cpp_type == FieldDescriptor.CPPTYPE_FLOAT:
264 dtype = np.float32
265 elif cpp_type == FieldDescriptor.CPPTYPE_BOOL:
266 dtype = np.bool
267 elif cpp_type == FieldDescriptor.CPPTYPE_ENUM:
268 dtype = np.int32
269 elif self._field.type == FieldDescriptor.TYPE_BYTES:
270 dtype = 'S'
271 elif self._field.type == FieldDescriptor.TYPE_STRING:
272 dtype = str
273 else:
274 raise SystemError(
275 'Code should never reach here: message type detected in'
276 ' RepeatedScalarFieldContainer'
277 )
278 return np.array(self._values, dtype=dtype, copy=True)
279
280
281# TODO: Constrain T to be a subtype of Message.
282class RepeatedCompositeFieldContainer(BaseContainer[_T], MutableSequence[_T]):
283 """Simple, list-like container for holding repeated composite fields."""
284
285 # Disallows assignment to other attributes.
286 __slots__ = ['_message_descriptor']
287
288 def __init__(self, message_listener: Any, message_descriptor: Any) -> None:
289 """Note that we pass in a descriptor instead of the generated directly,
290
291 since at the time we construct a _RepeatedCompositeFieldContainer we
292 haven't yet necessarily initialized the type that will be contained in the
293 container.
294
295 Args:
296 message_listener: A MessageListener implementation. The
297 RepeatedCompositeFieldContainer will call this object's Modified()
298 method when it is modified.
299 message_descriptor: A Descriptor instance describing the protocol type
300 that should be present in this container. We'll use the _concrete_class
301 field of this descriptor when the client calls add().
302 """
303 super().__init__(message_listener)
304 self._message_descriptor = message_descriptor
305
306 def _SetFrozen(self) -> None:
307 super()._SetFrozen()
308 for val in self._values:
309 val._SetFrozen()
310
311 def add(self, **kwargs: Any) -> _T:
312 """Adds a new element at the end of the list and returns it.
313
314 Keyword arguments may be used to initialize the element.
315 """
316 self._AssureWritable()
317 new_element = self._message_descriptor._concrete_class(**kwargs)
318 new_element._SetListener(self._message_listener)
319 self._values.append(new_element)
320 if not self._message_listener.dirty:
321 self._message_listener.Modified()
322 return new_element
323
324 def append(self, value: _T) -> None:
325 """Appends one element by copying the message."""
326 self._AssureWritable()
327 new_element = self._message_descriptor._concrete_class()
328 new_element._SetListener(self._message_listener)
329 new_element.CopyFrom(value)
330 self._values.append(new_element)
331 if not self._message_listener.dirty:
332 self._message_listener.Modified()
333
334 def insert(self, key: int, value: _T) -> None:
335 """Inserts the item at the specified position by copying."""
336 self._AssureWritable()
337 new_element = self._message_descriptor._concrete_class()
338 new_element._SetListener(self._message_listener)
339 new_element.CopyFrom(value)
340 self._values.insert(key, new_element)
341 if not self._message_listener.dirty:
342 self._message_listener.Modified()
343
344 def extend(self, elem_seq: Iterable[_T]) -> None:
345 """Extends by appending the given sequence of elements of the same type
346
347 as this one, copying each individual message.
348 """
349 self._AssureWritable()
350 message_class = self._message_descriptor._concrete_class
351 listener = self._message_listener
352 values = self._values
353 for message in elem_seq:
354 new_element = message_class()
355 new_element._SetListener(listener)
356 new_element.MergeFrom(message)
357 values.append(new_element)
358 listener.Modified()
359
360 def MergeFrom(
361 self,
362 other: Union['RepeatedCompositeFieldContainer[_T]', Iterable[_T]],
363 ) -> None:
364 """Appends the contents of another repeated field of the same type to this
365
366 one, copying each individual message.
367 """
368 self._AssureWritable()
369 self.extend(other)
370
371 def remove(self, elem: _T) -> None:
372 """Removes an item from the list. Similar to list.remove()."""
373 self._AssureWritable()
374 self._values.remove(elem)
375 self._message_listener.Modified()
376
377 def pop(self, key: Optional[int] = -1) -> _T:
378 """Removes and returns an item at a given index. Similar to list.pop()."""
379 self._AssureWritable()
380 value = self._values[key]
381 self.__delitem__(key)
382 return value
383
384 @overload
385 def __setitem__(self, key: int, value: _T) -> None:
386 ...
387
388 @overload
389 def __setitem__(self, key: slice, value: Iterable[_T]) -> None:
390 ...
391
392 def __setitem__(self, key, value):
393 # This method is implemented to make RepeatedCompositeFieldContainer
394 # structurally compatible with typing.MutableSequence. It is
395 # otherwise unsupported and will always raise an error.
396 raise TypeError(
397 f'{self.__class__.__name__} object does not support item assignment'
398 )
399
400 def __delitem__(self, key: Union[int, slice]) -> None:
401 """Deletes the item at the specified position."""
402 self._AssureWritable()
403 del self._values[key]
404 self._message_listener.Modified()
405
406 def __eq__(self, other: Any) -> bool:
407 """Compares the current instance with another one."""
408 if self is other:
409 return True
410 if not isinstance(other, self.__class__):
411 raise TypeError(
412 'Can only compare repeated composite fields against '
413 'other repeated composite fields.'
414 )
415 return self._values == other._values
416
417
418class ScalarMap(MutableMapping[_K, _V]):
419 """Simple, type-checked, dict-like container for holding repeated scalars."""
420
421 # Disallows assignment to other attributes.
422 __slots__ = [
423 '_key_checker',
424 '_value_checker',
425 '_values',
426 '_message_listener',
427 '_entry_descriptor',
428 '_frozen',
429 ]
430
431 def __init__(
432 self,
433 message_listener: Any,
434 key_checker: Any,
435 value_checker: Any,
436 entry_descriptor: Any,
437 ) -> None:
438 """Args:
439
440 message_listener: A MessageListener implementation.
441 The ScalarMap will call this object's Modified() method when it
442 is modified.
443 key_checker: A type_checkers.ValueChecker instance to run on keys
444 inserted into this container.
445 value_checker: A type_checkers.ValueChecker instance to run on values
446 inserted into this container.
447 entry_descriptor: The MessageDescriptor of a map entry: key and value.
448 """
449 self._message_listener = message_listener
450 self._key_checker = key_checker
451 self._value_checker = value_checker
452 self._entry_descriptor = entry_descriptor
453 self._values = {}
454 self._frozen = False
455
456 def _SetFrozen(self) -> None:
457 self._frozen = True
458
459 def _AssureWritable(self) -> 'ScalarMap[_K, _V]':
460 _CheckFrozen(self._frozen, 'Map is immutable')
461 return self
462
463 def __getitem__(self, key: _K) -> _V:
464 key = self._key_checker.CheckValue(key)
465 try:
466 return self._values[key]
467 except KeyError:
468 self._AssureWritable()
469 val = self._value_checker.DefaultValue()
470 self._values[key] = val
471 return val
472
473 def __contains__(self, item: _K) -> bool:
474 # We check the key's type to match the strong-typing flavor of the API.
475 # Also this makes it easier to match the behavior of the C++ implementation.
476 item = self._key_checker.CheckValue(item)
477 return item in self._values
478
479 @overload
480 def get(self, key: _K) -> Optional[_V]:
481 ...
482
483 @overload
484 def get(self, key: _K, default: _T) -> Union[_V, _T]:
485 ...
486
487 # We need to override this explicitly, because our defaultdict-like behavior
488 # will make the default implementation (from our base class) always insert
489 # the key.
490 def get(self, key, default=None):
491 checked_key = self._key_checker.CheckValue(key)
492 if checked_key in self._values:
493 return self[checked_key]
494 else:
495 return default
496
497 def __setitem__(self, key: _K, value: _V) -> _T:
498 self._AssureWritable()
499 checked_key = self._key_checker.CheckValue(key)
500 checked_value = self._value_checker.CheckValue(value)
501 self._values[checked_key] = checked_value
502 self._message_listener.Modified()
503
504 def __delitem__(self, key: _K) -> None:
505 self._AssureWritable()
506 checked_key = self._key_checker.CheckValue(key)
507 del self._values[checked_key]
508 self._message_listener.Modified()
509
510 def __len__(self) -> int:
511 return len(self._values)
512
513 def __iter__(self) -> Iterator[_K]:
514 return iter(self._values)
515
516 def __repr__(self) -> str:
517 return repr(self._values)
518
519 def setdefault(self, key: _K, value: Optional[_V] = None) -> _V:
520 self._AssureWritable()
521 checked_key = self._key_checker.CheckValue(key)
522 if value == None:
523 raise ValueError('The value for scalar map setdefault must be set.')
524 if checked_key not in self._values:
525 self.__setitem__(checked_key, value)
526 return self[key]
527
528 def MergeFrom(self, other: 'ScalarMap[_K, _V]') -> None:
529 self._AssureWritable()
530 self._values.update(other._values)
531 self._message_listener.Modified()
532
533 def InvalidateIterators(self) -> None:
534 # It appears that the only way to reliably invalidate iterators to
535 # self._values is to ensure that its size changes.
536 original = self._values
537 self._values = original.copy()
538 original[None] = None
539
540 # This is defined in the abstract base, but we can do it much more cheaply.
541 def clear(self) -> None:
542 self._AssureWritable()
543 self._values.clear()
544 self._message_listener.Modified()
545
546 def GetEntryClass(self) -> Any:
547 return self._entry_descriptor._concrete_class
548
549
550class MessageMap(MutableMapping[_K, _V]):
551 """Simple, type-checked, dict-like container for with submessage values."""
552
553 # Disallows assignment to other attributes.
554 __slots__ = [
555 '_key_checker',
556 '_values',
557 '_message_listener',
558 '_message_descriptor',
559 '_entry_descriptor',
560 '_frozen',
561 ]
562
563 def __init__(
564 self,
565 message_listener: Any,
566 message_descriptor: Any,
567 key_checker: Any,
568 entry_descriptor: Any,
569 ) -> None:
570 """Args:
571
572 message_listener: A MessageListener implementation.
573 The ScalarMap will call this object's Modified() method when it
574 is modified.
575 key_checker: A type_checkers.ValueChecker instance to run on keys
576 inserted into this container.
577 value_checker: A type_checkers.ValueChecker instance to run on values
578 inserted into this container.
579 entry_descriptor: The MessageDescriptor of a map entry: key and value.
580 """
581 self._message_listener = message_listener
582 self._message_descriptor = message_descriptor
583 self._key_checker = key_checker
584 self._entry_descriptor = entry_descriptor
585 self._values = {}
586 self._frozen = False
587
588 def _SetFrozen(self) -> None:
589 self._frozen = True
590 for val in self._values.values():
591 val._SetFrozen()
592
593 def _AssureWritable(self) -> 'MessageMap[_K, _V]':
594 _CheckFrozen(self._frozen, 'Map is immutable')
595 return self
596
597 def __getitem__(self, key: _K) -> _V:
598 key = self._key_checker.CheckValue(key)
599 try:
600 return self._values[key]
601 except KeyError:
602 self._AssureWritable()
603 new_element = self._message_descriptor._concrete_class()
604 new_element._SetListener(self._message_listener)
605 self._values[key] = new_element
606 self._message_listener.Modified()
607 return new_element
608
609 def get_or_create(self, key: _K) -> _V:
610 """get_or_create() is an alias for getitem (ie. map[key]).
611
612 Args:
613 key: The key to get or create in the map.
614
615 This is useful in cases where you want to be explicit that the call is
616 mutating the map. This can avoid lint errors for statements like this
617 that otherwise would appear to be pointless statements:
618
619 msg.my_map[key]
620 """
621 return self[key]
622
623 @overload
624 def get(self, key: _K) -> Optional[_V]:
625 ...
626
627 @overload
628 def get(self, key: _K, default: _T) -> Union[_V, _T]:
629 ...
630
631 # We need to override this explicitly, because our defaultdict-like behavior
632 # will make the default implementation (from our base class) always insert
633 # the key.
634 def get(self, key, default=None):
635 if key in self:
636 return self[key]
637 else:
638 return default
639
640 def __contains__(self, item: _K) -> bool:
641 item = self._key_checker.CheckValue(item)
642 return item in self._values
643
644 def __setitem__(self, key: _K, value: _V) -> NoReturn:
645 self._AssureWritable()
646 raise ValueError('May not set values directly, call my_map[key].foo = 5')
647
648 def __delitem__(self, key: _K) -> None:
649 self._AssureWritable()
650 key = self._key_checker.CheckValue(key)
651 del self._values[key]
652 self._message_listener.Modified()
653
654 def __len__(self) -> int:
655 return len(self._values)
656
657 def __iter__(self) -> Iterator[_K]:
658 return iter(self._values)
659
660 def __repr__(self) -> str:
661 return repr(self._values)
662
663 def setdefault(self, key: _K, value: Optional[_V] = None) -> _V:
664 self._AssureWritable()
665 raise NotImplementedError(
666 'Set message map value directly is not supported, call'
667 ' my_map[key].foo = 5'
668 )
669
670 def MergeFrom(self, other: 'MessageMap[_K, _V]') -> None:
671 self._AssureWritable()
672 # pylint: disable=protected-access
673 for key in other._values:
674 # According to documentation: "When parsing from the wire or when merging,
675 # if there are duplicate map keys the last key seen is used".
676 if key in self:
677 del self[key]
678 self[key].CopyFrom(other[key])
679 # self._message_listener.Modified() not required here, because
680 # mutations to submessages already propagate.
681
682 def InvalidateIterators(self) -> None:
683 # It appears that the only way to reliably invalidate iterators to
684 # self._values is to ensure that its size changes.
685 original = self._values
686 self._values = original.copy()
687 original[None] = None
688
689 # This is defined in the abstract base, but we can do it much more cheaply.
690 def clear(self) -> None:
691 self._AssureWritable()
692 self._values.clear()
693 self._message_listener.Modified()
694
695 def GetEntryClass(self) -> Any:
696 return self._entry_descriptor._concrete_class
697
698
699class _UnknownField:
700 """A parsed unknown field."""
701
702 # Disallows assignment to other attributes.
703 __slots__ = ['_field_number', '_wire_type', '_data']
704
705 def __init__(self, field_number, wire_type, data):
706 self._field_number = field_number
707 self._wire_type = wire_type
708 self._data = data
709 return
710
711 def __lt__(self, other):
712 # pylint: disable=protected-access
713 return self._field_number < other._field_number
714
715 def __eq__(self, other):
716 if self is other:
717 return True
718 # pylint: disable=protected-access
719 return (
720 self._field_number == other._field_number
721 and self._wire_type == other._wire_type
722 and self._data == other._data
723 )
724
725
726class UnknownFieldRef: # pylint: disable=missing-class-docstring
727
728 def __init__(self, parent, index):
729 self._parent = parent
730 self._index = index
731
732 def _check_valid(self):
733 if not self._parent:
734 raise ValueError(
735 'UnknownField does not exist. The parent message might be cleared.'
736 )
737 if self._index >= len(self._parent):
738 raise ValueError(
739 'UnknownField does not exist. The parent message might be cleared.'
740 )
741
742 @property
743 def field_number(self):
744 self._check_valid()
745 # pylint: disable=protected-access
746 return self._parent._internal_get(self._index)._field_number
747
748 @property
749 def wire_type(self):
750 self._check_valid()
751 # pylint: disable=protected-access
752 return self._parent._internal_get(self._index)._wire_type
753
754 @property
755 def data(self):
756 self._check_valid()
757 # pylint: disable=protected-access
758 return self._parent._internal_get(self._index)._data
759
760
761class UnknownFieldSet:
762 """UnknownField container"""
763
764 # Disallows assignment to other attributes.
765 __slots__ = ['_values']
766
767 def __init__(self):
768 self._values = []
769
770 def __getitem__(self, index):
771 if self._values is None:
772 raise ValueError(
773 'UnknownFields does not exist. The parent message might be cleared.'
774 )
775 size = len(self._values)
776 if index < 0:
777 index += size
778 if index < 0 or index >= size:
779 raise IndexError('index %d out of range'.index)
780
781 return UnknownFieldRef(self, index)
782
783 def _internal_get(self, index):
784 return self._values[index]
785
786 def __len__(self):
787 if self._values is None:
788 raise ValueError(
789 'UnknownFields does not exist. The parent message might be cleared.'
790 )
791 return len(self._values)
792
793 def _add(self, field_number, wire_type, data):
794 unknown_field = _UnknownField(field_number, wire_type, data)
795 self._values.append(unknown_field)
796 return unknown_field
797
798 def __iter__(self):
799 for i in range(len(self)):
800 yield UnknownFieldRef(self, i)
801
802 def _extend(self, other):
803 if other is None:
804 return
805 # pylint: disable=protected-access
806 self._values.extend(other._values)
807
808 def __eq__(self, other):
809 if self is other:
810 return True
811 # Sort unknown fields because their order shouldn't
812 # affect equality test.
813 values = list(self._values)
814 if other is None:
815 return not values
816 values.sort()
817 # pylint: disable=protected-access
818 other_values = sorted(other._values)
819 return values == other_values
820
821 def _clear(self):
822 for value in self._values:
823 # pylint: disable=protected-access
824 if isinstance(value._data, UnknownFieldSet):
825 value._data._clear() # pylint: disable=protected-access
826 self._values = None