Creating Methods and Classes in Python: A Deeper Dive
One of the most powerful features of Python is its ability to create custom classes and methods. In this tutorial, we'll take a closer look at how to do just that.
By creating a class with an `__init__` method, you can define the attributes and properties of your class. For example, let's say we want to create a simple `Person` class. We can define it as follows:
```python
class Person:
def __init__(self, name):
self.name = name
```
This `Person` class takes in a `name` parameter when it's instantiated, and assigns that value to an instance variable called `name`. This is known as an "initializer" or "constructor", and it's where you define the attributes of your class.
Now, let's say we want to create a method within our `Person` class. We can do this by defining a new function inside our class definition:
```python
class Person:
def __init__(self, name):
self.name = name
def say_name(self):
print("Hello, my name is", self.name)
```
In this example, we've defined a method called `say_name`, which takes no parameters and prints out a message that includes the value of our `name` attribute. We can call this method on an instance of the `Person` class like so:
```python
person = Person("Tim")
person.say_name()
```
This will output "Hello, my name is Tim".
Now, let's take it to the next level. What if we want to create a method that calls another method, or one that takes parameters? We can do this by simply defining our new method with those parameters.
For example:
```python
class Person:
def __init__(self, name):
self.name = name
def say_name(self):
print("Hello, my name is", self.name)
def greet(self, other_person):
self.say_name()
other_person.say_name()
person1 = Person("Tim")
person2 = Person("John")
person1.greet(person2)
```
This will output:
```
Hello, my name is Tim
Hello, my name is John
```
As you can see, the `greet` method takes in another instance of our `Person` class and calls its `say_name` method.
Now, let's talk about the difference between using a class like this versus creating it manually. When we create a class like this:
```python
class Person:
def __init__(self, name):
self.name = name
def say_name(self):
print("Hello, my name is", self.name)
```
We can access the `name` attribute directly using dot notation, like so:
```python
person = Person("Tim")
print(person.name) # Outputs: Tim
```
But if we want to create a new class that inherits from this one, we have to do something a bit more involved.
For example:
```python
class AdvancedPerson(Person):
def __init__(self, name, age):
super().__init__(name)
self.age = age
def say_age(self):
print("I'm", self.age, "years old")
person1 = AdvancedPerson("Tim", 30)
person2 = AdvancedPerson("John", 40)
print(person1.name) # Outputs: Tim
print(person2.name) # Outputs: John
```
In this example, we've defined a new class called `AdvancedPerson` that inherits from our original `Person` class. We use the `super()` function to call the `__init__` method of the parent class and assign it the value of our `name` attribute.
Now, let's talk about how Python actually implements this kind of thing at a lower level. When we define a class like this:
```python
class Person:
def __init__(self, name):
self.name = name
def say_name(self):
print("Hello, my name is", self.name)
```
Python internally creates an object that has attributes and methods associated with it. This object is called a "type" in Python.
For example:
```python
person = Person("Tim")
print(type(person)) # Outputs:
```
As you can see, the `person` variable now holds an instance of our `Person` class, and we can use the `type()` function to get its type.
But here's where things get interesting. What if we want to create a new type that is similar to our existing `Person` type? We can do this by defining a new class that inherits from our existing type:
```python
class AdvancedPerson(Person):
pass
person1 = AdvancedPerson("Tim")
print(type(person1)) # Outputs:
```
In this example, we've defined a new class called `AdvancedPerson` that inherits from our original `Person` class. We don't need to define any additional methods or attributes for this type - it simply inherits everything from the parent class.
Now, let's talk about how Python actually implements the `__init__` method at a lower level. When we define an `__init__` method like this:
```python
class Person:
def __init__(self, name):
self.name = name
def say_name(self):
print("Hello, my name is", self.name)
```
Python internally creates a special function called `__new__` that is responsible for creating new instances of our class. The `__init__` method is then called on the newly created object to initialize its attributes.
For example:
```python
person = Person("Tim")
print(person.__class__.__name__) # Outputs: Person
def create_person(name):
return Person(name)
new_person = create_person("John")
print(new_person.__class__.__name__) # Outputs: Person
```
In this example, we've defined a new function called `create_person` that creates an instance of our `Person` class. We can use the `__class__` attribute to get the type of the newly created object.
As you can see, Python provides a powerful set of tools for creating custom classes and methods. By understanding how these concepts work at a lower level, we can create more complex and flexible software systems that are tailored to our specific needs.