├── docs ├── doc-requirements.txt ├── index.rst ├── strategy.rst ├── exercise_2.rst ├── exercise_1.rst ├── inspiration.rst ├── vector.rst ├── make.bat ├── Makefile └── conf.py ├── requirements.txt ├── exercises ├── vector │ ├── test_vector_neg.py │ ├── test_xunit_vector_neg.py │ ├── test_vector_add.py │ ├── test_xunit_vector_add.py │ ├── vector.py │ └── solution │ │ └── vector.py └── strategy │ ├── promotions.py │ ├── strategy3.py │ ├── SOLUTION │ └── strategy.py │ ├── classic.py │ ├── strategy.py │ └── strategy2.py ├── README.md ├── examples ├── vector_v0.py ├── vector_v1.py ├── vector_v2.py ├── vector0a.py ├── vector_v3.py ├── vector_v4.py ├── vector_v5.py ├── vector_v6.py └── strategy_best4.py ├── .gitignore ├── LICENSE ├── syllabus.rst └── pythonic-api-notebook.ipynb /docs/doc-requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.4.1 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | py==1.4.31 2 | pytest==2.9.1 3 | -------------------------------------------------------------------------------- /exercises/vector/test_vector_neg.py: -------------------------------------------------------------------------------- 1 | from vector import Vector 2 | 3 | def test_vector_unary_minus(): 4 | assert -Vector([1, 2, 3]) == Vector([-1, -2, -3]) 5 | -------------------------------------------------------------------------------- /exercises/vector/test_xunit_vector_neg.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vector import Vector 4 | 5 | class TestStringMethods(unittest.TestCase): 6 | 7 | def test_vector_unary_minus(self): 8 | self.assertEqual(-Vector([1, 2, 3]), Vector([-1, -2, -3])) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pythonic-api 2 | 3 | Examples for "Implementing intuitive and productive APIs" workshops presented at OSCON and PyCon: 4 | 5 | * [OSCON US 2016, Tuesday May 17, 1:30pm–5:00pm](http://conferences.oreilly.com/oscon/open-source-us/public/schedule/detail/49831) 6 | * [PyCon US 2016, Saturday May 28, 9:00am–12:20pm](https://us.pycon.org/2016/schedule/presentation/1733/) 7 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Pythonic APIs documentation master file, created by 2 | sphinx-quickstart on Tue May 17 06:09:39 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Pythonic APIs's documentation! 7 | ========================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | inspiration 15 | vector 16 | exercise_1 17 | strategy 18 | exercise_2 19 | 20 | 21 | 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /docs/strategy.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Rethinking strategy 3 | =================== 4 | 5 | 6 | ``classic.py`` 7 | ---------------- 8 | 9 | .. literalinclude:: ../exercises/strategy/classic.py 10 | 11 | 12 | ``strategy.py`` 13 | ---------------- 14 | 15 | .. literalinclude:: ../exercises/strategy/strategy.py 16 | 17 | 18 | ``strategy2.py`` 19 | ----------------- 20 | 21 | .. literalinclude:: ../exercises/strategy/strategy2.py 22 | 23 | 24 | ``strategy3.py`` 25 | ----------------- 26 | 27 | .. literalinclude:: ../exercises/strategy/strategy3.py 28 | 29 | ``strategy_best4.py`` 30 | --------------------- 31 | 32 | .. literalinclude:: ../examples/strategy_best4.py 33 | -------------------------------------------------------------------------------- /examples/vector_v0.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 0 3 | """ 4 | 5 | from array import array 6 | import math 7 | 8 | 9 | class Vector: 10 | typecode = 'd' 11 | 12 | def __init__(self, components): 13 | self._components = array(self.typecode, components) 14 | 15 | def __len__(self): 16 | return len(self._components) 17 | 18 | def __iter__(self): 19 | return iter(self._components) 20 | 21 | def __abs__(self): 22 | return math.sqrt(sum(x * x for x in self)) 23 | 24 | def __eq__(self, other): 25 | return (len(self) == len(other) and 26 | all(a == b for a, b in zip(self, other))) 27 | 28 | -------------------------------------------------------------------------------- /exercises/vector/test_vector_add.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from vector import Vector 4 | 5 | 6 | @pytest.fixture 7 | def v2d(): 8 | return Vector([1, 2]) 9 | 10 | 11 | def test_2d_vector_addition(v2d): 12 | v = Vector([3, 4]) 13 | assert v + v2d == Vector([4, 6]) 14 | 15 | 16 | def test_mismatched_lengths_exception(v2d): 17 | v = Vector([1, 2, 3]) 18 | with pytest.raises(ValueError): 19 | v + v2d 20 | 21 | 22 | def test_not_implemented_exception(v2d): 23 | with pytest.raises(TypeError) as exc: 24 | v2d + 1 25 | assert 'unsupported operand' in str(exc.value) 26 | 27 | 28 | def test_reversed_operator(v2d): 29 | assert [10, 20] + v2d == Vector([11, 22]) 30 | -------------------------------------------------------------------------------- /exercises/strategy/promotions.py: -------------------------------------------------------------------------------- 1 | 2 | def fidelity_promo(order): 3 | """5% discount for customers with 1000 or more fidelity points""" 4 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 5 | 6 | 7 | def bulk_item_promo(order): 8 | """10% discount for each LineItem with 20 or more units""" 9 | discount = 0 10 | for item in order.cart: 11 | if item.quantity >= 20: 12 | discount += item.total() * .1 13 | return discount 14 | 15 | def large_order_promo(order): 16 | """7% discount for orders with 10 or more distinct items""" 17 | distinct_items = {item.product for item in order.cart} 18 | if len(distinct_items) >= 10: 19 | return order.total() * .07 20 | return 0 21 | -------------------------------------------------------------------------------- /docs/exercise_2.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | Exercise #2: Strategy without classes 3 | ===================================== 4 | 5 | 6 | A. Review class-based implementation 7 | ------------------------------------ 8 | 9 | 1. Go to ``exercises/strategy`` directory 10 | 11 | 2. Run doctest on ``strategy.py`` to check the expected behavior: 12 | 13 | .. code-block:: shell 14 | 15 | $ python3 -m doctest strategy.py 16 | 17 | 18 | B. Refactor ``strategy.py``, changing concrete strategies into functions 19 | ------------------------------------------------------------------------- 20 | 21 | 2. Run doctest with the ``-f`` option to stop at first error or failure: 22 | 23 | .. code-block:: shell 24 | 25 | $ python3 -m doctest strategy.py -f 26 | -------------------------------------------------------------------------------- /exercises/vector/test_xunit_vector_add.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vector import Vector 4 | 5 | class TestAddition(unittest.TestCase): 6 | 7 | 8 | def setUp(self): 9 | self.v2d = Vector([1, 2]) 10 | 11 | 12 | def test_2d_vector_addition(self): 13 | v = Vector([3, 4]) + self.v2d 14 | self.assertEqual(v, Vector([4, 6])) 15 | 16 | 17 | def test_mismatched_lengths_exception(self): 18 | v = Vector([1, 2, 3]) 19 | with self.assertRaises(ValueError): 20 | v + self.v2d 21 | 22 | 23 | def test_not_implemented_exception(self): 24 | with self.assertRaises(TypeError) as exc: 25 | self.v2d + 1 26 | assert 'unsupported operand' in str(exc.exception) 27 | 28 | 29 | def test_reversed_operator(self): 30 | v = [10, 20] + self.v2d 31 | self.assertEqual(v, Vector([11, 22])) 32 | -------------------------------------------------------------------------------- /examples/vector_v1.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 1 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | 9 | 10 | class Vector: 11 | typecode = 'd' 12 | 13 | def __init__(self, components): 14 | self._components = array(self.typecode, components) 15 | 16 | def __len__(self): 17 | return len(self._components) 18 | 19 | def __iter__(self): 20 | return iter(self._components) 21 | 22 | def __abs__(self): 23 | return math.sqrt(sum(x * x for x in self)) 24 | 25 | def __eq__(self, other): 26 | return (len(self) == len(other) and 27 | all(a == b for a, b in zip(self, other))) 28 | 29 | def __str__(self): 30 | return str(tuple(self)) 31 | 32 | def __repr__(self): 33 | components = reprlib.repr(self._components) 34 | components = components[components.find('['):-1] 35 | return 'Vector({})'.format(components) 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # LR 2 | .env35 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | #Ipython Notebook 65 | .ipynb_checkpoints 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Fluent Python 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/exercise_1.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Exercise #1: Vector operators 3 | ============================= 4 | 5 | A. Preparation 6 | --------------- 7 | 8 | 1. Install ``py.test``: ``$ pip install pytest`` 9 | 10 | 2. Clone repo at https://github.com/fluentpython/pythonic-api. You can also download a ``.zip`` file with the repository. 11 | 12 | 13 | B. Vector negation 14 | ------------------ 15 | 16 | 1. Go to ``exercises/vector`` directory 17 | 18 | 2. Run test for unary ``-`` with the ``-x`` option to stop at first error or failure: 19 | 20 | .. code-block:: shell 21 | 22 | $ py.test -x test_vector_neg.py 23 | 24 | 3. Edit ``vector.py`` to implement a ``__neg__`` method for unary negation. That method should return a new ``Vector`` instance with each component negated. See ``test_vector_neg.py`` for the expected behavior. 25 | 26 | 27 | C. Vector addition 28 | ------------------ 29 | 30 | 1. Run tests for vector addition ``+`` with the ``-x`` option to stop at first error or failure: 31 | 32 | .. code-block:: shell 33 | 34 | $ py.test -x test_vector_add.py 35 | 36 | 2. Edit ``vector.py`` to implement a ``__add__`` method for vector addition. That method should return a new ``Vector`` instance with each component of ``self`` added to the corresponding component of ``other``. See ``test_vector_add.py`` for the expected behaviors. 37 | -------------------------------------------------------------------------------- /examples/vector_v2.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 2 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | # ... 39 | 40 | def __getitem__(self, index): 41 | cls = type(self) 42 | if isinstance(index, slice): 43 | return cls(self._components[index]) 44 | elif isinstance(index, numbers.Integral): 45 | return self._components[index] 46 | else: 47 | msg = '{cls.__name__} indices must be integers' 48 | raise TypeError(msg.format(cls=cls)) 49 | -------------------------------------------------------------------------------- /examples/vector0a.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Vector: classe vetor euclideano 3 | 4 | >>> v1 = Vector([1, 2]) 5 | >>> v1 6 | Vector([1.0, 2.0]) 7 | >>> v1 * 10 8 | Vector([10.0, 20.0]) 9 | >>> v1 * 'x' 10 | Traceback (most recent call last): 11 | ... 12 | TypeError: can't multiply sequence by non-int of type 'Vector' 13 | >>> 10 * v1 14 | Vector([10.0, 20.0]) 15 | 16 | ''' 17 | 18 | from array import array 19 | import math 20 | import numbers 21 | 22 | class Vector: 23 | 24 | typecode = 'd' 25 | 26 | def __init__(self, components): 27 | self._components = array(self.typecode, components) 28 | 29 | def __len__(self): 30 | return len(self._components) 31 | 32 | def __iter__(self): 33 | return iter(self._components) 34 | 35 | def __abs__(self): 36 | return math.sqrt(sum(x * x for x in self)) 37 | 38 | def __eq__(self, other): 39 | return (len(self) == len(other) and 40 | all(a == b for a, b in zip(self, other))) 41 | 42 | def __repr__(self): 43 | return 'Vector({})'.format(list(self)) 44 | 45 | def __format__(self, format_spec): 46 | return repr(tuple(self)) 47 | 48 | def __mul__(self, scalar): 49 | if isinstance(scalar, numbers.Real): 50 | return Vector(n * scalar for n in self) 51 | else: 52 | return NotImplemented 53 | 54 | def __rmul__(self, other): 55 | return self * other 56 | 57 | -------------------------------------------------------------------------------- /examples/vector_v3.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 3 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | def __getitem__(self, index): 39 | cls = type(self) 40 | if isinstance(index, slice): 41 | return cls(self._components[index]) 42 | elif isinstance(index, numbers.Integral): 43 | return self._components[index] 44 | else: 45 | msg = '{cls.__name__} indices must be integers' 46 | raise TypeError(msg.format(cls=cls)) 47 | 48 | # ... 49 | 50 | def __mul__(self, scalar): 51 | if isinstance(scalar, numbers.Real): 52 | return Vector(n * scalar for n in self) 53 | else: 54 | return NotImplemented 55 | -------------------------------------------------------------------------------- /examples/vector_v4.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 4 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | def __getitem__(self, index): 39 | cls = type(self) 40 | if isinstance(index, slice): 41 | return cls(self._components[index]) 42 | elif isinstance(index, numbers.Integral): 43 | return self._components[index] 44 | else: 45 | msg = '{cls.__name__} indices must be integers' 46 | raise TypeError(msg.format(cls=cls)) 47 | 48 | # ... 49 | 50 | def __mul__(self, scalar): 51 | if isinstance(scalar, numbers.Real): 52 | return Vector(n * scalar for n in self) 53 | else: 54 | return NotImplemented 55 | 56 | def __rmul__(self, scalar): 57 | return self * scalar 58 | -------------------------------------------------------------------------------- /exercises/vector/vector.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | def __getitem__(self, index): 39 | cls = type(self) 40 | if isinstance(index, slice): 41 | return cls(self._components[index]) 42 | elif isinstance(index, numbers.Integral): 43 | return self._components[index] 44 | else: 45 | msg = '{cls.__name__} indices must be integers' 46 | raise TypeError(msg.format(cls=cls)) 47 | 48 | def __mul__(self, scalar): 49 | if isinstance(scalar, numbers.Real): 50 | return Vector(n * scalar for n in self) 51 | else: 52 | return NotImplemented 53 | 54 | def __rmul__(self, scalar): 55 | return self * scalar 56 | 57 | # exercise solutions below... 58 | -------------------------------------------------------------------------------- /docs/inspiration.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Inspiration 3 | ============= 4 | 5 | Consistency 6 | ------------ 7 | 8 | Python:: 9 | 10 | len(text) # string 11 | len(rate) # array of floats 12 | len(names) # list 13 | 14 | Java: 15 | 16 | .. code-block:: Java 17 | 18 | texto.length() // String 19 | pesos.length // array of floats 20 | nomes.size() // ArrayList 21 | 22 | 23 | The ``requests`` library 24 | ------------------------ 25 | 26 | Using ``requests``:: 27 | 28 | import requests 29 | 30 | r = requests.get('https://api.github.com', auth=('user', 'pass')) 31 | 32 | print r.status_code 33 | print r.headers['content-type'] 34 | 35 | # ------ 36 | # 200 37 | # 'application/json' 38 | 39 | 40 | Using ``urllib2``:: 41 | 42 | import urllib2 43 | 44 | gh_url = 'https://api.github.com' 45 | 46 | req = urllib2.Request(gh_url) 47 | 48 | password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() 49 | password_manager.add_password(None, gh_url, 'user', 'pass') 50 | 51 | auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) 52 | opener = urllib2.build_opener(auth_manager) 53 | 54 | urllib2.install_opener(opener) 55 | 56 | handler = urllib2.urlopen(req) 57 | 58 | print handler.getcode() 59 | print handler.headers.getheader('content-type') 60 | 61 | # ------ 62 | # 200 63 | # 'application/json' 64 | 65 | 66 | 67 | The ``py.test`` library and test runner 68 | ---------------------------------------- 69 | 70 | With ``py.test``: 71 | 72 | .. literalinclude:: ../exercises/vector/test_vector_neg.py 73 | 74 | .. literalinclude:: ../exercises/vector/test_vector_add.py 75 | 76 | With ``unittest``: 77 | 78 | .. literalinclude:: ../exercises/vector/test_xunit_vector_neg.py 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /examples/vector_v5.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 5 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | def __getitem__(self, index): 39 | cls = type(self) 40 | if isinstance(index, slice): 41 | return cls(self._components[index]) 42 | elif isinstance(index, numbers.Integral): 43 | return self._components[index] 44 | else: 45 | msg = '{cls.__name__} indices must be integers' 46 | raise TypeError(msg.format(cls=cls)) 47 | 48 | def __mul__(self, scalar): 49 | if isinstance(scalar, numbers.Real): 50 | return Vector(n * scalar for n in self) 51 | else: 52 | return NotImplemented 53 | 54 | def __rmul__(self, scalar): 55 | return self * scalar 56 | 57 | # ... 58 | 59 | def __matmul__(self, other): # Python 3.5 only! 60 | try: 61 | return sum(a * b for a, b in zip(self, other)) 62 | except TypeError: 63 | return NotImplemented 64 | 65 | def __rmatmul__(self, other): 66 | return self @ other # syntax error if Python < 3.5 67 | 68 | -------------------------------------------------------------------------------- /examples/vector_v6.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class, take 6 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | import functools 10 | import operator 11 | 12 | class Vector: 13 | typecode = 'd' 14 | 15 | def __init__(self, components): 16 | self._components = array(self.typecode, components) 17 | 18 | def __len__(self): 19 | return len(self._components) 20 | 21 | def __iter__(self): 22 | return iter(self._components) 23 | 24 | def __abs__(self): 25 | return math.sqrt(sum(x * x for x in self)) 26 | 27 | def __eq__(self, other): 28 | return (len(self) == len(other) and 29 | all(a == b for a, b in zip(self, other))) 30 | 31 | def __str__(self): 32 | return str(tuple(self)) 33 | 34 | def __repr__(self): 35 | components = reprlib.repr(self._components) 36 | components = components[components.find('['):-1] 37 | return 'Vector({})'.format(components) 38 | 39 | def __getitem__(self, index): 40 | cls = type(self) 41 | if isinstance(index, slice): 42 | return cls(self._components[index]) 43 | elif isinstance(index, numbers.Integral): 44 | return self._components[index] 45 | else: 46 | msg = '{cls.__name__} indices must be integers' 47 | raise TypeError(msg.format(cls=cls)) 48 | 49 | def __mul__(self, scalar): 50 | if isinstance(scalar, numbers.Real): 51 | return Vector(n * scalar for n in self) 52 | else: 53 | return NotImplemented 54 | 55 | def __rmul__(self, scalar): 56 | return self * scalar 57 | 58 | def __matmul__(self, other): 59 | try: 60 | return sum(a * b for a, b in zip(self, other)) 61 | except TypeError: 62 | return NotImplemented 63 | 64 | """ 65 | def __rmatmul__(self, other): 66 | return self @ other # só funciona em Python 3.5 67 | """ 68 | 69 | # ... 70 | 71 | def __hash__(self): 72 | hashes = (hash(x) for x in self) 73 | return functools.reduce(operator.xor, hashes, 0) -------------------------------------------------------------------------------- /exercises/vector/solution/vector.py: -------------------------------------------------------------------------------- 1 | """ 2 | A multi-dimensional ``Vector`` class 3 | """ 4 | 5 | from array import array 6 | import math 7 | import reprlib 8 | import numbers 9 | 10 | 11 | class Vector: 12 | typecode = 'd' 13 | 14 | def __init__(self, components): 15 | self._components = array(self.typecode, components) 16 | 17 | def __len__(self): 18 | return len(self._components) 19 | 20 | def __iter__(self): 21 | return iter(self._components) 22 | 23 | def __abs__(self): 24 | return math.sqrt(sum(x * x for x in self)) 25 | 26 | def __eq__(self, other): 27 | return (len(self) == len(other) and 28 | all(a == b for a, b in zip(self, other))) 29 | 30 | def __str__(self): 31 | return str(tuple(self)) 32 | 33 | def __repr__(self): 34 | components = reprlib.repr(self._components) 35 | components = components[components.find('['):-1] 36 | return 'Vector({})'.format(components) 37 | 38 | def __getitem__(self, index): 39 | cls = type(self) 40 | if isinstance(index, slice): 41 | return cls(self._components[index]) 42 | elif isinstance(index, numbers.Integral): 43 | return self._components[index] 44 | else: 45 | msg = '{cls.__name__} indices must be integers' 46 | raise TypeError(msg.format(cls=cls)) 47 | 48 | def __mul__(self, scalar): 49 | if isinstance(scalar, numbers.Real): 50 | return Vector(n * scalar for n in self) 51 | else: 52 | return NotImplemented 53 | 54 | def __rmul__(self, scalar): 55 | return self * scalar 56 | 57 | # exercise solutions below... 58 | 59 | def __neg__(self): 60 | return Vector(-x for x in self) 61 | 62 | def __add__(self, other): 63 | try: 64 | other = Vector(other) 65 | except TypeError: 66 | return NotImplemented 67 | if len(self) != len(other): 68 | raise ValueError('vectors must have same length for addition') 69 | return Vector(a + b for a, b in zip(self, other)) 70 | 71 | def __radd__(self, other): 72 | return self + other 73 | -------------------------------------------------------------------------------- /exercises/strategy/strategy3.py: -------------------------------------------------------------------------------- 1 | # strategy_best3.py 2 | # Strategy pattern -- function-based implementation 3 | # selecting best promotion from imported module 4 | 5 | """ 6 | >>> from promotions import * 7 | >>> joe = Customer('John Doe', 0) 8 | >>> ann = Customer('Ann Smith', 1100) 9 | >>> cart = [LineItem('banana', 4, .5), 10 | ... LineItem('apple', 10, 1.5), 11 | ... LineItem('watermellon', 5, 5.0)] 12 | >>> Order(joe, cart, fidelity_promo) 13 | 14 | >>> Order(ann, cart, fidelity_promo) 15 | 16 | >>> banana_cart = [LineItem('banana', 30, .5), 17 | ... LineItem('apple', 10, 1.5)] 18 | >>> Order(joe, banana_cart, bulk_item_promo) 19 | 20 | >>> long_order = [LineItem(str(item_code), 1, 1.0) 21 | ... for item_code in range(10)] 22 | >>> Order(joe, long_order, large_order_promo) 23 | 24 | >>> Order(joe, cart, large_order_promo) 25 | 26 | 27 | Tests for ``best_promo`` strategy:: 28 | 29 | >>> Order(joe, long_order, best_promo) 30 | 31 | >>> Order(joe, banana_cart, best_promo) 32 | 33 | >>> Order(ann, cart, best_promo) 34 | 35 | 36 | """ 37 | 38 | from collections import namedtuple 39 | import inspect 40 | 41 | import promotions 42 | 43 | Customer = namedtuple('Customer', 'name fidelity') 44 | 45 | 46 | class LineItem: 47 | 48 | def __init__(self, product, quantity, price): 49 | self.product = product 50 | self.quantity = quantity 51 | self.price = price 52 | 53 | def total(self): 54 | return self.price * self.quantity 55 | 56 | 57 | class Order: # the Context 58 | 59 | def __init__(self, customer, cart, promotion=None): 60 | self.customer = customer 61 | self.cart = list(cart) 62 | self.promotion = promotion 63 | 64 | def total(self): 65 | if not hasattr(self, '__total'): 66 | self.__total = sum(item.total() for item in self.cart) 67 | return self.__total 68 | 69 | def due(self): 70 | if self.promotion is None: 71 | discount = 0 72 | else: 73 | discount = self.promotion(self) 74 | return self.total() - discount 75 | 76 | def __repr__(self): 77 | fmt = '' 78 | return fmt.format(self.total(), self.due()) 79 | 80 | 81 | promos = [func for name, func in 82 | inspect.getmembers(promotions, inspect.isfunction)] 83 | 84 | def best_promo(order): 85 | """Select best discount available 86 | """ 87 | return max(promo(order) for promo in promos) 88 | -------------------------------------------------------------------------------- /exercises/strategy/SOLUTION/strategy.py: -------------------------------------------------------------------------------- 1 | # strategy.py 2 | # Strategy pattern -- function-based implementation 3 | 4 | """ 5 | >>> joe = Customer('John Doe', 0) # <1> 6 | >>> ann = Customer('Ann Smith', 1100) 7 | >>> cart = [LineItem('banana', 4, .5), 8 | ... LineItem('apple', 10, 1.5), 9 | ... LineItem('watermellon', 5, 5.0)] 10 | >>> Order(joe, cart, fidelity_promo) # <2> 11 | 12 | >>> Order(ann, cart, fidelity_promo) 13 | 14 | >>> banana_cart = [LineItem('banana', 30, .5), 15 | ... LineItem('apple', 10, 1.5)] 16 | >>> Order(joe, banana_cart, bulk_item_promo) # <3> 17 | 18 | >>> long_order = [LineItem(str(item_code), 1, 1.0) 19 | ... for item_code in range(10)] 20 | >>> Order(joe, long_order, large_order_promo) 21 | 22 | >>> Order(joe, cart, large_order_promo) 23 | 24 | """ 25 | 26 | from collections import namedtuple 27 | 28 | Customer = namedtuple('Customer', 'name fidelity') 29 | 30 | 31 | class LineItem: 32 | 33 | def __init__(self, product, quantity, price): 34 | self.product = product 35 | self.quantity = quantity 36 | self.price = price 37 | 38 | def total(self): 39 | return self.price * self.quantity 40 | 41 | 42 | class Order: 43 | 44 | def __init__(self, customer, cart, promotion=None): 45 | self.customer = customer 46 | self.cart = list(cart) 47 | self.promotion = promotion 48 | 49 | def total(self): 50 | if not hasattr(self, '__total'): 51 | self.__total = sum(item.total() for item in self.cart) 52 | return self.__total 53 | 54 | def due(self): 55 | if self.promotion is None: 56 | discount = 0 57 | else: 58 | discount = self.promotion(self) # invoke the promotion function 59 | return self.total() - discount 60 | 61 | def __repr__(self): 62 | fmt = '' 63 | return fmt.format(self.total(), self.due()) 64 | 65 | 66 | def fidelity_promo(order): 67 | """5% discount for customers with 1000 or more fidelity points""" 68 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 69 | 70 | 71 | def bulk_item_promo(order): 72 | """10% discount for each LineItem with 20 or more units""" 73 | discount = 0 74 | for item in order.cart: 75 | if item.quantity >= 20: 76 | discount += item.total() * .1 77 | return discount 78 | 79 | 80 | def large_order_promo(order): 81 | """7% discount for orders with 10 or more distinct items""" 82 | distinct_items = {item.product for item in order.cart} 83 | if len(distinct_items) >= 10: 84 | return order.total() * .07 85 | return 0 86 | -------------------------------------------------------------------------------- /exercises/strategy/classic.py: -------------------------------------------------------------------------------- 1 | # classic_strategy.py 2 | # Strategy pattern -- classic implementation 3 | 4 | """ 5 | >>> joe = Customer('John Doe', 0) # <1> 6 | >>> ann = Customer('Ann Smith', 1100) 7 | >>> cart = [LineItem('banana', 4, .5), # <2> 8 | ... LineItem('apple', 10, 1.5), 9 | ... LineItem('watermellon', 5, 5.0)] 10 | >>> Order(joe, cart, FidelityPromo()) # <3> 11 | 12 | >>> Order(ann, cart, FidelityPromo()) # <4> 13 | 14 | >>> banana_cart = [LineItem('banana', 30, .5), # <5> 15 | ... LineItem('apple', 10, 1.5)] 16 | >>> Order(joe, banana_cart, BulkItemPromo()) # <6> 17 | 18 | >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> 19 | ... for item_code in range(10)] 20 | >>> Order(joe, long_order, LargeOrderPromo()) # <8> 21 | 22 | >>> Order(joe, cart, LargeOrderPromo()) 23 | 24 | 25 | """ 26 | 27 | from abc import ABC, abstractmethod 28 | from collections import namedtuple 29 | 30 | Customer = namedtuple('Customer', 'name fidelity') 31 | 32 | 33 | class LineItem: 34 | 35 | def __init__(self, product, quantity, price): 36 | self.product = product 37 | self.quantity = quantity 38 | self.price = price 39 | 40 | def total(self): 41 | return self.price * self.quantity 42 | 43 | 44 | class Order: # the Context 45 | 46 | def __init__(self, customer, cart, promotion=None): 47 | self.customer = customer 48 | self.cart = list(cart) 49 | self.promotion = promotion 50 | 51 | def total(self): 52 | if not hasattr(self, '__total'): 53 | self.__total = sum(item.total() for item in self.cart) 54 | return self.__total 55 | 56 | def due(self): 57 | if self.promotion is None: 58 | discount = 0 59 | else: 60 | discount = self.promotion.discount(self) 61 | return self.total() - discount 62 | 63 | def __repr__(self): 64 | fmt = '' 65 | return fmt.format(self.total(), self.due()) 66 | 67 | 68 | class Promotion(ABC): # the Strategy: an Abstract Base Class 69 | 70 | @abstractmethod 71 | def discount(self, order): 72 | """Return discount as a positive dollar amount""" 73 | 74 | 75 | class FidelityPromo(Promotion): # first Concrete Strategy 76 | """5% discount for customers with 1000 or more fidelity points""" 77 | 78 | def discount(self, order): 79 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 80 | 81 | 82 | class BulkItemPromo(Promotion): # second Concrete Strategy 83 | """10% discount for each LineItem with 20 or more units""" 84 | 85 | def discount(self, order): 86 | discount = 0 87 | for item in order.cart: 88 | if item.quantity >= 20: 89 | discount += item.total() * .1 90 | return discount 91 | 92 | 93 | class LargeOrderPromo(Promotion): # third Concrete Strategy 94 | """7% discount for orders with 10 or more distinct items""" 95 | 96 | def discount(self, order): 97 | distinct_items = {item.product for item in order.cart} 98 | if len(distinct_items) >= 10: 99 | return order.total() * .07 100 | return 0 101 | -------------------------------------------------------------------------------- /exercises/strategy/strategy.py: -------------------------------------------------------------------------------- 1 | # strategy.py 2 | # Strategy pattern -- function-based implementation 3 | 4 | """ 5 | API for a function-based implementation of the Strategy pattern 6 | 7 | >>> joe = Customer('John Doe', 0) # <1> 8 | >>> ann = Customer('Ann Smith', 1100) 9 | >>> cart = [LineItem('banana', 4, .5), 10 | ... LineItem('apple', 10, 1.5), 11 | ... LineItem('watermellon', 5, 5.0)] 12 | >>> Order(joe, cart, fidelity_promo) # <2> 13 | 14 | >>> Order(ann, cart, fidelity_promo) 15 | 16 | >>> banana_cart = [LineItem('banana', 30, .5), 17 | ... LineItem('apple', 10, 1.5)] 18 | >>> Order(joe, banana_cart, bulk_item_promo) # <3> 19 | 20 | >>> long_order = [LineItem(str(item_code), 1, 1.0) 21 | ... for item_code in range(10)] 22 | >>> Order(joe, long_order, large_order_promo) 23 | 24 | >>> Order(joe, cart, large_order_promo) 25 | 26 | """ 27 | 28 | from abc import ABC, abstractmethod # DELETE THIS IMPORT. Yay! 29 | from collections import namedtuple 30 | 31 | Customer = namedtuple('Customer', 'name fidelity') 32 | 33 | 34 | class LineItem: 35 | 36 | def __init__(self, product, quantity, price): 37 | self.product = product 38 | self.quantity = quantity 39 | self.price = price 40 | 41 | def total(self): 42 | return self.price * self.quantity 43 | 44 | 45 | class Order: 46 | 47 | def __init__(self, customer, cart, promotion=None): 48 | self.customer = customer 49 | self.cart = list(cart) 50 | self.promotion = promotion 51 | 52 | def total(self): 53 | if not hasattr(self, '__total'): 54 | self.__total = sum(item.total() for item in self.cart) 55 | return self.__total 56 | 57 | def due(self): 58 | if self.promotion is None: 59 | discount = 0 60 | else: 61 | discount = self.promotion(self) # invoke the promotion function 62 | return self.total() - discount 63 | 64 | def __repr__(self): 65 | fmt = '' 66 | return fmt.format(self.total(), self.due()) 67 | 68 | 69 | class Promotion(ABC): # DELETE THIS CLASS. HOORAY! 70 | 71 | @abstractmethod 72 | def discount(self, order): 73 | """Return discount as a positive dollar amount""" 74 | 75 | 76 | class FidelityPromo(Promotion): # REFACTOR INTO A FUNCTION 77 | """5% discount for customers with 1000 or more fidelity points""" 78 | 79 | def discount(self, order): 80 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 81 | 82 | 83 | class BulkItemPromo(Promotion): # REFACTOR INTO ANOTHER FUNCTION 84 | """10% discount for each LineItem with 20 or more units""" 85 | 86 | def discount(self, order): 87 | discount = 0 88 | for item in order.cart: 89 | if item.quantity >= 20: 90 | discount += item.total() * .1 91 | return discount 92 | 93 | 94 | class LargeOrderPromo(Promotion): # REFACTOR INTO A THIRD FUNCTION 95 | """7% discount for orders with 10 or more distinct items""" 96 | 97 | def discount(self, order): 98 | distinct_items = {item.product for item in order.cart} 99 | if len(distinct_items) >= 10: 100 | return order.total() * .07 101 | return 0 102 | -------------------------------------------------------------------------------- /exercises/strategy/strategy2.py: -------------------------------------------------------------------------------- 1 | # strategy_best.py 2 | # Strategy pattern -- function-based implementation 3 | # selecting best promotion from static list of functions 4 | 5 | """ 6 | >>> joe = Customer('John Doe', 0) 7 | >>> ann = Customer('Ann Smith', 1100) 8 | >>> cart = [LineItem('banana', 4, .5), 9 | ... LineItem('apple', 10, 1.5), 10 | ... LineItem('watermellon', 5, 5.0)] 11 | >>> Order(joe, cart, fidelity_promo) 12 | 13 | >>> Order(ann, cart, fidelity_promo) 14 | 15 | >>> banana_cart = [LineItem('banana', 30, .5), 16 | ... LineItem('apple', 10, 1.5)] 17 | >>> Order(joe, banana_cart, bulk_item_promo) 18 | 19 | >>> long_order = [LineItem(str(item_code), 1, 1.0) 20 | ... for item_code in range(10)] 21 | >>> Order(joe, long_order, large_order_promo) 22 | 23 | >>> Order(joe, cart, large_order_promo) 24 | 25 | 26 | Tests for ``best_promo`` strategy:: 27 | 28 | >>> Order(joe, long_order, best_promo) # <1> 29 | 30 | >>> Order(joe, banana_cart, best_promo) # <2> 31 | 32 | >>> Order(ann, cart, best_promo) # <3> 33 | 34 | 35 | """ 36 | 37 | from collections import namedtuple 38 | 39 | Customer = namedtuple('Customer', 'name fidelity') 40 | 41 | 42 | class LineItem: 43 | 44 | def __init__(self, product, quantity, price): 45 | self.product = product 46 | self.quantity = quantity 47 | self.price = price 48 | 49 | def total(self): 50 | return self.price * self.quantity 51 | 52 | 53 | class Order: # the Context 54 | 55 | def __init__(self, customer, cart, promotion=None): 56 | self.customer = customer 57 | self.cart = list(cart) 58 | self.promotion = promotion 59 | 60 | def total(self): 61 | if not hasattr(self, '__total'): 62 | self.__total = sum(item.total() for item in self.cart) 63 | return self.__total 64 | 65 | def due(self): 66 | if self.promotion is None: 67 | discount = 0 68 | else: 69 | discount = self.promotion(self) 70 | return self.total() - discount 71 | 72 | def __repr__(self): 73 | fmt = '' 74 | return fmt.format(self.total(), self.due()) 75 | 76 | 77 | def fidelity_promo(order): 78 | """5% discount for customers with 1000 or more fidelity points""" 79 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 80 | 81 | 82 | def bulk_item_promo(order): 83 | """10% discount for each LineItem with 20 or more units""" 84 | discount = 0 85 | for item in order.cart: 86 | if item.quantity >= 20: 87 | discount += item.total() * .1 88 | return discount 89 | 90 | 91 | def large_order_promo(order): 92 | """7% discount for orders with 10 or more distinct items""" 93 | distinct_items = {item.product for item in order.cart} 94 | if len(distinct_items) >= 10: 95 | return order.total() * .07 96 | return 0 97 | 98 | 99 | promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1> 100 | 101 | def best_promo(order): # <2> 102 | """Select best discount available 103 | """ 104 | return max(promo(order) for promo in promos) # <3> 105 | -------------------------------------------------------------------------------- /syllabus.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Syllabus 3 | ======== 4 | 5 | Title 6 | Pythonic APIs Workshop 7 | 8 | Abstract 9 | Python is so consistent that often we can infer the behavior of new objects by assuming they work as the built-ins. The Python Data Model is the foundation of this consistent behavior. This workshop presents the construction of Pythonic objects: classes that feel "natural" to a Python programmer, and leverage some of the best language features by implementing key protocols of the Data Model. 10 | 11 | Category 12 | Best Practices & Patterns 13 | 14 | Audience level 15 | Intermediate Python 16 | 17 | Prerequisite knowledge 18 | Attendees should be very familiar with Python and know its key data structures and how to implement basic classes. 19 | 20 | Materials or downloads needed in advance 21 | Attendees should have a laptop with Python 3.5 or newer installed. Although the content also applies to Python 2.7, 3.5 will be used in the examples. 22 | 23 | 24 | Description 25 | =========== 26 | 27 | This tutorial will show how to implement objects which behave as "naturally" as the built-in types, and therefore deserve to be called *Pythonic*. The whole presentation and exercises will be guided by doctests, which support a form of BDD (behavior-driven design) and allow participants to check their progress in the hands-on parts of the tutorial. 28 | 29 | An API is considered Pythonic when it supports convenient and suitable Python idioms. For example, Python programmers expect that any collection is iterable and supports the ``len()`` function. Empty collections should evaluate "falsy" in boolean contexts. Objects of any type should have an user-friendly string representation, and another display format that doesn't hide details and is useful for debugging. Objects of several types support operators such as ``+`` and ``*`` when it makes sense. Pythonic objects are one of the keys to high programmer productivity with the language. 30 | 31 | All of these object features, and more, are defined in the Python Data Model: the API that applies to Python objects in general, from plain integers to collections and even to functions and classes -- when we treat them as first class objects in the language. The most important of the special methods defined in the Data Model will be shown and exercised during the tutorial. 32 | 33 | 34 | Outline 35 | ======= 36 | 37 | Estimated time per topic in minutes (total: 180', including 15' coffee break) 38 | 39 | * Introduction 40 | * (3') Tutorial Overview 41 | * A simple but full-featured Pythonic class 42 | * (4') Object representation 43 | * (7') Formatted Displays 44 | * (10') **Exercise:** Polar Format and Alternative Constructor 45 | * (9') A Hashable ``Vector2d`` 46 | * (4') “Private” and “Protected” Attributes in Python 47 | * (5') The ``__slots__`` Class Attribute: Use and Caveats 48 | * (9') Overriding Class Attributes 49 | * A Pythonic Sequence 50 | * (7') The Sequence Interface 51 | * (15') **Exercise:** Implementing Sequence Behavior 52 | * (15') -------------- Coffee Break -------------- 53 | * A Pythonic Sequence (continued) 54 | * (14') Slice-Aware ``__getitem__`` 55 | * (7') Dynamic Attribute Access 56 | * (10') Hashing and a Faster ``==`` 57 | * (12') Exercise: Implement Formatting Support 58 | * Operator Overloading Done Right 59 | * (5') Unary Operators 60 | * (7') Overloading ``*`` for Scalar Multiplication 61 | * (10') Overloading ``+`` for Vector Addition 62 | * (10') **Exercise:** Implement ``@`` for Dot Product 63 | * (7') Rich Comparison Operators 64 | * (7') Augmented Assignment Operators 65 | -------------------------------------------------------------------------------- /examples/strategy_best4.py: -------------------------------------------------------------------------------- 1 | # strategy_best4.py 2 | # Strategy pattern -- function-based implementation 3 | # selecting best promotion from list of functions 4 | # registered by a decorator 5 | 6 | """ 7 | >>> joe = Customer('John Doe', 0) 8 | >>> ann = Customer('Ann Smith', 1100) 9 | >>> cart = [LineItem('banana', 4, .5), 10 | ... LineItem('apple', 10, 1.5), 11 | ... LineItem('watermellon', 5, 5.0)] 12 | >>> Order(joe, cart, fidelity) 13 | 14 | >>> Order(ann, cart, fidelity) 15 | 16 | >>> banana_cart = [LineItem('banana', 30, .5), 17 | ... LineItem('apple', 10, 1.5)] 18 | >>> Order(joe, banana_cart, bulk_item) 19 | 20 | >>> long_order = [LineItem(str(item_code), 1, 1.0) 21 | ... for item_code in range(10)] 22 | >>> Order(joe, long_order, large_order) 23 | 24 | >>> Order(joe, cart, large_order) 25 | 26 | 27 | # BEGIN STRATEGY_BEST_TESTS 28 | 29 | >>> Order(joe, long_order, best_promo) 30 | 31 | >>> Order(joe, banana_cart, best_promo) 32 | 33 | >>> Order(ann, cart, best_promo) 34 | 35 | 36 | # END STRATEGY_BEST_TESTS 37 | """ 38 | 39 | from collections import namedtuple 40 | 41 | Customer = namedtuple('Customer', 'name fidelity') 42 | 43 | 44 | class LineItem: 45 | 46 | def __init__(self, product, quantity, price): 47 | self.product = product 48 | self.quantity = quantity 49 | self.price = price 50 | 51 | def total(self): 52 | return self.price * self.quantity 53 | 54 | 55 | class Order: # the Context 56 | 57 | def __init__(self, customer, cart, promotion=None): 58 | self.customer = customer 59 | self.cart = list(cart) 60 | self.promotion = promotion 61 | 62 | def total(self): 63 | if not hasattr(self, '__total'): 64 | self.__total = sum(item.total() for item in self.cart) 65 | return self.__total 66 | 67 | def due(self): 68 | if self.promotion is None: 69 | discount = 0 70 | else: 71 | discount = self.promotion(self) 72 | return self.total() - discount 73 | 74 | def __repr__(self): 75 | fmt = '' 76 | return fmt.format(self.total(), self.due()) 77 | 78 | # BEGIN STRATEGY_BEST4 79 | 80 | promos = [] # <1> 81 | 82 | def promotion(promo_func): # <2> 83 | promos.append(promo_func) 84 | return promo_func 85 | 86 | @promotion # <3> 87 | def fidelity(order): 88 | """5% discount for customers with 1000 or more fidelity points""" 89 | return order.total() * .05 if order.customer.fidelity >= 1000 else 0 90 | 91 | @promotion 92 | def bulk_item(order): 93 | """10% discount for each LineItem with 20 or more units""" 94 | discount = 0 95 | for item in order.cart: 96 | if item.quantity >= 20: 97 | discount += item.total() * .1 98 | return discount 99 | 100 | @promotion 101 | def large_order(order): 102 | """7% discount for orders with 10 or more distinct items""" 103 | distinct_items = {item.product for item in order.cart} 104 | if len(distinct_items) >= 10: 105 | return order.total() * .07 106 | return 0 107 | 108 | def best_promo(order): # <4> 109 | """Select best discount available 110 | """ 111 | return max(promo(order) for promo in promos) 112 | 113 | # END STRATEGY_BEST4 114 | -------------------------------------------------------------------------------- /docs/vector.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Pythonic APIs 3 | ================== 4 | 5 | 6 | ``vector_v0.py`` 7 | ---------------- 8 | 9 | Importing:: 10 | 11 | >>> from examples.vector_v0 import Vector 12 | 13 | Tests with 2 dimensions:: 14 | 15 | >>> v1 = Vector([3, 4]) 16 | >>> len(v1) 17 | 2 18 | >>> abs(v1) 19 | 5.0 20 | >>> v1 == Vector((3.0, 4.0)) 21 | True 22 | >>> x, y = v1 23 | >>> x, y 24 | (3.0, 4.0) 25 | >>> list(v1) 26 | [3.0, 4.0] 27 | 28 | Tests with 3 dimensions:: 29 | 30 | >>> v1 = Vector([3, 4, 5]) 31 | >>> x, y, z = v1 32 | >>> x, y, z 33 | (3.0, 4.0, 5.0) 34 | >>> abs(v1) # doctest:+ELLIPSIS 35 | 7.071067811... 36 | 37 | 38 | Tests with more dimensions:: 39 | 40 | >>> v7 = Vector(range(7)) 41 | >>> tuple(v7) 42 | (0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0) 43 | >>> abs(v7) # doctest:+ELLIPSIS 44 | 9.53939201... 45 | 46 | 47 | 48 | .. literalinclude:: ../examples/vector_v0.py 49 | 50 | 51 | ``vector_v1.py`` 52 | ---------------- 53 | 54 | 55 | Importing:: 56 | 57 | >>> from examples.vector_v1 import Vector 58 | 59 | 60 | Vector representations:: 61 | 62 | >>> Vector([3.1, 4.2]) 63 | Vector([3.1, 4.2]) 64 | >>> Vector(range(10)) 65 | Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) 66 | >>> v3 = Vector([3, 4, 5]) 67 | >>> v3 68 | Vector([3.0, 4.0, 5.0]) 69 | >>> v3_clone = eval(repr(v3)) 70 | >>> v3_clone == v3 71 | True 72 | >>> print(v3_clone) 73 | (3.0, 4.0, 5.0) 74 | 75 | 76 | .. literalinclude:: ../examples/vector_v1.py 77 | 78 | 79 | ``vector_v2.py`` 80 | ---------------- 81 | 82 | Importing:: 83 | 84 | >>> from examples.vector_v2 import Vector 85 | 86 | 87 | Emulating sequences:: 88 | 89 | >>> v = Vector([10, 20, 30, 40, 50]) 90 | >>> v[0] 91 | 10.0 92 | >>> v[-1] 93 | 50.0 94 | >>> v[:3] 95 | Vector([10.0, 20.0, 30.0]) 96 | 97 | 98 | .. literalinclude:: ../examples/vector_v2.py 99 | 100 | 101 | ``vector_v3.py`` 102 | ---------------- 103 | 104 | Importing:: 105 | 106 | >>> from examples.vector_v3 import Vector 107 | 108 | 109 | Basic tests of operator ``*``:: 110 | 111 | >>> v1 = Vector([1, 2, 3]) 112 | >>> v1 * 10 113 | Vector([10.0, 20.0, 30.0]) 114 | 115 | 116 | Tests of ``*`` with unusual but valid operands:: 117 | 118 | >>> v1 * True 119 | Vector([1.0, 2.0, 3.0]) 120 | >>> from fractions import Fraction 121 | >>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS 122 | Vector([0.3333..., 0.6666..., 1.0]) 123 | 124 | This version has a problem:: 125 | 126 | 127 | >>> 10 * v1 128 | Traceback (most recent call last): 129 | ... 130 | TypeError: unsupported operand type(s) for *: 'int' and 'Vector' 131 | 132 | 133 | .. literalinclude:: ../examples/vector_v3.py 134 | 135 | 136 | 137 | In Python, you can compute compound interest using a formula written like this:: 138 | 139 | interest = principal * ((1 + rate) ** periods - 1) 140 | 141 | The Python formula works with any numeric type that overloads the arithmetic operators. 142 | 143 | In Java, only the primitive types support the arithmentic operators. If a financial app needs to use BigDecimal for enhanced precision, the compound interest formula has to be coded with method calls, like this: 144 | 145 | .. code-block:: Java 146 | 147 | interest = principal.multiply(BigDecimal.ONE.add(rate).pow(periods).subtract(BigDecimal.ONE)); 148 | 149 | 150 | .. literalinclude:: ../examples/vector_v3.py 151 | 152 | 153 | ``vector_v4.py`` 154 | ---------------- 155 | 156 | Importing:: 157 | 158 | >>> from examples.vector_v4 import Vector 159 | 160 | 161 | The reversed operator:: 162 | 163 | >>> v1 = Vector([1, 2, 3]) 164 | >>> 10 * v1 165 | Vector([10.0, 20.0, 30.0]) 166 | 167 | 168 | .. literalinclude:: ../examples/vector_v4.py 169 | 170 | 171 | ``vector_v5.py`` 172 | ---------------- 173 | 174 | Importing:: 175 | 176 | >>> from examples.vector_v5 import Vector 177 | 178 | Tests for operator `@` (Python >= 3.5), computing the dot product:: 179 | 180 | >>> va = Vector([1, 2, 3]) 181 | >>> vz = Vector([5, 6, 7]) 182 | >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7 183 | True 184 | >>> [10, 20, 30] @ vz 185 | 380.0 186 | >>> va @ 3 187 | Traceback (most recent call last): 188 | ... 189 | TypeError: unsupported operand type(s) for @: 'Vector' and 'int' 190 | 191 | .. literalinclude:: ../examples/vector_v5.py 192 | 193 | 194 | ``vector_v5.py`` 195 | ---------------- 196 | 197 | Importing:: 198 | 199 | >>> from examples.vector_v5 import Vector 200 | 201 | Tests for operator `@` (Python >= 3.5), computing the dot product:: 202 | 203 | >>> va = Vector([1, 2, 3]) 204 | >>> vz = Vector([5, 6, 7]) 205 | >>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7 206 | True 207 | >>> [10, 20, 30] @ vz 208 | 380.0 209 | >>> va @ 3 210 | Traceback (most recent call last): 211 | ... 212 | TypeError: unsupported operand type(s) for @: 'Vector' and 'int' 213 | 214 | .. literalinclude:: ../examples/vector_v5.py 215 | 216 | 217 | 218 | ``vector_v6.py`` 219 | ---------------- 220 | 221 | Importing:: 222 | 223 | >>> from examples.vector_v6 import Vector 224 | 225 | Tests of hashing:: 226 | 227 | >>> v1 = Vector([3, 4]) 228 | >>> hash(v1) == 3 ^ 4 229 | True 230 | >>> v3 = Vector([3, 4, 5]) 231 | >>> hash(v3) == 3 ^ 4 ^ 5 232 | True 233 | >>> v6 = Vector(range(6)) 234 | >>> hash(v6) == 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 235 | True 236 | >>> v2 = Vector([3.1, 4.2]) 237 | >>> hash(v2) == hash(3.1) ^ hash(4.2) 238 | True 239 | >>> {v1, v2, v3, v6} 240 | {Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]), Vector([3.0, 4.0, 5.0]), Vector([3.0, 4.0]), Vector([3.1, 4.2])} 241 | 242 | Most hash values of non-integers vary from a 32-bit to 64-bit CPython build:: 243 | 244 | >>> import sys 245 | >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) 246 | True 247 | 248 | 249 | .. literalinclude:: ../examples/vector_v6.py 250 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PythonicAPIsWorkshop.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PythonicAPIsWorkshop.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonicAPIsWorkshop.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonicAPIsWorkshop.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonicAPIsWorkshop" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonicAPIsWorkshop" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /pythonic-api-notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pythonic APIs: the workshop notebook" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Tutorial overview\n", 15 | "\n", 16 | "* Introduction\n", 17 | "* A simple but full-featured Pythonic class\n", 18 | " * **Exercise:** custom formatting and alternate constructor\n", 19 | "* A Pythonic sequence\n", 20 | " * **Exercise:** implementing sequence behavior\n", 21 | "* *Coffee break*\n", 22 | "* A Pythonic sequence (continued)\n", 23 | " * **Exercise:** custom formatting\n", 24 | "* Operator overloading\n", 25 | " * **Exercise:** implement `@` for dot product\n", 26 | "* Wrap-up\n" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## What is *Pythonic*?\n", 34 | "\n", 35 | "Pythonic code is concise and expressive. It leverages Python features and idioms to acomplish maximum effect with minimum effort, without being unreadable. It uses the language as it's designed to be used, so it is most readable to the fluent Pythonista." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "### Real example 1: the `requests` API\n", 43 | "\n", 44 | "`requests` is pleasant HTTP client library. It's great but it would be awesome if it was asynchronous (but could it be pleasant **and** asynchronous at the same time?). The examples below are from Kenneth Reitz, the author of `requests` ([source](https://gist.github.com/kennethreitz/973705)).\n", 45 | "\n", 46 | "#### Pythonic, using `requests`\n", 47 | "\n", 48 | "```python\n", 49 | "import requests\n", 50 | "\n", 51 | "r = requests.get('https://api.github.com', auth=('user', 'pass'))\n", 52 | "\n", 53 | "print r.status_code\n", 54 | "print r.headers['content-type']\n", 55 | "\n", 56 | "# ------\n", 57 | "# 200\n", 58 | "# 'application/json'\n", 59 | "```\n", 60 | "\n", 61 | "#### Unpythonic, using `urllib2`\n", 62 | "\n", 63 | "```python\n", 64 | "import urllib2\n", 65 | "\n", 66 | "gh_url = 'https://api.github.com'\n", 67 | "\n", 68 | "req = urllib2.Request(gh_url)\n", 69 | "\n", 70 | "password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()\n", 71 | "password_manager.add_password(None, gh_url, 'user', 'pass')\n", 72 | "\n", 73 | "auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)\n", 74 | "opener = urllib2.build_opener(auth_manager)\n", 75 | "\n", 76 | "urllib2.install_opener(opener)\n", 77 | "\n", 78 | "handler = urllib2.urlopen(req)\n", 79 | "\n", 80 | "print handler.getcode()\n", 81 | "print handler.headers.getheader('content-type')\n", 82 | "\n", 83 | "# ------\n", 84 | "# 200\n", 85 | "# 'application/json'\n", 86 | "\n", 87 | "```\n" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": { 93 | "collapsed": true 94 | }, 95 | "source": [ 96 | "### Real example 2: classes are optional in `py.test` and `nosetests`" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "## Features of idiomaitc Python APIs\n", 104 | "\n", 105 | "* Let the user apply previous knowledge of the standard types and operations\n", 106 | "* Make it easy to leverage existing libraries\n", 107 | "* Come with “batteries included”\n", 108 | "* Use duck typing for enhanced interoperation with user-defined types\n", 109 | "* Provide ready to use objects (no instantiation needed)\n", 110 | "* Don't require subclassing for basic usage\n", 111 | "* Leverage standard language objects: containers, functions, classes, modules\n", 112 | "* Make proper use of the Data Model (i.e. special methods)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "## Introduction\n", 120 | "\n", 121 | "One of the keys to consistent, *Pythonic*, behavior in Python is understanding and leveraging the **Data Model**. The Python Data Model defines standard APIs which enable...\n", 122 | "\n", 123 | "### Iteration" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 19, 129 | "metadata": { 130 | "collapsed": false 131 | }, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "['F', 'l', 'u', 'e', 'n', 't']\n", 138 | "(10, 20, 50)\n", 139 | "10 20 30 40 50 " 140 | ] 141 | } 142 | ], 143 | "source": [ 144 | "s = 'Fluent'\n", 145 | "L = [10, 20, 30, 40, 50]\n", 146 | "\n", 147 | "print(list(s)) # list constructor iterates over its argument\n", 148 | "\n", 149 | "a, b, *middle, c = L # tuple unpacking iterates over right side\n", 150 | "print((a, b, c))\n", 151 | "\n", 152 | "for i in L:\n", 153 | " print(i, end=' ')" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "## Sizing with `len()`" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 3, 166 | "metadata": { 167 | "collapsed": false 168 | }, 169 | "outputs": [ 170 | { 171 | "data": { 172 | "text/plain": [ 173 | "(6, 5)" 174 | ] 175 | }, 176 | "execution_count": 3, 177 | "metadata": {}, 178 | "output_type": "execute_result" 179 | } 180 | ], 181 | "source": [ 182 | "len(s), len(L)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 4, 188 | "metadata": { 189 | "collapsed": false 190 | }, 191 | "outputs": [ 192 | { 193 | "data": { 194 | "text/plain": [ 195 | "(6, 5)" 196 | ] 197 | }, 198 | "execution_count": 4, 199 | "metadata": {}, 200 | "output_type": "execute_result" 201 | } 202 | ], 203 | "source": [ 204 | "s.__len__(), L.__len__()" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "metadata": {}, 210 | "source": [ 211 | "### Arithmetic" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": 10, 217 | "metadata": { 218 | "collapsed": false 219 | }, 220 | "outputs": [ 221 | { 222 | "data": { 223 | "text/plain": [ 224 | "(6, 6)" 225 | ] 226 | }, 227 | "execution_count": 10, 228 | "metadata": {}, 229 | "output_type": "execute_result" 230 | } 231 | ], 232 | "source": [ 233 | "a = 2\n", 234 | "b = 3\n", 235 | "a * b, a.__mul__(b)" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 24, 241 | "metadata": { 242 | "collapsed": false 243 | }, 244 | "outputs": [ 245 | { 246 | "data": { 247 | "text/plain": [ 248 | "[1, 2, 3, [...]]" 249 | ] 250 | }, 251 | "execution_count": 24, 252 | "metadata": {}, 253 | "output_type": "execute_result" 254 | } 255 | ], 256 | "source": [ 257 | "L = [1, 2, 3]\n", 258 | "L.append(L)\n", 259 | "L" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "## A simple but full-featured Pythonic class" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "## String formatting mini-language" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 27, 279 | "metadata": { 280 | "collapsed": false 281 | }, 282 | "outputs": [ 283 | { 284 | "data": { 285 | "text/plain": [ 286 | "1.4142135623730951" 287 | ] 288 | }, 289 | "execution_count": 27, 290 | "metadata": {}, 291 | "output_type": "execute_result" 292 | } 293 | ], 294 | "source": [ 295 | "x = 2**.5\n", 296 | "x" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 29, 302 | "metadata": { 303 | "collapsed": false 304 | }, 305 | "outputs": [ 306 | { 307 | "data": { 308 | "text/plain": [ 309 | "'1.414'" 310 | ] 311 | }, 312 | "execution_count": 29, 313 | "metadata": {}, 314 | "output_type": "execute_result" 315 | } 316 | ], 317 | "source": [ 318 | "format(x, '.3f')" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 31, 324 | "metadata": { 325 | "collapsed": false 326 | }, 327 | "outputs": [ 328 | { 329 | "name": "stdout", 330 | "output_type": "stream", 331 | "text": [ 332 | "2016-05-11 20:53:37.210333\n", 333 | "20:53\n" 334 | ] 335 | } 336 | ], 337 | "source": [ 338 | "from datetime import datetime\n", 339 | "agora = datetime.now()\n", 340 | "print(agora)\n", 341 | "print(format(agora, '%H:%M'))" 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 34, 347 | "metadata": { 348 | "collapsed": false 349 | }, 350 | "outputs": [ 351 | { 352 | "data": { 353 | "text/plain": [ 354 | "'20... 1.414!'" 355 | ] 356 | }, 357 | "execution_count": 34, 358 | "metadata": {}, 359 | "output_type": "execute_result" 360 | } 361 | ], 362 | "source": [ 363 | "'{1:%H}... {0:.3f}!'.format(x, agora)" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": null, 369 | "metadata": { 370 | "collapsed": true 371 | }, 372 | "outputs": [], 373 | "source": [] 374 | } 375 | ], 376 | "metadata": { 377 | "kernelspec": { 378 | "display_name": "Python 3", 379 | "language": "python", 380 | "name": "python3" 381 | }, 382 | "language_info": { 383 | "codemirror_mode": { 384 | "name": "ipython", 385 | "version": 3 386 | }, 387 | "file_extension": ".py", 388 | "mimetype": "text/x-python", 389 | "name": "python", 390 | "nbconvert_exporter": "python", 391 | "pygments_lexer": "ipython3", 392 | "version": "3.5.1" 393 | } 394 | }, 395 | "nbformat": 4, 396 | "nbformat_minor": 0 397 | } 398 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Pythonic APIs Workshop documentation build configuration file, created by 5 | # sphinx-quickstart on Tue May 10 17:49:32 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.githubpages', 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = 'Pythonic APIs Workshop' 52 | copyright = '2016, Luciano Ramalho' 53 | author = 'Luciano Ramalho' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '2016.05' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '2016.05.10' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | # This patterns also effect to html_static_path and html_extra_path 80 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 81 | 82 | # The reST default role (used for this markup: `text`) to use for all 83 | # documents. 84 | #default_role = None 85 | 86 | # If true, '()' will be appended to :func: etc. cross-reference text. 87 | #add_function_parentheses = True 88 | 89 | # If true, the current module name will be prepended to all description 90 | # unit titles (such as .. function::). 91 | #add_module_names = True 92 | 93 | # If true, sectionauthor and moduleauthor directives will be shown in the 94 | # output. They are ignored by default. 95 | #show_authors = False 96 | 97 | # The name of the Pygments (syntax highlighting) style to use. 98 | pygments_style = 'sphinx' 99 | 100 | # A list of ignored prefixes for module index sorting. 101 | #modindex_common_prefix = [] 102 | 103 | # If true, keep warnings as "system message" paragraphs in the built documents. 104 | #keep_warnings = False 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = False 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'alabaster' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a theme 117 | # further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. 125 | # " v documentation" by default. 126 | #html_title = 'Pythonic APIs Workshop v2016.05.10' 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (relative to this directory) to use as a favicon of 136 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not None, a 'Last updated on:' timestamp is inserted at every page 151 | # bottom, using the given strftime format. 152 | # The empty string is equivalent to '%b %d, %Y'. 153 | #html_last_updated_fmt = None 154 | 155 | # If true, SmartyPants will be used to convert quotes and dashes to 156 | # typographically correct entities. 157 | #html_use_smartypants = True 158 | 159 | # Custom sidebar templates, maps document names to template names. 160 | #html_sidebars = {} 161 | 162 | # Additional templates that should be rendered to pages, maps page names to 163 | # template names. 164 | #html_additional_pages = {} 165 | 166 | # If false, no module index is generated. 167 | #html_domain_indices = True 168 | 169 | # If false, no index is generated. 170 | #html_use_index = True 171 | 172 | # If true, the index is split into individual pages for each letter. 173 | #html_split_index = False 174 | 175 | # If true, links to the reST sources are added to the pages. 176 | #html_show_sourcelink = True 177 | 178 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 179 | #html_show_sphinx = True 180 | 181 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 182 | #html_show_copyright = True 183 | 184 | # If true, an OpenSearch description file will be output, and all pages will 185 | # contain a tag referring to it. The value of this option must be the 186 | # base URL from which the finished HTML is served. 187 | #html_use_opensearch = '' 188 | 189 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 190 | #html_file_suffix = None 191 | 192 | # Language to be used for generating the HTML full-text search index. 193 | # Sphinx supports the following languages: 194 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 195 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 196 | #html_search_language = 'en' 197 | 198 | # A dictionary with options for the search language support, empty by default. 199 | # 'ja' uses this config value. 200 | # 'zh' user can custom change `jieba` dictionary path. 201 | #html_search_options = {'type': 'default'} 202 | 203 | # The name of a javascript file (relative to the configuration directory) that 204 | # implements a search results scorer. If empty, the default will be used. 205 | #html_search_scorer = 'scorer.js' 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'PythonicAPIsWorkshopdoc' 209 | 210 | # -- Options for LaTeX output --------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | #'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | #'preamble': '', 221 | 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | (master_doc, 'PythonicAPIsWorkshop.tex', 'Pythonic APIs Workshop Documentation', 231 | 'Luciano Ramalho', 'manual'), 232 | ] 233 | 234 | # The name of an image file (relative to this directory) to place at the top of 235 | # the title page. 236 | #latex_logo = None 237 | 238 | # For "manual" documents, if this is true, then toplevel headings are parts, 239 | # not chapters. 240 | #latex_use_parts = False 241 | 242 | # If true, show page references after internal links. 243 | #latex_show_pagerefs = False 244 | 245 | # If true, show URL addresses after external links. 246 | #latex_show_urls = False 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #latex_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #latex_domain_indices = True 253 | 254 | 255 | # -- Options for manual page output --------------------------------------- 256 | 257 | # One entry per manual page. List of tuples 258 | # (source start file, name, description, authors, manual section). 259 | man_pages = [ 260 | (master_doc, 'pythonicapisworkshop', 'Pythonic APIs Workshop Documentation', 261 | [author], 1) 262 | ] 263 | 264 | # If true, show URL addresses after external links. 265 | #man_show_urls = False 266 | 267 | 268 | # -- Options for Texinfo output ------------------------------------------- 269 | 270 | # Grouping the document tree into Texinfo files. List of tuples 271 | # (source start file, target name, title, author, 272 | # dir menu entry, description, category) 273 | texinfo_documents = [ 274 | (master_doc, 'PythonicAPIsWorkshop', 'Pythonic APIs Workshop Documentation', 275 | author, 'PythonicAPIsWorkshop', 'One line description of project.', 276 | 'Miscellaneous'), 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | #texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | #texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | #texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | #texinfo_no_detailmenu = False 290 | --------------------------------------------------------------------------------