---
execution:
  timeout: 300
jupytext:
  notebook_metadata_filter: all
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.13.0
kernelspec:
  display_name: Python 3 (phys-581)
  language: python
  name: phys-581
---

```{code-cell} ipython3
:tags: [hide-cell]

import mmf_setup

mmf_setup.nbinit()
import logging

logging.getLogger("matplotlib").setLevel(logging.CRITICAL)
%matplotlib inline
import numpy as np, matplotlib.pyplot as plt
```

(sec:TypesAndClasses)=
# Types and Classes

```{code-cell}
:tags: [hide-input, margin]
print(f"{type(True) = }")
print(f"{type(5) = }")
print(f"{type(5.0) = }")
print(f"{type(5j) = }")
print(f"{type('abc') = }")
print(f"{type(b'abc') = }")
print(f"{type([1, 2]) = }")
print(f"{type((1, 2)) = }")
print(f"{type({'a': 1}) = }")
print(f"{type({'a'}) = }")
print(f"{type(None) = }")
print(f"{type(np.sin) = }")
print(f"{type(np.arange(5)) = }")
print(f"{type(type) = }")
```
:::{margin}
Some types in Python.  For details see [Python data model][].
:::
Programming languages use [type systems][] to organize data.  This allows you to ask
what type of "data" (or **object**) is referenced by a particular variable.  In Python,
you can ask this in the interpreter with the {func}`type` function.  Here are some
examples: In Python, **everything** has a type and that the type is a **class**.  From
this perspective:
* Classes are the mechanism for defining new types.

If you have not though much about types, I highly recommend you peruse the Wikipedia
article about [type systems][], and especially look at the **Major categories**:

## Static vs. Dynamic Typing

**Static** typing means that types are specified in the source code, and tools like a
compiler or type checker can check the type-correctness of the code without executing
it.  In contrast, **dynamic** typing means that the types cannot be determined until the
code is actually execute -- i.e., the type of an object might depend on the execution
path of the code which cannot be determined from the source code *(e.g. because it might
depend on user input).*

Python is dynamically typed, but support for type-hints through {mod}`typing` have been
gaining traction.  This adds some of the benefits of static typing through external
tools.

Compiled languages like C, C++, Haskell, etc. are almost exclusively strongly statically
typed. If you need to make robust code, this is highly advisable as the compiler can
catch many potential security issues through the type system.

:::::{admonition} My thoughts on type-hints.
:class: dropdown
I generally don't use type hints in my code: I generally find that type hints make code
harder to read, and take time to use, so, since they do not actually improve execution
in any way, I generally eschew their use in my code.  This might change if there were
potential performance gains through typing.  This **is** the case with {mod}`numba` for
example.

Arguing for type-hints: they allow you to perform addition static checks of your code
using tools like [mypy][].  This can catch many common bugs before you execute your
code, so this is a pretty strong argument for using type-hints.  Another argument in
favour is that some popular editors like [VS Code][] can apparently perform
significantly better at suggestion completions and finding code when type hints are
available.

For a discussion, see this thread: [Please, stop pushing for static
typing in Python][].

:::{important}

**Do not take my aversion to type-hints as a recommendation.**

I learned to program with strongly typed languages, and have a lot of experience, so
I probably naturally don't do things that are dangerous.  If you do not have such
experience, then it might be advantageous for you to start using type-hints and
static checkers until you develop good practices. 
:::
:::::

## Manifest vs. Inferred

Early incarnations of languages like C, C++, and Fortran required the type of all
objects to be **manifestly** declared in the code.  The robust type-system in the
[Haskell][] language allowed one to write code in a strongly-typed language where
declaring the types was mostly optional: In an expression like `add (x, y) = x+y`, the
type of the function `add :: Number -> Number -> Number can be **inferred** from the
type system as the most general type that supports addition.

Inferred typing has since been adopted in languages like C and C++ through the addition
of the `auto` keyword.

## Nominal vs. Structural vs. Duck typing

Languages with **nominal** static typing and subtyping need types of be explicitly
defined such that they can be compared based on the type definitions, and importantly
**the names** of the types.  Two types with the same structure, but different names, are
consider inequivalent.  This is the norm in C++ for example.

In contrast, languages with **structural** static typing allow types to be compared
based on their actual structure, independent of their names.  C++ templates provide an
example of structural typing on their type arguments.

**Duck typing** is similar to structural typing, but for dynamic type systems where the
type of an object is determined by its behaviour at run time.  If it "looks like a duck,
and quacks like a duck", then it is considered a duck.

Python exhibits all three types of typing.  Through the use of classes, and {mod}`abc`,
one has a form of nominal typing:

```{code-cell}
class Base:
    pass
    
class A(Base):
    pass

class B(Base):
    pass
    
class C:
    pass

# A != B even though they have the same structure because
# they have different names.  They have the same subclass.
A == B, [isinstance(x, Base) for x in [A(), B(), C()]]
```

Python also exhibits structural typing through the use of [Protocols][] and
{class}`typing.Protocol`.

```{code-cell}
from typing import Protocol, runtime_checkable

@runtime_checkable
class SupportsClose(Protocol):
    def close(self):
        pass
        
class A:
    def close(self):
        print("Closing A")

class B:
    def close(self):
        print("Closing B")

class C:
    pass
    
[isinstance(x, SupportsClose) for x in [A(), B(), C()]]
```
Note that unlike the previous example, `A` and `B` do not inherit from `SupportsClose`,
but are considered structural subclasses.  As noted in [the docs][Protocols], such
runtime checking is not 100% reliable.  Static typing -- like type-hints in general --
are really intended for static analysis.

Finally, Python supports duck typing and the associated coding paradigm.  Instead of
checking that something is an instance of some class before doing this, we just try to
use it.  Thus, you will commonly see code like this:

:::{margin}
![Dog camouflaged among ducks with a badminton bird on its
  nose.](../_static/DuckDog.jpg)
:::
```{code-cell}

class Duck:
    def quack(self):
        return "quack"

class Dog:
    def quack(self):
        return "woof"

class Cat:
    pass
    
a = Duck()
b = Dog()
c = Cat()

for x in [a, b, c]:
    name = type(x).__name__
    if hasattr(x, 'quack'):
        print(f"{name} looks like a duck and says '{x.quack()}'")

    # Using exceptions
    try:
        sound = x.quack()
        print(f"{name} look like a duck!")
        if sound == "quack":
            print(f"{name} sounds like a duck!")
        else:
            print(f"{name} does NOT sound like a duck! ('{sound}')")
    except Exception:
        print(f"{name} does not look like a duck!")
```





[Protocols]: <https://typing.python.org/en/latest/spec/protocol.html>




[Please, stop pushing for static typing in Python]: <https://news.ycombinator.com/item?id=30169077>
[type systems]: <https://en.wikipedia.org/wiki/Type_system>
[mypy]: <https://www.mypy-lang.org/>
[VS Code]: <https://code.visualstudio.com/>
[Haskell]: <https://www.haskell.org/>


[PP19: Version Control]: <https://research.ebsco.com/plink/27d11cab-6c6d-30a9-96a5-1c641b0fb342>
[PP20: Debugging]: <https://research.ebsco.com/plink/c77495b4-50ac-39c9-9b8e-7a869c79b401a>
[PP23: Design by Contract]: <https://research.ebsco.com/plink/edd2ed7b-3a73-3cc7-b98d-4aa1b8fdea4d>
[PP25: Assertive Programming]: <https://research.ebsco.com/plink/528d9b1d-b898-3f25-8a12-40d53542a609>
[PP28: Decoupling]: <https://research.ebsco.com/plink/a7c94c21-2be0-3932-a392-ee63296f8afc>
[PP31: Inheritance Tax]: <https://research.ebsco.com/plink/4cdd8d35-6e25-3851-ad1d-e04e3bc8a7c8>
[PP41: Test to Code]: <https://research.ebsco.com/plink/6fb62a24-cc5f-31c7-9888-023f450bc87f>
[Python Data Model]: <https://docs.python.org/3/reference/datamodel.html>

