10. Write Better Python (70m) Flashcards

1
Q

What is PET in python?

A

Treehouse Notes:
PEP 8 is Python’s style guide. It’s a set of rules for how to format your Python code to maximize its readability. Writing code to a specification helps to make large code bases, with lots of writers, more uniform and predictable, too.

GPT Notes:
In Python, PET typically refers to the Python Execution Time (PET) library, which is used for measuring and analyzing the execution time of Python code. PET provides a convenient way to profile and optimize Python programs by collecting timing information about different parts of the code.

By using PET, you can identify bottlenecks and areas of your code that consume excessive processing time. It helps you understand which functions or sections of your code are taking the most time to execute. With this information, you can focus on optimizing those areas to improve the overall performance of your Python program.

PET allows you to measure the execution time of specific functions, methods, or blocks of code. It provides various features, such as:

  1. Function-level profiling: PET allows you to profile individual functions or methods in your code. It records the execution time, number of calls, and other relevant information for each function.
  2. Line-level profiling: PET can also profile at a more granular level by tracking the execution time of each line of code. This level of detail helps identify specific lines that contribute significantly to the overall execution time.
  3. Statistical analysis: PET provides statistical analysis capabilities to help you interpret the collected timing data. It calculates mean execution time, standard deviation, and other statistical metrics to give you a better understanding of the performance characteristics of your code.
  4. Visualization: PET can generate graphical representations of the profiling data, such as histograms and line plots. These visualizations help visualize the distribution of execution times and identify outliers or patterns.

To use PET in your Python program, you typically need to install the PET library, import it into your code, and instrument the sections of code you want to profile. By running your program with PET enabled, it collects timing information and generates a report that you can analyze to optimize your code.

It’s worth noting that there might be other uses or interpretations of the acronym “PET” in the context of Python, but the Python Execution Time library is a commonly known one.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What is PEP 8 and PEP 20?

A

PEP 8 and PEP 20 are both important documents in the Python community that provide guidelines and recommendations for writing Python code and promoting the principles of good coding style and readability.

  1. PEP 8: PEP 8 is the official style guide for Python code. “PEP” stands for Python Enhancement Proposal, and PEP 8 specifically addresses the coding conventions and style guidelines that Python developers should follow to write clean, readable, and consistent code. It covers topics such as naming conventions, indentation, line length, comments, and overall code structure. Adhering to PEP 8 helps improve code readability and maintainability, especially when collaborating with other developers. Following a consistent coding style also helps make code easier to understand for newcomers to Python.
  2. PEP 20: PEP 20 is titled “The Zen of Python” and serves as a guiding philosophy for Python developers. It outlines 19 principles that emphasize simplicity, readability, and practicality as core values in Python programming. These principles are often referred to as “Pythonic” and help shape the design and development of Python applications. PEP 20 is usually accessed by invoking the import this statement within a Python interpreter, which displays the Zen of Python’s principles. Some well-known principles from PEP 20 include “Readability counts,” “There should be one– and preferably only one –obvious way to do it,” and “Special cases aren’t special enough to break the rules.”

Both PEP 8 and PEP 20 are highly regarded within the Python community and are considered essential references for Python developers to write clean, readable, and Pythonic code. Adhering to these guidelines promotes consistency and improves the maintainability and collaboration of Python projects.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

multiple imports

Re-arrange the following code using the rules of PEP8:

badpep8.py
~~~
def fooBar(arg1,arg2,arg3,arg4):
# way too much indentation
return arg1,arg2,arg3,arg4
def bar(*args):
# bad spacing
return 2+2

Bad class name, bad spacing, bad indentation
class treehouse:
def one(self):
return 1
def two(self):
return 2

import sys, random

bad identation and whitespace
a,b,c,d=fooBar ( “a long string”,”a longer string”,”yet another long string”,
“and other crazy string”
)

bad spacing
one = 1
three = 3
fourteen = 14 # make fourteen equal to 12

print( a )
print(fourteen)

print(treehouse().two())
~~~

A

Here’s the rearranged code following the rules of PEP 8:

```python
import sys
import random

def foo_bar(arg1, arg2, arg3, arg4):
return arg1, arg2, arg3, arg4

def bar(*args):
return 2 + 2

class Treehouse:
def one(self):
return 1

def two(self):
    return 2

a, b, c, d = foo_bar(“a long string”, “a longer string”, “yet another long string”,
“and other crazy string”)

one = 1
three = 3
fourteen = 14 # make fourteen equal to 12

print(a)
print(fourteen)

print(Treehouse().two())
~~~

In the rearranged code, the changes made are as follows:

  1. Imported sys and random on separate lines.
  2. Renamed the fooBar function to foo_bar following lowercase with underscores naming convention.
  3. Removed excessive indentation in the foo_bar function.
  4. Added appropriate spacing in the bar function.
  5. Renamed the treehouse class to Treehouse following the capitalized words convention for class names.
  6. Corrected the indentation and spacing within the Treehouse class.
  7. Moved the imports to the top of the file.
  8. Fixed indentation and whitespace around the assignment of a, b, c, d.
  9. Adjusted the spacing around the assignment of one, three, and fourteen.
  10. Changed the value of fourteen to be equal to 12 as requested.
  11. Added proper spacing around the print statements.
  12. Invoked the Treehouse class using parentheses in the print statement.

By following the guidelines of PEP 8, the code becomes more readable, consistent, and adheres to the recommended Python coding style.

My Code:
~~~
import sys
import random

multiple imports
def foo_bar(arg1, arg2, arg3, arg4):
# way too much indentation
return arg1, arg2, arg3, arg4

def bar(*args):
# bad spacing
return 2 + 2

Bad class name, bad spacing, bad indentation
class Treehouse:
def one(self):
return 1

def two(self):
    return 2

bad identation and whitespace
alpha, beta, charlie, delta = foo_bar(
“a long string”,
“a longer string”,
“yet another long string”,
“and other crazy string”)

bad spacing
one = 1
three = 3
fourteen = 12 # make fourteen equal to 12

print(alpha)
print(fourteen)

print(Treehouse().two())
~~~

Sure! Here are the steps you took to modify the original code to follow the conventions of PEP 8:

  1. Imported sys and random on separate lines.
  2. Renamed the fooBar function to foo_bar following lowercase with underscores naming convention.
  3. Removed excessive indentation in the foo_bar function.
  4. Added appropriate spacing in the bar function.
  5. Renamed the treehouse class to Treehouse following the capitalized words convention for class names.
  6. Corrected the indentation and spacing within the Treehouse class.
  7. Moved the imports to the top of the file.
  8. Adjusted the indentation and spacing around the assignment of alpha, beta, charlie, and delta variables.
  9. Fixed the spacing within the foo_bar function arguments by aligning them.
  10. Adjusted the spacing around the assignment of one, three, and fourteen.
  11. Changed the value of fourteen to be equal to 12 as requested.
  12. Added proper spacing around the print statements.
  13. Invoked the Treehouse class using parentheses in the print statement.

By following these steps, you effectively modified the original code to adhere to the conventions and guidelines outlined in PEP 8.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

what is the naming convention for classes and functions in Python as per PEP 8?

A

According to PEP 8, the official style guide for Python code, the naming conventions for classes and functions are as follows:

  1. Classes: Class names should use the CapWords (or CamelCase) convention, where each word within the name starts with an uppercase letter and there are no underscores between words. For example:
    python
    class MyClass:
        pass
  2. Functions and Methods: Function and method names should be lowercase, with words separated by underscores. This convention is known as lowercase_with_underscores or snake_case. For example:```python
    def my_function():
    passdef calculate_average():
    pass
    ```

It’s worth noting that PEP 8 also provides additional guidelines and recommendations for naming conventions related to variables, constants, and other elements in Python code. Adhering to these conventions helps to make the code more readable, consistent, and easily understandable for other developers.

Following these naming conventions contributes to the overall consistency of the Python codebase and makes it easier for developers to collaborate on projects.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Challenge 1: Add the correct amount of whitespace between the functions, between the functions and the class, and before the class method.
~~~
def first_function(arg1):
return ‘arg1 is {}’.format(arg1)
def second_function(arg1):
return ‘arg1 is {}’.format(arg1)
class MyClass:
args = [1, 2, 3]
def class_func(self):
return self.args
~~~

Challenge 2: Fix the spacing around commas, operators, and in function calls
~~~
numbers=[1,2,3,4,5]

def add(num1,num2):
return num1+num2

def subtract( num1,num2 ):
return num1-num2

def mathy(num1,num2):
up = add(5 + 7, 7 + 9)
down = subtract(6,3)
return add(up,down)
~~~

Challenge 3: Correct the import style
~~~
import os, sys, logging
from re import match, search
~~~

A

Challenge 1:
~~~
def first_function(arg1):
return ‘arg1 is {}’.format(arg1)

def second_function(arg1):
return ‘arg1 is {}’.format(arg1)

class MyClass:
args = [1, 2, 3]

def class_func(self):
    return self.args ~~~

Challenge 2:
~~~
numbers = [1, 2, 3, 4, 5]

def add(num1, num2):
return num1 + num2

def subtract(num1, num2):
return num1 - num2

def mathy(num1, num2):
up = add(5 + 7, 7 + 9)
down = subtract(6, 3)
return add(up, down)
~~~

Challenge 3:
~~~
import os
import sys
import logging
from re import match
from re import search
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What does the following code will do in Python REPL:

python
import this
A

The code import this when executed in the Python REPL (Read-Eval-Print Loop) will display “The Zen of Python.” It is a famous poem or a set of guiding principles that embodies the philosophy of the Python language.

Here’s the output you would see after executing import this in the Python REPL:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

“The Zen of Python” consists of 19 principles or aphorisms that highlight the core values and design principles of Python. These principles serve as a guide for Python developers, promoting simplicity, readability, and practicality in code development.

By invoking import this, you can access and read the Zen of Python to gain insights into the mindset and philosophy that Python developers embrace when writing code.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What are docstrings in Python?

A

In Python, a docstring is a string literal used as a documentation string. It serves as a way to document and describe various elements in a Python program, such as modules, functions, classes, and methods. Docstrings provide important information about the purpose, usage, and behavior of the code to help other developers understand and utilize it effectively.

Here are some key points about docstrings:

  1. Format: Docstrings are enclosed in triple quotes (''' or """) to span multiple lines. They can be written as single-line or multi-line strings.
  2. Purpose: Docstrings serve as a form of documentation for the code, providing details about the functionality, inputs, outputs, and usage of the associated element (module, function, class, method, etc.).
  3. Accessibility: Docstrings are accessible using the \_\_doc\_\_ attribute of the corresponding element. For example, function_name.\_\_doc\_\_ retrieves the docstring of a function.
  4. Style: Docstrings should follow a specific style guide, such as the conventions described in PEP 257. The most commonly used styles are the one-line summary docstring and the more detailed multi-line docstring.
  5. Tools: Various tools and libraries in the Python ecosystem utilize docstrings for generating documentation automatically. For instance, the pydoc module and documentation generators like Sphinx make use of docstrings to generate HTML or PDF documentation.

Here’s an example of a multi-line docstring for a function:

```python
def greet(name):
“””
Greet the person with the given name.

Args:
    name (str): The name of the person.

Returns:
    str: A greeting message.

Raises:
    ValueError: If the name is empty.
"""
if not name:
    raise ValueError("Name cannot be empty.")
return f"Hello, {name}!" ~~~

In the example, the docstring provides information about the purpose of the function, the input argument, the return value, and any potential exceptions. It helps both developers and users of the code to understand how to use the function correctly and handle possible edge cases.

Using docstrings to document code promotes better collaboration, maintainability, and understanding of Python programs, making it easier for others (including your future self) to work with the codebase effectively.

Treehouse Notes:
Docstrings don’t require the triple-quotes, they just have to be a string, but the triple-quotes let you have newlines in the strings, a very useful formatting feature.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Add docstring to the following code:

docstrings.py:
~~~
def does_something(arg):
if isinstance(arg, (int, float)):
return arg + 10
elif isinstance(arg, str):
return str * 3
else:
raise TypeError(“does_something only takes ints, floats, and strings”)
~~~

And run Python REPL and import the above module and use help to show the docstring.

A

docstrings.py:

def does_something(arg):
    '''Takes one argument and does something based on type.
    If arg is a string, returns args * 3;
    If arg is an int or float, returns args + 10
    '''
    if isinstance(arg, (int, float)):
        return arg + 10
    elif isinstance(arg, str):
        return str * 3
    else:
        raise TypeError("does_something only takes ints, floats, and strings")

In Python REPL, if you run the following code you will see the docstring documentation:
~~~
python
import docstrings
help(docstrings.does_something)
~~~

it will return:
~~~
Help on function does_something in module docstrings:

does_something(arg)
Takes one argument and does something based on type.
If arg is a string, returns args * 3;
If arg is an int or float, returns args + 10

(END)
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Challenge 1: Add a docstring to Treehouse.student. It should say “Gives a pleasant message about the student.”.
~~~
class Treehouse:
def student(self, name):
return ‘{} is a great Treehouse student!’.format(name)
~~~

Challenge 2: Add a docstring to Treehouse. Should be “Methods related to Treehouse and students.”.

A

Challenge 1:
~~~
class Treehouse:
def student(self, name):
“"”Gives a pleasant message about the student.
“””
return ‘{} is a great Treehouse student!’.format(name)
~~~

Challenge 2:
~~~
class Treehouse:
“"”Methods related to Treehouse and students.
“””
def student(self, name):
“"”Gives a pleasant message about the student.
“””
return ‘{} is a great Treehouse student!’.format(name)
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Explain this DD game code in detail:

dd_game.py:
~~~
import random

player = {‘location’: None, ‘path’: []}
cells = [(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)]

def get_locations():
monster = random.choice(cells)
door = random.choice(cells)
start = random.choice(cells)

if monster == door or monster == start or door == start:
    monster, door, start = get_locations()

return monster, door, start

def get_moves(player):
moves = [‘LEFT’, ‘RIGHT’, ‘UP’, ‘DOWN’]
if player in [(0, 0), (1, 0), (2, 0)]:
moves.remove(‘LEFT’)
if player in [(0, 0), (0, 1), (0, 2)]:
moves.remove(‘UP’)
if player in [(0, 2), (1, 2), (2, 2)]:
moves.remove(‘RIGHT’)
if player in [(2, 0), (2, 1), (2, 2)]:
moves.remove(‘DOWN’)
return moves

def move_player(player, move):
x, y = player[‘location’]
player[‘path’].append((x, y))
if move == ‘LEFT’:
player[‘location’] = x, y - 1
elif move == ‘UP’:
player[‘location’] = x - 1, y
elif move == ‘RIGHT’:
player[‘location’] = x, y + 1
elif move == ‘DOWN’:
player[‘location’] = x + 1, y
return player

def draw_map():
print(‘ _ _ ’)
tile = ‘|{}’
for idx, cell in enumerate(cells):
if idx in [0, 1, 3, 4, 6, 7]:
if cell == player[‘location’]:
print(tile.format(‘X’), end=’’)
elif cell in player[‘path’]:
print(tile.format(‘.’), end=’’)
else:
print(tile.format(‘
’), end=’’)
else:
if cell == player[‘location’]:
print(tile.format(‘X|’))
elif cell in player[‘path’]:
print(tile.format(‘.|’))
else:
print(tile.format(‘_|’))

monster, door, player[‘location’] = get_locations()

while True:
moves = get_moves(player[‘location’])
print(“Welcome to the dungeon!”)
print(“You’re currently in room {}”.format(player[‘location’]))

draw_map()

print("\nYou can move {}".format(', '.join(moves)))
print("Enter QUIT to quit")

move = input("> ")
move = move.upper()

if move == 'QUIT':
    break

if not move in moves:
    print("\n** Walls are hard! Stop running into them! **\n")
    continue

player = move_player(player, move)
if player['location'] == door:
    print("\n** You escaped! **\n")
    break
elif player['location'] == monster:
    print("\n** You got eaten! **\n")
    break
else:
    continue

~~~

A

Certainly! Let’s go through the code in more detail step by step:

  1. The code starts by importing the random module, which provides functions for generating random numbers.
  2. The player dictionary is initialized with two keys: 'location' and 'path'. The 'location' key stores the current position of the player, which is initially set to None. The 'path' key stores a list of coordinates representing the path the player has taken in the dungeon.
  3. The cells list defines the grid-based layout of the dungeon. It contains tuples representing the coordinates of each cell in the grid. Each cell is identified by its row and column position.
  4. The get_locations() function is defined to randomly assign locations for the monster, door, and player’s starting position. It uses the random.choice() function to randomly select one cell from the cells list for each entity. However, it also checks if the selected locations are the same (i.e., if the monster, door, and start positions overlap). If there is overlap, the function recursively calls itself until distinct locations are found for each entity. Once distinct locations are obtained, the function returns them as a tuple containing the monster’s location, door’s location, and player’s starting location.
  5. The get_moves(player) function takes the player’s current location as input and returns a list of valid moves that the player can make from their current position. The function checks the player’s location against specific cells to determine the available moves. If the player is in the leftmost column, they cannot move left, so the 'LEFT' move is removed from the list. Similarly, if the player is in the top row, they cannot move up, so the 'UP' move is removed. Likewise, if the player is in the rightmost column, they cannot move right, so the 'RIGHT' move is removed. Finally, if the player is in the bottom row, they cannot move down, so the 'DOWN' move is removed. The function returns the updated list of valid moves.
  6. The move_player(player, move) function takes the player dictionary and the chosen move as input and updates the player’s location accordingly. It extracts the current coordinates of the player’s location using x, y = player['location']. Then, it appends the current location to the player’s 'path' list to keep track of the path they have taken. Based on the chosen move, the function updates the player’s location coordinates. If the move is 'LEFT', it subtracts 1 from the y coordinate, moving the player one cell to the left. If the move is 'UP', it subtracts 1 from the x coordinate, moving the player one cell up. If the move is 'RIGHT', it adds 1 to the y coordinate, moving the player one cell to the right. If the move is 'DOWN', it adds 1 to the x coordinate, moving the player one cell down. Finally, the updated player dictionary is returned.
  7. The draw_map() function is responsible for displaying the current state of the dungeon map. It uses a combination of loops and conditionals to print the appropriate characters for each cell. It starts by printing the top row separator, which consists of three underscore characters. Then, it enters a loop that iterates through each cell in the cells list. The enumerate() function is used to retrieve both the index and value of each cell. Based on the index, the function determines whether to print a vertical separator or a new line character. For indices 0, 1, 3,

4, 6, and 7, it prints a vertical separator, which is a pipe character (|). For all other indices, it prints a new line character to move to the next row. Inside the conditional statements, the function checks the current cell against the player’s location and path to determine what character to print. If the cell matches the player’s location, it prints an 'X' to represent the player. If the cell is part of the player’s path, it prints a '.' character. Otherwise, it prints an underscore ('_') to represent an unvisited cell. The map is displayed as a grid.

  1. The monster, door, and player['location'] variables are assigned the values returned by the get_locations() function. This randomly assigns distinct locations for the monster, door, and player’s starting position, setting up the initial state of the game.
  2. The main game loop starts with a while True statement, which creates an infinite loop that continues until a game-ending condition is met or the player decides to quit.
  3. Inside the loop, the available moves, the player’s current location, and the map are displayed to the player. The available moves are obtained by calling the get_moves() function with the player’s current location.
  4. The player is prompted to enter a move. The input is obtained using the input() function and stored in the move variable. To ensure case-insensitive comparisons, the move variable is converted to uppercase using the upper() method.
  5. If the player enters 'QUIT', the game loop is terminated, and the game ends.
  6. If the entered move is not in the list of available moves, an error message is displayed, indicating that the player has encountered a wall and cannot move in that direction. The loop continues to the next iteration, prompting the player to enter another move.
  7. If the entered move is valid (i.e., it is in the list of available moves), the player’s location is updated using the move_player() function, passing the player dictionary and the chosen move as arguments. The move_player() function updates the player’s location based on the chosen move and appends the current location to the player’s path.
  8. If the player’s updated location matches the door’s location, it means the player has successfully reached the exit door. A victory message is displayed, indicating that the player has escaped the dungeon. The game loop is terminated using the break statement, and the game ends.
  9. If the player’s updated location matches the monster’s location, it means the player has encountered the monster and has been eaten. A defeat message is displayed, indicating that the player has lost the game. The game loop is terminated using the break statement, and the game ends.
  10. If neither the door nor the monster is encountered, the loop continues to the next iteration, and the player can make another move.

This provides a more detailed explanation of each part of the code and how it functions to create the dungeon game.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What does the following code does?

import logging

logging.info("You won't see this")
logging.warn("OH NO")
A

The provided code demonstrates the usage of the Python logging module to log messages with different levels of severity. Here’s what the code does:

  1. The code begins by importing the logging module, which is a built-in module in Python used for logging messages in applications.
  2. The line logging.info("You won't see this") logs an informational message with the severity level set to INFO. By default, the logging module is configured to log messages with a severity level of WARNING or higher, so this particular message won’t be displayed in the output.
  3. The line logging.warn("OH NO") logs a warning message with the severity level set to WARNING. The warn() function is an alias for warning() in the logging module. This message will be displayed in the output, assuming the logging module is configured to display messages with a severity level of WARNING or higher.

The code demonstrates how to use the logging module to log messages with different severity levels. In this case, an informational message is logged but not displayed, while a warning message is logged and displayed if the logging configuration allows it. The actual behavior and display of log messages depend on the configuration of the logging module, which can be customized to suit specific needs.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What are the 6 log levels available?

A

The logging module in Python provides six built-in log levels that can be used to classify and categorize log messages based on their severity. Here are the six log levels available, listed in increasing order of severity:

  1. DEBUG: The DEBUG level is used for detailed and low-level information typically intended for debugging purposes. These messages provide the most detailed information about the program’s execution and are typically not displayed in a production environment.
  2. INFO: The INFO level is used for general information messages that highlight the progress of the application. These messages provide informative details about the application’s execution, such as the start and completion of certain operations or milestones. They are typically used to give an overview of the application’s state.
  3. WARNING: The WARNING level is used to indicate potential issues or unexpected situations that are not critical but may require attention. These messages highlight events or conditions that may cause problems in the future or affect the normal behavior of the application. They serve as warnings to indicate that something might be wrong or needs attention.
  4. ERROR: The ERROR level is used to report errors that occurred during the execution of the application. These messages indicate that something has gone wrong, but the application can still continue running. Errors typically represent more severe issues that should be addressed.
  5. CRITICAL: The CRITICAL level is used to indicate critical errors or failures that may result in the termination or abnormal behavior of the application. These messages represent the highest severity level and typically indicate a severe problem that requires immediate attention.
  6. FATAL (Deprecated): The FATAL level used to represent log messages of the highest severity, similar to CRITICAL. However, in recent versions of the logging module, FATAL has been deprecated, and it is recommended to use CRITICAL instead.

These log levels allow developers to categorize and prioritize log messages based on their severity, making it easier to identify and troubleshoot issues during application development and debugging.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Explain logging in Python in detail?

A

Logging in Python is a built-in module that provides a flexible and configurable way to record log messages during the execution of a program. It allows developers to track and monitor the behavior of their application, aiding in debugging, troubleshooting, and analysis. The logging module offers various features and components for controlling the logging process. Let’s explore the key aspects of logging in Python:

  1. Loggers: Loggers are the main entry point for using the logging module. They are responsible for creating log records and sending them to appropriate destinations. Loggers are organized in a hierarchical structure based on their names, forming a logger hierarchy. Loggers can be obtained using the logging.getLogger(name) function, where name is a string representing the name of the logger. By default, there is a root logger that serves as the parent of all other loggers.
  2. Log Handlers: Log handlers determine what happens to log records once they are created by the loggers. Handlers are responsible for processing and sending log records to specific destinations, such as the console, files, network sockets, or external services. The logging module provides different types of handlers, including StreamHandler, FileHandler, SocketHandler, SMTPHandler, and more. Handlers can be associated with loggers using the logger.addHandler(handler) method.
  3. Log Levels: Log levels indicate the severity or importance of a log message. The logging module defines six built-in log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL, and FATAL (deprecated). Each log level has a numerical value associated with it, allowing log messages to be filtered and controlled based on their severity. Log levels are used to set the minimum severity level for log records that will be processed and displayed. For example, setting the log level to WARNING will only display log messages with a severity level of WARNING, ERROR, or CRITICAL.
  4. Log Formatters: Log formatters define the structure and content of log messages. They determine how log records are formatted before they are passed to the log handlers. Formatters are responsible for converting log records into human-readable text or other formats. The logging module provides the Formatter class, which can be customized to specify the desired log message format. Formatters are associated with log handlers using the handler.setFormatter(formatter) method.
  5. Logging Configuration: The logging module allows for configuration through various means. It supports both programmatic configuration and configuration via external files, such as INI files, JSON files, or YAML files. Programmatic configuration involves setting the logger hierarchy, handlers, log levels, and formatters explicitly in the code. External configuration files provide a more flexible approach by separating the logging configuration from the code. The logging.config module provides functions to load and apply configurations from external files.
  6. Logging Messages: To log a message, developers use the logger.log(level, message) method or one of the convenience methods such as logger.debug(message), logger.info(message), logger.warning(message), logger.error(message), and logger.critical(message). The log level determines the severity of the message. Log messages are processed by the logger and propagated up the logger hierarchy, where they are then handled by the associated handlers based on their log levels.
  7. Logging to Multiple Destinations: The logging module allows log records to be processed by multiple handlers simultaneously, enabling logging to different destinations. For example, log messages can be displayed on the console using a StreamHandler and simultaneously saved to a file using a FileHandler. This provides flexibility in directing log messages to various outputs based on the application’s needs.

By leveraging

the logging module, developers can incorporate comprehensive logging capabilities into their Python applications. This facilitates better understanding, debugging, and monitoring of the application’s behavior, aiding in the identification and resolution of issues during development, testing, and production stages.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

In the following DD game code:

dd_game.py:
~~~
import random

player = {‘location’: None, ‘path’: []}
cells = [(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)]

def get_locations():
monster = random.choice(cells)
door = random.choice(cells)
start = random.choice(cells)

if monster == door or monster == start or door == start:
    monster, door, start = get_locations()

return monster, door, start

def get_moves(player):
moves = [‘LEFT’, ‘RIGHT’, ‘UP’, ‘DOWN’]
if player in [(0, 0), (1, 0), (2, 0)]:
moves.remove(‘LEFT’)
if player in [(0, 0), (0, 1), (0, 2)]:
moves.remove(‘UP’)
if player in [(0, 2), (1, 2), (2, 2)]:
moves.remove(‘RIGHT’)
if player in [(2, 0), (2, 1), (2, 2)]:
moves.remove(‘DOWN’)
return moves

def move_player(player, move):
x, y = player[‘location’]
player[‘path’].append((x, y))
if move == ‘LEFT’:
player[‘location’] = x, y - 1
elif move == ‘UP’:
player[‘location’] = x - 1, y
elif move == ‘RIGHT’:
player[‘location’] = x, y + 1
elif move == ‘DOWN’:
player[‘location’] = x + 1, y
return player

def draw_map():
print(‘ _ _ ’)
tile = ‘|{}’
for idx, cell in enumerate(cells):
if idx in [0, 1, 3, 4, 6, 7]:
if cell == player[‘location’]:
print(tile.format(‘X’), end=’’)
elif cell in player[‘path’]:
print(tile.format(‘.’), end=’’)
else:
print(tile.format(‘
’), end=’’)
else:
if cell == player[‘location’]:
print(tile.format(‘X|’))
elif cell in player[‘path’]:
print(tile.format(‘.|’))
else:
print(tile.format(‘_|’))

monster, door, player[‘location’] = get_locations()

while True:
moves = get_moves(player[‘location’])
print(“Welcome to the dungeon!”)
print(“You’re currently in room {}”.format(player[‘location’]))

draw_map()

print("\nYou can move {}".format(', '.join(moves)))
print("Enter QUIT to quit")

move = input("> ")
move = move.upper()

if move == 'QUIT':
    break

if not move in moves:
    print("\n** Walls are hard! Stop running into them! **\n")
    continue

player = move_player(player, move)
if player['location'] == door:
    print("\n** You escaped! **\n")
    break
elif player['location'] == monster:
    print("\n** You got eaten! **\n")
    break
else:
    continue
What does adding the following code to it will do?

import logging
logging.basicConfig(filename=’game.log’, level=logging.DEBUG)

logging.info(‘monster: {}; door: {}; player: {}’.format(monster, door, player[‘location’]))
~~~

The code after adding above will become like this:
dd_game.py:
~~~
import logging
import random

logging.basicConfig(filename=’game.log’, level=logging.DEBUG)

player = {‘location’: None, ‘path’: []}
cells = [(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)]

def get_locations():
monster = random.choice(cells)
door = random.choice(cells)
start = random.choice(cells)

if monster == door or monster == start or door == start:
    monster, door, start = get_locations()

return monster, door, start

def get_moves(player):
moves = [‘LEFT’, ‘RIGHT’, ‘UP’, ‘DOWN’]
if player in [(0, 0), (1, 0), (2, 0)]:
moves.remove(‘LEFT’)
if player in [(0, 0), (0, 1), (0, 2)]:
moves.remove(‘UP’)
if player in [(0, 2), (1, 2), (2, 2)]:
moves.remove(‘RIGHT’)
if player in [(2, 0), (2, 1), (2, 2)]:
moves.remove(‘DOWN’)
return moves

def move_player(player, move):
x, y = player[‘location’]
player[‘path’].append((x, y))
if move == ‘LEFT’:
player[‘location’] = x, y - 1
elif move == ‘UP’:
player[‘location’] = x - 1, y
elif move == ‘RIGHT’:
player[‘location’] = x, y + 1
elif move == ‘DOWN’:
player[‘location’] = x + 1, y
return player

def draw_map():
print(‘ _ _ ’)
tile = ‘|{}’
for idx, cell in enumerate(cells):
if idx in [0, 1, 3, 4, 6, 7]:
if cell == player[‘location’]:
print(tile.format(‘X’), end=’’)
elif cell in player[‘path’]:
print(tile.format(‘.’), end=’’)
else:
print(tile.format(‘
’), end=’’)
else:
if cell == player[‘location’]:
print(tile.format(‘X|’))
elif cell in player[‘path’]:
print(tile.format(‘.|’))
else:
print(tile.format(‘_|’))

monster, door, player[‘location’] = get_locations()
logging.info(‘monster: {}; door: {}; player: {}’.format(monster, door, player[‘location’]))

while True:
moves = get_moves(player[‘location’])
print(“Welcome to the dungeon!”)
print(“You’re currently in room {}”.format(player[‘location’]))

draw_map()

print("\nYou can move {}".format(', '.join(moves)))
print("Enter QUIT to quit")

move = input("> ")
move = move.upper()

if move == 'QUIT':
    break

if not move in moves:
    print("\n** Walls are hard! Stop running into them! **\n")
    continue

player = move_player(player, move)
if player['location'] == door:
    print("\n** You escaped! **\n")
    break
elif player['location'] == monster:
    print("\n** You got eaten! **\n")
    break
else:
    continue
    
    # use ls to see game.log
    # then use cat game.log to printout game.log ~~~

Explain the use of logging in this code in detail now.

A

In the provided code, the logging module is used to incorporate logging functionality into the game. Let’s break down the use of logging in detail:

  1. The line import logging imports the logging module, which provides the necessary classes and functions for logging.
  2. The line logging.basicConfig(filename='game.log', level=logging.DEBUG) configures the logging system. It sets up the basic configuration for logging, including the output filename and the log level. The filename parameter specifies the name of the log file to which the log records will be written. In this case, the log file is named ‘game.log’. The level parameter sets the minimum log level to DEBUG, meaning all log records with DEBUG level or higher will be logged. This line ensures that the log records are written to the ‘game.log’ file.
  3. The line logging.info('monster: {}; door: {}; player: {}'.format(monster, door, player['location'])) logs an initial information message. It uses the logging.info() method to log an informational message with the severity level set to INFO. The message is formatted using the format() method and contains the values of monster, door, and player['location']. This log record provides initial information about the game, including the locations of the monster, door, and player’s starting position.
  4. Throughout the game loop, logging is not used explicitly. Instead, the code primarily relies on printing messages to the console using the print() function to provide feedback to the player.
  5. At the end of the code, there are comments suggesting using commands like ls and cat to view and print the contents of the ‘game.log’ file. These comments hint at the idea of inspecting the log file to examine the logged events and information related to the game.

In summary, the logging module is used to log an initial information message containing the locations of the monster, door, and player’s starting position. The log records are written to a file named ‘game.log’. However, beyond the initial information log, the code primarily relies on printing messages to the console for gameplay interaction. The logging functionality in this code is not extensively used but can be useful for capturing specific events or tracking game-related information for further analysis by inspecting the ‘game.log’ file.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Challenge 1: Log a message with a level of DEBUG. The message can say anything you want.

import logging

logging.basicConfig(filename='cc.log', level=logging.DEBUG)

Write your code below here

Challenge 2: Log “The French have the Grail” as a WARNING level message.

A

Challenge 1:
~~~
import logging

logging.basicConfig(filename=’cc.log’, level=logging.DEBUG)

Write your code below here

logging.debug(‘Hi, this is a debug message’)
~~~

Challenge 2:
~~~
import logging

logging.basicConfig(filename=’cc.log’, level=logging.DEBUG)

Write your code below here

logging.debug(‘Hi, this is a debug message’)

logging.warn(“The French have the Grail”)
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What is pdb in python? how to import and use it?

A

PDB (Python Debugger) is a built-in interactive debugger for Python programs. It allows developers to pause the execution of a program, inspect variables, and step through the code line by line to diagnose and debug issues.

To import and use the PDB module, you can follow these steps:

  1. Import the PDB module:
    ```python
    import pdb
    ~~~
  2. Set a breakpoint at a specific line in your code:
    ```python
    pdb.set_trace()
    ~~~
    This line acts as a breakpoint and pauses the program’s execution when reached. It allows you to interactively examine the program’s state.
  3. Run your Python script:
    ```python
    python your_script.py
    ~~~
    When the execution reaches the pdb.set_trace() line, the program will pause, and you will enter the PDB debugging environment.
  4. Once inside the PDB environment, you can use various commands to navigate and debug your code. Some commonly used commands include:
  • h or help: Display a list of available commands and their descriptions.
  • n or next: Execute the next line of code.
  • s or step: Step into a function call.
  • c or continue: Continue the execution until the next breakpoint or the program’s end.
  • p or print: Print the value of a variable.
  • q or quit: Quit the debugger and stop the program’s execution.

You can enter these commands in the PDB prompt to interact with the debugger and analyze the program’s state and behavior.

Here’s an example of using PDB:

```python
import pdb

def calculate_sum(a, b):
result = a + b
pdb.set_trace() # Set a breakpoint

return result

x = 5
y = 3
z = calculate_sum(x, y)

print(“The sum is:”, z)
~~~

In this example, the pdb.set_trace() line sets a breakpoint inside the calculate_sum() function. When running the script, the program will pause at that line, and you can use PDB commands to inspect and debug the code. For instance, you can print the values of variables a and b, or execute the next line of code using the n command.

By leveraging PDB, you can gain insight into your program’s execution and identify and resolve issues efficiently.

17
Q

Explain the following code:

import pdb

my_list = [5, 2, 1, True, "abcdefg", 3, False, 4]

the aim is to make my_list to contain only numbers only without using .remove

pdb.set_trace()
del my_list[3] # [5, 2, 1, "abcdefg", 3, False, 4]
del my_list[3] # [5, 2, 1, 3, False, 4]
del my_list[4] # [5, 2, 1, 3, 4]
print(my_list)

or

my_list = [5, 2, 1, True, "abcdefg", 3, False, 4]

the aim is to make my_list to contain only numbers only without using .remove

import pdb; pdb.set_trace()
del my_list[3] # [5, 2, 1, "abcdefg", 3, False, 4]
del my_list[3] # [5, 2, 1, 3, False, 4]
del my_list[4] # [5, 2, 1, 3, 4]
print(my_list)
A

The provided code demonstrates the use of the PDB (Python Debugger) module to debug and understand the manipulation of a list. Let’s break down the code and its execution:

  1. The code begins by importing the pdb module, which is the Python Debugger module.
  2. A list called my_list is defined with a mixture of values, including integers, booleans, and a string.
  3. The line pdb.set_trace() sets a breakpoint in the code at that specific line. When the program reaches this line during execution, it will pause and enter the PDB debugging environment.
  4. The next lines manipulate the my_list by removing specific elements using the del statement.
    • del my_list[3] removes the element at index 3 from my_list. After this line, the list becomes [5, 2, 1, "abcdefg", 3, False, 4].
    • del my_list[3] removes the new element at index 3 from the updated my_list. Now, the list becomes [5, 2, 1, 3, False, 4].
    • del my_list[4] removes the element at index 4 from the updated my_list. The final state of the list is [5, 2, 1, 3, 4].
  5. Finally, the list is printed, displaying the result after the manipulations: [5, 2, 1, 3, 4].

During the program’s execution, when it encounters the pdb.set_trace() line, it will pause and enter the PDB debugging environment. In this environment, you can interactively inspect variables, execute statements, and navigate through the code using various PDB commands. For example, you can print the value of my_list to verify its contents or execute the next line of code using the n command.

By leveraging the PDB debugger, you can analyze the state of the list at different points in the code and debug the manipulation steps to understand how the list is modified.

Treehouse Notes:
Reminder: Updating a script while PDB is running will not update the version in your PDB console. PDB doesn’t magically reload (it would lose its place if it did).

18
Q

Challenge: Import PDB and call set_trace() where it’s needed.

def something_silly(arg1, arg2):
    if len(arg1) > len(arg2):
        # Import and use PDB here
        arg1[0] = arg2[0]
    return arg1, arg2
A

Answer to Challenge 1:
~~~
import pdb

def something_silly(arg1, arg2):
if len(arg1) > len(arg2):
# Import and use PDB here
pdb.set_trace()
arg1[0] = arg2[0]
return arg1, arg2
~~~

or

def something_silly(arg1, arg2):
    if len(arg1) > len(arg2):
        # Import and use PDB here
        import pdb; pdb.set_trace()
        arg1[0] = arg2[0]
    return arg1, arg2
19
Q

Docstrings are only for functions. Is this true?

A

No, that statement is not true. While docstrings are commonly associated with functions, they can be used to provide documentation for various other Python entities, including modules, classes, methods, and even modules.

Docstrings are simply string literals that appear as the first statement within a function, class, or module, and they are used to provide documentation and describe the purpose, behavior, and usage of the entity.

Here are some examples of how docstrings can be used:

  1. Function Docstring:
    ```python
    def add_numbers(a, b):
    “””
    Adds two numbers together and returns the result.Parameters:
    a (int): The first number.
    b (int): The second number.Returns:
    int: The sum of the two numbers.
    “””
    return a + b
    ~~~
  2. Class Docstring:
    ```python
    class Person:
    “””
    Represents a person with a name and age.Attributes:
    name (str): The person’s name.
    age (int): The person’s age.
    “””def __init__(self, name, age):
    self.name = name
    self.age = age
    ~~~
  3. Module Docstring:
    ```python
    “””
    This module contains utility functions for mathematical operations.
    “””

def square(x):
“””
Calculates the square of a number.

Parameters:
x (int): The number to be squared.

Returns:
int: The square of the number.
"""
return x ** 2 ~~~

In all these examples, docstrings are used to provide documentation and describe the purpose, parameters, and return values of the respective functions, classes, or modules. They serve as valuable documentation for users and developers to understand the functionality and usage of the code.

It’s good practice to include docstrings for all significant Python entities to make the code more readable, maintainable, and understandable by others.