# Variables and Data Types

In programming, a **variable** is like a storage box where we can store information (data). You can think of it as a label that holds something inside it.

For example:

```python
a = 1
```

Here, `a` is the variable, and it stores the number `1`. The equals sign (`=`) is an **assignment operator** that assigns the value on the right (in this case `1`) to the variable on the left (`a`). This is how we give a name to a value.

* **a**: The variable name.
* **=**: The assignment operator (it doesn’t mean "equal" like in math, it just means "assign").
* **1**: The value or data we are assigning to `a`.

**What is `print()`?**

The `print()` function in Python is used to display the value of a variable, or any message, to the screen (also called "standard output"). This helps us see what's inside our variables or show results of our calculations.

For example:

```python
a = 1
print(a)
```

This will display `1` because that’s the value stored in `a`.

{% hint style="info" %}
Variables can store any data type we will learn below.
{% endhint %}

## Data Types in Python

Data types are the types of values that can be stored in a variable. Each piece of information we store in a variable has a certain type, and understanding data types is very important because different types of data behave differently.

### **Integers (int)**

An **integer** is a whole number, without a decimal point.

Example:

```python
a = 10
print(a)  # Output: 10
```

Here, `a` is an integer, and `print(a)` will display `10`.

### **Floating Point Numbers (float)**

A **float** is a number that has a decimal point.

Example:

```python
b = 3.14
print(b)  # Output: 3.14
```

In this case, `b` is a float, and `print(b)` will show `3.14`.

### **Strings (str)**

A **string** is a sequence of characters (letters, numbers, or symbols) enclosed in quotation marks.

Example:

```python
name = "Hello NCA"
print(name)  # Output: Hello NCA
```

Here, `"Hello NCA"` is a string, and we use `print(name)` to display it.

Strings can also include numbers, but they are treated as text, not numerical values:

```python
age = "25"
print(age)  # Output: 25
```

### **Booleans (bool)**

A **boolean** is a type that can have one of two values: `True` or `False`.

Example:

```python
is_sunny = True
print(is_sunny)  # Output: True
```

Booleans are used for logic and comparisons. For example, you can use them to check if something is true or false.

### **Lists (list)**

A **list** is an ordered collection of items, which can be of any type. Lists are defined by square brackets `[]`.

Example:

```python
my_list = [1, 2, 3, "Hello NCA", 4.5]
print(my_list)  # Output: [1, 2, 3, 'Hello NCA', 4.5]
```

In this case, `my_list` contains a mixture of different data types: integers, a string, and a float.

### **Dictionaries (dict)**

A **dictionary** is a collection of key-value pairs. Each key is associated with a value.

Example:

```python
person = {"name": "John", "age": 25, "is_student": True}
print(person)  # Output: {'name': 'John', 'age': 25, 'is_student': True}
```

Here, `person` is a dictionary where `"name"`, `"age"`, and `"is_student"` are keys, and the values are `"John"`, `25`, and `True`, respectively.

### **Tuples (tuple)**

A **tuple** is similar to a list, but once it’s created, it cannot be changed (it is immutable). Tuples are defined by parentheses `()`.

Example:

```python
coordinates = (10, 20)
print(coordinates)  # Output: (10, 20)
```

### **Sets (set)**

A **set** is an unordered collection of unique items. Sets are defined by curly braces `{}`.

Example:

```python
unique_numbers = {1, 2, 3, 4}
print(unique_numbers)  # Output: {1, 2, 3, 4}
```

{% hint style="success" %}
The main difference between a **set and a list** is that a set does not allow duplicate values.
{% endhint %}

***

## Accessing Data in Different Data Types

In Python, each data type has its own way of storing and accessing data. Below, we will go through how to retrieve values from **strings, lists, tuples, dictionaries, sets, and nested structures**.

### Strings (str)

Strings in Python are sequences of characters, and we can access individual characters using **indexing** (starting from 0). We can also use **slicing** to get a portion of the string.

<pre class="language-python"><code class="lang-python">text = "Hello NCA"

<strong># Accessing characters using indexing
</strong>print(text[0])   # Output: H
print(text[6])   # Output: N

<strong># Accessing a range using slicing
</strong>print(text[0:5])  # Output: Hello

print(text[:])  # Output: Hello NCA
<strong>## Note: Above "[:]" means from the beginning to the end of  
</strong><strong>## the string, effectively returning the full string as is.
</strong>
print(text[-3:])  # Output: NCA
<strong>## Note: Above "[-3:]" means (length-3) to the end of  
</strong><strong>## the string, extracting the last three characters.
</strong>
print(text[:-3])  # Output: Hello 
<strong>## Note: Above "[:-3]" means from the beginning up to (length-3),  
</strong><strong>## effectively removing the last three characters from the string.
</strong></code></pre>

{% hint style="danger" %}
Negative Indexing and Slicing works for all data types and not just strings, so try it for all data types.
{% endhint %}

**Explanation:**

* `text[0]` retrieves the first character (`H`), while `text[6]` retrieves `N`.
* `text[0:5]` gets a substring from index **0 to 4** (`Hello`).
* `text[-3:]` uses negative indexing to get the last three characters (`NCA`).

### Lists

Lists are ordered collections that allow **indexing, slicing, and iteration** to retrieve elements.

<pre class="language-python"><code class="lang-python">my_list = ["Hello", "NCA", 100, 3.14, True]

<strong># Accessing individual elements
</strong>print(my_list[1])   # Output: NCA
print(my_list[-1])  # Output: True

<strong># Accessing a range using slicing
</strong>print(my_list[1:4])  # Output: ['NCA', 100, 3.14]
</code></pre>

**Explanation:**

* `my_list[1]` retrieves `"NCA"`, and `my_list[-1]` retrieves the last element (`True`).
* `my_list[1:4]` gets elements from index **1 to 3** (`['NCA', 100, 3.14]`).

### Tuples

Tuples work like lists but are **immutable**, meaning they cannot be changed after creation.

<pre class="language-python"><code class="lang-python">my_tuple = ("Hello", "NCA", 42, 9.99)

<strong># Accessing elements using indexing
</strong>print(my_tuple[2])  # Output: 42

<strong># Using slicing
</strong>print(my_tuple[:2])  # Output: ('Hello', 'NCA')
</code></pre>

**Explanation:**

* `my_tuple[2]` retrieves the third element (`42`).
* `my_tuple[:2]` retrieves the first two elements (`Hello, NCA`).

### Dictionary

Dictionaries store **key-value pairs**, and we retrieve values using keys instead of indexes.

<pre class="language-python"><code class="lang-python">my_dict = {"name": "John", "age": 25, "city": "Kathmandu"}

<strong># Accessing values using keys
</strong>print(my_dict["name"])   # Output: John
print(my_dict.get("age"))  # Output: 25
</code></pre>

**Explanation:**

* `my_dict["name"]` retrieves the value associated with `"name"` (`John`).
* `my_dict.get("age")` is another way to get the value (`25`), which avoids errors if the key doesn't exist.

### Sets

Sets store **unordered and unique** elements, so we <mark style="color:yellow;">**cannot use indexing**</mark>. Instead, we check if an element exists or iterate through the set.

<pre class="language-python"><code class="lang-python">my_set = {1, 2, 3, 4, 5}

<strong># Checking if an element exists
</strong>print(3 in my_set)  # Output: True

<strong># Iterating through a set
</strong>for item in my_set:
    print(item)
</code></pre>

**Explanation:**

* `3 in my_set` checks if `3` exists in the set.
* We use a `for` loop to iterate over all elements since indexing is not possible.

***

## Nested Data Types

We can store complex data by **nesting** different data types inside each other. This means we can put **lists inside lists, dictionaries inside lists, tuples inside dictionaries**, and so on. This allows us to store structured data in an organized way.

<pre class="language-python"><code class="lang-python"><strong># Nested List
</strong>nested_list = [[1, 2, 3], ["Hello", "NCA"], [4.5, 6.7]]

<strong># Nested Dictionary
</strong>nested_dict = {
    "person": {
        "name": "John",
        "age": 25,
        "hobbies": ["Reading", "Gaming"]
    },
    "address": {
        "city": "New York",
        "zip": 10001
    }
}

<strong># Mixing List and Dictionary
</strong>mixed_data = [
    {"name": "Alice", "scores": [90, 85, 88]},
    {"name": "Bob", "scores": [78, 80, 79]}
]

<strong># Accessing Data
</strong>print(nested_list[1][0])  # Output: Hello
print(nested_dict["person"]["hobbies"][1])  # Output: Gaming
print(mixed_data[0]["scores"][2])  # Output: 88

<strong># Using Methods on Nested Data
</strong>nested_list[1].append("Python")  # Adding a new element to a nested list
nested_dict["person"]["name"] = nested_dict["person"]["name"].upper()  # Converting name to uppercase
mixed_data[1]["scores"].append(85)  # Adding a new score for Bob

<strong># Print modified structures
</strong>print(nested_list)
print(nested_dict)
print(mixed_data)
</code></pre>

In the above code, we first created a **nested list** that contains multiple sublists, where each sublist holds different types of data such as numbers and strings. To access a specific element, we used **double indexing**, such as `nested_list[1][0]`, which retrieves `"Hello"` from the second sublist. We also modified the nested list by appending `"Python"` to one of its sublists using the `.append()` method. Next, we worked with a **nested dictionary**, which organizes data into key-value pairs. Inside the dictionary, the `"person"` key holds another dictionary containing details like `"name"`, `"age"`, and `"hobbies"`. We accessed the `"Gaming"` hobby using `nested_dict["person"]["hobbies"][1]`, and we also updated `"John"` to uppercase using the `.upper()` method on the name value.

Additionally, we demonstrated a **mix of lists and dictionaries** by creating a list where each element is a dictionary representing a person and their scores. To retrieve a specific score, we used `mixed_data[0]["scores"][2]`, which navigates through the list to the dictionary and then accesses the third score. To further modify the data, we added a new score (`85`) to Bob’s scores using `.append()`. This showcases how we can manipulate **nested data types using indexing and built-in methods**, making it easier to store and process complex structured data efficiently.

{% hint style="info" %}
If you don't understand accessing data in nested structure in the above example properly read the topic below.
{% endhint %}

### Accessing Data in Nested Structures

We can **nest** different data types inside each other (lists inside dictionaries, dictionaries inside lists, etc.), and we use multiple indexing or key-value lookups to access nested data.

#### **Nested List Example:**

<pre class="language-python"><code class="lang-python">nested_list = [[1, 2, 3], ["Hello", "NCA"], [4.5, 6.7]]

<strong># Accessing elements from nested lists
</strong>print(nested_list[1][0])  # Output: Hello
</code></pre>

**Explanation:**

* `nested_list[1]` retrieves the second sublist (`["Hello", "NCA"]`).
* `nested_list[1][0]` accesses the first element of that sublist (`Hello`).

#### **Nested Dictionary Example:**

<pre class="language-python"><code class="lang-python">nested_dict = {
    "person": {
        "name": "Alice",
        "age": 30,
        "hobbies": ["Reading", "Gaming"]
    }
}

<strong># Accessing nested dictionary values
</strong>print(nested_dict["person"]["name"])  # Output: Alice
print(nested_dict["person"]["hobbies"][1])  # Output: Gaming
</code></pre>

**Explanation:**

* `nested_dict["person"]["name"]` retrieves `"Alice"`.
* `nested_dict["person"]["hobbies"][1]` gets the second hobby (`"Gaming"`).

#### **Mixing Lists & Dictionaries Example:**

<pre class="language-python"><code class="lang-python">data = [
    {"name": "Bob", "scores": [85, 90, 92]},
    {"name": "Sara", "scores": [88, 76, 95]}
]

<strong># Accessing values from mixed data types
</strong>print(data[0]["scores"][2])  # Output: 92
</code></pre>

**Explanation:**

* `data[0]` gets the first dictionary.
* `data[0]["scores"]` accesses the `"scores"` list inside the dictionary.
* `data[0]["scores"][2]` retrieves the third score (`92`).

***

## Common Methods for Data Types

Each data type has methods (or functions) that we can use to perform specific actions on the data.

### **String Methods**

Strings have many built-in methods. Some common ones include:

* **`upper()`**: Converts all characters to uppercase.
* **`lower()`**: Converts all characters to lowercase.
* **`replace(old, new)`**: Replaces a substring with another substring.

Example:

```python
message = "Hello NCA"
print(message.upper())  # Output: HELLO NCA
print(message.lower())  # Output: hello nca
```

### **List Methods**

Lists also have methods. Some common ones include:

* **`append(item)`**: Adds an item to the end of the list.
* **`remove(item)`**: Removes the first occurrence of an item.
* **`pop()`**: Removes the last item from the list.

Example:

```python
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)  # Output: [1, 2, 3, 4]
```

### **Dictionary Methods**

Dictionaries have several useful methods. Some of the common ones are:

* **`get(key)`**: Retrieves the value associated with a key.
* **`keys()`**: Returns a list of all keys in the dictionary.
* **`values()`**: Returns a list of all values in the dictionary.

Example:

```python
person = {"name": "John", "age": 25}
print(person.get("name"))  # Output: John
```

### **Set Methods**

Sets support methods like:

* **`add(item)`**: Adds an item to the set.
* **`remove(item)`**: Removes an item from the set.
* **`union(set)`**: Combines two sets into one.

Example:

```python
numbers = {1, 2, 3}
numbers.add(4)
print(numbers)  # Output: {1, 2, 3, 4}
```

## So many Methods

Yes, it can feel overwhelming to remember all the methods for different data types, but don’t worry! We have **cheat sheets** available to help us.

I'll list some **W3Schools cheat sheet links** where you can find the methods for each data type along with explanations of what they do:

1. [String Methods](https://www.w3schools.com/python/python_strings_methods.asp)
2. [List Methods](https://www.w3schools.com/python/python_lists_methods.asp)
3. [Tuples Methods](https://www.w3schools.com/python/python_tuples_methods.asp)
4. [Set Methods](https://www.w3schools.com/python/python_sets_methods.asp)
5. [Dictionary Methods](https://www.w3schools.com/python/python_dictionaries_methods.asp)
6. Even LLMs (ChatGPT, GrokAI, Gemini, etc) will help you choose the correct method for your use case. :smile:

## Conclusion

Understanding variables and data types is key to becoming proficient in Python. When you define variables, you are choosing a name to hold certain types of data. There are various types of data in Python, such as integers, strings, floats, and more, and each type has different methods and behavior. The `print()` function is a handy tool for displaying results to the screen.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://handbook.ncateam.xyz/fundamentals/python/basics-of-python/variables-and-data-types.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
