Hide code cell content

import mmf_setup

mmf_setup.nbinit()
import logging

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

This cell adds /home/docs/checkouts/readthedocs.org/user_builds/wsu-phys-581-computation/checkouts/latest/src to your path, and contains some definitions for equations and some CSS for styling the notebook. If things look a bit strange, please try the following:

  • Choose "Trust Notebook" from the "File" menu.
  • Re-execute this cell.
  • Reload the notebook.

Types and Classes#

Hide code cell source

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) = }")
type(True) = <class 'bool'>
type(5) = <class 'int'>
type(5.0) = <class 'float'>
type(5j) = <class 'complex'>
type('abc') = <class 'str'>
type(b'abc') = <class 'bytes'>
type([1, 2]) = <class 'list'>
type((1, 2)) = <class 'tuple'>
type({'a': 1}) = <class 'dict'>
type({'a'}) = <class 'set'>
type(None) = <class 'NoneType'>
type(np.sin) = <class 'numpy.ufunc'>
type(np.arange(5)) = <class 'numpy.ndarray'>
type(type) = <class 'type'>

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 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 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.

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 abc, one has a form of nominal typing:

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()]]
(False, [True, True, False])

Python also exhibits structural typing through the use of Protocols and typing.Protocol.

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()]]
[True, True, False]

Note that unlike the previous example, A and B do not inherit from SupportsClose, but are considered structural subclasses. As noted in the docs, 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:

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!")
Duck looks like a duck and says 'quack'
Duck look like a duck!
Duck sounds like a duck!
Dog looks like a duck and says 'woof'
Dog look like a duck!
Dog does NOT sound like a duck! ('woof')
Cat does not look like a duck!