LinkedIn Learning - Python Essentials

Date: 15 April 2018

Category: Learning

Tags: Python, LinkedIn Learning

Whilst I’ve written many Python scripts, and have completed various types of Python training courses before, I find it’s useful to occasionally go back and recap on the basics.

For this reason, this weekend I spent a few hours completing the LinkedIn Learning Python Essentials course by Bill Weinman, which teaches Python3.

This blog consists of the notes I wrote down whilst taking this course, and some links to additional reading I completed. A lot of the notes are very basic, but are good to remember, and be reminded of (especially as technical interview questions are often based on key principles of a language).

Notes:

  • Everything is an object in Python3, hence we can call a function directly of a string as follows:

    hello world'.upper()
    
  • Shebang (#!)
    • Use #!/usr/bin/env python3
    • This will identify the location of python3, regardless of the Linux distribution being used
  • Statement: unit of execution
    • A line of code, not requiring a semi-colon in python
    • Can use semi-colons to implement more than one statement on a single line
  • Expression: unit of evaluation; anything which returns a value
    • All function calls return a value; although this may often be none
  • If __name__ == '__main__': main() is implemented at the very end the script, and all statements are contained within functions, no code will run until the entire script has been loaded, which prevents null assignment issues
  • No such thing as a multiline comment in Python, need to use # at the start of each commented line
  • Print formatting:
    • Use curly brackets as placeholder
      x = 42
      print('Hello, World {}'.format(x))
      
  • In Python2, string was not an object and print was not a function:

    x = 42
    print 'Hello, World %d' % x
    
    • THIS IS NOW DEPRECATED AND SHOULD NOT BE USED; it will be removed eventually
    • Always use the format method
  • New in Python 3.6; can use what’s called f string (where f stands for format):

    x = 42
    print(f'Hello, World {x}')
    
  • Blocks do not define the scope of variables:
    • Hence this is fine: ```python if true: x = 3

    print(f’x is {x}’) ```

  • It’s frowned upon to have statement on the same line as a condition:
    • i.e. if True: print('Hello, World')
  • No switch statement in Python
  • A class is a definition, and an object is an instance of a class
    • The first argument for a method inside of a class is always self. This is a reference to the object when the class is used to create an object.
  • Python uses a form of dynamic typing sometimes called dynamic typing: the type of a value is determined by the value itself
    • If it walks like a duck, it’s a duck
    • Useful blog post on static vs. dynamic typing.
  • Single quotes and double quotes are interchangeable
  • Triple quotes are used to define multiline strings
  • Specified positional arguments in string formatting:
    '{1} is the {0} day of the week'.format('first', 'Monday')
    
    • Will produce Monday is the first day of the week
    • Probably want to use f strings though
  • Don’t use floats when dealing with money
    • Something like 0.1 + 0.1 + 0.1 - 0.3 can return 5.551...e-17
    • This is a documented limitation of the Float type
    • It’s a problem of precision vs. accuracy, as detailed in the Floating-point Arithmetic Wikipedia page
    • Instead, we want to use something like the decimal library:
      from decimal import Decimal
      
      # We pass in a string, rather than a float
      # Otherwise, we would end up with the same problem as if we'd just used a float
      a = Decimal('0.10')
      b = Decimal('0.30')
      x = a + a + a - b
      
    • The value of x will be 0.00, and its type will be decimal.Decimal
    • Don’t always need to be concerned with this, but it is worth bearing in mind
  • The following evaluate as False:
    • The NoneType
    • An empty string
    • The number 0
  • A mutable object can be changed after it is created, an immutable object can’t
    • Good blog post on mutability in Python

      Objects of built-in types like (int, float, bool, str, tuple, unicode) are immutable. Objects of built-in types like (list, set, dict) are mutable. Custom classes are generally mutable. To simulate immutability in a class, one should override attribute setting and deletion to raise exceptions.

    • Should remember that the contents of immutable containers are often themselves mutable:

      Python containers liked tuples are immutable. That means value of a tuple can’t be changed after it is created. But the “value” of a tuple is infact a sequence of names with unchangeable bindings to objects. The key thing to note is that the bindings are unchangeable, not the objects they are bound to.

    • For example:
      a = 1
      b = 2
      c = ['a', 'b', 'c']
      
      # The following tuple would print as (1, 2, ['a', 'b', 'c'])
      t = (a, b, c)
      
      # This cannot be done as tuples are immutable; a TypeError would be returned
      t[1] = c
      
      # This will not change the value of the first item in the tuple, as int objects are immutable
      a = 4
      
      # Lists are mutable, and so the first value of the list in the tuple will be changed
      c[0] = 'd'
      
      # The tuple would now print as (1, 2, ['d', 'b', 'c'])
      
  • Preferable to favour immutable type tuple over mutable type list, unless you know you are going to re-assign the items in the list
  • Range objects are immutable
  • Can iterate over the keys and values of a dictionary as follows:
    # x is a dictionary
    for k, v in x.items():
      print(f'k: {k}, v: {v}')
    
  • The built-in Python function id (documented here):

    Returns the “identity” of an object. This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

    • The significance is as follows:
      x = 1
      y = 1
      
      # These would both have the same value
      print(id(x))
      print(id(y))
      
      # The variable a would be a Boolean type with value True
      # There is only  one literal object that is the number 1
      # There's no reason for Python to create separate objects as the int 1 is immutable
      a = x is y
      
  • In the same way that all variables which point to an immutable int of value 1 will have the same id (because they are the same object), the same is true for all other immutable types. For instance if both x = 'thatscotdatasci' and y = 'thatscotdatasci' then x is y will return true, and id(x) and id(y) will have the same value.
  • Check type using isinstance:
      a = [1, 2, 3]
    
      # t would be a Boolean type with value True
      t = isinstance(a, list)
    
    • Useful when you know a variable may have different types, and you want to handle each possible type
  • According to the Wikipedia definition of a ternary operator:

    In computer science, a ternary operator is an operator that takes three arguments.[1] The arguments and result can be of different types. Many programming languages that use C-like syntax[2] feature a ternary operator, ?:, which defines a conditional expression.

    • In Python:

      Ternary operators are more commonly known as conditional expressions in Python. These operators evaluate something based on a condition being true or not.

    • For example:
      x = True if 2 > 1 else False
      
  • In Python3, when you perform division the result will always be a float object, even if you started with two integers and there is no remainder
  • Python does not have an XOR operator
    • One way of acheiving this is as follows (Stack Overflow):
        (a and not b) or (not a and b)
      

      Which comes from the definition of XOR.

    • Alternatively, the following also works:
        bool(a) ^ bool(b)
      

      The reason for this is:

      The xor operator on two booleans is logical xor (unlike on ints, where it’s bitwise). Which makes sense, since bool is just a subclass of int, but is implemented to only have the values 0 and 1. And logical xor is equivalent to bitwise xor when the domain is restricted to 0 and 1. For instance: ```python a = 1 b = 2

      print(a < 0 ^ b < 5) # Returns True, as b < 5 is True and a < 0 is False

      print(a < 0 ^ b > 5) # Returns False, as both are False

      print(a > 0 ^ b < 5) # Returns False, as both are True *