Merge branch 'master' of github.com:thomasabishop/computer-science

This commit is contained in:
thomasabishop 2023-02-17 18:50:30 +00:00
commit 11acfb8221
8 changed files with 332 additions and 19 deletions

View file

@ -1,17 +0,0 @@
# BBC Python Course notes
## Day 2
With lists you have to use copy if you wish to make a new version. You cannot just reassign to a new version. This will still update the original. Since it copies the pointer.
Distinguish functions that will create new list and methods which will modify existing list
Functions: named parameter passing, use for default parameter values
Python does not have constants but has a convention of upper case to mimic constants
More on addresses and pointers in Python
With classes we don't need to use `new` when instantiating an instance of a class.
You do not need to define properties in classes if they exist in the constructor

View file

@ -0,0 +1,106 @@
---
categories:
- Programming Languages
tags: [python, OOP]
---
# Python modules
Modules in Python are very similar to modules in Node.js: a mix of several pieces of functionality (often written in a mix of syntactic styles) bundled together to be used as a unit.
## Creating modules
### Naming modules
The name of a module is the name of its file, e.g. `utils.py` defines the module `utils`. When we import a module we do not need to specify the extension.
### Example module
Here is an example of a module. We will use this throughout.
```py
"""utils module"""
print('Hello I am the utils module')
def printer(some_object):
print('printer')
print(some_object)
print('done')
class Shape:
def __init__(self, id):
self.id = id
def __str__(self):
return 'Shape - ' + self.id
default_shape = Shape('square')
def _special_function():
print('Special function')
```
## Importing modules
To use the module defined in `utils.py`:
### Method one
```py
import utils
```
Now we can use the functionality provided by the module, e.g:
```py
import utils
utils.printer(utils.default_shape)
shape = utils.Shape("circle")
```
Here we have invoked the `printer()` function contained in the module. We have used it to print the `default_shape` variable in `utils` which is itself and instance of the `Shape` class, also defined in `utils`.
After this we have created a new instance of the `Shape` class in the file that imports `utils`.
### Method two
In the last example we used the name `utils` to reference our import. I prefer this because it shows the origin of the code you are using. But if we imported like so:
```py
from utils import *
```
Then we wouldn't need to prepend our use of `utils` functions with `utils`. Instead out code would look like this:
```py
from utils import *
printer(default_shape)
shape =Shape("circle")
```
### Importing a subset of the module
It doesn't look like you need to explicity export the values you want to be able to use once your module has been imported in Python. Everything seems to be exported and accessible.
However, you can choose to import only a subset of the module:
```py
from utils import Shape
```
And you can use an alias, e.g:
```py
from util import Shape as banana
```

View file

@ -0,0 +1,68 @@
---
categories:
- Programming Languages
tags: [python, OOP]
---
# Class inheritance in Python
We distinguish the **parent/source/superclass** class and the **subclass** that **extends** it.
A subclass can have more than one parent class that it extends, but this is atypical.
> The subclass will inherit all attributes and methods of the superclass unless it overrides them.
In addition to overriding the methods of the parent we can extend them in the child. This is a bit like using an abstract.
## Example of class inheritance
```py
class Person:
""" Our superclass
"""
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
print('Happy birthday you were', self.age)
self.age += 1
print('You are now', self.age)
def __str__(self):
return self.name + ' is ' + str(self.age)
class Employee(Person):
""" Our subclass that extends `Person`
"""
def __init__(self, name, age, id):
super().__init__(name, age)
self.id = id
def calculate_pay(self, hours_worked):
rate_of_pay = 7.50
if self.age >= 21:
rate_of_pay += 2.50
return hours_worked * rate_of_pay
def __str__(self):
return super().__str__() + ' - id(' + str(self.id) + ')'
```
### Key points
- We pass the name of the parent class to the child class as a parameter
- We must include all of the parent class's attributes in the constructor of the subclass
- We must use a special `super().__init__(..,...)` method to enact the extension of the parent. This method references the `__init__` class in the parent.
## Protected/private methods and attributes
> Protected methods and attributes are private to the class but can be accessed from any subclass
> Private methods cannot be accessed from anywhere outside of the class
We declare private methods and attributes with a double underscore (`__`). We declare protected methods and attributes with a single underscore (`_`).
This is **convention**, there are not actually private and protected attributes or properties.

View file

@ -0,0 +1,138 @@
---
categories:
- Programming Languages
tags: [python, OOP]
---
# Classes in Python
## General points
- We don't need to use `new` when instantiating an instance of a class.
- All properties and methods must be defined in the constructor. This is in contrast to JavaScript where the properties in the constructor are those we want to initialise when the object is created and where there may be other properties and methods that are used outside of the instantiation process.
- In contrast to TypeScript and other OOP languages it is not necessary to declare the properties you wish to define in the constructor, outside of the constructor. As long as they are defined in the constructor they are accessible.
## Basic syntax
```py
class Person:
""" An example class to hold a persons name and age"""
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
print('Happy birthday you were', self.age)
self.age += 1
print('You are now', self.age)
p1 = Person('John', 36)
p2 = Person('Thomas', 34)
print(p1)
print(p2)
print(p1.name)
# John
```
Key points to note:
- The `__init__` method is the constructor function and must exist on every class to define the properties of the class
- `self` is a reference to the class itself and the object it will create, akin to `this` in other languages
- You must pass `self` as a parameter to every method (this is a difference from JS)
- As with functions, we can use docstrings to document the class. What you write here will show up in Intellisense etc
## More complex class
```py
class Person:
""" An example class to hold a persons name and age"""
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name + ' is ' + str(self.age)
def birthday(self):
print ('Happy birthday you were', self.age)
self.age += 1
print('You are now', self.age)
def calculate_pay(self, hours_worked):
rate_of_pay = 7.50
if self.age >= 21:
rate_of_pay += 2.50
return hours_worked * rate_of_pay
def is_teenager(self):
return self.age < 20
```
## Object references
When you log a class you get a reference to its hexadecimal [memory](/Computer_Architecture/Memory/Memory.md) reference.
```py
p1 = Person('John', 36)
p2 = Person('Thomas', 34)
print(p1)
print(p2)
# <__main__.Person object at 0x102e75510>
# <__main__.Person object at 0x102e75511>
```
This shows each object is unique. You can also generate a specific ID with the `id()` method:
```py
print(id(p1))
print(id(p2))
# 4379088272
# 4379088656
```
## Copying objects
The same principle that applies to [copying functions](/Programming_Languages/Python/Syntax/Functions_in_Python.md) applies to copying objects created through classes: redeclaration results in a duplicate entity. Thus changes to the duplicate will affect the original.
This becomes obvious when you use `id()` but otherwise might not be apparent. To copy you should therefore declare a new instance of the class.
### The `str` method
The memory reference isn't very helpful for viewing the data contents. To get around this we can use the builtin `__string__` method which automatically logs whatever properties you put in there:
```py
class Person:
""" An example class to hold a persons name and age"""
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name + ' is ' + str(self.age)
p3 = Person('Thomas', 34)
print(p3)
# Thomas is 34
```
## Deleting objects
You might want to delete an object reference because:
- the variable referencing the object goes out of scope
- the variable is set to `None`
After the `del` statement is applied to a variable that holds an object, the object will no longer be available and any attempt to reference it will result in an error.
```py
p1 = Person('J-Man', 76)
del p1
```

View file

@ -15,10 +15,14 @@ They are most often used unnamed with the functional methods [map, filter and re
Here is the two syntaxes side by side: Here is the two syntaxes side by side:
```js ```js
// JavaScript
const double = (x) => x * x; const double = (x) => x * x;
``` ```
```py ```py
# Python
double = lambda x: x * x double = lambda x: x * x
``` ```

View file

@ -18,6 +18,14 @@ Lists have the following properties:
> Lists are denoted with `[...]` > Lists are denoted with `[...]`
## Duplicating lists
When we want to duplicate a list, we can't just reassign the list to a new variable and expect this to be a copy.
If we edit this "copy" it will update the original list since it copies the pointer and will therefore point to the same address in memory. Instead we have to use the List `copy()` function which returns a new list and doesn't modify the original list.
Relatedly, we should distinguish those List methods and functions which create a new list (non-destructive) and those that mutate the existing list.
## Basic usage ## Basic usage
```python ```python

View file

@ -15,3 +15,7 @@ To name a variable or method with multiple words we use underscores to separate
an_int = 32 an_int = 32
print(an_integer.is_integer) # true print(an_integer.is_integer) # true
``` ```
## Constants
There are no constants in Python. However there a pseudo-constants established by convention. To denote that a variable should not change you use `UPPER_CASE`.

View file

@ -15,10 +15,10 @@
BBC Course, remaining topics: BBC Course, remaining topics:
- Classes and object-oriented paradigms in Python - Entrypoint modules
- Modules
- Error handling - Error handling
- Testing - Testing
- I/O
## Bash ## Bash
@ -59,6 +59,8 @@
## NodeJS ## NodeJS
- Build examples of read stream and write stream - Build examples of read stream and write stream
- Can you override parent class methods in JS subclass?
- If not in JS, can you do this in TS?
## Git ## Git