Python classes - ghdrako/doc_snipets GitHub Wiki
class Task:
def __init__(self):
print("Creating an instance of Task class")
task = Task() # output: Creating an instance of Task class
The construction involves the sequential automatic invocation of two special methods under the hood: __new__
and __init__
. The __new__
method creates and returns (boldfaced) the new instance object, and the __init__
method doesn’t return anything. The reason for this difference in returning a value is that after you call __new__
, you need to refer to the instance object that you just created. Thus, if the __new__
method doesn’t return that new instance object, you can’t access and use it. By contrast, the __init__
method takes self as an argument; it refers to the new instance and manipulates the instance in-place.
task = Task()
- Create the new instance object
task = Task.__new__(Task)
- Complete the initialization process.
Task.__init__(task) # return None
From the syntax perspective, we’re not obligated to use self in __init__
. But we should use self anyway; using self in __init__
is a conven-tion, and every Python programmer should respect this convention.
The __init__
method is intended to complete the initialization process for the new instance object, particularly setting the essential attributes to the instance.
class Task:
def __init__(self, title, desc, urgency):
self.title = title
self.desc = desc
self.urgency = urgency
task = Task("Laundry", "Wash clothes", 3)
To inspect the new instance’s attributes, you can check the instance’s special attribute __dict__
.
print(task.__dict__)
# output: {'title': 'Laundry', 'desc':
three kinds of methods in a class:
- instance (note that the first parameter is self),
- static (using the @staticmethod decorator), and
- class (using the @classmethod decorator).
The hallmark of an instance method is that you set self as its first parametr.
class <name>:
def instance_method(self, arg1, arg2, arg3):
instance.instance_method(arg0, arg1, arg2)
To define a static method, we use the static-method decorator for the function within the body of the class.
from datetime import datetime
class Task:
@staticmethod
def get_timestamp():
now = datetime.now()
timestamp = now.strftime("%b %d %Y, %H:%M")
return timestamp
To call this method, we use the follow-ing pattern: CustomClass.static_method(arg0, arg1, arg2)
.
when you need to define utility-related methods that are inde-pendent of any instance object, you should use the @staticmethod decorator to cre-ate static methods.
In general, I recommend that you place a static method outside a class if it addresses a more general utility functionality than a class should handle. Taking the data processing library pandas as an example, the core data models are Series and DataFrame classes. One utility function, to_datetime, converts data to a date object. This function addresses a more general need; thus, it’s not imple-mented as a static method within Series or DataFrame.
The first hallmark of a class method is that you use cls
as its first parameter. Like self in an instance method, cls is not a keyword, and you can give this argument other applicable names, but it’s a convention to name it cls
, and every Python programmer should respect this convention.
Class method also uses the classmethod decorator—the second hallmark of a class method. The method is called a class method because it needs to access the attributes or meth-ods of the class.
class Task:
def __init__(self, title, desc, urgency):
self.title = title
self.desc = desc
self.urgency = urgency
self._status = "created"
@classmethod
def task_from_dict(cls, task_dict):
title = task_dict["title"]
desc = task_dict["desc"]
urgency = task_dict["urgency"]
task_obj = cls(title, desc, urgency)
return task_obj As
task_dict = {"title": "Laundry", "desc": "Wash clothes", "urgency": 3}
# create object using constructor
task = Task(task_dict["title"], task_dict["desc"], task_dict["urgency"])
# create obiect from dicta using class method
task = Task.task_from_dict(task_dict)
From a general perspective, a class method is used mostly as a factory method, meaning that this kind of method is used to create an instance object from a particular form of data.
The DataFrame is a spreadsheet-like data model in the pandas library. It has a couple of class methods—from_dict and from_records—that you can use to construct instance objects of the DataFrame class.
The convention in creating an access-control mechanism is to use underscores as the prefix for the attribute or method. A one-underscore prefix means protected, and a double-underscore prefix means private.
The property decorator creates a getter
class Task:
def __init__(self, title, desc, urgency):
self.title = title
self.desc = desc
self.urgency = urgency
self._status = "created"
@property
def status(self):
return self._status
def complete(self):
self._status = "completed"
property represents a read-only attribute. You can read it as shown in the preceding code snippet. You can’t set it, however, which is exactly what you want: to prevent users from setting status directly
class Task:
# __init__ stays the same
@property
def status(self):
return self._status
@status.setter
def status(self, value):
allowed_values = ["created", "started", "completed", "suspended"]
if value in allowed_values:
self._status = value
print(f"task status set to {value}")
else:
print(f"invalid status: {value}")
create a setter for this property by using @status.setter, which adopts the general form @property_name.setter. This setter is an instance method, which takes a value argument that stands for the value we want to assign to the property. In the body of the setter, we verify that the value is one of the four possibilities.
str is the method that underlies both the print function and the interpolation in an f-string.
repr is the method to use when you try to inspect an instance in an interactive console.
The string provided by repr is intended for debugging and development, so it’s for developers. Specifically, developers should be able to construct an instance literally from the string.
from enum import Enum
class Direction(Enum):
NORTH = 0
SOUTH = 1
EAST = 2
WEST = 3
all_directions = list(Direction)
for direction in Direction:
pass
from dataclasses import dataclass
@dataclass
class Bill:
table_number: int
meal_amount: float
served_by: str
tip_amount: float = 0 # field with default value
bill0 = Bill(5, 60.5, "John", 10)
Compared with immutable named tuples, the fields of data classes can be modified for each instance; thus, data classes are mutable.
The dataclass decorator cannot only be used by itself without any arguments, in the form of @dataclass, but it can also take additional arguments to provide custom-ized decoration behaviors. Some notable arguments include init and repr, which are set to True by default, meaning that we request that the dataclass decorator imple-ment init and repr. Among other arguments, one pertains to mutability: frozen. When you want your data class to be immutable, you should set frozen to True. The following code snippet shows the usage:
@dataclass(frozen=True)
class ImmutableBill:
meal_amount: float
served_by: str
immutable_bill = ImmutableBill(50, "John")
immutable_bill.served_by = "David" # ERROR: dataclasses.FrozenInstanceError: cannot assign ➥ to field 'served_by'
Creating a subclass of an existing data class
@dataclass
class BaseBill:
meal_amount: float
@dataclass
class TippedBill(BaseBill):
tip_amount: float
When you create an instance of the subclass, remember that the superclass’s fields come first, followed by the subclass’s fields. The order matters!
AVOIDING DEFAULT VALUES FOR THE SUPERCLASS
We have seen that a subclass of a data class uses all the fields from its superclass and its own fields, following the order superclass -> subclass.
that fields with default values must come behind those that don’t have default values. This requirement has an important implication: if a superclass has fields with default values, you must specify default values for each subclass’s fields. Otherwise, your code won’t worka.
In most cases, you may want to avoid setting default values for the superclass so that you’ll have more flexibility to implement your subclasses. If you do set default val-ues for the superclass, you must specify default values for the subclasses too:
@dataclass
class BaseBill:
meal_amount: float = 50
@dataclass
class TippedBill(BaseBill):
tip_amount: float = 0