Class
A class, in programming, is a blueprint for creating objects. It defines the properties and functions that an object will have. Classes provide a way to organize and structure code in an object-oriented programming (OOP) paradigm.
Python
Define a class using the class
keyword, followed by the name of the class. The naming convention for classes is to use CamelCase notation.
class MyClass:
# class variables
class_var = "This is a class variable"
# constructor (optional)
# instance variables
def __init__(self, arg1, arg2):
self.instance_var1 = arg1
self.instance_var2 = arg2
# instance method
def instance_method(self):
print("This is an instance method")
Create an object (instantiate)
To use a class, create an object or instance of that class. This is done by calling the class name followed by parentheses.
obj = MyClass(arg1_value, arg2_value)
Access class variables and instance variables
Class variables can be accessed directly using the class name, while instance variables are accessed through the object.
MyClass.class_var # access class variable
obj.instance_var1 # access instance variable
Call instance methods
Instance methods are called on the object and can access the instance variables and class variables.
obj.instance_method() # call instance method
Inheritance
Class inheritance is a mechanism that allows a class to inherit properties and methods from another class. The class that is being inherited from is called the parent or superclass, and the class inheriting from it is called the child or subclass.
To inherit from a class, the child class is defined using the following syntax:
class ChildClass(ParentClass):
# child class definition
The child class now has access to all the properties and methods of the parent class, and can also define its own additional properties and methods. This allows for code reuse and the ability to create specialized classes that inherit and extend functionality from a more general class.
class Animal:
def __init__(self, name):
self.name = name
def sound(self):
pass
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def sound(self):
return "Woof!"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name)
self.color = color
def sound(self):
return "Meow!"
Creating instances of the child classes
dog = Dog("Buddy", "Labrador")
cat = Cat("Simba", "Orange")
print(dog.name) # Output: Buddy
print(dog.breed) # Output: Labrador
print(dog.sound()) # Output: Woof!
print(cat.name) # Output: Simba
print(cat.color) # Output: Orange
print(cat.sound()) # Output: Meow!
In this example, the Dog
and Cat
classes inherit from the Animal
class. They both override the sound()
method defined in the parent class, providing their own implementation. The child classes also define additional properties specific to their type of animal.
Dataclass
A data class is a type of class leveraging the `dataclasses` module. It provides a convenient way to define classes that are primarily used to hold data, similar to structs in other programming languages.
Use a dataclass by:
- Import the
dataclasses
. - Decorate the class with
@dataclass
- Add the \`@dataclass\` decorator just above the class declaration. This decorator will automatically generate common methods such as \`\_<sub>init</sub>\_<sub>()</sub>\`, \`\_<sub>repr</sub>\_<sub>()</sub>\`, \`\_<sub>eq</sub>\_<sub>()</sub>\`, etc., based on the class attributes.
- Define class attributes:
- Inside the class, define the attributes that represent the data you want to store. Each attribute is defined by assigning a value like you would in a regular class.
Customize the behavior (optional):
- You can customize the behavior of the generated methods by specifying various class decorators, such as \`@property\`, \`@staticmethod\`, and \`@classmethod\`.
from dataclasses import dataclass
@dataclass class Person: name: str age: int country: str = "Unknown" # You can also provide default values
def greet(self): print(f"Hello, I'm {self.name} from {self.country}!")
In the above example, the \`Person\` class is defined as a data class using the \`@dataclass\` decorator. It has three attributes: \`name\`, \`age\`, and \`country\`. The \`country\` attribute is assigned a default value of "Unknown". It also has a method called \`greet()\` that prints a greeting.
The main difference between a data class and a normal class is that a data class automatically generates methods such as \`\_<sub>init</sub>\_<sub>()</sub>\`, \`\_<sub>repr</sub>\_<sub>()</sub>\`, \`\_<sub>eq</sub>\_<sub>()</sub>\`, etc., based on the class attributes. This saves you from having to write these methods yourself. Additionally, data classes promote immutability, meaning their attributes are typically declared as \`read-only\`, which prevents accidental modification of the data.
Managed Attributes (@property)
> With Python’s property(), you can create managed attributes in your classes. You can use managed attributes, also known as properties, when you need to modify their internal implementation without changing the public API of the class. Providing stable APIs can help you avoid breaking your users’ code when they rely on your classes and objects. — [RealPython](https://realpython.com/python-property/)
The `@property` decorator is used in Python to transform a method into a read-only attribute. It allows you to define a method that behaves like an attribute, so you can access it without using parentheses, as if it were a regular attribute.
When the `@property` decorator is applied to a method within a class, it converts the method into a "getter" method. This means that when the property is accessed, the method is automatically called and its return value is returned instead. Essentially, `@property` enables you to define a method that can be accessed like an attribute, providing a more user-friendly and intuitive interface.
Syntax: `property(fget=None, fset=None, fdel=None, doc=None)`
- fget: function to return the value of the managed attribute
- fget: function to set the value of the managed attribute
- fdel: function to handle managed attribute deletion
doc: string representing the property's docstring
class Circle: def _init_(self, radius): self.radius = radius
@property def radius(self): return self.radius
@property def area(self): return 3.14 * (self.radius ** 2)
In the code above, the `radius` method is decorated with `@property`, transforming it into a property. Now, instead of accessing the radius as `circle.radius()`, you can simply access it as `circle.radius`. Similarly, the `area` method is decorated with `@property` too, allowing you to access it as `circle.area`.
Note that `@property` is typically used with getter methods, but not restricted to them. You can also define `setter` and `deleter` methods for a property using additional decorators - `@<propertyname>.setter` and `@<propertyname>.deleter`, respectively. These are used to modify the value of the property or delete it, providing more control and encapsulation for managing attributes within a class.
@staticmethod
The `@staticmethod` decorator in Python is used to define a static method within a class. A static method is a method that belongs to the class itself rather than an instance of the class. This decorator allows a method to be called on the class directly, without creating an instance of the class.
When a method is defined as a static method using the `@staticmethod` decorator, it does not receive any attributes related to the class or the instance (such as `self` in regular instance methods). Instead, it behaves similarly to a regular function, receiving only the arguments passed to it.
Static methods are typically used when a method does not require access to instance-specific or class-specific data and can be called directly on the class itself. These methods provide utility-like behavior, which is independent of any specific instance or state of the class.
@classmethod
The `@classmethod` decorator in Python is used to define a class method within a class. A class method is a method that is bound to the class rather than an instance of the class. It receives the class itself as the first argument, conventionally named `cls`, instead of the instance itself for regular methods.
The `@classmethod` decorator allows a method to be called on the class directly, without creating an instance of the class. This can be useful for methods that need to access or manipulate class-specific data, without requiring access to instance-specific data.
When a method is defined as a class method using the `@classmethod` decorator, it receives the class as the first argument, allowing it to access and modify class-level variables or invoke other class methods. Class methods can also create new instance objects of the class if needed.
Class methods are often used for alternative constructors or factory methods, where they provide a way to create instances of a class with different initialization parameters or setups, without having to rely solely on the primary constructor.
In summary, the `@classmethod` decorator is used to define a method that operates on the class itself rather than an instance. It provides a way to manipulate class-specific data and create alternative constructors or factory methods.
Class Terms:
- attributes are variables that belong to a class. Unlike class attributes, you can’t access instance attributes through the class. You need to access them through their containing instance.
- methods are functions that belong to a class.
- class._init__: The constructor of a class. Self is used to refer to the instance of the class.
- class.attribute: A variable that is shared by all instances of a class.
- In general, you should use class attributes for sharing data between instances of a class. Any changes on a class attribute will be visible to all the instances of that class.
- leading with an underscore: A convention to indicate that the attribute or method is intended to be private.
- leading with two underscores: mangles the name and prevents use from outside the class
- _str__: A special method that is called when an instance of a class is printed. Use to give a friendly name.
- @classmethod: A function that is defined inside a class and is used to modify the state of an instance. A class method receives the class as an implicit first argument, just like an instance method receives the instance.
- @staticmethod: A method that is bound to a class rather than its object. A static method does not receive an implicit first argument. A static method is also a method that is bound to the class and not the object of the class. This method can’t access or modify the class state. It is present in a class because it makes sense for the method to be present in class.
- Use `isinstance()` to check if an object is an instance of a class.
- Use `issubclass()` to check if a class is a subclass of another class.
- vars() returns the dict attribute of the given object.
- Property and Descriptor Based Attributes:
Data Classes:
- Use `field` with `defaultfactory` to create a mutable default value.
- Data class decorator takes the following parameters:
- init: Add ._init_() method? (Default is True.)
- repr: Add ._repr_() method? (Default is True.)
- eq: Add ._eq_() method? (Default is True.)
- order: Add ordering methods? (Default is False.)
- unsafehash: Force the addition of a ._hash_() method? (Default is False.)
- frozen: If True, assigning to fields raise an exception. (Default is False.)
- class._postinit_() is called after the init method.
- Field support the types: int, float, str, bytes, bool, tuple, list, dict, set, frozenset, array, datetime, uuid
- Field supports the following parameters:
- default: Default value of the field
- defaultfactory: Function that returns the initial value of the field
- init: Use field in ._init_() method? (Default is True.)
- repr: Use field in repr of the object? (Default is True.)
- compare: Include the field in comparisons? (Default is True.)
- hash: Include the field when calculating hash()? (Default is to use the same as for compare.)
- metadata: A mapping with information about the field
Tuple: - is a collection which is ordered and unchangeable. Allows duplicate members.
- Create tuple >>> t = (1, 2, 3)
- Concatenate two tuples >>> t + (4, 5, 6)
- Multiply a tuple >>> t * 2
Unpacking tuple >>> a, b, *c = t
- If the asterisk is added to another variable name than the last, Python will assign values to the variable until
the number of values left matches the number of variables left.
- delete tuple >>> del t
- Tuple methods:
- count: Returns the number of times a specified value occurs in a tuple.
- index: Searches the tuple for a specified value and returns the position of where it was found.
Named Tuples
- Gives more readable code. Use dot notation to access.
Example of class usage
class DemoOfClassInstantiation:
def __new__(cls, *args, **kwargs):
print("1. Create new instance at Point.")
return super().__new__(cls)
def __init__(self, x, y):
print("2. Initialize the new instance at Point.")
self.x = x
self.y = y
def __repr__(self) -> str:
return f"{type(self).__name__}(x={self.x}, y={self.y})"
class DemoOfClassUsingAnotherClassAsNew:
def __new__(cls, *args, **kwargs):
return DemoOfClassInstantiation(42, 24)
def __init__(self, b_value):
print("Initialize a new instance of B, which is never done.")
self.b_value = b_value
class Rectangle:
def __init__(self, width, height):
if not (isinstance(width, (int, float)) and width > 0):
raise TypeError("Width must be a positive integer or float value.")
self.width = width
if not (isinstance(height, (int, float)) and height > 0):
raise TypeError("Height must be a positive integer or float value.")
self.height = height
@property
def area(self):
return self.width * self.height
class Person:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = birth_date
def __repr__(self):
return f"{type(self).__name__}(name={self.name}, birth_date={self.birth_date})"
def __str__(self):
return f"{self.name} was born on {self.birth_date}"
class Employee(Person):
def __init__(self, name, birth_date, position):
super().__init__(name, birth_date)
self.position = position
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __repr__(self):
class_name = type(self).__name__
return f"{class_name}(title={self.title!r}, author={self.author!r})"
def __str__(self):
return self.title
Swift
A custom data type that can have one or more properties and one or more methods. Unlike structs, classes are reference types.
Similar to structs since they allow to create properties and methods, but they are different in 5 other ways.
\#+begin<sub>src</sub> swift class Employee { let hours: Int
init(hours: Int) { self.hours = hours }
func printSummary() { print("I work \\(hours) hours a day") } }
class Developer: Employee { func work() { print("I'm writing code for \\(hours) hours.") } }
class Manager: Employee { func meet() { print("I'm going to meetings for \\(hours) hours.") }
final func work() { // Final: consider to use as default print("I'm doing kind of work for \\(hours) hours.") }
override func printSummary() { // Override: use override to override a parents function print("I'm cruising \\(hours) hours a day.") } }
let kim = Developer(hours: 5) kim.work() kim.printSummary()
<a id="orgf359d94"></a>
## Override
If the function in the child class is using different arguments or return type, then is it not necessary to use the override keyword.
let kristian = Manager(hours: 6) kristian.work() kristian.meet() kristian.printSummary()
<a id="orgd67da9c"></a>
## Final
Final means that nothing will be able to inherit from this thing
<a id="orgd885e7c"></a>
## Adding initializers
class Vehicle { let isElectric: Bool
init (isElectric: Bool) { self.isElectric = isElectric } }
<a id="orga7ec8f0"></a>
## Super
Use super to initialize.
class Car: Vehicle { let isConvertible: Bool
init(isElectric: Bool, isConvertible: Bool) { self.isConvertible = isConvertible super.init(isElectric: isElectric) } }
let teslaX = Car(isElectric: true, isConvertible: false)
<a id="org83de8a7"></a>
## Copy
Copy a class changes all the other classes too.
class User { var username = "Anonymous"
func copy() -> User { // this creates a deep copy let user = User() user.username = username return user } }
var user1 = User() var user2 = user1 print(user2.username) user2.username = "Taylor" print(user1.username) print(user2.username)
If you want to make a copy then you need to create a deep copy
let user3 = user1.copy()
user3.username = "kippe" print(user3.username) print(user2.username)
<a id="org4d4efa7"></a>
## Deinitializer
Is called when the last copy of an instance of a class is destroyed
- Don't use func keyword
- Can't take parameters or return data
- Never call deinitializers directly
Structs don't have deinitializers
class Programmer { let id: Int
init(id: Int) { self.id = id print("User \(id): I'm alive!") }
deinit { //can't take any parameters print("User \(id): I'm dead!") } }
let a = Programmer(id: 1) let b = Programmer(id: 2)
for i in 1…3 { let programmer = Programmer(id: i) print("User \(programmer.id): I'm in control!") }
It is not possible to change an instance of a class which is instantiated with "let". It is however still possible to change a "var" inside of that class.
f we make the instantiated class a variable, then is it possible to "make it point to something else entirely"
var user = User() user.username = "Mupp" print(user.username) user = User() print(user.username)
<a id="org8e1b60b"></a>
## Property observer
Code added to a property using willSet and didSet, that gets called whenever a property is being changed.
<a id="org0ce5851"></a>
## Property wrapper
An attribute placed around a property that gives it custom abilities. For example, you could write a @UserDefaults property wrapper to make loading and saving data to user defaults easier.
<a id="orgea2d79a"></a>
## Create initializer
You must always create your own initializer for your classes.
"init" should not have a "func" in front of it.
class Dog { var name: String var breed: String init(name: String, breed: String) { self.name = name self.breed = breed } func makeNoise() { print("Woof!") } }
let poppy = Dog(name: "Poppy", breed: "Poodle")
Playing with classes - creating a BlogPost class.
class BlogPost { var title: String var body: String var author: String var numberOfComments: Int = 0 init(title: String, body:String, author:String) { self.title = title self.body = body self.author = author } func addComment() { numberOfComments += 1 } }
let blogPost1 = BlogPost(title: "hej", body: "Det här är en bra blog post", author: "Puppe") blogPost1.addComment() print(blogPost1.numberOfComments) let blogPost2 = BlogPost(title: "hej igen", body: "Det här är en till bra blog post", author: "Puppe") print(blogPost2.numberOfComments)
<a id="org8e2b3ba"></a>
## Convenience initializer
They are used to make it easier to init. They use `self.init()` to get started.
<a id="org06e4d5d"></a>
## Class inheritance (or subclassing)
The child- subclass inherits all properties and methods.
Using :Dog same properties and initializer as class "Dog".
class Poodle: Dog { init(name: String) { super.init(name: name, breed: "Poodle") } }
This makes the "Poodle" initializer call the "Dog" initializer directly, which causes all the setup to happen. You always have to use "super" when calling from a child class
<a id="org90fa6b2"></a>
## Overriding methods
Use "override" to make a child class have it's method do something else than the parent.
class Terjer: Dog { init(name: String) { super.init(name: name, breed: "Terjer") } override func makeNoise() { print("Yip!") } }
let terjer = Terjer(name: "lutan") terjer.makeNoise()
<a id="org05e5545"></a>
## Final classes
Makes it impossible (for other developers) to build upon your class.
final class Labrador: Dog { }
<a id="org9e2d23b"></a>
## Copying objects
When copying in a class both objects point to the same thing (in memory) so both instances are changed. This is not true for a struct, which always points to different objects in memory.
class Singer { //if this was a struct then the name would be only Taylor Swift var name = "Taylor Swift" }
var singer = Singer() print(singer.name)
var singerCopy = singer singerCopy.name = "Justin Bieber" print(singer.name)
<a id="org1e2c08c"></a>
## Deinitializers
Code that get's run when an instance of a class is destroyed.
class Person { var name = "John Doe" init() { print("\(name) is alive!") } func printGreeting() { print("Hello, I'm \(name)") } deinit { print("\(name) is no more.") } } for _ in 1…3 { let person = Person() person.printGreeting() }
<a id="orgbbb72c1"></a>
## Mutability
A property on a struct can't be changed since the struct iself is a constant. This is not the case with classes.
class Sångaren { var name = "Taylor Swift" //this can be changed let constantName = "Muppet" //this can't be changed }
let taylor = Sångaren() taylor.name = "Ed Sheeran" print(taylor.name) taylor.constantName = "maffioso" print(taylor.constantName)
<a id="org5bfff0d"></a>
## @objc
@objc makes the class available for the objective-c environment to run.
class Sångare { var name: String var age: Int init(name: String, age: Int) { self.name = name self.age = age } @objc func sing() { print("La la la la la laaaa!") } }
Makes it possible for the system to call my method after, for example, one second has passed
Use "@objcMembers" to make the whole class available to the OS.
@objcMembers class Drummer { var name: String var age: Int init(name: String, age: Int) { self.name = name self.age = age } func sing() { print("La la la la la laaaa!") } }
<a id="orge3712bf"></a>
## Polymorphism
Since a sub-class inherits from it's parent it means that it is possible to work with both the sub-class (B) and the parent class (A) at the same time
class Album { var name: String init(name: String) { self.name = name } func getPerformance() -> String { return "The album \(name) sold well" } }
class StudioAlbum: Album { var studio: String init(name: String, studio: String) { self.studio = studio super.init(name: name) } override func getPerformance() -> String { return "The studio album \(name) sold well" } }
class LiveAlbum: Album { var location: String init(name: String, location: String) { self.location = location super.init(name: name) } override func getPerformance() -> String { return "The live album \(name) sold well in \(location)" } }
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The castles studios") var fearless = StudioAlbum(name: "speak now", studio: "aimeed homeland") var iTunesLive = LiveAlbum(name: "itunes live from soho", location: "new york")
var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]
for album in allAlbums { print(album.getPerformance()) }
<a id="orgeb8610e"></a>
## Typecasting
Is useful if you know something that swift doesn't know. It comes in three forms. Downcasting is not converting an object, but just changes the way that swift treats the object.
- as?: optional downcasting
as!: forced downcasting
for album in allAlbums { let studioAlbum = album as? StudioAlbum }
for album in allAlbums { print(album.getPerformance()) if let studioAlbum = album as? StudioAlbum { print(studioAlbum.studio) } else if let liveAlbum = album as? LiveAlbum { print(liveAlbum.location) } }
<a id="org021096b"></a>
## Converting common types with initializers
let number = 5 let text = String(number) print(text)