# Classes

## Classes

Classes are the core of Python.&#x20;

## Instance & Class variables

The difference is:

* **Instance variables:** for data which is **unique to every object**
* **Class variables:** for data **shared between different instances of a class**

Let’s take a look at an example:

```python
class Cal(object):
    # pi is a class variable
    pi = 3.142

    def __init__(self, radius):
        # self.radius is an instance variable
        self.radius = radius

    def area(self):
        return self.pi * (self.radius ** 2)

a = Cal(32)
a.area()
# Output: 3217.408
a.pi
# Output: 3.142
a.pi = 43
a.pi
# Output: 43

b = Cal(44)
b.area()
# Output: 6082.912
b.pi
# Output: 3.142
b.pi = 50
b.pi
# Output: 50
```

There are not many issues while using immutable class variables. This is the major reason due to which beginners do not try to learn more about this subject because everything works! If you also believe that instance and class variables can not cause any problem if used incorrectly then check the next example.

```python
class SuperClass(object):
    superpowers = []

    def __init__(self, name):
        self.name = name

    def add_superpower(self, power):
        self.superpowers.append(power)

foo = SuperClass('foo')
bar = SuperClass('bar')
foo.name
# Output: 'foo'

bar.name
# Output: 'bar'

foo.add_superpower('fly')
bar.superpowers
# Output: ['fly']

foo.superpowers
# Output: ['fly']
```

That is the beauty of the wrong usage of mutable class variables. To make your code safe against this kind of surprise attacks then **make sure that you do not use mutable class variables**. You may use them only if you know what you are doing.

## New style classes

* **Old** base classes **do not inherit** from anything
* **New** style base classes **inherit from `object`**

Example:

```python
class OldClass():
    def __init__(self):
        print('I am an old class')

class NewClass(object):
    def __init__(self):
        print('I am a jazzy new class')

old = OldClass()
# Output: I am an old class

new = NewClass()
# Output: I am a jazzy new class
```

**Inheritance from `object` allows new style classes to utilize some&#x20;*****magic:***

1. Optimizations like **`__slots__`**.
2. **`super()`** and descriptors and the likes

Bottom line? **Always try to use new-style classes!**

**Note:** **Python 3 only has new-style classes. It does not matter whether you subclass from `object`or not. However it is recommended that you still subclass from `object`.**

## Magic Methods (dunder)

Magic methods, commonly called **dunder** (double underscore) methods.

### `__init__`

It is a **class initializer**.

Whenever an instance of a class is created its `__init__` method is called.

Example:

```python
class GetTest(object):
    def __init__(self):
        print('Greetings!!')
    def another_method(self):
        print('I am another method which is not'
              ' automatically called')

a = GetTest()
# Output: Greetings!!

a.another_method()
# Output: I am another method which is not automatically
# called
```

**`__init__` is called immediately after an instance is created**.

You can pass arguments to the class during it’s initialization:

```python
class GetTest(object):
    def __init__(self, name):
        print('Greetings!! {0}'.format(name))
    def another_method(self):
        print('I am another method which is not'
              ' automatically called')

a = GetTest('yasoob')
# Output: Greetings!! yasoob

# Try creating an instance without the name arguments
b = GetTest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 2 arguments (1 given)
```

### `__getitem__`

Implementing **getitem** in a class **allows its instances to use the \[] (indexer) operator**.&#x20;

Example:

```python
class GetTest(object):
    def __init__(self):
        self.info = {
            'name':'Yasoob',
            'country':'Pakistan',
            'number':12345812
        }

    def __getitem__(self,i):
        return self.info[i]

foo = GetTest()

foo['name']
# Output: 'Yasoob'

foo['number']
# Output: 12345812
```

Without the `__getitem__` method we would get this error:

```python
>>> foo['name']

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'GetTest' object has no attribute '__getitem__'
```


---

# 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://ricardomol.gitbook.io/notes/backend/python/classes.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.
