Skip to content

Commit a76a766

Browse files
authored
New section for cleaner code. (#9)
1 parent e384cb5 commit a76a766

4 files changed

Lines changed: 100 additions & 91 deletions

File tree

‎docs/source/clean_code.rst‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
Clean(er) code
2+
==============
3+
4+
Below are a few tips for writing clean(er) code.
5+
6+
Code formatting
7+
---------------
8+
9+
Code formatting is important for readability. It allows you to write code that
10+
is easy to follow (for your future self as much as others), and (least importantly)
11+
adheres to the `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_ standard.
12+
13+
However, remembering all the rules and manually formatting your code is not
14+
how we want to spend our time as developers. To this end, there are several
15+
tools that can help us with this task. The ones we use are:
16+
17+
* `Black <https://github.com/psf/black>`_ which will reformat your code
18+
in-place to conform to the PEP8 standard.
19+
* `Flake8 <https://flake8.pycqa.org/en/latest/>`__ which is a *linter* that
20+
will check your code for errors and style violations, but not reformat it. For
21+
example, for me it has identified code where I have unused variables or
22+
scripts / functions that are too long.
23+
24+
While you can use these tools manually, it is much more convenient to use them
25+
as pre-commit hooks. This means that before you commit your code, these tools
26+
will be run automatically. If they find any errors, the commit will be aborted
27+
and you will have to fix the errors before you can commit again. Pre-commit
28+
helps you to automate this process and avoid commits that do not conform to
29+
the PEP8 standard (and commits that are just for formatting).
30+
31+
A few files are needed to setup pre-commit hooks:
32+
33+
* `.pre-commit-config.yaml <https://github.com/ebezzam/python-dev-tips/blob/main/.pre-commit-config.yaml>`_: This file contains the configuration for the
34+
pre-commit hooks. It specifies which tools to use, and how to use them.
35+
* `.flake8 <https://github.com/ebezzam/python-dev-tips/blob/main/.flake8>`_: This file contains the configuration for Flake8. It specifies
36+
e.g. which errors to ignore, and which line length to use.
37+
* `pyproject.toml <https://github.com/ebezzam/python-dev-tips/blob/main/pyproject.toml>`_: This file contains the configuration for Black. It
38+
specifies e.g. which line length to use.
39+
40+
You can then install the pre-commit hooks for your project by running the
41+
following commands:
42+
43+
.. code:: bash
44+
45+
# inside virtual environment
46+
(project_env) pip install pre-commit
47+
(project_env) pip install black
48+
49+
# Install git hooks
50+
(project_env) pre-commit install
51+
# pre-commit installed at .git/hooks/pre-commit
52+
53+
54+
Avoiding long ``if-else`` statements with object instantiation
55+
--------------------------------------------------------------
56+
57+
In :ref:`Reproducible examples<Reproducible examples>` we presented Hydra for separating configuration from code.
58+
Another cool feature of Hydra is `object instantiating <https://hydra.cc/docs/advanced/instantiate_objects/overview/>`_.
59+
Imagine you want to try different optimizers for your Deep Neural Network (DNN) or you want to try different DNNs in the same pipeline.
60+
Instead of doing ``if-else`` statements, you write one line of code and let Hydra choose the
61+
appropriate object class based on your configuration. See the script
62+
`examples/real_convolve.py <https://github.com/ebezzam/python-dev-tips/blob/main/examples/real_convolve.py>`_
63+
for the example.
64+
65+
.. code-block:: python
66+
67+
@hydra.main(version_base=None, config_path="configs", config_name="defaults")
68+
def main(config):
69+
# instantiate object from config
70+
signal = instantiate(config.signal)
71+
# application specific choice of object class
72+
73+
``instantiate`` function from ``hydra.utils`` allows you to define an object in a YAML file
74+
without being tied to a particular class. To do this, you need to define ``_target_`` in
75+
your config (see configs in ``configs/signal``) and object initialization arguments. Object class
76+
can be either defined in your project (``configs/signal/ExampleZeros``, ``configs/signal/ExampleCustom``)
77+
or taken from a package (``configs/signal/ExampleNumpy``).
78+
79+
Note that here we use another Hydra feature: config grouping and splitting. Instead of writing
80+
configurations for all objects in the main config and copying configuration files, we create a sub-directory ``signal``,
81+
where all ``signal`` configs are defined. Now we can run the main config with the ``signal`` of
82+
our choice simply by specifying it in the command line. For example, ``python examples/real_convolve.py signal=ExampleNumpy``
83+
or ``python examples/real_convolve.py signal=ExampleZeros``.
84+
85+
If we need to define some of the arguments inside the code before creating an object, we can pass them directly to the ``instantiate`` function.
86+
For example, we did not define ``signal_len`` in the ``signal`` configuration file and passed it by hand:
87+
``signal = instantiate(config.signal, config.signal_len)``. This is especially useful when you have positional-only arguments
88+
like ``numpy.random.randn`` in our example. Note that we can both define arguments in the configuration file and pass new ones to ``instantiate`` like we did for
89+
``ExampleCustom``.
90+
91+
Object instantiating is recursive, i.e. some of the arguments of the class can also be
92+
defined using ``_target_`` and they will be created automatically. For example,
93+
``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power`` defines the ``transform`` argument of
94+
the ``ExampleCustom`` class as the ``PowerTransform`` class. The ``+signal/transform=power`` in the command line
95+
means adding the ``transform`` argument to the current ``signal`` configuration from the ``power.yaml`` config defined
96+
in ``configs/signal/transform``. That is, you can have sub-sub-directories. The default values from sub-sub-directories
97+
can also be changed in the command-line: ``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power signal.transform.pow=3``

‎docs/source/code_formatting.rst‎

Lines changed: 0 additions & 46 deletions
This file was deleted.

‎docs/source/index.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Contents
1919
version_control
2020
reproducible
2121
documentation
22-
code_formatting
22+
clean_code
2323
testing
2424
packaging
2525
remote_development

‎docs/source/reproducible.rst‎

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ can also save the results of the run in that folder:
8787
# Save the results
8888
np.save(os.path.join(os.getcwd(), "results.npy"), results)
8989
90-
This makes hydra a great tool for keeping tracking of experiment runs and their
90+
This makes Hydra a great tool for keeping tracking of experiment runs and their
9191
parameters, while limiting changes to the code (and having new code commits).
92+
Check out `this blog post <https://medium.com/@bezzam/hydra-for-cleaner-python-code-and-better-reproducibility-in-research-c035028101f9>`_ for more on Hydra.
9293

9394
Setting the seed
9495
----------------
@@ -105,46 +106,3 @@ configuration file and use it the script like so:
105106
np.random.seed(config.seed)
106107
# application-specific seed setting
107108
108-
Instantiating objects
109-
---------------------
110-
111-
Another cool feature of Hydra is `object instantiating <https://hydra.cc/docs/advanced/instantiate_objects/overview/>`_.
112-
Imagine you want to try different optimizers for your Deep Neural Network (DNN) or you want to try different DNNs in the same pipeline.
113-
Instead of doing ``if-else`` statements, you write one line of code and let Hydra choose the
114-
appropriate object class based on your configuration. See the script
115-
`examples/real_convolve.py <https://github.com/ebezzam/python-dev-tips/blob/main/examples/real_convolve.py>`_
116-
for the example.
117-
118-
.. code-block:: python
119-
120-
@hydra.main(version_base=None, config_path="configs", config_name="defaults")
121-
def main(config):
122-
# instantiate object from config
123-
signal = instantiate(config.signal)
124-
# application specific choice of object class
125-
126-
``instantiate`` function from ``hydra.utils`` allows you to define an object in a YAML file
127-
without being tied to a particular class. To do this, you need to define ``_target_`` in
128-
your config (see configs in ``configs/signal``) and object initialization arguments. Object class
129-
can be either defined in your project (``configs/signal/ExampleZeros``, ``configs/signal/ExampleCustom``)
130-
or taken from a package (``configs/signal/ExampleNumpy``).
131-
132-
Note that here we use another Hydra feature: config grouping and splitting. Instead of writing
133-
configurations for all objects in the main config and copying configuration files, we create a sub-directory ``signal``,
134-
where all ``signal`` configs are defined. Now we can run the main config with the ``signal`` of
135-
our choice simply by specifying it in the command line. For example, ``python examples/real_convolve.py signal=ExampleNumpy``
136-
or ``python examples/real_convolve.py signal=ExampleZeros``.
137-
138-
If we need to define some of the arguments inside the code before creating an object, we can pass them directly to the ``instantiate`` function.
139-
For example, we did not define ``signal_len`` in the ``signal`` configuration file and passed it by hand:
140-
``signal = instantiate(config.signal, config.signal_len)``. This is especially useful when you have positional-only arguments
141-
like ``numpy.random.randn`` in our example. Note that we can both define arguments in the configuration file and pass new ones to ``instantiate`` like we did for
142-
``ExampleCustom``.
143-
144-
Object instantiating is recursive, i.e. some of the arguments of the class can also be
145-
defined using ``_target_`` and they will be created automatically. For example,
146-
``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power`` defines the ``transform`` argument of
147-
the ``ExampleCustom`` class as the ``PowerTransform`` class. The ``+signal/transform=power`` in the command line
148-
means adding the ``transform`` argument to the current ``signal`` configuration from the ``power.yaml`` config defined
149-
in ``configs/signal/transform``. That is, you can have sub-sub-directories. The default values from sub-sub-directories
150-
can also be changed in the command-line: ``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power signal.transform.pow=3``

0 commit comments

Comments
 (0)