Introduction
This page is a draft
I am going to go for a Raymond Hettinger style presentation, https://www.cs.odu.edu/~tkennedy/cs330/f20/Public/languageResources/#python-programming-videos.
These materials are web-centric (i.e., do not need to be printed and are available at https://www.cs.odu.edu/~tkennedy/python-workshop).
Who am I?
I have taught various courses, including:
- CS 300T - Computers in Society
- CS 333 - Programming and Problem Solving
- CS 330 - Object Oriented Programming and Design
- CS 350 - Introduction to Software Engineering
- CS 410 - Professional Workforce Development I
- CS 411W - Professional Workforce Development II
- CS 417 - Computational Methods & Software
Most of my free time is spent writing Python 3 and Rust code, tweaking my Vim configuration, or learning a new (programming) language. My current language of interest is Rust (at the time of writing).
Referenced Courses & Materials
I will reference materials (e.g., lecture notes) and topics from from CS 330, CS 350, CS 411W, and CS 417.
- CS 330 - Object Oriented Programming & Design
- CS 350 - Introduction to Software Engineering
- CS 417 - Computational Methods & Software
I will also reference a couple examples from the previous:
The Broad Strokes
This workshop is intended as discussion on how to write Rust code that makes use of:
Tentative Topics
I will focus on:
- Profiling python, engineering best practices
- Debugging options in python ( A language that promotes rapid development is usually hard to debug. How can we do it in python? )
- Documenting code
- Advanced tutorials for modern development : Classes, Polymorphism, Interfaces, etc.
- Structuring large python codebases
- Lambda functions, usage, syntax, and expressions
- Multithreading/Concurrent python
Classes and OOP will take a while (the topic is quite vast). I will also discuss testing python code (with unit testing and integration testing) and code coverage, along with tox for basic configuration management.
I will try to fit in a few of the remaining topics:
- Correct usage of the .loc and .iloc functionality
- How to use NumPy
- Python for Machine learning, Tensorflow
- Data and workflow management
- Pandas Dataframe usage
- How do we use RDD and DataFrame.
- Implementing cryptographic algorithms
I should be able to fit in some discussion of NumPy.
Code Documentation
These notes are based on my CS 330 (Object Oriented Programming and Design) and CS 417/517 (Computation Methods) notes, https://www.cs.odu.edu/~tkennedy/cs330/f20/Public/codeDocumentation/index.html.
Most of your code has probably had quite a fewiin-line comments. Inline comments are not the focus of this discussion. The focus of thisS discussion is documentation of classes, functions, and methods.
A Few Starting Examples
I work in few different languages. Throughout my
- C++ code you will find Doxygen style comments.
- Java code you will find Javadoc style comments.
- Python code you will find Pydoc style comments.
- Rust code you will find Rustdoc style comments.
You have definitely been told to "comment your code" in the past, but (probably) in a less formal fashion.
Let us start with a few selected documentation examples from my CS 330 and CS 417 notes.
C++
Doxygen can be used for C++. Consider the following Doxygen Example:
/**
* Retrieve the value stored in three selected Cells
*
* @param cell1Id numeric id representing the 1st desired cell
* @param cell2Id numeric id representing the 2nd desired cell
* @param cell3Id numeric id representing the 3rd desired cell
*
* @return value stored in the Cell
*
* @pre (cell1Id > 0 && cell1Id < 10) &&
* (cell2Id > 0 && cell2Id < 10) &&
* (cell3Id > 0 && cell3Id < 10)
*/
CellTriple get3Cells(int cell1Id, int cell2Id, int cell3Id) const;
Java
Javadoc can be used for Java. Consider the following Javadoc Example:
/**
* Multi-thread Coin Flip.
*
* @param numTrials # flips to simulate
* @param numThreads number of threads to use
*
* @return Completed FlipTasks
*
* @throws InterruptedException if a thread is stopped prematurely
*/
public static FlipTask[] multiThread(long numTrials, int numThreads)
throws InterruptedException
Python
Pydoc or Sphinx can be used for Python. Consider the following Pydoc Example:
def parse_raw_temps(original_temps: TextIO,
step_size: int=30, units: bool=True) -> Iterator[Tuple[float, List[float]] ]:
"""
Take an input file and time-step size and parse all core temps.
:param original_temps: an input file
:param step_size: time-step in seconds
:param units: True if the input file includes units and False if the file
includes only raw readings (no units)
:yields: A tuple containing the next time step and a List containing _n_
core temps as floating point values (where _n_ is the number of
CPU cores)
"""
I prefer the Sphinx/Google style for Python.
def parse_raw_temps(original_temps: TextIO,
step_size: int=30, units: bool=True) -> Iterator[Tuple[float, List[float]] ]:
"""
Take an input file and time-step size and parse all core temps.
Args:
original_temps: an input file
step_size: time-step in seconds
units: True if the input file includes units and False if the file
includes only raw readings (no units)
Yields:
A tuple containing the next time step and a List containing _n_
core temps as floating point values (where _n_ is the number of
CPU cores)
"""
Rust
///
/// Take a room and change the flooring
///
/// # Arguments
///
/// * `original` - House to change
///
/// # Returns
///
/// House with the updated flooring
///
fn upgrade_flooring(original: &House) -> House {
//...
}
Rust and Python have similar documentation styles (give or take some markdown
formatting). Since we only cover small snippets of Rust in this course (for
context), we will forgo a complete
Rustdoc discussion.
Writing Good Documentation
All code should be properly and fully documented using a language appropriate comment style. All functions (including parameters and return types) must be documented.
Documentation for a New Function
Suppose we have just finished writing a quick program to simulate a trick coin (i.e., a coin where heads and tails are not equally probable).
def one_flip(p):
return True if random.random() < p else False
def main():
num_flips = 8;
for _i in range(0, num_flips):
if one_flip(0.7):
print("Heads")
else:
print("Tails")
if __name__ == "__main__":
main()
The one_flip
function needs a description.
def one_flip(p):
"""
Simulate a single coin flip.
"""
What does p
represent? Does it represent the probability of heads or tails?
def one_flip(p):
"""
Simulate a single coin flip.
Args:
p: probability of heads in the range [0, 1]
"""
Now what about the return? We know that bool
means a true
or false
. Which
one do I get for heads? Let us add an @return
.
/**
* Simulate a single coin flip.
*
* @param p probability of heads
*
* @return true if the result is heads and false if the result is tails
*/
bool one_flip(double p);
def one_flip(p):
"""
Simulate a single coin flip.
Args:
p: probability of heads in the range [0, 1]
Returns:
True if the result is heads and False if the result is tails
"""
There is no more ambiguity or guesswork. Both p
and the possible return
values are documented.
Type Hints
I am a stickler for type hints...
def one_flip(p: float) -> bool:
"""
Simulate a single coin flip.
Args:
p: probability of heads in the range [0, 1]
Returns:
True if the result is heads and False if the result is tails
"""
Object Oriented
We need to discuss the rules of a class checklist.
C++ | Java | Python 3 | Rust |
---|---|---|---|
Default Constructor | Default Constructor | __init__ |
new() or Default trait |
Copy Constructor | Clone and/or Copy Constructor | __deepcopy__ |
Clone trait |
Destructor | |||
finalize (deprecated/discouraged) | __del__ |
Drop trait |
|
Assignment Operator (=) | |||
Accessors (Getters) | Accessors (Getters) | Accessors (@property ) |
Accessors (Getters) |
Mutators (Setters) | Mutators (Setters) | Setter (@attribute.setter ) |
Mutators (setters) |
Swap | |||
Logical Equivalence Operator (==) | equals | __eq__ |
std::cmp::PartialEq trait |
Less-Than / Comes-Before Operator (<) | hashCode | __hash__ |
std::cmp::PartialOrd trait |
std::hash (actual hashing)
|
hashCode | __hash__ |
std::hash::Hash trait |
Stream Insertion Operator (<<) | toString | __str__ |
std::fmt::Display trait |
__repr__ |
std::fmt::Debug trait |
||
begin() and end()
|
iterator |
__iter__ |
iter() and iter_mut()
|
Whenever Python code is written, the first function most people write is usually
__init__
... since it serves as a constructor to initialize the fields (data
members) of each new object. For now... let us focus on three methods:
-
__str__
- generates a human readable string for output. -
__repr--
- generates a complete string for debugging, often in the form of a string that fully describes an object. -
__eq__
- compares two objects, returningTrue
if they are equal. The objects need not be of the same type.
Tic-Tac-Toe Example
The code snippets in this section are part of a larger Tic-Tac-Toe example. The full source code can be found in this workshop's Git repository.
Let us start with the Player class. Note that the code is fully documented with pydoc documentation and type hints.
Note:
-
The use of
class Player(object):
is a holdover from Python 2.* In modern Python 3, it should not be used. The(object)
should be omitted. The line should beclass Player:
. -
The
Player
class as written violates the MVC (Model-View-Controller) design pattern and S.O.L.I.D. ThePlayer
class should ony handled representing a single player. All user interaction should be handled outside the class.
class Player(object):
"""
This is more a Player interface than a Player class.
<p>
However, such distinctions and discussions belong in
the OOP and Inheritance Modules
"""
PROMPT_MSG = "Enter your desired move (1-9): "
"""
Message used to prompt a human player for a move.
"""
@staticmethod
def is_generic(possible_cylon: "Player") -> bool:
"""
Checks whether a player is a placeholder or
an actual player.
Args:
possible_cylon (Player): player whose humanity is in question
Returns:
True if the player is a Cylon
"""
# print(REFERENCE_CYLON)
return possible_cylon == REFERENCE_CYLON
def __init__(self, n: str = "I. C. Generic"):
"""
Create a Player with a selected name.
Args:
n: desired name
"""
self._name = n
self._symbol = '?' # testing caught this
def get_name(self) -> str:
"""
Retrieve name.
Returns:
player name
"""
return self._name
def set_name(self, n: str):
"""
Set player name.
@param n new name
@pre (n.size() > 0)
"""
self._name = n
def next_move(self) -> str:
"""
Retrieve the next move.
@return board cell id representing the selected move
@throws IOException if the move can not be retreived from the player.
"""
choice = int(input(self._name + ", " + Player.PROMPT_MSG))
return choice
def is_human(self) -> bool:
"""
Is this a Human Player?
In this discussion, always yes :(
Returns:
True if the player is a human
"""
return True
def is_computer(self):
"""
Is this a Computer Player?
In this discussion, always no :(
Returns:
True if the player is a Cylon
"""
return False
def get_symbol(self) -> str:
"""
Retrieve player symbol to be used
for marking moves.
Returns:
current player symbol
"""
return self._symbol
def set_symbol(self, new_symbol: str):
"""
Change the player symbol.
Args:
new_symbol: new character to be used by the player
"""
self._symbol = new_symbol
def __eq__(self, rhs):
if not isinstance(rhs, self.__class__):
return False
return self._name == rhs._name
def __hash__(self):
return hash(self._name)
def __str__(self):
"""
Generate a player string, but only the name.
"""
return self._name
def __deepcopy__(self, memo):
"""
Create a new duplicate Player.
"""
cpy = Player(self._name)
cpy.set_symbol(self._symbol)
return cpy
REFERENCE_CYLON = Player()
"""
A Player that serves as a sentinal value or placeholder.
"""