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.

Instantiation

task = Task()

Detailed instantiation process

  • 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':

Method

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).

Instance method

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)

Static method

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.

Class method

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.

Creating read-only attributes with the property decorator

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

Verifying data integrity with a property setter

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_ and repr

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.

Class for enumerations

from enum import Enum 
class Direction(Enum):
  NORTH = 0  
  SOUTH = 1  
  EAST = 2  
  WEST = 3 
all_directions = list(Direction)
for direction in Direction:
  pass

Data classes

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
⚠️ **GitHub.com Fallback** ⚠️