# Debugging Python with Print Statements: A Mini Guide

### **The Simple Print Statement**:

Let's print a variable to inspect its value. This can be as straightforward as:

```python
result = 5 * 5
print(result)  # Output: 25
```

### **Listing attributes and methods of a variable**:

The `dir()` function is a powerful tool when examining an object, revealing associated attributes and methods. The power of `dir()` becomes evident when comparing its output to that of a simple print statement.

```python
result = 10
print(result)  # Output: 10
print(dir(result))  # Output: ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', ..., '__xor__']
```

Here, `print(result)` only shows the value of the integer, while `print(dir(result))` lists all applicable methods and attributes.

### Outputing the key-value pairs:

The `vars()` function provides an in-depth look into an object's attributes and their corresponding values, unlike a simple print statement which only displays the object.

```python
class Test:
    def __init__(self, value):
        self.value = value

result = Test(10)
# Not so readable
print(result)  # Output: <__main__.Test object at 0x7f9abc7def10>
# Easier to get a picture of the values
print(vars(result))  # Output: {'value': 10}
```

In this case, `print(result)` only shows the memory location, while `print(vars(result))` reveals the actual content of the object.

### Copy-pasting values for Python Unit Testing

`repr()` returns a string that represents a printable version of an object, which can be particularly useful for complex data structures like nested dictionaries or custom objects.

Consider a function that returns a nested dictionary:

```python
def create_nested_dict():
    return {
        "key1": {
            "nested_key1": True,
            "nested_key2": [1, 2, 3],
            "nested_key3": None,
            "nested_key4": {
                "double_nested_key1": "Hello, World!"
            }
        },
        "key2": 42
    }
```

To get a string representation of the result that can be used directly in a unit test, we can use `repr()`:

```python
result = create_nested_dict()
print(repr(result))
```

This will output:

```python
{'key1': {'nested_key1': True, 'nested_key2': [1, 2, 3], 'nested_key3': None, 'nested_key4': {'double_nested_key1': 'Hello, World!'}}, 'key2': 42}
```

Now, we can use this exact output as the expected result in our unit test:

```python
import unittest

class TestCreateNestedDictFunction(unittest.TestCase):
    def test_create_nested_dict(self):
        expected = {'key1': {'nested_key1': True, 'nested_key2': [1, 2, 3], 'nested_key3': None, 'nested_key4': {'double_nested_key1': 'Hello, World!'}}, 'key2': 42}
        self.assertEqual(create_nested_dict(), expected)
```

The `repr()` function provides a convenient way to generate string representations of complex data structures, which can be directly used in unit tests as expected results. This can significantly simplify the process of writing unit tests, especially for functions that return complex data structures.  
  
Happy Coding!
