Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
lfa:2023:lab_python_extras [2023/10/23 18:56]
mihai.udubasa
lfa:2023:lab_python_extras [2023/11/15 20:54] (current)
mihai.udubasa [Dataclasses]
Line 2: Line 2:
  
 These extra sections are slightly more advanced python topics which will be useful for the project. These extra sections are slightly more advanced python topics which will be useful for the project.
 +
 +===== Python 3.12 generics =====
 +
 +Python 3.12 introduces a more intuitive syntax for generics than the one presented in [[lfa:​2023:​lab01|Programming introduction - Python]]. The new syntax is closer to generics in other languages such as Java or C++. The following code (in old syntax):
 +
 +<code python>
 +T = typeVar('​T'​)
 +U = typeVar('​U'​)
 +class Example(Generic[T]):​
 +    def exampleMethod(param1 : T, param2: U) -> U:
 +        ...
 +    ...
 +</​code>​
 +
 +becomes, in python3.12:
 +
 +<code python>
 +class Example[T]:
 +    def exampleMethod[U](param1 : T, param2: U) -> U:
 +        ...
 +    ...
 +</​code>​
  
 ===== F-Strings ===== ===== F-Strings =====
Line 28: Line 50:
 Dataclasses are a shorthand way of implementing certain methods and generally removing some boilerplate code. Dataclasses are a shorthand way of implementing certain methods and generally removing some boilerplate code.
  
-Assume you're writing a class to implement linked lists, which can be printed in a similar way to ADT's (so [1,2,3] should be printed as `Cons(1,​Cons(2,​Cons(3,​Empty())))`). Your code may look like this:+Assume you're writing a class to implement linked lists, which can be printed in a similar way to ADT's (so ''​[1,2,3]'' ​should be printed as ''​Cons(1,​Cons(2,​Cons(3,​Empty())))''​). Your code may look like this:
  
 <code python> <code python>
Line 54: Line 76:
 </​code>​ </​code>​
  
-contrast this to how a similar level of functionality would be implemented in haskell:+contrast this to how a similar level of functionality would be implemented in Haskell:
  
 <code haskell> <code haskell>
-data List a = Cons a List | Empty deriving Show+data List a = Cons a (List a) | Empty deriving Show
 </​code>​ </​code>​
  
 Is it possible to eliminate most of the boilerplate code present in the python version while keeping most of the functionality?​ YES, using dataclasses. Is it possible to eliminate most of the boilerplate code present in the python version while keeping most of the functionality?​ YES, using dataclasses.
  
-''​dataclass''​ is a type of python object known as a decorator. In general, decorators are placed right before a class or function definition (preappended with the ''​@''​ symbol) and alter their behaviour in certain ways. In our case, the ''​dataclass''​ decorator implements some generic methods of the class, most importantly,​ ''​`_`_init`_`_'',​ ''​`_`_str`_`_''​ and ''​`_`_repr`_`_''​. If we want our class to contain member variables, we only need to list them in the fucntion ​body. The list code rewritten with dataclasses looks like this:+''​dataclass''​ is a type of python object known as a decorator. In general, decorators are placed right before a class or function definition (preappended with the ''​@''​ symbol) and alter their behaviour in certain ways. In our case, the ''​dataclass''​ decorator implements some generic methods of the class, most importantly,​ ''​`_`_init`_`_'',​ ''​`_`_str`_`_''​ and ''​`_`_repr`_`_''​. If we want our class to contain member variables, we only need to list them in the function ​body. The list code rewritten with dataclasses looks like this:
 <code python> <code python>
 from dataclasses import dataclass from dataclasses import dataclass
Line 85: Line 107:
 ===== Dictionaries,​ Sets and Hashable Objects ===== ===== Dictionaries,​ Sets and Hashable Objects =====
  
-As part of your project, ​it is very likely that you will attempt to use a set or list either as an element of another set, or as a part of the key of a dictionary. However, if you actually try this, you will get an error stating that your element is not hashable. Why is this?+As part of your project, ​
 +is very likely that you will attempt to use a set or list either as an element of another set, or as a part of the key of a dictionary. However, if you actually try this, you will get an error stating that your element is not hashable. Why is this?
  
 In short, both dictionaries and sets are implemented as hashmaps in the python library (for quick search and append of unique elements). This means that elements should have a hash method implemented (alongside a method to check equality between objects) and, moreover, this hash fuction should always be consistent with the equality comparison (objects which are equal should always have the same hash, but the other way around is not necessary). In short, both dictionaries and sets are implemented as hashmaps in the python library (for quick search and append of unique elements). This means that elements should have a hash method implemented (alongside a method to check equality between objects) and, moreover, this hash fuction should always be consistent with the equality comparison (objects which are equal should always have the same hash, but the other way around is not necessary).
Line 93: Line 116:
 All of this essentially boils down to: **mutable collections (such as lists, sets and dictionaries) can __NOT__ be used as set elements or dictionary keys**. However, the use of a collection in such a situation may still be desirable in some situations (such as the subset constructon algorithm). All of this essentially boils down to: **mutable collections (such as lists, sets and dictionaries) can __NOT__ be used as set elements or dictionary keys**. However, the use of a collection in such a situation may still be desirable in some situations (such as the subset constructon algorithm).
  
-To work around this restriction we can use **IMMUTABLE ​COLELCTIONS** (collections which can no longer have elements added/​removed/​replaced),​ such as tuples (immutable lists, their elements may or may not be hashable, and the resulting tuple is hashable if all elements are hashable) and frozensets (immutable sets, their elements should still be hashable, as is the case of sets, and so frozensets are always hashable).+To work around this restriction we can use **IMMUTABLE ​COLLECTIONS** (collections which can no longer have elements added/​removed/​replaced),​ such as tuples (immutable lists, their elements may or may not be hashable, and the resulting tuple is hashable if all elements are hashable) and frozensets (immutable sets, their elements should still be hashable, as is the case of sets, and so frozensets are always hashable).
  
 Converting from a mutable to immutable collection is quite easy (you only apply the constructor over the original collection):​ Converting from a mutable to immutable collection is quite easy (you only apply the constructor over the original collection):​
Line 111: Line 134:
  
 # etc. # etc.
 +
 +</​code>​
 +
 +===== Match statements =====
 +
 +Match statements are a way of performing structural matches (similar to the pattern matches in functional languages such as Haskell of Scala). They are able to differentiate the type of the object and match its attributes to a given list. Match statements can also be used on lists.
 +
 +The syntax is as follows:
 +
 +<code python>
 +match <​expression to match>:
 +   case <Class to match to>: <code to execute on this case>
 +   case <Class to match to>​(<​members to check>​=<​value>​ [...] (optional)):​ <code to execute on this case>
 +   case <Class to match to>​(<​members>​ = <​label>​ [...] (optional)):​ <code to execute on this case (label will be available as a variable)>​
 +   
 +   # for lists
 +   case []: <code to execute on empty list>
 +   case [x,y]: <code to execute on list with exactly 2 elements, which will be labeled x and y>
 +   case [x,*xs]: <code to execute on a list with at least one element, the first of which will be labeled x, and a list conatining the rest of the elements will be labeled xs>
 +</​code>​
 +
 +The case statements will be evaluated in order, stopping when the first match occurs.
 +
 +The following example covers most features of the match statement:
 +
 +<code python>
 +from dataclasses import dataclass
 +
 +
 +@dataclass
 +class A:
 +    x: int
 +    y: int = 5
 +
 +
 +class B:
 +    def __init__(self,​ x):
 +        self.x = x
 +        self.z = 5
 +
 +    def set_z(self, z):
 +        self.z = z
 +
 +
 +class C(B):
 +    def __init__(self,​ x, y, z):
 +        super().__init__(x)
 +        self.y = y
 +        self.z = z
 +
 +
 +def match_example(obj):​
 +    match obj:
 +        case []:
 +            return f'​empty list'
 +        case [x, y, xs]:
 +            return f'list starting with {x}, then {y}, with the rest being {xs}'
 +        case [x, *_]:
 +            return f'list starting with {x}'
 +        case A(y=name_for_y):​
 +            return f'An A with x = {obj.x}, y = {name_for_y}'​
 +        case B(z=5):
 +            return f'​Default B with x = {obj.x}'​
 +        case B(z=modified_z,​ x=5):
 +            return f'B with x = 5 and z modified to {modified_z}'​
 +        case C():
 +            return f'C with y = {obj.y}'​
 +        case B(z=modified_z):​
 +            return f'this case will not be reached in examples'​
 +
 +
 +if __name__ == '​__main__':​
 +    objects = [
 +        [], # will print 'empty list'
 +        [1, 2, 3], # will print 'list starting with 1, then 2, with the rest being [3]'
 +        [1], # will print 'list starting with 1'
 +        A(4), # will print 'An A with x = 4, y = 5'
 +        B(5), # will print '​Default B with x = 5'
 +        C(5, 6, 7), # will print 'B with x = 5 and z modified to 7' ​ (the 'B with modified z' case is matched before reaching the case with C)
 +        C(6, 6, 7) # will print 'C with y = 6'
 +    ]
 +    for o in objects:
 +        print(match_example(o))
  
 </​code>​ </​code>​