python-crash-course-book/chap9_classes.md

353 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Chapter 9. Classes
Objectives:
* Making an object from a class is called **instantiation**, and you work with **instances of a class**.
* In this chapter youll write classes and create instances of those classes.
* Youll specify the kind of information that can be stored in instances, and youll define actions that can be taken with these instances.
* Youll also write classes that extend the functionality of existing classes, so similar classes can share code efficiently.
* Youll store your classes in modules and import classes written by other programmers into your own program files.
## Creating and using a class
```python
class Dog:
"""A Simple attempt to model a dog"""
def __init__(self, name, age):
"""Initialize name and age attributes."""
self.name = name
self.age = age
def sit(self):
"""Simulate a dog sitting in response to a command."""
print(f"{self.name} is now sitting.")
def roll_over(self):
"""Simulate rolling over in response to a command"""
print(f"{self.name} rolled over!")
```
### __init__() method
* A special method python run automatically whenever we create a new instance.
* `self` in parameters is required in method definition, always come first
* So method call will automatically pass the `self` argument, it's a reference to itself.
### Making an Instance from a Class
```python
my_dog = Dog('Willie', 6)
(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
```
### Accessing Attributes
```python
my_dog.name
```
### Calling Methods
```python
class Dog:
--snip--
my_dog = Dog('Willie', 6)
my_dog.sit()
my_dog.roll_over()
```
### Creating Multiple Instances
```python
class Dog:
--snip--
my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
my_dog.sit()
print(f"\nYour dog's name is {your_dog.name}.")
print(f"Your dog is {your_dog.age} years old.")
your_dog.sit();
```
## Working with Classes and Instances
After we create class, we can edit object
* need write **getter/setter functions** to access attributes
* may need default value for attribute
```python
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
"""Initialize attributes to describe a car."""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""Return a neatly formatted descriptive name."""
long_name = f"{self.year} {self.make} {self.model}"
return long_name.title()
def read_odometer(self):
"""Print a statement showing the car's mileage."""
print(f"This car has {self.odometer_reading} miles on it.")
def update_odometer(self, mileage):
"""Set the odometer reading to the given value."""
self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
```
* `get_descriptive_name()` is a getter function, so we can get private attributes
* `odometer_reading` has default value as 0
## Inheritance
Use **inheritance** when the class is a specialized version of another class.
* When one class *inherits* from another, it takes on the attributes and methods of the first class.
* Original class is called **parent class**, New class is called **child class**
* Child class can define new attributes/methods
### __init__() for a Child Class
Creating a `__init__()` of a child class:
1. Call `__init__()` from parent class use `super().__init__()`, to initialize parent's attributes (as shown below)
2. Add specialized attributes
```python
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""Initialize attributes of the parent class."""
super().__init__(make, model, year)
```
* `super()` is a special function that allows us to call a method from parentclass. It will initialize all inherited attributes for ElectricCar
### Defining Attributes and Methods for the Child Class
Add attributes/methods for child class
```python
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
"""
super().__init__(make, model, year)
self.battery_size = 75
def describe_battery(self):
"""Print a statement describing the battery size."""
print(f"This car has a {self.battery_size}-kWh battery.")
```
* `self.battery_size` is attribute unique to ElectricCar, not for any parent class
* You can add attribute/method for child class as many as you want.
### Overriding Methods from Parent Class
Python can disregard parent class's method and only use redefined method in child class
```python
class Car:
"""A simple attempt to represent a car."""
def __init__(self, make, model, year):
"""Initialize attributes to describe a car."""
...
self.gas_tank = 0
...
def fill_gas_tank(self, gas):
"""Add gas into tank"""
self.gas_tank = self.gas_tank + gas
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
...
def fill_gas_tank(self):
"""Electric cars don't have gas tanks"""
print("This car doesn't need gas tank!")
```
* `fill_gas_tank()` override parent method, with argument list changed
### Instance as Attributes
Sometimes we can use an instance of an class (i.e. an object) as an attribute of another class.
e.g. After grouping multiple all battery related attributes/method into a new class
```python
class ElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""
def __init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
"""
super().__init__(make, model, year)
self.battery = Battery()
...
class Battery:
"""A simple attempt to model a battery for an electric car"""
def __init__(self, battery_size=75):
"""initialize the battery's attributes"""
self.battery_size = battery_size
def describe_battery(self):
"""Print a statement describing the battery size"""
print(f"This car has {self.battery_size}-kWh battery.")
def get_range(self):
"""Print a statement about the range this battery provides"""
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge.")
```
Calling these functions are also simple
```python
print(my_tesla.get_descriptive_name())
print(my_tesla.battery.describe_battery())
print(my_tesla.battery.get_range())
```
### Modeling Real-World Objects
Question: `get_range()` is this property of battery or car? Need think on higher-level.
ANS: No right or wrong approach. Sometimes it's more efficient, sometimes not.
## Importing Classes
Python lets you store classes in modules and then import the classes you need into your main program.
### Importing a Single Class
e.g.
1. after separating `Car` class into a python script `car.py` (i.e. **modules**)
2. New python script can reuse class
```python
from car import Car
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
```
### Storing Multiple Classes in a Module
A module (.py script) can stores as many classes as it can. Although these classes should be related.
e.g.
1. add `ElectricCar(Car)` & `Battery` into `car.py` module script
2. New python script can reuse these classes
```python
from car import ElectricCar
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
```
### Importing Multiple Classes from a Module
```python
from car import Car, ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
```
### Importing an Entire Module
```python
import car
my_beetle = car.Car('volkswagen', 'beetle', 2019)
```
### Importing All Classes from a Module
```python
from module_name import *
```
### Importing a Module into a Module
```python
from car import Car
...
```
### Using Aliases
Using Aliases can make coding easier (less wordy)
```python
from electric_car import ElectricCar as EC
```
### Finding Your Own Workflow
* Keep project simple/flat
* When start out, keep code structure simple.
* Try doing everything in one file and moving classes to separate modules once everything is working.
## Python Standard Library
Python has modules in python std library for use. Use them just like normal customized library
```python
from random import randint
randint(1,6)
```
## Styling Classes
Following PEP-8:
* Class names should be written in *CamelCase*
* Every class should have a docstring immediately following the class definition.
* Use blank lines to organize code, but don't over-use them.
* Sequence of importing modules: Python Standard Library + blanklines + customized modules