0% found this document useful (0 votes)
3 views14 pages

Python Refresh

The document is a reference guide on Python programming, covering various topics such as comprehensions, exceptions, threading, and new features since 2016. It includes detailed explanations of concepts like the Global Interpreter Lock, decorators, and the differences between threading, multiprocessing, and asynchronous programming. The notes also highlight important updates in the Python standard library, including f-strings and dataclasses.

Uploaded by

gaccorto
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views14 pages

Python Refresh

The document is a reference guide on Python programming, covering various topics such as comprehensions, exceptions, threading, and new features since 2016. It includes detailed explanations of concepts like the Global Interpreter Lock, decorators, and the differences between threading, multiprocessing, and asynchronous programming. The notes also highlight important updates in the Python standard library, including f-strings and dataclasses.

Uploaded by

gaccorto
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Refresh

Reference Notes

Giacomo Accorto

February 27, 2026

Contents

1 Dict / List Comprehensions 3

2 Grouping with Dict 3

3 Exceptions 3
3.1 1. Catch and handle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3.2 2. Catch and re-raise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.3 3. Catch and raise new . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.4 4. Raise conditionally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

4 GIL — Global Interpreter Lock 4

5 Threading vs Multiprocessing vs Async 5


5.1 Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.2 Multiprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.3 Async . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

6 Iterables and Iterators 5

7 Generators 5

8 Decorators and Closures 6

9 What Is New in Python Since 2016 7


9.1 Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
9.2 Third Party . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

10 Why Python Is Cool 7

11 Mutable Default Arguments, Mutable and Immutable 7


11.1 Mutables vs Immutables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

12 Type Hints 8

13 Different “Times” in Python 8

14 Shallow vs Deep Copy 9

15 *args and **kwargs, Unpacking 9

1
16 Higher-Order Functions and Comparators 10

17 Singleton 10

18 slots 10

19 Nine Flavors of Callable 11

20 Context Manager 11

21 Garbage Collection 11

22 Tuple vs namedtuple vs Dataclass 12


22.1 Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
22.2 namedtuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
22.3 Dataclass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

23 Dunder Methods 13
23.1 Object lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
23.2 Representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
23.3 Comparison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
23.4 Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
23.5 Container protocol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
23.6 Attribute access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
23.7 Callable and context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
23.8 Type system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2
Dict / List Comprehensions
List comprehension — concise syntax for building lists:
squares = [ x * x for x in range (10) ]
evens = [ x for x in range (20) if x % 2 == 0]

Dict comprehension — same idea for dicts:


inv = { v : k for k , v in original . items () }

Nested comprehensions — flatten or transform 2D structures:


flat = [ x for row in matrix for x in row ]

Comprehensions are generally faster than equivalent for loops because the iteration is done in
C inside the interpreter.

Grouping with Dict


A common pattern is to group items by some key into a dict of lists:
from collections import defaultdict

words = [ " apple " , " ant " , " banana " , " avocado " , " blueberry " ]
grouped = defaultdict ( list )
for word in words :
grouped [ word [0]]. append ( word )
# { ’ a ’: [ ’ apple ’, ’ ant ’, ’ avocado ’] , ’b ’: [ ’ banana ’, ’ blueberry ’]}

defaultdict avoids the KeyError you would get with a plain dict and removes the need for an
explicit setdefault or if key not in d check.

Exceptions
Hierarchy:
BaseException
+ - - SystemExit # sys . exit ()
+ - - Keyboa rdInt errupt # ctrl + c
+ - - Exception # everything you normally deal with
+ - - ValueError # right type , wrong value : int (" hello ")
+ - - TypeError # wrong type : 1 + " a "
+ - - KeyError # dict key doesn ’ t exist : d [" missing "]
+ - - IndexError # list index out of range : l [99]
+ - - AttributeError # object has no attribute : " str ". foo
+ - - NameError # variable not defined : print ( x )
+ - - FileNo tFound Error
+ - - ImportError
+ - - StopIteration # iterator exhausted
+ - - RuntimeError # generic , often subclassed
+ - - N o tI m p le m e nt e d E rr o r # abstract method not implemented

1. Catch and handle


Exception is consumed, error not propagated.

3
try :
risky ()
except SomeError as e :
print ( e )

2. Catch and re-raise


Log then propagate.
try :
risky ()
except SomeError as e :
print ( e )
raise

3. Catch and raise new


Wrap with context.
try :
risky ()
except SomeError as e :
raise NewError ( " message " ) from e

4. Raise conditionally
No try needed.
if condition :
raise SomeError ( " message " )

GIL — Global Interpreter Lock


A mutex that allows only one thread to execute Python bytecode at a time, even on a multi-
core machine. CPython’s memory management (reference counting) is not thread-safe. The
GIL protects it from race conditions without needing fine-grained locks on every object.
• CPU-bound tasks: threading gives you no speedup — you’re still limited to one core.
• I/O-bound tasks: threading works fine — the GIL is released while waiting for I/O.
When a thread does I/O (reading a file, network request, etc.) it makes a syscall and
hands control to the OS — the Python interpreter isn’t doing anything during that wait.
So CPython explicitly releases the GIL before the syscall and reacquires it after.
The GIL is a CPython implementation detail; other implementations don’t have it. The com-
munity has had a love/hate relationship with it for years. The feeling is generally:
• It’s a necessary evil that made CPython simple and safe, but it’s a real pain for CPU-bound
parallelism.
• There have been multiple attempts to remove it over the decades, all abandoned because
removing it made single-threaded code significantly slower.
• PEP 703 (Python 3.13) made the GIL optional — you can run CPython with PYTHON GIL=0
or python -X gil=0. The goal is to make it the default eventually, but that’s years away.

4
Threading vs Multiprocessing vs Async

Threading
• Multiple threads, one process, one GIL.
• Good for I/O-bound: GIL released during I/O.
• Shared memory — easy to share state but race conditions possible.
• [Link] or [Link].
• Moderate number of concurrent tasks (tens to hundreds), since the overhead of OS thread-
switching matters.

Multiprocessing
• Multiple processes, each with its own GIL and memory.
• Good for CPU-bound: true parallelism across cores.
• No shared memory — communication via queues or pipes.
• [Link] or [Link].
• Use pickle, joblib, or dill to transfer serialized data among processes.

Async
• Single thread concurrency, single process, event loop: Cooperative Multitasking, very high
concurrency.
• Good for I/O-bound with many concurrent connections (web servers, scrapers).
• No race conditions — only one coroutine runs at a time.
• async/await, asyncio.
• Thousands of simultaneous connections.

Iterables and Iterators


for loops call iter() then next() repeatedly under the hood, until a StopIteration is raised.
Python has a fallback mechanism: if iter is not found but getitem is, Python will try to
iterate by calling getitem with indices 0, 1, 2. . . until it gets an IndexError. This is legacy
behavior from Python 2.
An iterable is an object you can loop over. It implements iter () which returns an iterator.
An iterator is the object that does the actual iteration. It implements both iter () and
next (). The reason iterators also implement iter (returning self) is so they can be used
directly in a for loop, which always calls iter first. Iterators are stateful and exhaustible.
All iterators are iterable; not all iterables are iterators. Files and generators are both iterable
and iterators.

Generators
A generator is a function that uses yield to return values lazily, one at a time. It automatically
implements the iterator protocol.
• Calling the function returns a generator object — no code runs yet.
• Each next() runs until the next yield, pauses, and returns the value.
• Local state (variables) is preserved between next() calls.
• When the function returns, StopIteration is raised.
Generator expression — lazy list comprehension:
squares = ( x * x for x in range (1000000) )

5
yield from is used to delegate to a subgenerator:
def gen () :
yield from [1 , 2 , 3]
# equivalent to :
def gen () :
for x in [1 , 2 , 3]:
yield x

def chain (* iterables ) :


for it in iterables :
yield from it

list ( chain ([1 , 2] , [3 , 4]) ) # [1 , 2 , 3 , 4]

Decorators and Closures


A closure is a function that captures variables from its enclosing scope, even after that scope
has finished executing. Inside the closure you can have free variables that live in the closure, or
nonlocal variables that capture values from the enclosing function. nonlocal is needed when
reassigning a variable defined in the enclosing scope, not when reading it. global can be used
to refer to the module-level scope. With mutables you don’t need nonlocal for mutations —
only for full reassignment.
A decorator is a higher-order function that takes a function as input and returns a new function,
replacing the original at definition time. The @ syntax is just Python’s way of saying “apply
this transformation when the function is defined.” The decorator itself runs at import time —
the wrapping happens once when the function is defined. Only the wrapper inside runs at call
time. This is why decorators are great for one-time setup.
from functools import wraps

def my_decorator ( func ) :


@wraps ( func )
def wrapper (* args , ** kwargs ) :
# before
result = func (* args , ** kwargs )
# after
return result
return wrapper

@my_decorator
def foo () :
pass
# equivalent to : foo = my_decorator ( foo )

Stacking decorators are applied bottom-up. Cross-cutting concerns: logging, timing, caching,
retry logic, auth checks — anything you’d otherwise copy-paste across many functions. @property,
@staticmethod, @classmethod, @lru cache — all decorators.
It is possible to pass parameters to decorators by adding an extra level of nesting outside the
decorator: the outer function takes params, the middle is the decorator, the inner is the wrapper.

6
What Is New in Python Since 2016

Standard Library
• 3.6 — f-strings: f"hello {name}", way cleaner than .format().
• 3.7 — dataclasses: @dataclass decorator, auto-generates init , repr etc.
• 3.8 — walrus operator :=: assign and evaluate in one expression: if (n := len(data))
> 10: print(f"too long: {n}").
• 3.9 — type hints with builtins: list[int] instead of List[int].
• 3.10 — match-case (structural pattern matching).
• 3.14 — maxheap.

Third Party
• mypy, pyright — type checkers, now mainstream.
• ruff — replaced flake8/black/isort all at once, extremely fast.
• pytest became the undisputed standard.
• torch / tensorflow — deep learning, exploded post-2016.
• httpx — async-compatible replacement for requests.
• tox — test automation across multiple Python versions.

Why Python Is Cool


Two things I really like about Python. First, it’s a fantastic glue language — I used it a lot
to interface with C++ code, and it just works. You can wrap anything, orchestrate any tool,
shell out, hit APIs. It’s the layer that connects everything, which is why it’s everywhere in data
pipelines and MLOps. Second, it’s incredibly consistent. Python almost never surprises you.
And if you want custom behavior on your own objects, you can always get it through dunder
methods — want your object to support +, iteration, with statements, truthiness — it’s all
there. Go doesn’t give you that; you’re stuck with what the language exposes.

Mutable Default Arguments, Mutable and Immutable

Mutables vs Immutables
• Immutable: int, float, str, tuple, frozenset — can’t be changed after creation.
• Mutable: list, dict, set, custom objects — can be changed in place.
A tuple is immutable — you can’t reassign its elements. But if it contains a mutable object,
that object can still be mutated: the tuple itself doesn’t change (same references), but the
object it points to can change. Immutability is about the reference, not the content. Tuples are
hashable only if all their contents are immutable — so (1, [2, 3]) is not hashable. Strings
being immutable means every “modification” creates a new object — relevant for performance
in loops.
Python automatically interns:
• String literals that look like identifiers (alphanumeric + underscore, no spaces).
• Small strings and small integers.
• Compile-time constants.
• You can force interning with [Link]().
Interning behavior is a CPython implementation detail, not guaranteed by the language
spec. Never rely on is for string comparison — always use ==.

7
Never use mutable objects ([], {}, set()) as default arguments unless you explicitly want
shared state. When Python imports the module it executes the function definition and creates
the default argument object once at that point — it then lives on the function object forever.
So every call that uses the default is sharing the same object. Immutables are fine as defaults
because you can’t modify them in place — a new object gets created on reassignment anyway.
# wrong
def append_to ( item , lst =[]) :
lst . append ( item )

# right
def append_to ( item , lst = None ) :
if lst is None :
lst = []
lst . append ( item )

# wrong
class Bus :
def __init__ ( self , passengers =[]) :
self . passengers = passengers

def add_passenger ( self , name ) :


self . passengers . append ( name )

bus1 = Bus ()
bus1 . add_passenger ( " Alice " )
bus2 = Bus ()
print ( bus2 . passengers ) # [" Alice "] -- ghost passenger !

# right
class Bus :
def __init__ ( self , passengers = None ) :
self . passengers = passengers if passengers is not None else []

Type Hints
Added in Python 3.5, they let you annotate variables and functions with expected types. They’re
not enforced at runtime — Python ignores them. They’re for static analysis tools (mypy,
pyright) and readability.
Abstract Base Classes use type hints to enforce interfaces — you define abstract methods with
type signatures that subclasses must implement. Type hints make the contract explicit —
mypy/pyright will verify that subclasses implement the right signatures. Without type hints
you can still use abc but you lose static verification. Also ABC ties into isinstance checks —
isinstance(circle, Shape) returns True, which is useful when you want to check if something
implements an interface without caring about the concrete type.
Either use built-in type hints or explore the typing module for more options, like Optional,
Any, Self, and more. In modern releases it’s possible to use x | None. The trend is clear: move
everything out of typing and into native syntax. typing is becoming legacy for the basic stuff.

Different “Times” in Python


• Definition time — when the def or class statement is executed. Default arguments,
decorators, and class body are evaluated here.

8
• Import time — when a module is first imported. Module-level code runs, classes and
functions are defined, default arguments are created. Happens once, then cached in
[Link].
• Call time — when a function is actually invoked. The function body runs, local variables
are created fresh each call.
• Runtime — general term for “while the program is executing”, encompasses everything
after the interpreter starts.
In practice: module top-to-bottom at import time, class bodies immediately when the class
definition is hit, function bodies only when called. This is why you wrap the entry point of a
script in if name == " main " — so the script can be both imported as a module (without
side effects) and run directly.

Shallow vs Deep Copy


When you assign an object you just copy the reference — both variables point to the same
object.
Shallow copy — creates a new object but doesn’t copy nested objects. Outer objects are inde-
pendent, inner ones are shared, since the copy is not recursive. Obtained with [Link](a),
[Link](), or a[:].
Deep copy — recursively copies everything: [Link](a). Deep copy is slower and
memory-hungry — only use when needed. pickle performs deep copies.

*args and **kwargs, Unpacking


*args captures positional arguments as a tuple; **kwargs captures keyword arguments as a
dict:
def foo (* args , ** kwargs ) :
print ( args ) # tuple
print ( kwargs ) # dict

foo (1 , 2 , 3 , name = " alice " , age =30)


# (1 , 2 , 3)
# { ’ name ’: ’ alice ’, ’ age ’: 30}

args = (1 , 2 , 3)
kwargs = { " name " : " alice " }
foo (* args , ** kwargs ) # unpacks when calling

Order must be: regular args → *args → keyword-only args → **kwargs.


Positional-only and keyword-only params (modern Python):
def foo (a , b , / , c , * , d , e ) :
pass

/ defines the boundary for positional-only parameters — anything before it must be passed
positionally. * defines keyword-only parameters, enforcing keywords after that. The middle is
flexible.
• *args marks the end of positionals AND the beginning of keyword-only. Everything after
it is keyword-only.

9
• **kwargs must always be last — it doesn’t mark a boundary, it just hoovers up whatever
keyword arguments are left.

Higher-Order Functions and Comparators


A higher-order function is simply a function that takes or returns another function. A com-
parator is a function that compares two things. The classic use case is customizing sorted()
or [Link]() via the key parameter.
def by_attribute ( attr ) :
return lambda obj : getattr ( obj , attr )

people = [ Person ( " alice " , 30) , Person ( " bob " , 25) , Person ( " charlie " ,
35) ]
sorted ( people , key = by_attribute ( " age " ) )

from functools import cmp_to_key

def cmp (a , b):


if a < b : return -1
if a > b : return 1
return 0

sorted ( nums , key = cmp_to_key ( cmp ) )

Singleton
A singleton is a class that allows only one instance to exist. Classic examples in Python are
None, True, and False. Common use cases are configuration objects — where you want one
global config shared across the whole app — and connection pools or database connections,
where you don’t want to open multiple connections unnecessarily.
You can implement one by overriding new and storing the instance as a class variable —
if it already exists return it, otherwise create it. Alternatively, the most Pythonic way is the
module pattern: just instantiate your object at module level and import it — since imports
are cached in [Link] and only run once, you always get the same object. Worth noting
that the new approach is not thread-safe without a lock.
class Singleton :
_instance = None

def __new__ ( cls , * args , ** kwargs ) :


if cls . _instance is None :
cls . _instance = super () . __new__ ( cls )
return cls . _instance

slots
By default Python stores instance attributes in a dict on every object — a dict per instance,
which has memory overhead. slots replaces that dict with a fixed set of attributes, saving
memory. Good for data-heavy classes with millions of instances, or when you want to enforce a
strict interface on a class.
class Point :
__slots__ = ( " x " , " y " )

10
def __init__ ( self , x , y ) :
self . x = x
self . y = y

Trade-offs: no dict , so you can’t add arbitrary attributes at runtime; no default support for
weakref unless weakref is added to slots; inheritance requires care — subclasses that don’t
define slots reintroduce dict .

Nine Flavors of Callable


In Python, anything that implements call is callable — it supports (). You can check with
callable(obj).
1. Functions — defined with def.
2. Lambda functions — anonymous functions with lambda.
3. Methods — functions bound to a class instance.
4. Classes — calling a class invokes new then init .
5. Instances with call — objects that behave like functions.
6. Built-in functions — len, print, range etc., implemented in C.
7. Built-in methods — [Link], [Link] etc.
8. Generators — calling a generator function returns a generator object.
9. Coroutines / async functions — defined with async def, calling returns a coroutine
object.

Context Manager
An object that defines setup and teardown behavior using enter and exit . Used with
the with keyword. as binds the return value of enter .
• enter runs setup, returns the object bound to as.
• exit runs teardown, receives exception info, returns False to let exceptions propagate,
True to suppress them.
• [Link] lets you write one as a generator — setup before yield,
teardown in finally.
• Common uses: file handling, DB connections, locks, timing, mocking in tests.
• Stackable: with A() as a, B() as b:
• Guarantees teardown even if an exception is raised.
from contextlib import contextmanager

@contextmanager
def managed_resource () :
resource = acquire ()
try :
yield resource
finally :
release ( resource )

Garbage Collection
CPython uses two mechanisms:

11
Reference counting — primary mechanism. Every object tracks how many references point
to it. When it hits 0, the object is freed immediately. This is why the GIL exists.
Cyclic garbage collector — handles reference cycles that reference counting can’t, for example
when using del. The gc module runs periodically to detect and collect these cycles.
A weak reference points to an object without incrementing its reference count — so it doesn’t
prevent garbage collection.

Tuple vs namedtuple vs Dataclass


Different layers of records: tuple → namedtuple → dataclass → full class. Each adds expres-
siveness at the cost of some memory and simplicity.

Tuple
Plain and fast. Fields accessed by index only. No labels, no methods. Immutable.
point = (3 , 4)
x , y = point

namedtuple
Still immutable and memory-efficient (no dict ). Fields accessed by name and index. Ideal
for small, read-only records. Defined with [Link] or [Link].
from collections import namedtuple
Point = namedtuple ( " Point " , [ " x " , " y " ])
p = Point (3 , 4)
print ( p .x , p [1]) # 3 , 4

# typed version ( preferred )


from typing import NamedTuple
class Point ( NamedTuple ) :
x : float
y : float

Dataclass
Mutable by default (can be frozen with frozen=True). Auto-generates init , repr , eq
etc. Supports default values, field() for complex defaults, and post init for validation.
Has a dict — slightly more overhead than namedtuple.
from dataclasses import dataclass , field

@dataclass
class Point :
x : float
y : float
tags : list = field ( default_factory = list )

p = Point (3 , 4)
print ( p ) # Point ( x =3 , y =4 , tags =[])

Use @dataclass(frozen=True) to get an immutable, hashable version comparable to namedtuple


but with better readability and richer tooling.

12
Dunder Methods
Dunder (double underscore) methods are Python’s protocol hooks — they let you define how
your objects respond to built-in operations. They’re called by the interpreter, not directly by
user code.

Object lifecycle
• new (cls, ...) — creates the instance (called before init ). Override to control
instance creation (e.g. singletons, immutable subclasses).
• init (self, ...) — initializes the instance. Most common override.
• del (self) — called when the object is about to be garbage collected. Unreliable;
avoid for critical cleanup — use context managers instead.

Representation
• repr (self) — unambiguous string for developers. Called by repr(). Should ideally
be valid Python to recreate the object.
• str (self) — human-readable string. Called by str() and print(). Falls back to
repr if not defined.
• format (self, spec) — called by format() and f-strings.

Comparison
• eq , ne , lt , le , gt , ge
• [Link] ordering fills in missing comparison methods from eq and one of
the others.
• hash (self) — must be consistent with eq . If you define eq , Python sets
hash to None unless you define it explicitly.

Arithmetic
__add__ # self + other
__sub__ # self - other
__mul__ # self * other
__truediv__ # self / other
__floordiv__ # self // other
__mod__ # self % other
__pow__ # self ** other
# reflected versions : __radd__ , __rsub__ , ...
# in - place versions : __iadd__ , __isub__ , ...

Container protocol
• len (self) — called by len().
• getitem (self, key) — obj[key], also enables iteration as a fallback.
• setitem (self, key, value) — obj[key] = value.
• delitem (self, key) — del obj[key].
• contains (self, item) — item in obj. Falls back to linear scan via iter if not
defined.
• iter (self) — returns an iterator. Called by iter().
• next (self) — returns next value. Called by next().
• reversed (self) — called by reversed().

13
Attribute access
• getattr (self, name) — called only when normal lookup fails (not found in dict
or class). Good for lazy attributes or proxies.
• getattribute (self, name) — called on every attribute access. Override with care
— easy to cause infinite recursion.
• setattr (self, name, value) — called on every attribute assignment.
• delattr (self, name) — called on del [Link].

Callable and context


• call (self, ...) — makes an instance callable: obj().
• enter (self) / exit (self, exc type, exc val, exc tb) — context manager pro-
tocol (with statement).

Type system
• class getitem (cls, item) — enables generic syntax: list[int], dict[str, int].
• init subclass (cls, ...) — called when a class is subclassed. Lets a base class react
to subclassing without a metaclass.
class Vector :
def __init__ ( self , x , y ) :
self . x = x
self . y = y

def __repr__ ( self ) :


return f " Vector ({ self . x } , { self . y }) "

def __add__ ( self , other ) :


return Vector ( self . x + other .x , self . y + other . y )

def __eq__ ( self , other ) :


return self . x == other . x and self . y == other . y

def __hash__ ( self ) :


return hash (( self .x , self . y ) )

def __len__ ( self ) :


return 2

def __iter__ ( self ) :


yield self . x
yield self . y

14

You might also like