# Python ## Main characteristics ### Types: * Dynamic typed: not specify types in advance and a variable can be assigned to different types. * Strongly typed: types are not coerced to match. ### “Interpreted language” * Compiled before executed transparently ### Implementations of the Python language: * CPython: * In C runs native. * Commonly used, standard de facto. * Jython: * In Java runs on JVM. * IronPython: * In C# runs in .NET. * pypy: * RPython runs native. ### Python standard library * Extensive. Most of the cases you won’t need anything else. * Good documentation. * Simplicity, readability and consistency. ``` >>> import math >>> help() >>> help(math) >>> help(math.factorial) >>> math.factorial(6) >>> from math import factorial as fact >>> fact(6) ``` ### Python main release streams: * Python 2. Broadly used. Support ends in 2020. * Python 3. Around for more than 10 years. * Incompatible. ### Installation Virtual environments: light-weight, self-contained Python installation. venv or virtualenv ``` $ python3 -m venv venv3 $ source venv3/bin/activate (venv3) $ (Venv3) $ deactivate $ ``` ### Python Interpreter *REPL: Read Evaluate Print Loop* _ => Last return value. (Only in REPL). Exit: Ctrl + D in Linux and Mac ``` $ python3 >>> 2+2 4 >>> _ + 1 5 >>> print("Hello World!") Hello World! >>> ``` ### Other important things to consider **Important!** Indentation level defines the structure of the program. Instead of braces like many other languages. Not convinced. Be careful with using different IDE setups: NEVER mix spaces and tabs! ### Python Enhancement Proposals (PEPs) Python language guidances: * PEP8: Python Style Guide. E.g. Use 4 spaces for indentation. * PEP20: The Zen of Python. * ... ``` >>> import this ``` ## Python data types ### Basic Types #### int UNLIMITED precision signed integer ``` >>> 10 (decimal) >>> 0b10 (binary) >>> 0o10 (onto) >>> 0x10 (hexadecimal) >>> int(-3.5) >>> int(“496") ``` #### float IEEE-754 double precision (64 bits) => 53 bits of binary precision and 15-16 bits of decimal precision. ``` >>> 3.123 3.123 >>> 3e0 3.0 >>> 1.245e-190 1.245e-190 >>> float("1.3") 1.3 >>> 3E8 300000000.0 >>> float(7) 7.0 >>> float("nan") nan >>> float("inf") inf >>> import sys >>> sys.float_info sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1) ``` #### Decimal The standard library module decimal contains the class Decimal, which represents decimals in base 10 instead of base 2 and has a configurable (although finite) precision, which defaults to 28 digits of decimal precision. ``` >>> import decimal >>> decimal.getcontext() Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow]) >>> from decimal import Decimal >>> Decimal(7) Decimal('7') >>> Decimal('0.8') Decimal('0.8') >>> 0.8 - 0.7 0.10000000000000009 >>> Decimal(0.8) - Decimal(0.7) Decimal('0.1000000000000000888178419700') >>> Decimal('0.8') - Decimal('0.7') Decimal('0.1') >>> Decimal('Infinity') Decimal('Infinity') >>> Decimal('-Infinity') Decimal('-Infinity') >>> Decimal('Nan') Decimal('NaN') >>> Decimal('Nan') + Decimal('1.414') Decimal('NaN') ``` Decimal is preferred in financial applications such as accounting. #### Fraction Rational numbers: Fraction class from fractions module. ``` F = Fraction(“2/3”) ``` #### None Special type that represents the absence of a value. ``` >>> None >>> a = None >>> a is None True >>> a = 1 >>> a is None False ``` #### Bool Logical value. True or False ``` >>> True True >>> False False >>> bool(0) False >>> bool(42) True >>> bool(-1) True >>> bool(0.0) False >>> bool(-0.3) True >>> bool([]) False >>> bool([1]) True >>> bool("") False >>> bool("kk") True >>> bool("False") True ``` **NOTE:** Comparison: * `==` : reference equality * `is` : identity equality ### Collection types #### Tuple Heterogeneous immutable collections. ``` >>> t = ("kk", 4.5, 3) >>> t ('kk', 4.5, 3) >>> t[0] 'kk' >>> len(t) 3 >>> h = (1) >>> type(h) <class 'int'> >>> k = (1,) >>> type(k) <class 'tuple'> ``` #### Str Immutable sequences of unicode codepoints. * Simple or doble quotes. * Multiline strings: `“””…”””` or `‘…\n…’` * Raw strings: `r”…”` (doesn’t interpret the `\`}}) Strings are enriched with many methods: ``` >>> c.capitalize() ``` To concatenate invoke join on empty text (more efficient): ``` >>> "".join(["He", "llo"]) 'Hello' ``` The partition() method divides a string into 3 around a separator: prefix, separator, suffix ``` >>> origin, _, destination = "Seattle-Boston".partition('-') >>> origin 'Seattle' >>> destination 'Boston' Tuple unpacking - return values and idiomatic swap. ``` Use format() to insert values into strings. ``` >>> "The age of {0} is {1}".format('Jim', 33) 'The age of Jim is 33' >>> "Reticulating spline {} of {}".format(4,23) 'Reticulating spline 4 of 23' >>> "Math constants: pi={m.pi:.3f}, e={m.e:.3f}".format(m=math) 'Math constants: pi=3.142, e=2.718' ``` #### Bytes Sequences of bytes. ``` >>> d = b"Hello" ``` #### Range Arithmetic progression of integers. ``` >>> for i in range(5): ... print(i) ... 0 1 2 3 4 range(5): 0,1,2,3,4 range(5,10): 5,6,7,8,9 range(10,20,2): 10,12,14,16,18 ``` Prefer enumerate() for counters: ``` >>> t = ["a", "b", "c", "d"] >>> for p in enumerate(t): ... print(p) ... (0, 'a') (1, 'b') (2, 'c') (3, 'd') >>> for i, v in enumerate(t): ... print("i = {}, v = {}".format(i, v)) ... i = 0, v = a i = 1, v = b i = 2, v = c i = 3, v = d ``` #### List Mutable, heterogeneous sequence of objects. ``` >>> [1,2,3] [1, 2, 3] >>> a = [] >>> a.append(1.2) >>> a [1.2] >>> list("hello") ['h', 'e', 'l', 'l', 'o'] ``` You can an access elements of the list with positive indexes from the beginning and negative from the end. ``` >>> s = "show how to index into sequences".split() >>> s ['show', 'how', 'to', 'index', 'into', 'sequences'] >>> s[5] 'sequences' >>> s[-1] 'sequences' ``` Slicing extracts part of a list: slice = seq[start:stop] ``` >>> s[1:2] ['how'] >>> s[:3] ['show', 'how', 'to'] >>> s[3:] ['index', 'into', 'sequences'] ``` Copy a list: ``` >>> copy_of_s = s[:] ``` Shallow copy (references are copied by objects are not). ``` >>> s.sort(key=len) >>> s ['to', 'how', 'show', 'into', 'index', 'sequences'] ``` #### Dictionaries Key, value pairs. Order is arbitrary! ``` {1: “a”, 2: “b”} ``` * Keys must be immutable * Values may be mutable ``` >>> m = { 'H': [1,2,3],'He': [3,4], ... 'Li': [6,7], 'Be': [7,9,10], 'B': [10,11], 'C': [11,12,13,14] } >>> >>> m {'H': [1, 2, 3], 'He': [3, 4], 'Li': [6, 7], 'Be': [7, 9, 10], 'B': [10, 11], 'C': [11, 12, 13, 14]} >>> m['H'] += [4,5,6,7] >>> m {'H': [1, 2, 3, 4, 5, 6, 7], 'He': [3, 4], 'Li': [6, 7], 'Be': [7, 9, 10], 'B': [10, 11], 'C': [11, 12, 13, 14]} >>> from pprint import pprint as pp >>> pp(m) {'B': [10, 11], 'Be': [7, 9, 10], 'C': [11, 12, 13, 14], 'H': [1, 2, 3, 4, 5, 6, 7], 'He': [3, 4], 'Li': [6, 7]} ``` #### Set Unordered collection of unique, immutable objects. ``` >>> p = {6, 28, 496, 81282, 473824} >>> s = set() >>> s set() ``` Adding elements: ``` >>> k = {1,2} >>> k.add(3) >>> k {1, 2, 3} >>> k.add(1) >>> k {1, 2, 3} >>> k.update([4,5,6]) >>> k {1, 2, 3, 4, 5, 6} ``` Removing elements: ``` >>> k.remove(1) >>> k.remove(11) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 11 >>> k {2, 3, 4, 5, 6} >>> k.discard(2) >>> k.discard(22) ``` #### Implementing Collections Protocols such as sized, iterable, sequence and container characterise the collections. * Container protocol: * `__contains__()` * Sized protocol: * ` __len__()` * Iterable protocol: * `__iter__()` * Sequence protocol: * `__getitem__()` * `__reversed__()` * `index()` * `count()` * `__add__()` * `__mul__()` * `__rmul__()` * String representation: * `__repr__()` * Value equality: * `__eq__()` * `__ne__()` `collections.abc`: abstract base classes that define and assist on the collection protocols: * Container: container protocol * Sized: sized protocol * Sequence: sequence protocol * Set * … ## Modularity Reusability. Files with *.py extension. They can be executed directly with: ``` $ python module_name.py ``` Imported into the REPL or other modules with: ``` import module_name ``` Use `__name__` to determine how the module is being executed. `__main__` means that it is being executed directly as an executable. ``` if __name__ == '__main__': fetch_words() ``` This allows the function to be executed automatically when invoking the module as a script but not when importing it as a library. ## Objects Everything is an Object, including modules and functions (they can be treated just like other objects). Variables are named references to objects. The GC reclaims unreachable objects.`id()` returns the unique identifier of the object (debugging purpose). Function arguments are passed by object-reference, so functions can modify mutable arguments. Return statement also is passed by reference. Function arguments can be specified with defaults. Default argument expressions are evaluated once, when def is executed. Scopes: * Local * Enclosing * Global * Built-ins. Use global keyword to assign to global references from a local scope. * `type()` can be used to determine the type of an object. * `dir()` can be used to introspect an object and get its attributes. ## Handling Exceptions Exception handling is a mechanism for stopping “normal” program flow and continuing at some surrounding context or code block. Raising an exception interrupts normal program flow and transfers control to an exception handler. Exception handlers defined using the `try…except` construct. Use common or existing exception types when possible (*IndexError*, *KeyError*, *ValueError*, *TypeError*, etc). Avoid protecting against *TypeErrors*. Just let the code fail. 2 philosophies when dealing with failures: * Look Before You Leap (LBYP). * It’s Easier to Ask Forgiveness than Permission (EAFP). This is Python's culture. Resource cleanup with `finally` block. ## Iterables ### Comprehensions Concise syntax for describing lists, sets or dictionaries in a declarative or functional way. * List comprehension: `[ expr(item) for item in iterable]` * Set comprehension: `{ expr(item) for item in iterable}` * Dictionary comprehension: `{ key_expr:value_expr for item in iterable }` ``` >>> words = "Why sometimes I have believed as many as six impossible things before breakfast".split() >>> words ['Why', 'sometimes', 'I', 'have', 'believed', 'as', 'many', 'as', 'six', 'impossible', 'things', 'before', 'breakfast'] >>> [len(word) for word in words] [3, 9, 1, 4, 8, 2, 4, 2, 3, 10, 6, 6, 9] >>> from math import factorial >>> f = [len(str(factorial(x))) for x in range(20)] >>> f [1, 1, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18] >>> type(f) <class 'list'> >>> s = {len(str(factorial(x))) for x in range(20)} >>> s {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18} >>> type(s) <class 'set'> >>> from pprint import pprint as p >>> from pprint import pprint as pp >>> country_to_capital = {'UK': 'London', ... 'Spain': 'Madrid', ... 'Morocco': 'Rabat', ... 'France': 'Paris'} >>> capital_to_country = {capital:country for country,capital in country_to_capital.items()} >>> capital_to_country {'London': 'UK', 'Madrid': 'Spain', 'Rabat': 'Morocco', 'Paris': 'France'} ``` Comprehensions operate on an iterable source object and apply an optional predicate filter and a mandatory expression, both of which are usually in terms of the current item. Iterables are objects over which we can iterate item by item. They have to implement the `__iter__()` method. We retrieve an iterator from an iterable object using the built-in `iter()` function. Iterators produce items one-by-one-from the underlying iterable series each time they are passed to the built-in `next()` function. When there are not more elements to retrieve, `StopIteration` exception is raised. Comprehensions can have several sequences (nested loops), conditions and other comprehensions. ### Generators Provide means of describing iterable series in a descriptive way. Generator functions contain at least one use of the yield keyword. Generators are iterators. When advanced with `next()` the generator starts or resumes execution up too and including the next yield. Each call to a generator function creates a new generator object. Generators can maintain explicit state in local variables between iterations. Generators are lazy, and so can model infinite series of data. Generator expressions have a similar syntactic form to list comprehensions and allow for a more declarative and concise way of creating generator objects. Replace [] by () ``` (expr(item) for item in iterable) ``` ``` >>> first_squares = (x*x for x in range(1, 101)) >>> list(first_squares) [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801, 10000] >>> list(first_squares) [] >>> sum(x*x for x in range(1, 101)) 338350 ``` ### Iteration tools Built-ins such as: * `map(func, *iterables)`: lazy * `filter(func, iterable)`: lazy ``` >>> positives = filter(lambda x: x>0, [1,-5,0,6,-2,8]) >>> positives <filter object at 0x1044fff60> >>> list(positives) [1, 6, 8] Passing None as the first argument to filter() will remove elements which evaluate too False. ``` **NOTE:** Difference with Python 2.x, where `map()` and `filter()` are eagerly evaluated and return list objects. * `sum()` * `any()` * `zip()` * `all()` * `min()` * `max()` * `enumerate()` Standard library `itertools` module: * `chain()` * `islice()` * `count()` * … Standard library functools module: * `reduce(func, sequence[, initial_value]) -> value`: repeatedly apply a function to the elements of a sequence, reducing them to a single value. ``` >>> from functools import reduce >>> import operator >>> reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) ``` ## Classes All types in Python have a class. Class define the structure and behaviour of objects. An object’s class controls its initialisation. Instance methods must always accept as first argument a reference to the instance, by convention called `self`. ``` >>> from airtravel import Flight >>> f = Flight() >>> f.number() 'SN060' >>> Flight.number(f) ``` Method to initialise new objects: `__init__()` **NOTE:** It is not a constructor. The object has been already created by the time this method is called. It is an initialiser. In Python everything is public, names starting with `_` indicate that they are internal implementation and they shouldn’t be accessed directly. ``` >>> from airtravel import * >>> f = Flight("BA748", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) >>> from pprint import pprint as pp >>> pp(f._seating) [None, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, …] ``` Class attributes: value shared by all the instances of the class. Defined in the scope of the class. Can be access by class name or self. **WARNING:** Trying to assign a new value to a class attribute with self will create a new instance attribute instead. Class methods: methods defined at the class level instead of the instance level. * `@staticmethod` decorator: Use when no access needed to either class or instance objects. * `@classmethod` decorator: use it when the method requires access to the class object to call other class methods or the constructor. Typical use case: named constructors. ``` @classmethod def a_anamed_constructor(cls): return cls() ``` object: the core of the Python object model, is the ultimate base class for every class. It is automatically added as a base class. ### Polymorphism Using objects of different types through a common interface. Achieved through Duck Typing: *“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.* **NOTE:** For static methods to have polymorphic behaviour you have to invoke them via the instance with self.method_name instead of class_name.method_name. ### Inheritance A sub-class can derive from a base-class, inheriting its behaviour and making behaviour specific to the sub-class. * `isinstance()`: determines if an object is of a specific type. * `issubclass()`: determines if one type is a subclass of another. Multiple inheritance: defining a class with more than one base class. ``` class SubClass(BaseClass1, BaseClass2, …) ``` If a class has multiple base classes and defines no initialiser, only the initialiser of the first base class is automatically called. `__bases__` : tuple of base classes. **Method resolution order (MRO):** ordering that determines method name lookup. Looks for the method name in order of the base classes definition. **C3 algorithm**: * subclasses come before base classes. * base classes order from class definition is preserved. * First 2 qualities are preserved no matter where you start in the inheritance graph. ``` ClassName.__mro__ ``` `super():` given a method resolution order and a class C, super() gives you an object which resolves methods using only the part of the MRO which comes after C. It returns a proxy object which routes method calls. * `super(base-class, derived-class)` => class super proxy * `super(class, instance-of-class)` => instance super proxy * `super() == super(class-of-method, self)` or `super(class-of-method, class)` `super()` uses everything after a specific class in an MRO to resolve method calls. In Python inheritance is not needed to achieve polymorphism, so it is mainly used for sharing implementation. ### Encapsulation `@property` and `@p.setter` decorators. ``` class Example: @property def p(self): return self._p @p.setter def p(self, value): self._p = value ``` To overwrite properties in a hierarchy follow the Template Method design pattern. ## Files **With block:** resource cleanup for objects that support the context-manager protocol. ## Shipping Working and Maintainable Code ### Testing Python Standard library contains the unittest module, provides support for: * Unit tests. * Integration tests. * Acceptance tests. `TestCase`: basic unit of test organisation in unittest framework, groups together related test functions. **Fixtures**: code run before and/or after each test function. **Assertions**: specific tests for conditions and behaviours. tests have to start with `test_` to be detected and executed automatically. **PDB**: Python DeBugger. ``` >>> import pdb >>> pdb.set_trace() ``` ``` $ python3 -m pub palindrome.py ``` ### Packaging Packaging and distributing the Python code use standard distutils module. ``` $ python3 -m venv palindrome_env $ source palindrome_env/bin/activate (palindrome_env) $ (palindrome_env) $ python setup.py intall (palindrome_env) $ python … >>> import palindrome >>> palindrome.__file__ ‘…/palindrome/palindrome.py' ``` Packaging to a Source distribution: ``` $ python setup.py sdist —format zip ``` Installing packages with pip Common tools for installing third-party packages are distutils and pip. The central repository for Python packages is the Python Package Index, also called PyPI or “cheeseshop”. ### Organising larger programs Package: special type of module which can contain other modules and/or packages. Packages are generally directories. Modules are generally files. `sys.path`: list of directories Python searches for modules. ``` sys.path = [‘’, dir_1, dir_2, dir_3] ``` ``` >>> import sys >>> sys.path.append(’new_package’) ``` **PYTHONPATH:** environment variable listing paths added to sys.path. To create a package: * Create a directory * Create `__init__.py` file inside the directory. (Executed when the package is imported) ### Relative imports Imports which use a relative path to modules in the same package. ``` from .b import B (same directory) from ..a import A (parent directory) ``` **Namespace packages:** packages that split across several directories. Useful for splitting large packages into multiple parts. They don’t have the `__init__.py` files, avoiding that way complex initialisation ordering problems. **Executable directories:** directories containing an entry point for Python execution. Place a `__main__.py` file. ### Recommended project structure ``` project_name |— __main__.py |— project_name | |— __init__.py | |— more_source.py | |— subpackage1 | | |— __init__.py | |— test | |— __init__.py | |— test_code.py |— setup.py ``` ## Misc ### Conditional expression ``` result = true_value if condition else false_value ``` ### Lambda functions Lambda is an expression which evaluates to a function. ``` lambda name: name.split()[-1] ``` ``` >>> is_odd = lambda x: x % 2 == 1 ``` ### Extended formal arguments ``` def extended(*args, **kwargs): ``` ``` >>> list_of_lists = [list1, list2, list3] >>> transposed = list(zip(*list_of_lists)) ``` ### Closures Maintain references to objects from earlier scopes. ### Function factory Function that returns new, specialised functions. ### Decorators Modify or enhance functions without changing their definition. Implemented as callables that take and return other callable. ``` def escape_unicode(f): def wrap(*args, **kwargs): x = f(*args, **kwargs) return ascii(x) return wrap @escape_unicode def vegetable(): return ascii(‘blomkål’) @escape_unicode def animal(): return ascii(‘bjørn’) @escape_unicode def mineral(): return ascii(’stål’) ``` Classes can be decorators if the implement the `__call__()` method. Instances can be decorators. Multiple decorators are processed in inverse order. ``` @decorator1 @decorator2 @decorator3 def my_function(): ``` Decorators can be used to decorate methods in classes as well as functions. functools.wraps() properly update metadata on wrapped functions. Typical use case for decorators: validate arguments. ### String representations Python has 2 primary string representations for objects: * `str(obj)`: calls `__str__(self)` method. Default are more concise representation. Fallback to repr(). * `repr(obj)`: calls `__repr__(self)` method. It should provide an unambiguous and precise representation of the object (including types). Standard library includes reprlib library: ``` >>> import reprlib >>> reprlib.repr(obj) ``` ### Exceptions Always specify an exception type with except, but don’t be too general. ``` BaseException(args, __context__, __cause__, __traceback__) |- KeyboardInterrupt |- Exception |- SystemExit ``` ``` class MyException(Exception): pass ``` ## Context Manager (PEP343) Object designed to be used in a with-statement. A context-manager ensures that resources are properly and automatically managed. ``` with context-manager: ENTER: prepares the context to be used. body EXIT: cleans up the context. ``` ``` >>> with open(‘file.txt’, ‘w’) as f: f.write(“blah”) ``` Context Manager protocol: * `__enter__()`: executed before entering the with-statement body. If it throws an exception the body is never executed. The return value is bound to as variable. Commonly they return themselves. * `__exit__(self, exc_type=None, exc_val=None, exc_tb=None)`: executed when with-statement body exists. Exception information is passed to `__exit__()`: * `exc_type`: exception type. * `exc_val`: exception object * `exc_tb`: exception traceback By default `__exit__()` propagates exceptions thrown from the with-statement’s code block. If `__exit__()` returns False, the exception is propagated. `__exit__()` should never explicitly re-raise exceptions. It should only raise exceptions if it fails itself. `contextlib`: standard library module for working with context managers. It provides utilities for common tasks involving the with statement. * context `lib.contextmanager`: decorator you can use to create new context managers. ``` @contextlib.contextmanager def my_context_manager(): # <ENTER> try: yield [value] # <NORMAL EXIT> except: # <EXCEPTIONAL EXIT> raise with my_context_manager() as x: ``` Unlike standard context managers, those created with the context manager decorator have to use standard exception handling to propagate exceptions. Multiple context managers (= nested): ``` with cmd1() as a, cmd2() as b: BODY ``` ## Introspection Ability of a program of examine its own structure or state. ``` >>> i = 5 >>> type(i) <class 'int'> >>> dir(i) ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] >>> getattr(i, 'denominator') 1 >>> hasattr(i, 'bit_length') True ``` Introspecting scopes ``` >>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'i': 7} >>> a = 42 >>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'i': 7, 'a': 42} >>> locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'i': 7, 'a': 42} ``` Handy for debugging purposes: ``` >>> name = "Joe Bloggs" >>> age = 28 >>> country = "New Zealand" >>> "{name} is {age} years old and is from {country}".format(**locals()) 'Joe Bloggs is 28 years old and is from New Zealand' ``` The inspect module in the Python standard library provides with advance tools for introspection. # Python for Data Science and Machine Learning ## Main tools ### Anaconda Distribution of Python with “all-in-one” install very popular in data science and machine learning: * Python * Libraries * Virtual Environment ``` $ conda create -n myds anaconda $ conda activate myds $ conda env list $ conda list --explicit > spec-file.txt $ conda deactivate ``` ### Jupyter Development environment where we can write code, display images and write down markdown notes. ## Most popular libraries ### NumPy Linear algebra Library for Python. NumPy Arrays: ``` import numpy as np np.arange(0,10) np.zeros(3) np.zeros((2,3)) np.ones(4) np.linspace(0,5,10) np.eye(4) np.random.rand(5) np.random.randn(4,4) np.random.randint(1,100) ```` Array methods/attributes: ``` arr.reshape(5,5) arr.max() arr.min() arr.argmax() arr.shape arr.dtype arr_copy = arr.copy() ``` **NOTE:** slice only creates a new reference to the existing list, it doesn’t copy the values. ``` arr_2d[1][2] == arr_2d[1,2] arr_2d[:2,1:] ``` arr[boolean_expression] Only returns instances with the same index of those boolean array elements that are true. ``` arr[arr>5] ```