Advanced Programming Notes Univeristy Level
Advanced Programming Notes Univeristy Level
2
Iterable Unpacking ............................................................................................... 2
Extended Unpacking - * operator ........................................................................... 2
Extended Unpacking - ** operator ......................................................................... 3
*args ................................................................................................................... 3
**kwargs ............................................................................................................. 4
HIGHER-ORDER FUNCTIONS................................................................................... 6
Decorators .......................................................................................................... 9
Decorators for Classes ....................................................................................... 11
OBJECT-ORIENTED PROGRAMMING ...................................................................... 13
Encapsulation ................................................................................................... 14
Inheritance ........................................................................................................ 15
Overriding ...................................................................................................... 16
Overloading .................................................................................................... 17
Overwriting ..................................................................................................... 17
Data abstraction ................................................................................................ 17
Polymorphism ................................................................................................... 19
UNIFIED MODELLING LANGUAGE (UML)................................................................. 20
NUMPY................................................................................................................. 24
PANDAS ............................................................................................................... 25
1
*ARGS and **KWARGS
Iterable Unpacking
Any iterable in python, e.g. a list or a tuple, is said to have a pack of values. We can
unpack the values inside an iterable into individual variables.
my_list = [1, '2', {1,2}, 2.3]
a, b, c, d = my_list
a: 1
b: 2
c: {1, 2}
d: 2.3
N.B. with unordered objects like sets, the unpacking does not necessarily happen in the order
shown in the assignment. E.g., a, b, c = {‘a’, ‘b’, ‘c’} might result in ‘a’ getting assigned to b.
When we unpack the values of an iterable into individual variables we must know in advance the
number of elements contained by (length of) the iterable.
a, b, c, d, e = [1, '2', {1,2}, 2.3]
ValueError: not enough values to unpack (expected 5, got 4)
Moving a step further, we always don't wish to unpack a single index value of the iterable
in a single variable. What we want to do now resembles the slicing of a list.
a, b, *r = [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10]
a: 1
b: 2
r: [3, 4, 5, 6, 7, 8, 9, 10]
Here, the first index value of iterable list gets assigned to a, the second is assigned to b and the
rest of the values of the iterable are assigned to r as a list.
v = ['python', 'json', 'rdf', 'xml', 'java']
*b, = v #to unpack must have multiple receivers => comma “tricks” interpreter
b: ['python’, ‘json’, ‘rdf’, ‘xml’, ‘java’]
2
Extended Unpacking - ** operator
When working with dictionaries * operator is only able to unpack the keys of the dictionary.
d = {
1: 'Andrea',
2: 'John',
5: 'Mary'}
*b, = d
b: [1, 2, 5]
*args
You might have come across this many times inside the function parameters. It is used to
exhaust positional arguments that are passed to any function. For example, doing so we can
use the function somma with an undefined number of arguments:
def somma(*args):
print('Function somma:', args, type(args))
out = 0
for arg in args:
out += arg
return out
Other solutions:
3
Relies on recursion and unpacking. This is based on recursion and dìvide et
def sumall_recursive(a, b, *c): impera (divide and conquer) paradigm.
if not c or len(c) == 0: def sum_merge(*num):
return a+b if not num or len(num) == 0:
else: return 0
first, *rest = c elif len(num) == 1:
return sumall_recursive(a+b, first, return num[0]
*rest) else:
p = int(len(num)/2)
part_1 = num[0:p]
part_2 = num[p:]
return sum_merge(*part_1) +
sum_merge(*part_2)
def func(a,b,*c,d=0):
print(f'Positional argument a is set to {a}')
print(f'Positional argument b is set to {b}')
print(f'Starred argument c is set to {c}')
print(f'Keyword-based argument d is set to {d}')
func(10, 20, 123, 582, 52, 4, d=543)
func(10, 20, 30, 40, 50, 60, 70, 80)
Positional argument a is set to 10 Positional argument a is set to 10
Positional argument b is set to 20 Positional argument b is set to 20
Starred argument c is set to (123, 582, 52, 4) Starred argument c is set to (30, 40, 50, 60, 70, 80)
Keyword-based argument d is set to 543 Keyword-based argument d is set to 0
We need to pass only keyword-only arguments once extended unpacking has been used.
**kwargs
If you do not know how many keyword arguments will be passed into your function,
then you add two stars, i.e. ** before the parameter name in the function definition. This way the
function will receive a dictionary of arguments and can access the items accordingly.
def func(a,b=1,*args, e, **kwargs):
print(f'Positional argument a is set to {a}')
print(f'Positional argument b is set to {b}')
print(f'Starred argument args is set to {args}')
print(f'Keyword-based argument e is set to {e}')
print(f'Double starred argument kwargs is set to a dictionary object containing
{kwargs}')
4
func(10, 20, 89, e=89)
func(a=10, b=20, e=3)
func(10, 20, 30, 40, 50, e=60, f=70, g=80)
func(10, 20, 30, 40, 50, 60, f=70, g=80)
Positional argument a is set to 10 Positional argument a is set to 10
Positional argument b is set to 20 Positional argument b is set to 20
Starred argument args is set to (89,) Starred argument args is set to ()
Keyword-based argument e is set to 89 Keyword-based argument e is set to 3
Double starred argument kwargs is set to Double starred argument kwargs is set to
a dictionary object containing {} a dictionary object containing {}
The kwargs is empty. The kwargs is empty.
Positional argument a is set to 10 TypeError: func() missing 1 required
Positional argument b is set to 20 keyword-only argument: 'e'
Starred argument args is set to (30, 40,
50)
Keyword-based argument e is set to 60
Double starred argument kwargs is set to
a dictionary object containing {'f': 70,
'g': 80}
We do have kwargs!
Remember: only keyword args can follow keyword args
def func(a, b=1, *args, e, g=2, **kwargs):
Breaking the pieces of the puzzle
• a - positional argument, mandatory, can be a named argument;
• b - positional argument, not mandatory, can be a named argument;
• *args - catches all following positional arguments. Remember, no additional
positional arguments without names allowed after this;
• e - keyword-only argument, mandatory, should be named argument;
• g - keyword-only argument, not mandatory as a default value is associated with the
argument into the function definition;
• **kwargs - catches all following named arguments. No arguments follow this.
Possible uses:
def create_dictionary(**kwargs):
d = dict()
d.update(kwargs)
return d
def create_general_dictionary(**kwargs):
return kwargs
5
HIGHER-ORDER FUNCTIONS
Examples:
def to_uppercase(text):
return text.upper()
def to_lowercase(text):
return text.lower()
var_x = to_uppercase
<function to_uppercase at 0x7f06905f68c0>
print(var_x)
CIAO ITALIA
res = var_x('ciao italia')
print(res)
l = [to_uppercase, to_lowercase]
for f in l:
ret = f('PrOgraMMing') PROGRAMMING
print(ret) programming
6
Defining functions inside other functions
def plus_one(number): #wrapping (or nesting) function !!
def add_(number): #wrapped (or nested) !!
return number + 1
result = add_(number)
return result
plus_one(4) 5
7
print(msg) you to call it later with a specific argument (in
return x / y # y is defined in the outer scope of the this case, 1).
inner function divide When you immediately call (1) after divider(2,
value = 0 'Hello there!'), you're invoking the divide
print(value)
function that was returned. This is where the
return divide
actual division happens and a number is
returned.
w=2 So, in this context:
print(divider(2, 'Hello there!')(1))
divider(2, 'Hello there!') returns the divide
0
function. When you call that returned
Hello there! function with (1), it executes the code inside
0.5 divide, prints the message, and returns the
result of 1 / 2.
Same as
div = divider(2, 'Hello there!')
print(div(1))
Description:
This program demonstrates the concept of closures in Python using a nested function structure.
It defines a function `divider` which takes two arguments: `y` and `msg`.
Inside `divider`, another function `divide` is defined, which takes `x` as an argument.
`divide` accesses `y` and `msg` which are defined in the outer scope of `divide` within the
`divider` function.
The `divider` function returns the `divide` function.
Explanation of Closure:
A closure is a function object that remembers values in enclosing scopes even if they are not
present in memory.
In this program, when `divider` is called with arguments (2, 'Hello there!'), it returns the `divide`
function.
The `divide` function remembers the value of `y` (2) and `msg` ('Hello there!') even after the
execution of the `divider` function.
This is because `divide` is a closure. This is evident when `divider(2, 'Hello there!')(1)` is executed.
The `divider` function is called, returning the `divide` function. This `divide` function is then
called with the argument 1.
Inside `divide`, the value of `y` is still accessible and it is used to divide `x` (1) by `y` (2) to
produce the result.
In essence, the `divide` function "closes over" the variables `y` and `msg` from its enclosing
scope. This allows us to create functions with specific data attached to them, without relying on
global variables or explicit parameter passing for every invocation of the inner function.
def division(x): Closure occurs when a nested function retains
def second_division(): access to its enclosing scope’s variables even
t=6 after that outer function has finished
return x/w #w is defined out of the outer function executing. In this case, second_division retains
u=5
access to x and w.
return second_division()/w
w=2
8
print(division(8)) Variable Scope: the w variable is defined in
the global scope but is accessible within both
2.0 division and second_division.
The variable x is defined in the local scope of
division, and second_division can access it even
after division has been called.
Decorators
Decorators are the most common use of higher-order functions in Python. Has
function in input, has a wrapped function and returns it.
Let's go ahead and create a simple decorator that will convert a sentence to uppercase. We do
this by defining a wrapper inside an enclosed function. As you can see it is very similar to the
function inside another function that we created earlier.
def uppercase_decorator(function: callable) -> callable:
return wrapper
Our decorator function takes a function as an argument, and we shall, therefore, define a
function and pass it to our decorator. We learned earlier that we could assign a function to a
variable. We'll use that trick to call our decorator function. However, Python provides a much
easier way for us to apply decorators. We simply use the @decorator_function syntax before the
function we'd like to decorate.
def say_hi(name: str): @uppercase_decorator
return f'Hello {name}' def say_hi(name: str) -> str:
return f'hello {name}'.lower()
9
def split_string(function):
def wrapper(*strings):
func = function(*strings)
splitted_string = func.split() # 'This is my string'.split(): 'This is my string'.split() -> ['This', 'is',
'my', 'string']
return splitted_string
return wrapper
@split_string
@uppercase_decorator
def say_hi(name: str) -> str:
return f'hello {name}'
say_hi('Mary')
['HELLO', 'MARY']
To define a general-purpose decorator that can be applied to any function we use *args and
**kwargs. *args and **kwargs collect all positional, keyword arguments, and store them in the
args and kwargs variables. args and kwargs allow us to pass as many arguments as we would like
during function calls.
Now let's see how we'd pass arguments to the decorator itself. To achieve this, we define a
decorator maker that accepts arguments then define a decorator inside it. We then define a
wrapper function inside the decorator as we did earlier.
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
def decorator(func):
def wrapper(function_arg1, function_arg2, function_arg3) :
#This is the wrapper function
print("The wrapper can access all the variables\n"
"\t- from the decorator maker: {0} {1} {2}\n"
"\t- from the function call: {3} {4} {5}\n"
"and pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,decorator_arg3,
function_arg1, function_arg2,function_arg3))
return func(function_arg1 + decorator_arg1, function_arg2,decorator_arg2 + function_arg3)
return wrapper
return decorator
@decorator_maker_with_arguments("Pandas", "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
# We experiment with alternative solutions for formatting strings
print("This is the decorated function and it only knows about its arguments: {0}"
" {1}" " {2}".format(function_arg1, function_arg2,function_arg3))
print(f"This is the decorated function and it only knows about its arguments: {function_arg1} {function_arg2}
{function_arg3}")
print("This is the decorated function and it only knows about its arguments: " + function_arg1 + " " +
function_arg2 + " " +function_arg3)
print("This is the decorated function and it only knows about its arguments:", function_arg1, function_arg2,
function_arg3)
10
The wrapper can access all the variables
- from the decorator maker: Pandas Numpy Scikit-learn
- from the function call: Data Science Tools and pass them to the decorated function
This is the decorated function and it only knows about its arguments: DataPandas Science NumpyTools
This is the decorated function and it only knows about its arguments: DataPandas Science NumpyTools
This is the decorated function and it only knows about its arguments: DataPandas Science NumpyTools
This is the decorated function and it only knows about its arguments: DataPandas Science NumpyTools
The @classmethod and @staticmethod decorators are used to define methods inside a class
namespace that are not connected to a particular instance of that class
• A class method is a method that is bound to the class and not the object of the class.
It has access to the state of the class as it takes a class parameter (class as an implicit
first argument, just like an instance method receives the instance) that points to the
class and not the object instance.
It can modify a class state that would apply across all the instances of the class. For
example, it can modify a class variable that will be applicable to all instances.
• A static method is bound to a class rather than the objects for that class. It can be
called without an object for that class. This means that static methods cannot modify
the state of an object as they are not bound to it.
• Class vs Static:
o A class method takes class as the first parameter while a static method needs
no specific parameters
o A class method can access or modify the class state while a static method
cannot
o We generally use class methods to create factory methods. Factory methods
return class objects (like a constructor) for different use cases. In this case it is
possible to create inheritable constructors. We generally use static methods to
create utility functions.
11
The @property decorator is used to customize getters and setters for class attributes. It is used
to give "special" functionality to certain methods to make them act as getters, setters, or deleters
when we define properties in a class.
Properties can be considered the "Pythonic" way of working with attributes. The syntax used to
define properties is very concise and readable. You can access instance (private) attributes exactly
as if they were public attributes while using the "magic" of intermediaries (getters and setters) to
validate new values and to avoid accessing or modifying the data directly. By using @property,
you can "reuse" the name of a property to avoid creating new names for the getters, setters, and
deleters.
12
OBJECT-ORIENTED PROGRAMMING
OOP is a programming language model in which programs are organised around data, or
objects, rather than functions and logic. An object can be defined as a data field that has unique
attributes and behaviour. Examples of an object can range from physical entities, such as a
human being that is described by properties like name and address, down to small computer
programs, such as widgets.
The first step in OOP is to identify all the objects a programmer wants to manipulate and
how they relate to each other, an exercise often known as data modelling. Once an object is
known, it is generalised as a class of objects that defines the kind of data it contains and any
logical sequences that can manipulate it. Each distinct logic sequence is known as a method and
objects can communicate with well-defined interfaces called messages.
This approach to programming is well-suited for programs that are large, complex and actively
updated or maintained.
Procedural programming creates a step-by-step program that guides the application through a
sequence of instructions. Each instruction is executed in order. PP also focuses on the idea that
all algorithms are executed with functions and data that the programmer has access to and can
change. OOP is much more like the way the real-world works - a message must be sent
requesting the data. Just like people must ask one another for information and we cannot see
inside each other’s heads.
Objects are the basic run-time entities in an object-oriented system. Objects interact by
sending messages to one another. Objects have two components:
1. Attributes (i.e., data)
2. Methods (i.e., behaviors)
An object, practically speaking, is a segment of memory (RAM) that references both data
(instance variables) of various types and associated functions that can operate on the data.
A Class is a special data type which defines how to build a certain kind of object. The
class also stores some data items that are shared by all the instances of this class Instances are
objects that are created following the definition given inside of the class. Functions belonging to
classes are called methods. Said another way, a method is a function that is associated with a
class, and an instance variable is a variable that belongs to a class.
13
OOP in Python
Python is naturally “object oriented”. Objects are the data that we have been associating with
variables. Everything is an object. What the methods are, how they work, and what the data are
(e.g., a list of numbers, dictionary of strings, etc.) are defined by a class. Example: the array class
in Python defines methods like “append”, “pop”, “sort”, etc.
Class definition
Each class definition requires three things:
1. Methods (functions)
2. Attributes (instance variables referring to data)
3. The constructor __init__ – a special method called automatically whenever a new
object of the class is created. It usually does some initialization work. It can take any
number of arguments but the first is a reference to the current instance of the class.
By convention, it is named self. You do not give a value for this parameter when
you call the method, Python will provide it. Although you must specify self explicitly
when defining the method, you don’t include it when calling the method. Python
passes it for you automatically.
After the instantion, a method can be invoked using the dot notation – e.g., obj.method(args).
When you are done with an object, you don’t have to delete or free it explicitly. Python
has automatic garbage collection - it will automatically detect when all the references to a piece of
memory have gone out of scope and free that memory. It generally works well with few memory
leaks. There’s also no “destructor” method for classes.
The four major principles of object orientation are: Encapsulation, Data Abstraction,
Inheritance and Polymorphism.
Encapsulation
The terms encapsulation and abstraction (also data hiding) are often used as synonyms.
They are nearly synonymous, i.e. abstraction is achieved though encapsulation. Data hiding and
encapsulation are the same concept, so it's correct to use them as synonyms. Encapsulation is the
mechanism for restricting access to some of an object's components, this means, that the
internal representation of an object can't be seen from outside of the object's definition.
Access to this data is typically only achieved through special methods: Getters and
Setters. By using solely get() and set() methods, we can make sure that the internal data cannot be
accidentally set into an inconsistent or invalid state. C++, Java, and C# rely on the public,
private, and protected keywords to implement variable scoping and encapsulation.
14
Public Protected Private
name _name __attributename
Accessed from both inside Like public but they should Can’t be seen and accessed
and outside of the class. In not be directly accessed from from outside of the class.
Python, all class members are outside. Protected members Subclasses can’t access it.
public by default. are accessible within the class
and in subclasses. This is a
convention in Python, as the
language does not enforce
access restrictions strictly.
Protected members should be
treated as non-public and used
responsibly.
Also note that all these access modifiers are not strict like other languages such as C++,
Java, C#, etc. since they can still be accessed if they are called by their original or mangled
names. For example (from Geeksforgeeks), we can still access private members of a class outside
the class. We cannot directly call obj.__name, because they throw errors. In the list of callable
fields and methods, __name is saved as _Geek__name, . This conversion is called name
mangling, where the python interpreter automatically converts any member preceded with two
underscores to _<class name>__<member name>. Hence, we can still call all the supposedly
private data members of a class using the above convention.
>>> obj.public_attribute
Shows value of the public attribute
>>> obj.protected_attribute
Shows value of the protected attribute. NO ERRORS BUT BAD PRACTICE
>>> obj.private_attribute
SCOPING ERROR
Inheritance
The derived class can have its own initialiser. In python you may or may not call the base class
initialiser, but it is good practice to do so. It doesn't have to be, no, but it really should. If the
base class has any methods or properties which aren't overridden in the derived class then the
15
initialiser from the base class should be called, otherwise those methods and properties may not
work. You can define an initialiser for the derived class, but that initialiser really should call
BaseClassName.__init__ to make sure the base class is initialized properly.
Overriding
Overriding is OOP feature that allows a subclass or child class to provide a specific
implementation of a method that is already provided by one of its superclasses or parent
classes. The implementation in the subclass overrides (replaces) the implementation in the
superclass by providing a method that has same name, same parameters or signature, and same
return type as the method in the parent class. The version of a method that is executed will be
determined by the object that is used to invoke it (or by the closest class it is an instance of
where it is defined).
If a method is overridden in a class, the original method can still be accessed. The
original method defined in a superclass can be accessed from the derived class by calling the
method directly with the class name, e.g. Robot.say_hi(y) , where Robot is the superclass name and
y is an object instance of a subclass.
Multiple inheritance
Multiple inheritance is a feature in which a class can inherit attributes and methods from more
than one parent class.
class Robot: y=
def __init__(self, name): PhysicianRobot("James
self.name = name ", "Cardiovascular
def say_hi(self): medicine")
print("Hi, I am " + self.name)
y.say_hi()
class Physician: y.print_specialization()
def __init__(self, specialization):
self.specialization = specialization
def print_specialization(self):
print("My specialization is " + self.specialization)
>>> Everything will be
class PhysicianRobot(Robot, Physician): okay!
def __init__(self, name, specialization): James takes care of
you!
Robot.__init__(self, name)
My specialization is
Physician.__init__(self, specialization) Cardiovascular
def say_hi(self): medicine
print("Everything will be okay! ")
print(self.name + " takes care of you!")
The critics point out that multiple inheritance comes along with a high level of
complexity and ambiguity in situations such as the diamond problem.
The "diamond problem" (sometimes referred to as the "deadly diamond of death") is the
generally used term for an ambiguity that arises when two classes B and C inherit from a
16
superclass A, and another class D inherits from both B and C. If there is a method "m" in A that
B or C (or even both of them) )has overridden, and furthermore, if does not override this
method, then the question is which version of the method does D inherit? You can’t know.
Overloading
Overloading is the ability to define a function with the same name multiple times. The
definitions are different concerning the number of parameters and types of parameters. It's the
ability of one function to perform different tasks, depending on the number of parameters or the
types of them. We cannot overload functions like this in Python, but it is not necessary either.
OOP languages like Java or C++ implement overloading.
Overwriting
Overwriting is the process of replacing old information with new information If we overwrite a
function, the original function will be gone. The function will be redefined. This process has
nothing to do with object orientation or inheritance.
def f(x):
return x + 42
print(f(3)) >>> 45
def f(x):
>>> 46
return x + 43
print(f(3))
Data abstraction
Abstract classes:
• Are classes which contain one or more (but not necessarily all) abstract methods.
An abstract method is a method that has declaration but no implementation.
Abstract classes having only abstract methods are called interfaces.
• Unlike concrete classes, they cannot be instantiated and they need subclasses to
provide implementations for those abstract methods which are defined in abstract
classes.
• can be considered as blueprints for other classes, allows you to create a set of
methods that must be created within any child classes built from your abstract class.
abc module
The abc module provides the infrastructure for defining Abstract Base Classes (ABCs) in
Python.
@abc.abstractmethod is a decorator indicating abstract methods. Using this decorator
17
requires that the class’s metaclass is ABCMeta or is derived from it and that class cannot be
instantiated unless all its abstract methods and properties are overridden. abstractmethod() may be
used to declare abstract methods for properties and descriptors.
from abc import ABC, abstractmethod A = Animal()
class Human(Animal):
def doAction(self):
print("I can walk and run")
class Snake(Animal):
def doAction(self):
print("I can crawl")
Abstract properties
18
Writable abstract properties
Polymorphism
Polymorphism is used when you have methods with the same name across classes or
subclasses. This allows functions to use objects of any of these polymorphic classes without
needing to be aware of distinctions across the classes. Polymorphism can be carried out through
inheritance with subclasses making use of base class methods or overriding them.
The “Data abstraction” code is an example of polymorphism – see how “doAction()”
method is redefined in each subclass.
human = Human() I can walk and run
snake = Snake() I can crawl
dog = Dog() I can bark
lion = Lion() I can roar
animals = [human, snake, dog, lion] Python is unaware of the actual type of each
for animal in animals: animal
animal.doAction()
19
“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that
bird a duck.” [James Whitcomb Riley]
A special case of dynamic typing - uses techniques characteristic of polymorphism,
including late binding and dynamic dispatch. The use of duck typing is concerned with
establishing the suitability of an object for a specific purpose. When using normal typing this
suitability is determined by the type of an object alone, but with duck typing the presence of
methods and properties are used to determine suitability rather than the actual type of the object
in question.
20
UML (Unified Modelling Language): a graphical language based on diagrams which helps
to develop an understanding of the relationships between the software that is being designed and
its external environment. It is independent from the development process and the programming
language and natively supports object-oriented design.
Class-Responsibility-Collaboration
(CRC) cards are a brainstorming
tool used in the design of object-
oriented software.
A class is represented as a rectangle
with internal slots for:
• Class name
(UpperCamelCase) -
mandatory
• Attributes (lowerCamelCase) - optional
• Operators (lowerCamelCase) - optional
Visibility types for attributes and operations
+ Public: a public element is visible to all elements that can access the contents of
the namespace that owns it.
- Private: element is only visible inside the namespace that owns it.
# Protected: element is visible to elements that have a generalization relationship to
the namespace that owns it.
⁓ Package: element is only visible by elements within a package and its sub
packages
21
Generalization
The process of a child or subclass taking on the
functionality of a parent or superclass, also known as
inheritance. It's symbolised with a straight connected line
with a closed arrowhead pointing towards the superclass
Realization
A semantic relation used for representing a provider that
exposes an interface and a client that realises the interface.
The canonical example is the relation between an interface
and a class implementing such an interface.
Association
The relationship between two classes. Both classes are
aware of each other and their relationship with the other.
This association is represented by a straight line between
two classes
Dependency
Is a directed relationship which is used to show that some
element or a set of elements requires, needs or depends on
other model elements for specification or implementation.
Because of this, dependency is called a supplier - client
relationship, where supplier provides something to the
client, and thus the client is in some sense incomplete
while semantically or structurally dependent on the
supplier element(s). Modification of the supplier may
impact the client elements
Aggregation
A binary association between a property and one or more
composite objects which group together a set of instances.
Aggregation has the following characteristics:
-- it is binary association it is asymmetric
- only one end of association can be an aggregation it is
transitive
- aggregation links should form a directed, acyclic graph,
so that no composite instance could be indirect part of
itself
22
Composition
Composite aggregation (composition) is a "strong" form
of aggregation with the following characteristics: ---- it is
binary association it is a whole/part relationship a part
could be included in at most one composite (whole) at a
time if a composite (whole) is deleted, all of its composite
parts are "normally" deleted with it
23
NUMPY
N-dimensional arrays
24
]
])
PANDAS
25
Pandas is a library providing high-performance data manipulation and analysis tools using
its powerful data structures.
A DataFrame is a two-dimensional array of values with both a row and a column index. A
Series is a one-dimensional array of values with an index.
df = pd.DataFrame(data = [
[ 'NJ', 'Towaco', 'Square'],
[ 'CA', 'San Francisco', 'Oval'],
[ 'TX', 'Austin', 'Triangle'],
[ 'MD', 'Baltimore', 'Square'],
[ 'OH', 'Columbus', 'Hexagon'],
[ 'IL', 'Chicago', 'Circle']
],
columns = [ 'State', 'City', 'Shape'])
• df.head(n_rows): returns a new DataFrame composed of the first n_rows rows. The
parameter n_rows is optional, and it is set to 5 by default
• df.tail(n_rows): returns a new DataFrame composed of the last n_rows rows. The
parameter n_rows is optional, and it is set to 5 by default
• df.shape: returns the shape of the DataFrame that provides the number of elements
for both the dimensions of the DataFrame
• df.index: returns the labels of the DataFrame indexes
• df.to_numpy(): coverts the DataFrame to a NumPy array
• df.describe(): shows a quick statistic summary of your data.
• df.to_dict(orient='dict', index=True) returns a dictionary. Possible values for orient:
‘dict’, ‘list’, ‘series’, ‘split’, ‘tight’, ‘records’, ‘index’
• df.to_html(header=True, index=True, classes=None, escape=True, table_id=None):
returns an html table
• pd.read_csv(filename, delimiter=’,’) and df.to_csv(path, delimiter=’,’)
26
• df.apply(function) or df.map(function)
• Slicing:
o series = df['State'] by label (columns)
o sliced_df = df[1:4] getting a slice (rows)
o multiaxis_slice = df.loc[1:3, ['State', 'City']] slice by label (rows and cols),
includes the fourth row
o multiaxis_slice_iloc = df.iloc[1:3, 0:2] slice by position (rows and cols)
• Arithmetical and statistical methods:
o df.mean() Mean column per column
o df.max() Max value in each column
o df.min() Min value in each column
o df.sum() Sum of the values in each column
o df.count() Count non-NA cells for each column or row
o df.diff() First discrete difference of element
27
28