Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fsl/pytreat-practicals-2020
  • mchiew/pytreat-practicals-2020
  • ndcn0236/pytreat-practicals-2020
  • nichols/pytreat-practicals-2020
4 results
Show changes
Showing
with 1519 additions and 1218 deletions
1.056802026 -0.01924547726 0.02614687181 -36.51723948
0.009055463297 0.9745460053 0.09056277052 -8.771603455
-0.04315832679 -0.1680837227 1.136420957 -1.399839791
0 0 0 1
File added
File added
%% Cell type:markdown id: tags:
# Structuring a Python project
If you are writing code that you are sure will never be seen or used by
anybody else, then you can structure your project however you want, and you
can stop reading now.
However, if you are intending to make your code available for others to use,
you will make their lives easier if you spend a little time organising your
project directory.
either as end users, or as a dependency of their own code, you will make their
lives much easier if you spend a little time organising your project
directory.
* [Recommended project structure](#recommended-project-structure)
* [The `mypackage/` directory](#the-mypackage-directory)
* [`README`](#readme)
* [`LICENSE`](#license)
* [`requirements.txt`](#requirements-txt)
* [`setup.py`](#setup-py)
* [Appendix: Tests](#appendix-tests)
* [Appendix: Versioning](#appendix-versioning)
* [Include the version in your code](#include-the-version-in-your-code)
* [Deprecate, don't remove!](#deprecate-dont-remove)
* [Appendix: Cookiecutter](#appendix-cookiecutter)
Official documentation:
https://packaging.python.org/tutorials/distributing-packages/
<a class="anchor" id="recommended-project-structure"></a>
## Recommended project structure
A Python project directory should, at the very least, have a structure that
resembles the following:
> ```
> myproject/
> mypackage/
> __init__.py
> mymodule.py
> README
> LICENSE
> requirements.txt
> setup.py
> ```
## The `mypackage/` directory
This example structure is in the `example_project/` sub-directory - have a
look through it if you like.
<a class="anchor" id="the-mypackage-directory"></a>
### The `mypackage/` directory
The first thing you should do is make sure that all of your python code is
organised into a sensibly-named
[_package_](https://docs.python.org/3.5/tutorial/modules.html#packages). This
[*package*](https://docs.python.org/3/tutorial/modules.html#packages). This
is important, because it greatly reduces the possibility of naming collisions
when people install your library alongside other libraries. Hands up those of
you who have ever written a file called `utils.[py|m|c|cpp]`!
Check out the `advanced_topics/02_modules_and_packages.ipynb` practical for
more details on packages in Python.
## `README`
<a class="anchor" id="readme"></a>
### `README`
Every project should have a README file. This is simply a plain text file
which describes your project and how to use it. It is common and acceptable
for a README file to be written in plain text,
[reStructuredText](http://www.sphinx-doc.org/en/stable/rest.html)
(`README.rst`), or
[markdown](https://guides.github.com/features/mastering-markdown/)
(`README.md`).
## `LICENSE`
<a class="anchor" id="license"></a>
### `LICENSE`
Having a LICENSE file makes it easy for people to understand the constraints
under which your code can be used.
## `requirements.txt`
<a class="anchor" id="requirements-txt"></a>
### `requirements.txt`
This file is not strictly necessary, but is very common in Python projects.
It contains a list of the Python-based dependencies of your project, in a
standardised syntax.
standardised syntax. You can specify the exact version, or range of versions,
that your project requires. For example:
> ```
> six==1.*
> numpy==1.*
> scipy>=0.18
> nibabel==2.*
> ```
If your project has optional dependencies, i.e. libraries which are not
critical but, if present, will allow your project to offer some extra
features, you can list them in a separate requirements file called, for
example, `requirements-extra.txt`.
Having all your dependencies listed in a file in this way makes it easy for
others to install the dependencies needed by your project, simply by running:
> ```
> pip install -r requirements.txt
> ```
<a class="anchor" id="setup-py"></a>
### `setup.py`
This is the most important file (apart from your code, of course). Python
projects are installed using
[`setuptools`](https://setuptools.readthedocs.io/en/latest/), which is used
internally during both the creation of, and installation of Python libraries.
The `setup.py` file in a Python project is akin to a `Makefile` in a C/C++
project. But `setup.py` is also the location where you can define project
metadata (e.g. name, author, URL, etc) in a standardised format and, if
necessary, customise aspects of the build process for your library.
You generally don't need to worry about, or interact with `setuptools` at all.
With one exception - `setup.py` is a Python script, and its main job is to
call the `setuptools.setup` function, passing it information about your
project.
## Tests
The `setup.py` for our example project might look like this:
> ```
> #!/usr/bin/env python
>
> from setuptools import setup
> from setuptools import find_packages
>
> # Import version number from
> # the project package (see
> # the section on versioning).
> from mypackage import __version__
>
> # Read in requirements from
> # the requirements.txt file.
> with open('requirements.txt', 'rt') as f:
> requirements = [l.strip() for l in f.readlines()]
>
> # Generate a list of all of the
> # packages that are in your project.
> packages = find_packages()
>
> setup(
>
> name='Example project',
> description='Example Python project for PyTreat',
> url='https://git.fmrib.ox.ac.uk/fsl/pytreat-practicals-2020/',
> author='Paul McCarthy',
> author_email='pauldmccarthy@gmail.com',
> license='Apache License Version 2.0',
>
> packages=packages,
>
> version=__version__,
>
> install_requires=requirements,
>
> classifiers=[
> 'Development Status :: 3 - Alpha',
> 'Intended Audience :: Developers',
> 'License :: OSI Approved :: Apache Software License',
> 'Programming Language :: Python :: 2.7',
> 'Programming Language :: Python :: 3.4',
> 'Programming Language :: Python :: 3.5',
> 'Programming Language :: Python :: 3.6',
> 'Topic :: Software Development :: Libraries :: Python Modules'],
> )
> ```
The `setup` function gets passed all of your project's metadata, including its
version number, depedencies, and licensing information. The `classifiers`
argument should contain a list of
[classifiers](https://pypi.python.org/pypi?%3Aaction=list_classifiers) which
are applicable to your project. Classifiers are purely for descriptive
purposes - they can be used to aid people in finding your project on
[`PyPI`](https://pypi.python.org/pypi), if you release it there.
See
[here](https://packaging.python.org/tutorials/distributing-packages/#setup-args)
for more details on `setup.py` and the `setup` function.
<a class="anchor" id="appendix-tests"></a>
## Appendix: Tests
There are no strict rules for where to put your tests (you have tests,
right?). There are two main conventions:
You can store your test files _inside_ your package directory:
You can store your test files *inside* your package directory:
> ```
> myproject/
> mypackage/
> __init__.py
> mymodule.py
> tests/
> __init__.py
> test_mymodule.py
> ```
Or, you can store your test files _alongside_ your package directory:
Or, you can store your test files *alongside* your package directory:
> ```
> myproject/
> mypackage/
> __init__.py
> mymodule.py
> tests/
> test_mymodule.py
> ```
If you want your test code to be completely independent of your project's
code, then go with the second option. However, if you would like your test
code to be distributed as part of your project (so that e.g. end users can run
code to be distributed as part of your project (e.g. so that end users can run
them), then the first option is probably the best.
But in the end, the standard Python unit testing frameworks
([`pytest`](https://docs.pytest.org/en/latest/) and
[`nose`](http://nose2.readthedocs.io/en/latest/)) are pretty good at finding
your test functions no matter where you've hidden them.
your test functions no matter where you've hidden them, so the choice is
really up to you.
## Versioning
<a class="anchor" id="appendix-versioning"></a>
## Appendix: Versioning
If you are intending to make your project available for public use (e.g. on
[PyPI](https://pypi.python.org/pypi) and/or
[conda](https://anaconda.org/anaconda/repo)), it is __very important__ to
[conda](https://anaconda.org/anaconda/repo)), it is **very important** to
manage the version number of your project. If somebody decides to build their
software on top of your project, they are not going to be very happy with you
if you make substantial, API-breaking changes without changing your version
number in an appropriate manner.
Python has [official standards](https://www.python.org/dev/peps/pep-0440/) on
what constitutes a valid version number. These standards can be quite
complicated but, in the vast majority of cases, a simple three-number
versioning scheme comprising _major_, _minor_, and _patch_ release
versioning scheme comprising *major*, *minor*, and *patch* release
numbers should suffice. Such a version number has the form:
> major.minor.patch
> ```
> major.minor.patch
> ```
For example, a version number of `1.3.2` has a _major_ release of 1, _minor_
release of 3, and a _patch_ release of 2.
If you follow some simple and rational guidelines for versioning
`your_project`, then people who use your project can, for instance, specify
that they depend on `your_project==1.*`, and be sure that their code will work
for _any_ version of `your_project` with a major release of 1. Following these
for *any* version of `your_project` with a major release of 1. Following these
simple guidelines greatly improves software interoperability, and makes
everybody (i.e. developers of other projects, and end users) much happier!
Many modern Python projects use some form of [_semantic
versioning_](https://semver.org/). Semantic versioning is simply a set of
Many modern Python projects use some form of [*semantic
versioning*](https://semver.org/). Semantic versioning is simply a set of
guidelines on how to manage your version number:
- The _major_ release number should be incremented whenever you introduce any
- The *major* release number should be incremented whenever you introduce any
backwards-incompatible changes. In other words, if you change your code
such that some other code which uses your code would break, you should
increment the major release number.
- The _minor_ release number should be incremented whenever you add any new
- The *minor* release number should be incremented whenever you add any new
(backwards-compatible) features to your project.
- The _patch_ release number should be incremented for backwards-compatible
- The *patch* release number should be incremented for backwards-compatible
bug-fixes and other minor changes.
If you like to automate things,
[`bumpversion`](https://github.com/peritus/bumpversion) is a simple tool that
you can use to help manage your version number.
<a class="anchor" id="include-the-version-in-your-code"></a>
### Include the version in your code
While the version of a library is ultimately defined in `setup.py`, it is
standard practice for a Python library to contain a version string called
`__version__` in the `__init__.py` file of the top-level package. For example,
our `example_project/mypackage/__init__.py` file contains this line:
> ```
> __version__ = '0.1.0'
> ```
This makes a library's version number programmatically accessible and
queryable.
<a class="anchor" id="deprecate-dont-remove"></a>
### Deprecate, don't remove!
If you really want to change your API, but can't bring yourself to increment
your major release number, consider _deprecating_ the old API, and postponing
its removal until you are ready for a major release. This will allow you to
change your API, but retain backwards-compatilbiity with the old API until it
can safely be removed at the next major release.
your major release number, consider
[*deprecating*](https://en.wikipedia.org/wiki/Deprecation#Software_deprecation)
the old API, and postponing its removal until you are ready for a major
release. This will allow you to change your API, but retain
backwards-compatilbiity with the old API until it can safely be removed at the
next major release.
You can use the built-in
[`warnings`](https://docs.python.org/3.5/library/exceptions.html#DeprecationWarning)
module to warn about uses of deprecated items. There are also some
[third-party libraries](https://github.com/briancurtin/deprecation) which make
it easy to mark a function, method or class as being deprecated.
## Cookiecutter
<a class="anchor" id="appendix-cookiecutter"></a>
## Appendix: Cookiecutter
It is worth mentioning
[Cookiecutter](https://github.com/audreyr/cookiecutter), a little utility
program which you can use to generate a skeleton file/directory structure for
a new Python project.
You need to give it a template (there are many available templates, including
for projects in languages other than Python) - a couple of useful templates
are the [minimal Python package
template](https://github.com/kragniz/cookiecutter-pypackage-minimal), and the
[full Python package
template](https://github.com/audreyr/cookiecutter-pypackage) (although the
latter is probably overkill for most).
Here is how to create a skeleton project directory based off the minimal
Python packagetemplate:
%% Cell type:code id: tags:
```
pip install cookiecutter
# tell cookiecutter to create a directory
# from the pypackage-minimal template
cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git
# cookiecutter will then prompt you for
# basic information (e.g. projectname,
# author name/email), and then create a
# new directory containing the project
# skeleton.
```
> ```
> pip install cookiecutter
>
> # tell cookiecutter to create a directory
> # from the pypackage-minimal template
> cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git
>
> # cookiecutter will then prompt you for
> # basic information (e.g. projectname,
> # author name/email), and then create a
> # new directory containing the project
> # skeleton.
> ```
......
......@@ -7,8 +7,30 @@ can stop reading now.
However, if you are intending to make your code available for others to use,
you will make their lives easier if you spend a little time organising your
project directory.
either as end users, or as a dependency of their own code, you will make their
lives much easier if you spend a little time organising your project
directory.
* [Recommended project structure](#recommended-project-structure)
* [The `mypackage/` directory](#the-mypackage-directory)
* [`README`](#readme)
* [`LICENSE`](#license)
* [`requirements.txt`](#requirements-txt)
* [`setup.py`](#setup-py)
* [Appendix: Tests](#appendix-tests)
* [Appendix: Versioning](#appendix-versioning)
* [Include the version in your code](#include-the-version-in-your-code)
* [Deprecate, don't remove!](#deprecate-dont-remove)
* [Appendix: Cookiecutter](#appendix-cookiecutter)
Official documentation:
https://packaging.python.org/tutorials/distributing-packages/
<a class="anchor" id="recommended-project-structure"></a>
## Recommended project structure
A Python project directory should, at the very least, have a structure that
......@@ -27,12 +49,17 @@ resembles the following:
> ```
## The `mypackage/` directory
This example structure is in the `example_project/` sub-directory - have a
look through it if you like.
<a class="anchor" id="the-mypackage-directory"></a>
### The `mypackage/` directory
The first thing you should do is make sure that all of your python code is
organised into a sensibly-named
[_package_](https://docs.python.org/3.5/tutorial/modules.html#packages). This
[*package*](https://docs.python.org/3/tutorial/modules.html#packages). This
is important, because it greatly reduces the possibility of naming collisions
when people install your library alongside other libraries. Hands up those of
you who have ever written a file called `utils.[py|m|c|cpp]`!
......@@ -42,7 +69,8 @@ Check out the `advanced_topics/02_modules_and_packages.ipynb` practical for
more details on packages in Python.
## `README`
<a class="anchor" id="readme"></a>
### `README`
Every project should have a README file. This is simply a plain text file
......@@ -54,30 +82,143 @@ for a README file to be written in plain text,
(`README.md`).
## `LICENSE`
<a class="anchor" id="license"></a>
### `LICENSE`
Having a LICENSE file makes it easy for people to understand the constraints
under which your code can be used.
## `requirements.txt`
<a class="anchor" id="requirements-txt"></a>
### `requirements.txt`
This file is not strictly necessary, but is very common in Python projects.
It contains a list of the Python-based dependencies of your project, in a
standardised syntax.
standardised syntax. You can specify the exact version, or range of versions,
that your project requires. For example:
> ```
> six==1.*
> numpy==1.*
> scipy>=0.18
> nibabel==2.*
> ```
If your project has optional dependencies, i.e. libraries which are not
critical but, if present, will allow your project to offer some extra
features, you can list them in a separate requirements file called, for
example, `requirements-extra.txt`.
Having all your dependencies listed in a file in this way makes it easy for
others to install the dependencies needed by your project, simply by running:
> ```
> pip install -r requirements.txt
> ```
<a class="anchor" id="setup-py"></a>
### `setup.py`
This is the most important file (apart from your code, of course). Python
projects are installed using
[`setuptools`](https://setuptools.readthedocs.io/en/latest/), which is used
internally during both the creation of, and installation of Python libraries.
The `setup.py` file in a Python project is akin to a `Makefile` in a C/C++
project. But `setup.py` is also the location where you can define project
metadata (e.g. name, author, URL, etc) in a standardised format and, if
necessary, customise aspects of the build process for your library.
You generally don't need to worry about, or interact with `setuptools` at all.
With one exception - `setup.py` is a Python script, and its main job is to
call the `setuptools.setup` function, passing it information about your
project.
## Tests
The `setup.py` for our example project might look like this:
> ```
> #!/usr/bin/env python
>
> from setuptools import setup
> from setuptools import find_packages
>
> # Import version number from
> # the project package (see
> # the section on versioning).
> from mypackage import __version__
>
> # Read in requirements from
> # the requirements.txt file.
> with open('requirements.txt', 'rt') as f:
> requirements = [l.strip() for l in f.readlines()]
>
> # Generate a list of all of the
> # packages that are in your project.
> packages = find_packages()
>
> setup(
>
> name='Example project',
> description='Example Python project for PyTreat',
> url='https://git.fmrib.ox.ac.uk/fsl/pytreat-practicals-2020/',
> author='Paul McCarthy',
> author_email='pauldmccarthy@gmail.com',
> license='Apache License Version 2.0',
>
> packages=packages,
>
> version=__version__,
>
> install_requires=requirements,
>
> classifiers=[
> 'Development Status :: 3 - Alpha',
> 'Intended Audience :: Developers',
> 'License :: OSI Approved :: Apache Software License',
> 'Programming Language :: Python :: 2.7',
> 'Programming Language :: Python :: 3.4',
> 'Programming Language :: Python :: 3.5',
> 'Programming Language :: Python :: 3.6',
> 'Topic :: Software Development :: Libraries :: Python Modules'],
> )
> ```
The `setup` function gets passed all of your project's metadata, including its
version number, depedencies, and licensing information. The `classifiers`
argument should contain a list of
[classifiers](https://pypi.python.org/pypi?%3Aaction=list_classifiers) which
are applicable to your project. Classifiers are purely for descriptive
purposes - they can be used to aid people in finding your project on
[`PyPI`](https://pypi.python.org/pypi), if you release it there.
See
[here](https://packaging.python.org/tutorials/distributing-packages/#setup-args)
for more details on `setup.py` and the `setup` function.
<a class="anchor" id="appendix-tests"></a>
## Appendix: Tests
There are no strict rules for where to put your tests (you have tests,
right?). There are two main conventions:
You can store your test files _inside_ your package directory:
You can store your test files *inside* your package directory:
> ```
......@@ -91,8 +232,7 @@ You can store your test files _inside_ your package directory:
> ```
Or, you can store your test files _alongside_ your package directory:
Or, you can store your test files *alongside* your package directory:
> ```
......@@ -107,22 +247,24 @@ Or, you can store your test files _alongside_ your package directory:
If you want your test code to be completely independent of your project's
code, then go with the second option. However, if you would like your test
code to be distributed as part of your project (so that e.g. end users can run
code to be distributed as part of your project (e.g. so that end users can run
them), then the first option is probably the best.
But in the end, the standard Python unit testing frameworks
([`pytest`](https://docs.pytest.org/en/latest/) and
[`nose`](http://nose2.readthedocs.io/en/latest/)) are pretty good at finding
your test functions no matter where you've hidden them.
your test functions no matter where you've hidden them, so the choice is
really up to you.
## Versioning
<a class="anchor" id="appendix-versioning"></a>
## Appendix: Versioning
If you are intending to make your project available for public use (e.g. on
[PyPI](https://pypi.python.org/pypi) and/or
[conda](https://anaconda.org/anaconda/repo)), it is __very important__ to
[conda](https://anaconda.org/anaconda/repo)), it is **very important** to
manage the version number of your project. If somebody decides to build their
software on top of your project, they are not going to be very happy with you
if you make substantial, API-breaking changes without changing your version
......@@ -132,11 +274,13 @@ number in an appropriate manner.
Python has [official standards](https://www.python.org/dev/peps/pep-0440/) on
what constitutes a valid version number. These standards can be quite
complicated but, in the vast majority of cases, a simple three-number
versioning scheme comprising _major_, _minor_, and _patch_ release
versioning scheme comprising *major*, *minor*, and *patch* release
numbers should suffice. Such a version number has the form:
> major.minor.patch
> ```
> major.minor.patch
> ```
For example, a version number of `1.3.2` has a _major_ release of 1, _minor_
......@@ -146,25 +290,25 @@ release of 3, and a _patch_ release of 2.
If you follow some simple and rational guidelines for versioning
`your_project`, then people who use your project can, for instance, specify
that they depend on `your_project==1.*`, and be sure that their code will work
for _any_ version of `your_project` with a major release of 1. Following these
for *any* version of `your_project` with a major release of 1. Following these
simple guidelines greatly improves software interoperability, and makes
everybody (i.e. developers of other projects, and end users) much happier!
Many modern Python projects use some form of [_semantic
versioning_](https://semver.org/). Semantic versioning is simply a set of
Many modern Python projects use some form of [*semantic
versioning*](https://semver.org/). Semantic versioning is simply a set of
guidelines on how to manage your version number:
- The _major_ release number should be incremented whenever you introduce any
- The *major* release number should be incremented whenever you introduce any
backwards-incompatible changes. In other words, if you change your code
such that some other code which uses your code would break, you should
increment the major release number.
- The _minor_ release number should be incremented whenever you add any new
- The *minor* release number should be incremented whenever you add any new
(backwards-compatible) features to your project.
- The _patch_ release number should be incremented for backwards-compatible
- The *patch* release number should be incremented for backwards-compatible
bug-fixes and other minor changes.
......@@ -173,14 +317,36 @@ If you like to automate things,
you can use to help manage your version number.
<a class="anchor" id="include-the-version-in-your-code"></a>
### Include the version in your code
While the version of a library is ultimately defined in `setup.py`, it is
standard practice for a Python library to contain a version string called
`__version__` in the `__init__.py` file of the top-level package. For example,
our `example_project/mypackage/__init__.py` file contains this line:
> ```
> __version__ = '0.1.0'
> ```
This makes a library's version number programmatically accessible and
queryable.
<a class="anchor" id="deprecate-dont-remove"></a>
### Deprecate, don't remove!
If you really want to change your API, but can't bring yourself to increment
your major release number, consider _deprecating_ the old API, and postponing
its removal until you are ready for a major release. This will allow you to
change your API, but retain backwards-compatilbiity with the old API until it
can safely be removed at the next major release.
your major release number, consider
[*deprecating*](https://en.wikipedia.org/wiki/Deprecation#Software_deprecation)
the old API, and postponing its removal until you are ready for a major
release. This will allow you to change your API, but retain
backwards-compatilbiity with the old API until it can safely be removed at the
next major release.
You can use the built-in
......@@ -190,7 +356,8 @@ module to warn about uses of deprecated items. There are also some
it easy to mark a function, method or class as being deprecated.
## Cookiecutter
<a class="anchor" id="appendix-cookiecutter"></a>
## Appendix: Cookiecutter
It is worth mentioning
......@@ -212,16 +379,16 @@ Here is how to create a skeleton project directory based off the minimal
Python packagetemplate:
```
pip install cookiecutter
# tell cookiecutter to create a directory
# from the pypackage-minimal template
cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git
# cookiecutter will then prompt you for
# basic information (e.g. projectname,
# author name/email), and then create a
# new directory containing the project
# skeleton.
```
> ```
> pip install cookiecutter
>
> # tell cookiecutter to create a directory
> # from the pypackage-minimal template
> cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git
>
> # cookiecutter will then prompt you for
> # basic information (e.g. projectname,
> # author name/email), and then create a
> # new directory containing the project
> # skeleton.
> ```
The example_project library
Copyright 2016-2017 University of Oxford, Oxford, UK.
Copyright 2016-2020 University of Oxford, Oxford, UK.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
#!/usr/bin/env python
from setuptools import setup
from setuptools import find_packages
# Import version number from
# the project package (see
# the section on versioning).
from mypackage import __version__
# Read in requirements from
# the requirements.txt file.
with open('requirements.txt', 'rt') as f:
requirements = [l.strip() for l in f.readlines()]
# Generate a list of all of the
# packages that are in your project.
packages = find_packages()
setup(
name='Example project',
description='Example Python project for PyTreat',
url='https://git.fmrib.ox.ac.uk/fsl/pytreat-2018-practicals/',
url='https://git.fmrib.ox.ac.uk/fsl/pytreat-practicals-2020/',
author='Paul McCarthy',
author_email='pauldmccarthy@gmail.com',
license='Apache License Version 2.0',
packages=packages,
version=__version__,
install_requires=requirements,
......
......@@ -15,4 +15,4 @@ order, but we recommend going through them in this order:
4. Operator overloading
5. Context managers
6. Decorators
7. Testing
7. Threading and parallel processing
This diff is collapsed.
......@@ -10,7 +10,17 @@ explanations. You can run each block by using _shift + enter_
(including the text blocks, so you can just move down the document
with shift + enter).
It is also possible to _change_ the contents of each code block (these pages are completely interactive) so do experiment with the code you see and try some variations!
It is also possible to _change_ the contents of each code block (these pages
are completely interactive) so do experiment with the code you see and try
some variations!
> **Important**: We are exclusively using Python 3 in FSL - as of FSL 6.0.4 we
> are using Python 3.7. There are some subtle differences between Python 2 and
> Python 3, but instead of learning about these differences, it is easier to
> simply forget that Python 2 exists. When you are googling for Python help,
> make sure that the pages you find are relevant to Python 3 and *not* Python
> 2! The official Python docs can be found at https://docs.python.org/3/ (note
> the _/3/_ at the end!).
## Contents
......@@ -53,7 +63,7 @@ Python has many different types and variables are dynamic and can change types (
* lists
* dictionaries
N-dimensional arrays and other types are supported through common modules (e.g., numpy, scipy, scikit-learn). These will be covered in a subsequent exercise.
N-dimensional arrays and other types are supported through common modules (e.g., [numpy](https://numpy.org/), [scipy](https://docs.scipy.org/doc/scipy-1.4.1/reference/), [scikit-learn](https://scikit-learn.org/stable/)). These will be covered in a subsequent exercises.
```
a = 4
......@@ -104,16 +114,20 @@ print(s3)
<a class="anchor" id="Format"></a>
### Format
More interesting strings can be created using the `format` statement, which is very useful in print statements:
More interesting strings can be created using an [f-string](https://realpython.com/python-f-strings/),
which is very useful in print statements:
```
x = 1
y = 'PyTreat'
s = 'The numerical value is {} and a name is {}'.format(x, y)
s = f'The numerical value is {x} and a name is {y}'
print(s)
print('A name is {} and a number is {}'.format(y, x))
print(f'A name is {y} and a number is {x}')
```
Note the `f` before the initial quote. This lets python know to fill in the variables between the curly brackets.
There are also other options along these lines, but this is the more modern version, although you will see plenty of the other alternatives in "old" code (to python coders this means anything written before last week).
There are also other options along these lines, which will be discussed in the next practical.
This is the more modern version, although you will see plenty of the other alternatives in "old" code
(to python coders this means anything written before last week).
<a class="anchor" id="String-manipulation"></a>
### String manipulation
......@@ -150,7 +164,8 @@ where the `r` before the quote is used to force the regular expression specifica
For more information on matching and substitutions, look up the regular expression module on the web.
Two common and convenient string methods are `strip()` and `split()`. The first will remove any whitespace at the beginning and end of a string:
Two common and convenient string methods are `strip()` and `split()`. The
first will remove any whitespace at the beginning and end of a string:
```
s2 = ' A very spacy string '
......@@ -159,6 +174,7 @@ print('*' + s2.strip() + '*')
```
With `split()` we can tokenize a string (to turn it into a list of strings) like this:
```
print(s.split())
print(s2.split())
......@@ -170,11 +186,27 @@ s4 = ' This is, as you can see , a very weirdly spaced and punctuated
print(s4.split(','))
```
There are more powerful ways of dealing with this like csv files/strings, which are covered in later practicals, but even this can get you a long way.
A neat trick, if you want to change the delimiter in some structured data (e.g.
replace `,` with `\t`), is to use `split()` in combination with another string
method, `join()`:
```
csvdata = 'some,comma,separated,data'
tsvdata = '\t'.join(csvdata.split(','))
tsvdata = tsvdata.replace('comma', 'tab')
print('csvdata:', csvdata)
print('tsvdata:', tsvdata)
```
There are more powerful ways of dealing with this like csv files/strings,
which are covered in later practicals, but even this can get you a long way.
> Note that strings in python 3 are _unicode_ so can represent Chinese characters, etc, and is therefore very flexible. However, in general you can just be blissfully ignorant of this fact.
> Note that strings in python 3 are _unicode_ so can represent Chinese
> characters, etc, and is therefore very flexible. However, in general you
> can just be blissfully ignorant of this fact.
Strings can be converted to integer or floating-point values by using the `int()` and `float()` calls:
Strings can be converted to integer or floating-point values by using the
`int()` and `float()` calls:
```
sint='23'
......@@ -191,8 +223,8 @@ print(float(sint) + float(sfp))
<a class="anchor" id="Tuples-and-lists"></a>
## Tuples and lists
Both tuples and lists are builtin python types and are like vectors,
but for numerical vectors and arrays it is much better to use _numpy_
Both tuples and lists are builtin python types and are like vectors,
but for numerical vectors and arrays it is much better to use `numpy`
arrays (or matrices), which are covered in a later tutorial.
A tuple is like a list or a vector, but with less flexibility than a full list (tuples are immutable), however anything can be stored in either a list or tuple, without any consistency being required. Tuples are defined using round brackets and lists are defined using square brackets. For example:
......@@ -222,7 +254,8 @@ a += [80]
print(a)
```
> Similar things can be done for tuples, except for the last one: that is, a += (80) as a tuple is immutable so cannot be changed like this.
> Similar things can be done for tuples, except for the last one: that is,
> `a += (80)` because a tuple is immutable so cannot be changed like this.
<a class="anchor" id="Indexing"></a>
### Indexing
......@@ -242,7 +275,9 @@ print(a[0])
print(a[2])
```
Indices naturally run from 0 to N-1, _but_ negative numbers can be used to reference from the end (circular wrap-around).
Indices naturally run from 0 to N-1, _but_ negative numbers can be used to
reference from the end (circular wrap-around).
```
print(a[-1])
print(a[-6])
......@@ -294,7 +329,7 @@ print(a[1:3]) # same as a(2:3) in MATLAB
> _*Pitfall:*_
>
> Unlike in MATLAB, you cannot use a list as indices instead of an
> integer or a slice (although these can be done in _numpy_).
> integer or a slice (although this can be done in `numpy`).
```
b = [3, 4]
......@@ -372,7 +407,9 @@ dir(d)
```
> Note that google is often more helpful! At least, as long as you find pages relating to the right version of python - we use python 3 for FSL, so check that what you find is appropriate for that.
> Note that google is often more helpful! At least, as long as you find pages
> relating to Python 3 - Python 2 is no longer supported, but there is still
> lots of information about it on the internet, so be careful!
---
......@@ -430,14 +467,20 @@ for k in e:
print((k, e[k]))
```
> Note that in both cases the order is arbitrary. The `sorted` function can be used if you want keys in a sorted order; e.g. `for k in sorted(e):` ...
> In older versions of Python 3, there was no guarantee of ordering when using dictionaries.
> However, a of Python 3.7, dictionaries will remember the order in which items are inserted,
> and the `keys()`, `values()`, and `items()` methods will return elements in that order.
>
> There are also [other options](https://docs.python.org/3.5/library/collections.html#collections.OrderedDict) if you want a dictionary with ordering.
> If you want a dictionary with ordering, *and* you want your code to work with
> Python versions older than 3.7, you can use the
> [`OrderedDict`](https://docs.python.org/3/library/collections.html#collections.OrderedDict)
> class.
---
<a class="anchor" id="Copying-and-references"></a>
## Copying and references
## Copying and references
In python there are immutable types (e.g. numbers) and mutable types (e.g. lists). The main thing to know is that assignment can sometimes create separate copies and sometimes create references (as in C++). In general, the more complicated types are assigned via references. For example:
```
......@@ -517,9 +560,13 @@ print('b: ', b)
<a class="anchor" id="Boolean-operators"></a>
### Boolean operators
There is a boolean type in python that can be `True` or `False` (note the capitals). Other values can also be used for True or False (e.g., 1 for True; 0 or None or [] or {} or "") although they are not considered 'equal' in the sense that the operator `==` would consider them the same.
There is a boolean type in python that can be `True` or `False` (note the
capitals). Other values can also be used for True or False (e.g., `1` for
`True`; `0` or `None` or `[]` or `{}` or `""` for `False`) although they are
not considered 'equal' in the sense that the operator `==` would consider them
the same.
Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`
Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`.
For example:
```
......@@ -539,7 +586,9 @@ print(3 in [1, 2, 3, 4])
```
A useful keyword is `None`, which is a bit like "null". This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.
A useful keyword is `None`, which is a bit like "null".
This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.
Do not use the `is` instead of the `==` operator for any other comparisons (unless you know what you are doing).
<a class="anchor" id="If-statements"></a>
......@@ -564,7 +613,7 @@ a = [] # just one of many examples
if not a:
print('Variable is true, or at least not empty')
```
This can be useful for functions where a variety of possible input types are being dealt with.
This can be useful for functions where a variety of possible input types are being dealt with.
---
......@@ -581,7 +630,7 @@ where a list or any other sequence (e.g. tuple) can be used.
If you want a numerical range then use:
```
for x in range(2, 9):
print(x)
print(x)
```
Note that, like slicing, the maximum value is one less than the value specified. Also, `range` actually returns an object that can be iterated over but is not just a list of numbers. If you want a list of numbers then `list(range(2, 9))` will give you this.
......@@ -722,7 +771,7 @@ You will often see python functions called with these named arguments. In fact,
Let's say you are given a single string with comma separated elements
that represent filenames and ID codes: e.g., `/vols/Data/pytreat/AAC, 165873, /vols/Data/pytreat/AAG, 170285, ...`
Write some code to do the following:
Write some code to do the following:
* separate out the filenames and ID codes into separate lists (ID's
should be numerical values, not strings) - you may need several steps for this
* loop over the two and generate a _string_ that could be used to
......@@ -738,5 +787,3 @@ Write some code to do the following:
mstr = '/vols/Data/pytreat/AAC, 165873, /vols/Data/pytreat/AAG, 170285, /vols/Data/pytreat/AAH, 196792, /vols/Data/pytreat/AAK, 212577, /vols/Data/pytreat/AAQ, 385376, /vols/Data/pytreat/AB, 444600, /vols/Data/pytreat/AC6, 454578, /vols/Data/pytreat/V8, 501502, /vols/Data/pytreat/2YK, 667688, /vols/Data/pytreat/C3PO, 821971'
```
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.