Key Word(s): Privacy, Name mangling, Modules, vars



Exercise 2: "Private" Methods and Names in Python

Background

We will consider two extensions to the real numbers: complex numbers and dual numbers.

Complex Numbers

A complex number is defined as $$z = a + ib$$ where $i^{2} = -1$. $a$ is the real part and $b$ is the imaginary part. The polar form of a complex number is $$z = \left|z\right|e^{i\theta}$$ where $\left|z\right|^{2} = zz^{*} = a^{2} + b^{2}$ and $z^{*} = a - ib$ is the complex conjugate of $z$. The angle between $a$ and $b$ is given by $$\theta = \tan^{-1}\left(\frac{b}{a}\right).$$

Dual Numbers

The dual numbers look similar. We have $$d = a + \epsilon b$$ where $\epsilon$ is a number (not zero!) such that $\epsilon^{2} = 0$. Once again $a$ is the real part, but here $b$ is the dual part. The polar form of this number is $$d = \displaystyle a\left(1 + \epsilon \frac{b}{a}\right).$$ Note that the magnitude of the dual number $\left|d\right| = a$ since $dd^{*} = \left(a + \epsilon b\right)\left(a - \epsilon b\right) = a$ where $d^{*} = a - \epsilon b$ is the conjugate of $d$. Finally, the angular part is $$m = \dfrac{b}{a}.$$

Dual numbers are a route to automatic differentiation. We'll mention them again in the AD lectures.

Problem Description

Part 1

For today, your task is to write a module called mynumbers.py. The module should contain at a minimum the following:

  • A base class called RealExtensions with a constructor that accepts $a$ and $b$.
  • A subclass called Complex (inherits from RealExtensions) that has the following methods:
    • Compute the magnitude of the complex number
      • Note: This method should be preceeded by a single underscore to indicate that it should not be accessed by a user.
    • Compute the "angle" of the complex number
      • Note: This method should be preceeded by a single underscore to indicate that it should not be accessed by a user.
    • Compute the polar form of the complex number using _magnitude() and _angle()
  • A subclass called Dual (inherits from RealExtensions) that has methods for compute the magnitude and "angle" of the dual number.
    • Compute the magnitude of the dual number
      • Note: This method should be preceeded by a single underscore to indicate that it should not be accessed by a user.
    • Compute the "angle" of the dual number
      • Note: This method should be preceeded by a single underscore to indicate that it should not be accessed by a user.
    • Compute the polar form of the dual number using _magnitude() and _angle()

The choice of "hiding" the magnitude() and angle() methods may not be a good one. The goal is to show that this is not really privacy; it's more of a contract between people using the code. Note that a user can still access _magnitude() and _angle().

Import your module using import mynumbers and play around with creating complex and dual numbers. Demo your code in the cell below:

In [1]:
import mynumbers as myn

zc = myn.Complex(2.0,1.0) # complex number
print(zc.real, zc.imag)

zd = myn.Dual(2.0,2.0)
print(zd.real, zd.dual)

zc.polar_form()
print(zc.r, zc.theta)

zd.polar_form()
print(zd.r, zd.theta)
2.0 1.0
2.0 2.0
2.23606797749979 0.46364760900080615
2.0 1.0

Part 2

Once you're happy with your module, make the following change:

  • Rename the Complex subclass as _Complex. Save the new module as mynumbers_p.
  • Now import your module using from mynumbers_p import *
  • Try to create a complex number. What happens?
    • Note: You can use dir() to see what's in your namespace.

Demo your code in the cell below.

In [2]:
from mynumbers_p import *

zc = Complex(2.0, 1.0)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
 in 
      1 from mynumbers_p import *
      2 
----> 3 zc = Complex(2.0, 1.0)

NameError: name 'Complex' is not defined

Part 3: Extra

For some reason you decide to store the components of your number in an immutable data structure. You do this in the base class via a tuple. You also decide to provide a method called number in a subclass which stores the number as a list (mutable). If the user calls the number method, then you automatically store the number in an immutable data structure as specified in the base class constructor. You use super() to call the base class constructor. Meanwhile, you store the mutable list of numbers as an attribute in the subclass. To avoid possible namespace collisions, you elect to mangle the names.

Save your module as mynumbers_m.py.

Your module should be importatable and demoed as follows:

In [4]:
import mynumbers_m as myn_m 

z = myn_m.Complex(1,1)
z.number()
print(z._RealExtensions__number, z._Complex__number) 
(1, 1) [1, 1]

Deliverables

  • mynumbers.py
  • mynumbers_p.py
  • mynumbers_m.py --- Optional
  • A demo notebook saved as lecture8-demo.ipynb
    • The notebook can look exactly like this one (problem statements and all).