From ca624367e03b01532cac3a8a55270bb358b22296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jure=20=C5=A0orn?= Date: Thu, 4 Jul 2019 18:12:44 +0200 Subject: [PATCH] Duck types --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++----------- index.html | 66 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 52097d2..3a1f13d 100644 --- a/README.md +++ b/README.md @@ -1083,31 +1083,71 @@ class MySortable: return NotImplemented ``` +### Iterable +* **Only required method for iterable is iter(), that should return an iterator of its contents.** +```python +class MyIterable: + def __init__(self, a): + self.a = a + def __iter__(self): + for el in self.a: + yield el +``` + ### Collection -* **Methods do not depend on each other, so they can be skipped if not needed.** -* **Any object with defined getitem() is considered iterable, even if it lacks iter().** +* **Every collection is also an iterable.** +* **This cheatsheet actually means `''` when it uses `''`.** +* **I chose not to use the name iterable because it sounds scarier and more vague than collection.** ```python class MyCollection: def __init__(self, a): self.a = a + def __iter__(self): + for el in self.a: + yield el def __len__(self): return len(self.a) - def __getitem__(self, i): - return self.a[i] - def __setitem__(self, i, el): - self.a[i] = el def __contains__(self, el): return el in self.a - def __iter__(self): - for el in self.a: - yield el ``` +### Sequence +* **Every sequence is also an iterable.** +* **That is because iter() is automatically generated if getitem() is defined.** ```python ->>> from collections.abc import Sequence, Collection, Iterable ->>> a = MyCollection([1, 2, 3]) ->>> isinstance(a, Sequence), isinstance(a, Collection), isinstance(a, Iterable) -(False, True, True) +class MySequence: + def __init__(self, a): + self.a = a + def __len__(self): + return len(self.a) + def __getitem__(self, i): + return self.a[i] +``` + +### Collections.abc.Sequence +* **A much richer interface than the basic sequence.** +* **Extending it generates contains(), iter(), reversed(), index(), and count().** +* **It is not a duck type, so `'issubclass(MySequence, collections.abc.Sequence)'` would return 'False' even if it had all the methods defined.** +```python +class MyAbcSequence(collections.abc.Sequence): + def __init__(self, a): + self.a = a + def __len__(self): + return len(self.a) + def __getitem__(self, i): + return self.a[i] +``` + +#### Table of provided methods: +```text ++------------+----------+------------+----------+--------------+ +| | iterable | collection | sequence | abc.Sequence | ++------------+----------+------------+----------+--------------+ +| iter() | yes | yes | yes | yes | +| len() | | yes | yes | yes | +| getitem() | | | yes | yes | +| contains() | | yes | | yes | ++------------+----------+------------+----------+--------------+ ``` ### Iterator diff --git a/index.html b/index.html index 854c1e5..a9d40ba 100644 --- a/index.html +++ b/index.html @@ -1015,30 +1015,70 @@ Z = dataclasses.make_dataclass('Z', [return self.a < other.a return NotImplemented +

Iterable

+
    +
  • Only required method for iterable is iter(), that should return an iterator of its contents.
  • +
+
class MyIterable:
+    def __init__(self, a):
+        self.a = a
+    def __iter__(self):
+        for el in self.a:
+            yield el
+

Collection

    -
  • Methods do not depend on each other, so they can be skipped if not needed.
  • -
  • Any object with defined getitem() is considered iterable, even if it lacks iter().
  • +
  • Every collection is also an iterable.
  • +
  • This cheatsheet actually means '<iterable>' when it uses '<collection>'.
  • +
  • I chose not to use the name iterable because it sounds scarier and more vague than collection.
class MyCollection:
     def __init__(self, a):
         self.a = a
+    def __iter__(self):
+        for el in self.a:
+            yield el
     def __len__(self):
         return len(self.a)
-    def __getitem__(self, i):
-        return self.a[i]
-    def __setitem__(self, i, el):
-        self.a[i] = el
     def __contains__(self, el):
         return el in self.a
-    def __iter__(self):
-        for el in self.a:
-            yield el
 
-
>>> from collections.abc import Sequence, Collection, Iterable
->>> a = MyCollection([1, 2, 3])
->>> isinstance(a, Sequence), isinstance(a, Collection), isinstance(a, Iterable)
-(False, True, True)
+

Sequence

+
    +
  • Every sequence is also an iterable.
  • +
  • That is because iter() is automatically generated if getitem() is defined.
  • +
+
class MySequence:
+    def __init__(self, a):
+        self.a = a
+    def __len__(self):
+        return len(self.a)
+    def __getitem__(self, i):
+        return self.a[i]
+
+

Collections.abc.Sequence

+
    +
  • A much richer interface than the basic sequence.
  • +
  • Extending it generates contains(), iter(), reversed(), index(), and count().
  • +
  • It is not a duck type, so 'issubclass(MySequence, collections.abc.Sequence)' would return 'False' even if it had all the methods defined.
  • +
+
class MyAbcSequence(collections.abc.Sequence):
+    def __init__(self, a):
+        self.a = a
+    def __len__(self):
+        return len(self.a)
+    def __getitem__(self, i):
+        return self.a[i]
+
+

Table of provided methods:

+
+------------+----------+------------+----------+--------------+
+|            | iterable | collection | sequence | abc.Sequence |
++------------+----------+------------+----------+--------------+
+| iter()     |   yes    |    yes     |   yes    |     yes      |
+| len()      |          |    yes     |   yes    |     yes      |
+| getitem()  |          |            |   yes    |     yes      |
+| contains() |          |    yes     |          |     yes      |
++------------+----------+------------+----------+--------------+
 

Iterator

class Counter: