UNCLASSIFIED

Skip to content
Snippets Groups Projects
Commit b3b63387 authored by Kevin Crotty's avatar Kevin Crotty
Browse files

update abc

parent 22b8e949
No related merge requests found
Pipeline #116628 passed with stage
in 10 seconds
......@@ -2,8 +2,9 @@
## Client and Interface Definition
* Interfaces in programming are abstract types that define a contract or a set of methods that a class must implement. They serve as a blueprint for classes, specifying what methods the class should have without dictating how these methods should be implemented.
* Client: Code that calls the interface
* **Interfaces:** in programming are abstract types that define a contract or a set of methods that a class must implement. They serve as a blueprint for classes, specifying what methods the class should have without dictating how these methods should be implemented.
* **Client:** Code that calls the interface
* Does not care about implementation and doesn't have to even be the same language (Think web API)
## Duck Typing Review
......@@ -22,41 +23,8 @@
* `__iter__`
* `__next__`
```python
class Duck:
def make_sound(self):
print("The duck quacks")
def swim(self):
print("The duck is swimming")
def fly(self):
print("The duck is flying")
class Penguin:
def make_sound(self):
print("The penguin makes whatever sound penguins make")
def swim(self):
print("The penguin is swimming")
class Ostritch:
def make_sound(self):
print("The ostrich honks")
for bird in [Duck(), Penguin(), Ostritch()]:
bird.make_sound() # Duck typed for make_sound()
# The duck quacks
# The penguin makes whatever sound penguins make
# The ostrich honks
for bird in [Duck(), Penguin(),]:
bird.swim() # Duck typed for swim()
# The duck is swimming
# The penguin is swimming
```
[duck.py](./duck.py)
Static type checking using the above concepts is not possible. That's where Abstract Base Classes and Protocols come in.
......@@ -79,28 +47,7 @@ The two common ways to create ABCs are:
* Using the `abc` module `from abc import ABC, abstractmethod`
* Using the `collections.abc` module `from collections.abc import Iterable, Collection`
```python
from abc import ABC, abstractmethod
class Bird(ABC):
@abstractmethod
def make_sound(self):
pass
class Duck(Bird):
def make_sound(self):
print("The duck quacks")
def fly(self):
print("The duck is flying")
class Penguin(Bird):
def make_sound(self):
print("The penguin makes whatever sound penguins make")
def swim(self):
print("The penguin is swimming")
```
[abc_1.py](./abc_1.py)
This is a generic ABC example. You don't "get" anything from inheriting from `ABC`. It's just a way to define a set of methods that must be implemented by any class that inherits from it. `@abstractmethod` is a decorator that is used to mark a method as abstract. You also can't create an instance of `Bird` because it is an abstract class.
......@@ -109,16 +56,7 @@ If either of the subclasses of `Bird` do not implement the `make_sound` method,
`TypeError: Can't instantiate abstract class Duck with abstract methods make_sound`
```python
from collections.abc import Iterable
def process_items(items: Iterable[int]) -> None:
for item in items:
print(item)
# This function can now accept lists, tuples, sets, or any custom iterable
```
[hinting.py](./hinting.py)
The collections.abc module is used to define a set of protocols that can be used to check if an object conforms to a protocol.
Since lists, tuples, sets, and other built-in types already implement the `__iter__` and `__len__` methods, they can be used as arguments to the `process_items()` function.
......@@ -132,37 +70,7 @@ This also enables static type checking with IDEs and other tools.
* If two object support a protocol (Python defined or user defined), then they are considered to be duck-typed and compatible.
* To use "capital P" Protocols, you need to import the `typing` module and use the `Protocol` class.
```python
from typing import Protocol, List
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self) -> None:
print("Drawing a circle")
class Square:
def draw(self) -> None:
print("Drawing a square")
class Triangle:
def draw(self) -> None:
print("Drawing a triangle")
def draw_shapes(shapes: List[Drawable]) -> None:
for shape in shapes:
shape.draw()
# Usage
shapes: List[Drawable] = [Circle(), Square(), Triangle()]
draw_shapes(shapes)
# Drawing a circle
# Drawing a square
# Drawing a triangle
```
[proto.py](./proto.py)
**Explanation:**
......@@ -197,4 +105,4 @@ External Resources:
Credits:
- [ArjanCodes YT](https://youtu.be/xvb5hGLoK0A?si=miPVoF7m2g63ZyZl)
- [ArjanCodes GitHub](https://github.com/ArjanCodes/2021-protocol-vs-abc/tree/main/protocol/iot)
- [ArjanCodes GitHub](https://github.com/ArjanCodes/2021-protocol-vs-abc/tree/main/protocol/iot)
\ No newline at end of file
from abc import ABC, abstractmethod
class Bird(ABC):
def __init__(self, species, color):
self.species = species
self.color = color
@abstractmethod
def make_sound(self):
pass
class Duck(Bird):
def __init__(self, color):
super().__init__("Duck", color)
def make_sound(self):
print("The duck quacks")
def fly(self):
print("The duck is flying")
class Penguin(Bird):
def __init__(self, color):
super().__init__("Penguin", color)
def make_sound(self):
print("The penguin makes whatever sound penguins make")
def swim(self):
print("The penguin is swimming")
class BrokenBird(Bird): # This class is incorrect!
def __init__(self, color):
super().__init__("BrokenBird", color)
# No make_sound method implemented!
# Demonstrate polymorphism
# birds = [Duck("white"), Penguin("black and white"), BrokenBird("gray")]
birds = [Duck("white"), Penguin("black and white")]
for bird in birds:
bird.make_sound()
if isinstance(bird, Duck):
bird.fly()
if isinstance(bird, Penguin):
bird.swim()
\ No newline at end of file
class Duck:
def __init__(self, color):
self.color = color
def make_sound(self):
print("The duck quacks")
def swim(self):
print("The duck is swimming")
def fly(self):
print("The duck is flying")
class Penguin:
def __init__(self, color):
self.color = color
def make_sound(self):
print("The penguin makes whatever sound penguins make")
def swim(self):
print("The penguin is swimming")
class Ostrich: # Fixed spelling
def __init__(self, color):
self.color = color
def make_sound(self):
print("The ostrich honks")
# Usage
for bird in [Duck("white"), Penguin("black and white"), Ostrich("brown")]:
bird.make_sound() # Duck typed for make_sound()
print('\n###\n')
for bird in [Duck("white"), Penguin("black and white")]:
bird.swim() # Duck typed for swim()
\ No newline at end of file
from collections.abc import Sequence, Mapping
def process_items(items: Sequence[int]) -> None:
for item in items:
print(item)
def process_mapping(mapping: Mapping[str, int]) -> None:
for key, value in mapping.items():
print(f"{key}: {value}")
# Usage with a list of integers
process_items([1, 2, 3, 4])
# Usage with a tuple of integers
process_items((10, 20, 30))
# Usage with a dictionary
process_mapping({"a": 1, "b": 2, "c": 3})
\ No newline at end of file
from typing import Protocol, List
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self) -> None:
print("Drawing a circle")
class Square:
def draw(self) -> None:
print("Drawing a square")
class Triangle:
def draw(self) -> None:
print("Drawing a triangle")
def draw_shapes(shapes: List[Drawable]) -> None:
for shape in shapes:
shape.draw()
# Usage
shapes: List[Drawable] = [Circle(), Square(), Triangle()]
draw_shapes(shapes)
\ No newline at end of file
import argparse
def ex1():
def add(a, b):
return a + b
result1 = add(10, 20) # Works fine; returns 30
print(result1)
result2 = add("Hello, ", "World!") # Works fine; returns "Hello, World!"
print(result2)
result3 = add("Hello, ", 20) # Raises a TypeError
print(result3)
def ex2():
def add(a: int, b: int) -> int:
return a + b
result1 = add(10, 20) # Works fine; returns 30
result2 = add("Hello, ", "World!") # Works fine (Even with the str type); returns "Hello, World!"
result3 = add("Hello, ", 20) # Raises a TypeError
print(f"Typed add results: {result1}, {result2}")
# Note: result3 will raise an error, so we don't print it
def ex3():
def add(a, b):
if not (isinstance(a, int) and isinstance(b, int)):
raise TypeError("Both arguments must be of type int")
return a + b
result1 = add(10, 20) # Works fine; returns 30
try:
result2 = add("Hello, ", "World!") # Raises a TypeError
except TypeError as e:
print(f"TypeError occurred: {e}")
try:
result3 = add("Hello, ", 20) # Raises a TypeError
except TypeError as e:
print(f"TypeError occurred: {e}")
print(f"Strict add result: {result1}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Section A Examples")
parser.add_argument("function", choices=["ex1", "ex2", "ex3"], help="Choose which add function to run")
args = parser.parse_args()
if args.function == "ex1":
ex1()
elif args.function == "ex2":
ex2()
elif args.function == "ex3":
ex3()
\ No newline at end of file
"""
B. Invocation vs Inspection
- Invocation: calling a method on an object
- obj.method()
- Inspection: External code that examines an object
- isinstance(obj, type)
- issubclass(subclass, type)
- dir(obj)
- inspect.getmembers(obj)
- inspect.getsource(obj)
- inspect.getmodule(obj)
- inspect.getfile(obj)
- inspect.getsourcefile(obj)
- inspect.getsourcelines(obj)
- inspect.getargvalues(obj)
"""
import argparse
import inspect
from pprint import pformat
import io
import sys
class myObject:
def __init__(self, value):
self.value = value
def __str__(self):
return f"MyObject({self.value})"
def __repr__(self):
return f"MyObject({self.value})"
def __format__(self, format_spec):
return f"MyObject({self.value})"
def my_getattr(self, attr):
return f"MyObject({self.value})"
def my_setattr(self, attr, value):
self.value = value
def ex1():
def inspect_object(obj):
print(f"Object type: {type(obj)}")
print(f"Object attributes: {pformat(dir(obj))}")
# Capture help() output
old_stdout = sys.stdout
help_output = io.StringIO()
sys.stdout = help_output
help(obj)
sys.stdout = old_stdout
print("Help output:")
print(help_output.getvalue())
# Only try to get source code for supported types
if inspect.ismodule(obj) or inspect.isclass(obj) or inspect.ismethod(obj) or inspect.isfunction(obj) or inspect.iscode(obj):
try:
print(f"Object source code: {inspect.getsource(obj)}")
except TypeError:
print("Source code not available for this object type")
else:
print("Source code not available for this object type")
inspect_object(myObject)
# inspect_object(pformat)
# inspect_object("Hello")
# inspect_object([1, 2, 3])
# inspect_object({"a": 1, "b": 2})
def ex2():
pass
def ex3():
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Section B Examples")
parser.add_argument("function", choices=["ex1", "ex2", "ex3"], help="Choose which add function to run")
args = parser.parse_args()
if args.function == "ex1":
ex1()
elif args.function == "ex2":
ex2()
elif args.function == "ex3":
ex3()
\ No newline at end of file
"""
abc vs collections.abc
- abc is a standard library module that provides the infrastructure for defining abstract base classes (ABCs) in Python.
- collections.abc is a module that provides a set of abstract base classes for collections in Python.
"""
# Example 1: Using standard abc
from abc import ABC, abstractmethod
class MyListABC(ABC):
@abstractmethod
def __getitem__(self, index):
pass
@abstractmethod
def __len__(self):
pass
class MyCustomList(MyListABC):
def __init__(self, data):
self._data = list(data)
def __getitem__(self, index):
return self._data[index]
def __len__(self):
return len(self._data)
# Usage
custom_list_abc = MyCustomList([1, 2, 3, 4, 5])
print(len(custom_list_abc)) # 5
print(custom_list_abc[2]) # 3
# But we're missing a lot of expected list-like behavior:
# This will raise an AttributeError
# print(list(custom_list_abc))
# Example 2: Using collections.abc
from collections.abc import Sequence
class MyCustomSequence(Sequence):
def __init__(self, data):
self._data = list(data)
def __getitem__(self, index):
return self._data[index]
def __len__(self):
return len(self._data)
# Usage
custom_sequence = MyCustomSequence([1, 2, 3, 4, 5])
print(len(custom_sequence)) # 5
print(custom_sequence[2]) # 3
# Now we get a lot more functionality for free:
print(list(custom_sequence)) # [1, 2, 3, 4, 5]
print(3 in custom_sequence) # True
print(custom_sequence.index(4)) # 3
print(custom_sequence.count(5)) # 1
# We can even use slicing!
print(custom_sequence[1:4]) # [2, 3, 4]
# And reverse
print(list(reversed(custom_sequence))) # [5, 4, 3, 2, 1]
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment