Types and Classes#
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.
My thoughts on type-hints.
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 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 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!