Monday, June 29, 2020

Numbers

 Numbers

An int can be unlimited length.

Very small floats are auto converted to scientific notation when you try and print them.

Very large or very small floats auto convert to an a.bEd format on print. a is a single digit when d is positive. d can be positive or negative and must be greater than 13 or any negative integer to get the auto conversion to occur. Otherwise, the scientific notation is expanded out and a decimal point is used.


population  = 327E+6

print(population)

# result is:
327000000.0

-----------------

population  = 327E+14

print(population)

# result is:

3.27E+16

-----------------

population  = 327E+13

print(population)

# result is:

3270000000000000.0

-----------------------


population  = 327E-1

print(population)

# result is:

32.7

-----------------------


Math can be performed on very small float numbers, even if it means making them smaller.


Printing a complex number that only has a negative imaginary part will format with a -0 for the real part.

imag = -9j

print(imag)

# result is:

-0-9j

pos_imag = 9j

print(pos_imag)

# result is:

9j

import the random module

There is a randrange() function that takes two integers A&B. The range covered is from A to B-1 inclusive. B must be greater than A.

randrange(1,8) covers from 1 to 7 and returns an integer.








Saturday, June 27, 2020

User Input and String Formatting

 User Input


Use the input() function to return a value to the variable. The input function has an optional string prompt that can be displayed to the screen. Python code will wait indefinitely for user input. 


basket = input("groceries to buy?:")
print("The food is ", basket)


After typing in the response to the keyboard at the input function's prompt, just press Enter to move the code along.


String Formatting


Use the format() function with arguments passed into the format function which will match up with pairs of curly braces in a string variable from left to right.


vacations = "There are {} , {} and {} islands."

# format is a member function of the vacations string object.
# just by making a string assignment, you have created a string 
# object and you do not need a constructor for that.

print(vacations.format("Caribbean", "Greek", "Japanese"))

# result is:

There are Caribbean, Greek, and Japanese islands.

Leaving out the curly braces in the string variable with a call to the format function will just ignore any arguments in the format function and print null instead.

vacations = "There are  ,  and islands."

# format is a member function of the vacations string object.

print(vacations.format("Caribbean", "Greek", "Japanese"))

# result is:

There are  ,  and islands.


In the case if you make a mistake where you have more curly braces in a target variable than arguments into the format function, python returns a IndexError: tuple index out of range response.

vacations = "There are {} , {} and {} islands."

# format is a member function of the vacations string object.

print(vacations.format("Caribbean", "Greek"))

# result is:

IndexError: tuple index out of range


If however on the other hand, you have a number of arguments passed into the format() which exceed the number of {} then the extra rightmost arguments are ignored and no errors are generated.

vacations = "There are {} , {} and islands."

# format is a member function of the vacations string object.

print(vacations.format("Caribbean", "Greek", "Japanese"))

# result is:

There are Caribbean, Greek, and islands.


You can specify a large number of field properties also known in python as "standard format specifiers". They allow you to fine-tune the format, placement, or style of a string.


debt = 1000000

# :e is the standard format specifier for scientific notation

pay = "I owe this much: {:e}"


print(pay.format(debt))

# result is:

I owe this much: 1.000000e+06


Arguments to a format function call can be comma separated or key-value pairs or both. They can be of any data type including an index.



There are three options for formatting the curly braces pairs in a target string variable: 


1. You can put the name of a key in the {}; in that case a key-value pair must be passed in for the format function.


dessert = "I like {cookie} cookies, {cake} cakes and {ice_cream} ice cream."

print(dessert.format(cookie = "oatmeal raisin", cake = "vanilla", ice_cream = "strawberry"))

# result is:


I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.


2. You can use an integer index starting as low as 0 to indicate which of the members of the argument collection in the format call are to be inserted in various places. Placement can be out of order and also arguments can be skipped (not used). It must be in list format; no key=value pairs are allowed.

dessert = "I like {0} cookies, {1} cakes and {2} ice cream."

print(dessert.format(cookie = "oatmeal raisin", cake = "vanilla", ice_cream = "strawberry"))

# result is an error because you used key-value pairs:

IndexError: tuple index out of range

dessert = "I like {0} cookies, {1} cakes and {2} ice cream."

print(dessert.format("oatmeal raisin", "vanilla", "strawberry"))

# result is valid because the flavors are given as a list:

I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.

This also works, but note you have to index into the list.

foods = ["oatmeal raisin", "vanilla", "strawberry"]
dessert = "I like {0} cookies, {1} cakes and {2} ice cream."

print(dessert.format(foods[0], foods[1], foods[2]))

# result is:

I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.

3. If you just have a plain empty pair of {} you can use a list or key value pairs.

dessert = "I like {} cookies, {} cakes and {} ice cream."
# arguments as a list
print(dessert.format("oatmeal raisin", "vanilla", "strawberry"))

# result is:

I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.

The same result is achieved with:

dessert = "I like {a} cookies, {b} cakes and {c} ice cream."
# arguments as key-value pairs
print(dessert.format(a = "oatmeal raisin", b = "vanilla", c = "strawberry"))

# result is:

I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.


You can even mix key=value pairs and list items in the same format() call:

dessert = "I like {} cookies, {b} cakes and {c} ice cream."

print(dessert.format("oatmeal raisin", b = "vanilla", c = "strawberry"))

# result is:

I like oatmeal raisin cookies, vanilla cakes and strawberry ice cream.


Python is even smart enough to handle key-value pairs and list items out of order:

dessert = "I like {b} cookies, {} cakes and {c} ice cream."

print(dessert.format("oatmeal raisin", b = "vanilla", c = "strawberry"))


# result prints without errors but now the cookies are vanilla and cakes are oatmeal raisin:

I like vanilla cookies, oatmeal raisin cakes and strawberry ice cream.


However, forgetting one of the keys, which is the same as having too many empty {} in a mixed call, will confuse Python and generate an error:

# the b key is left out by mistake, and there are more {} than list items now.

dessert = "I like {} cookies, {} cakes and {c} ice cream."
# oatmeal raisin is a list-type item
print(dessert.format("oatmeal raisin", b = "vanilla", c = "strawberry"))


# result is

IndexError: tuple index out of range


When you're using field properties, again also known as standard format specifiers, you only use the colon once, even if you have multiple options passed in. If you try to use two colons - say one for each of two options - python kicks back a ValueError.

boats = 334
marinas = 7

# :+ puts a plus sign in front of positive numbers by brute force
# :e is for scientific notation
# a specifier of :+:e will generate an error; drop the rightmost of the colons as shown correctly below.

docking = "{1:+e} boats are too many for {0} marinas."

print(docking.format(marinas, boats))

# result is:

+3.340000e+02 boats are too many for 7 marinas.

The example above also works without a run-time python error if you leave off the index of 0 and 1 since variables are being passed in.  However, the meaning of the sentence is confused, so be careful about using indices out of order.


boats = 334
marinas = 7

# :+ puts a plus sign in front of positive numbers by brute force
# :e is for scientific notation
# a specifier of :+:e will generate an error; drop one of the colons as shown correctly below.

docking = "{:+e} boats are too many for {} marinas."

print(docking.format(marinas, boats))

# result is:

+7.000000e+00 boats are too many for 334 marinas.


However, with strings passed in, it will fail because specifiers won't work with pure strings.


docking = "There are only 7 {0} for 334 {1:+e}."

print(docking.format("bays", "ships"))

# results in:

ValueError: Unknown format code 'e' for object of type 'str'


# Also incorrect:

docking = "There are only 7 {} for 334 {1:+e}."

print(docking.format("bays", "ships"))

# results in:

ValueError: cannot switch from automatic field numbering to manual field specification, meaning, mixing empty parentheses with indexed parentheses.


However, turning "ships" into a proper variable gets the job done without error. 

Also, mixing variables and string list items in a format() function call is valid code.

ships = 334

# standard format specifiers and open parentheses are OK 
# in the same string.

docking = "There are only 7 {} for {:e} ships."

print(docking.format("bays", ships))

# results in:

There are only 7 bays for 3.340000e+02 ships.

Finally, you can even use the same index more than once.

docking = "There are only 7 {1} for 334 {1}."

print(docking.format("bays", "ships"))

# results in:

There are only 7 ships for 334 ships.



Anyhow, you get the idea that there many permutations for mixing and matching index vs. empty curly braces vs. variables vs. key=value pairs for getting the format function to handshake with {} in a target string variable.


See the following website for the full description of all the standard format specifiers, also known as custom string formaters:


 a few examples are:

 :,       --> display thousands separator


 :e      --> for scientific format


:f       --> fixed point number format 

:>      --> right align the result


Thursday, June 25, 2020

try ... except blocks

try ... except blocks


Use the try keyword with a colon and some code with the idea that python needs to see if it fails or generates an error. If it does fail you need to have the except keyword with a colon to catch or trap that error or failure.

try:
  print(football)
except:
  print("An exception occurred")

 Optionally you can use an else: with a colon if the try block finishes cleanly without error or failure and also optionally a finally: block which executes regardless of whether the try block's code fails or succeeds, with or without error. The finally block is good for closing files or cleaning up your variable space. 

football = "inflated"

try:
  print(football)
except:
  print("An exception occurred")
else:
  print("Caught for a touchdown!")
finally:
  print("score may have changed")

You can use a series of excepts, one for each type of error.

else will only execute if something went wrong and it must be written after all the except calls. There can only be one else for each try.

football = "inflated"

try:
  print(football)
except:
  print("A name exception occurred")
except:
  print("A type exception occurred")
else:
  print("Caught for a touchdown!")
finally:
  print("score unchanged")

You can force/throw an exception - a programmer induced error or failure - with the raise keyword with no colon. raise needs an argument which is an object of general type of the built-in Exception class and its behavior will be like a real error to the terminal rather than a forced one by the programmer; the two will look the same other than the customized text message, if any (the Exception object can be devoid of any arguments in its parentheses).

time_on_clock = 5

if time_on_clock == 0
    raise Exception("Game over.")
else:
    print("Keep playing")

# result is

Keep Playing

another example:

try:
        print(score)
except:
        raise Exception()

# result is:

NameError: name 'score' is not defined. During handling of the above exception, another exception occurred: Traceback (most recent call last): File "./yourscript", line 123, in Exception

Note there are 2 errors, one naturally occurring from Python and the other forced by the programmer.


You can also raise more specific error types such as TypeError or NameError. See this URL for a complete list of python errors: 


When the programmer uses a raise keyword, no code will be executed after the raise command is complete.




PIP Package Manager

PIP

PIP is a package manager. You should check to see if it's installed by going to your python script subdirectory and typing

 pip --version. 

Version 3.4 and newer should already have PIP, but if not, go here to get it:



Typical ways to use IP would be: 

pip install package-name
pip uninstall package-name
pip list package-name

You can find packages at pypi.org

Package are really just another name for modules, so use import to bring in the package functionality into your code.



Tuesday, June 23, 2020

Regular Expressions

Regex

The name of the built-in package for doing regular expression parsing is re

 An asterisk * means there are zero or more occurrences and * needs a character to the left of it to know what to match against.

 A .* pair is like a wild card.

 \b looks to see if the target string is at the start of a word. You can use the optional lowercase r outside of the double quotes for "raw strings". A "raw string" is one where the \ escape character is NOT honored, and instead treated just like any other character. This is particularly useful for Windows directory pathnames that have pairs of \ or when a string has \ before a letter that normally would escape it.

 Generally lower case specials are for positive findings of a target string and uppercase for NOT finding the target string.

 [] notes a set of eligible matching characters.

 + * . | () $ {} have no special meaning in a set so just treat them literally.

 The indices on a match on a substring goes from the first character matched to the first character not matched.

.search() returns what's called a match object that has its own functions.

.span() returns a tuple of matching substrings

.string() function returns the searchable string itself and 

.group() returns what is actually matching

re objects have four workhorse functions: findall(), search(), split() and sub()

re objects have 10 main metacharacters

[] \ . ^ $ * + {} | ()

A metacharacter is like a placeholder related to a particular type of character and is part of the calling function's parameter list.

re objects have 10 primary special characters

\A \b \B \d \D \s \S \w \W \Z

The special characters are useful when the position of the match matters or when distinct words or whitespace considerations matter.

re objects have 6 primary types of sets:

[letters]: one or more characters possibly unrelated
[a-z]: anything in that continuous range
[^]: one or more NOT in the set
[digits]: one or more numbers possibly unrelated
[0-9]: numbers in the range
[a-zA-Z]: broadens out for capitalization matches

findall(): returns matches in order found.

search(): returns a match object but only for the first match found.

split(): returns all the fragments that match in a tuple.

sub(): replaces all matches with a substitution string.

These functions generally return None if there is no match.

Examples

import re

txt = "That will be 43 pesos"

# Find any 2 consecutive digit characters:

x = re.findall("\d{2}", txt)
print(x)

# result is ['43']

------------------------

import re

txt = "Basketball is my favorite sport."

#Search for a sequence that starts with "ke", followed by two (any) characters, and an "a":

x = re.findall("ke..a", txt)
print(x)


# result is ['ketba']

------------------------------------------


import re

txt = "Basketball is my favorite sport."

# Search for a sequence that begins the string only

x = re.findall("^Bas", txt)
print(x)


# result is ['Bas']


---------------------------------

import re

txt = "Basketball is my favorite sport."

# Search if the target string ends in ort

x = re.findall("ort$", txt)
print(x)


# result is [] because the period was not included in the target of ort


import re

txt = "Basketball is my favorite sport."


# Check if the string contains "ll" followed by 1 or more periods 

# note that since a period is a regex metacharacter, we have to escape it with a \.

x = re.findall("ll\.*", txt)



# result is ['ll is my favorite sport.']

------------------------------

import re

txt = "Basketball is my favorite sport."



# Check if the string contains "a" followed by any characters, and only return the second match. Within that second match, search for "a" again and return everything that comes after it:

x = re.findall("(a(.*)){2}", txt)

print(x)

[('avorite sport.', 'vorite sport.')]


-----------------------------


import re

txt = "Basketball is my favorite sport."

x = re.search("is", txt)

print(x)

# This is what a raw dump of the Match Object looks like.

# result is <_sre.SRE_Match object; span=(11, 13), match='is'>


------------------------------------


import re

txt = "Basketball is my favorite sport."

x = re.split("\s", txt)

print(x)

# result is ['Basketball', 'is', 'my', 'favorite', 'sport.']

-------------------------------------


import re

txt = "Basketball is my favorite sport."

# replace all spaces with underscores
 y = sub("\s", "_", txt)

print(y)

# result is Basketball_is_my_favorite_sport.

# Note that the special character \s has a different meaning in the sub() function vs. the split() function.

---------------------------------------------


import re

# The search() function returns a Match object:

txt = "Basketball_is_my_favorite_sport."

x = re.search("or", txt)
print(x)



# result is <_sre.SRE_Match object; span=(20, 22), match='or'>

# Notice that it only found the first instance of "or" and ignored the "or" in "sport"




Sunday, June 21, 2020

JSON

JSON

 There's a JSON module that can be imported.


 JSON is a protocol that is used for storing data or exchanging data described as JavaScript objects.

 JSON can be read into python and the resulting variable will be a dictionary. Use the loads() method to bring in JSON objects and use the dumps() method if you want to go from python to JSON.


import json

# example JSON:
x = '{ "team":"Lions", "size":20, "field":"grass"}'

# parse x:
y = json.loads(x)

# the result is a Python dictionary:
print(y)

# note that JSON to Python drops the double quote marks in favor of # single quote marks, and the outermost pair of single quotes
# enclosing the entire JavaScript object is dropped by python.

{'team': 'Lions', 'size': 20, 'field': 'grass'}


import json

# a Python object (dict):
x = {
  "team": "Lions",
  "size": 20,
  "field": "grass"
}

# convert into JSON:
y = json.dumps(x)

# the result is a JSON string. Notice the curly braces and the reverse ordering.

print(y)

{"team": "Lions", "size": 20, "field": "grass"}


Converting a python tuple goes into a JSON with square braces.
 
Converting the python None value goes into the JSON null value.

Converting a python pair of single quotes or double quotes goes to a JSON pair of double quotes.

Converting python scientific notation into JSON with all the scientific notation expanded and with one numeral to the right of the decimal point.

Optional format parameters for the dumps() method:

You say indent = number of spaces when you want smart indenting automatically done.

separators = in a pair of parentheses and double quotes; the first one tells you how to separate the objects and the second pair of double quotes tells you how to separate the key-value pairs.

The other option is order which is a Boolean (True or False) if you want them alphabetically ordered.

Here is an example that demonstrates all the possible conversions and formatting options.

# convert python to JSON

import json

# the variable business is a python dictionary
# founders variable is a tuple
# floors variable is a list
# which itself contains two short dictionaries

business = {
  "location": "John",
  "years in business": 30,
  "open at night": True,
  "profitable": False,
  "founders": ("Jackson","Emily"), 
  "parking lot": None,
  "floors": [
    {"first": "legal", "lawyers": 15},
    {"second": "collections", "dollars": 24000.46}
  ]
}

# use - and a space to separate objects, and 
# a space and a = to separate keys from their values:

print(json.dumps(business, indent=4, separators=("- ", "= ")))

###########   JSON output ##############

{
    "location"= "John"- 
    "years in business"= 30- 
    "open at night"= true- 
    "profitable"= false- 
    "founders"= [
        "Jackson"- 
        "Emily"
    ]- 
    "parking lot"= null- 
    "floors"= [
        {
            "first"= "legal"- 
            "lawyers"= 15
        }- 
        {
            "second"= "collections"- 
            "dollars"= 24000.46
        }
    ]
}


Saturday, June 20, 2020

Math

Math


There are ceiling and floor functions that find the nearest integer, i.e., math.ceil(1.49), math.ceil(1.45) math.ceil(1.44) will all give you a return value of 2.  math.ceil(-1.49) gives -1. 

It appears that the square root function when you import the math module will not take or return complex numbers.

The pow() function however, will accept arguments that lead to complex numbers. It returns the machine epsilon though instead of zero.

# the square root of -4
x = pow(-4,0.5) ###  gives 1.22E16 + 2j

 

Other interesting math functions that you don't normally see built into other languages are the combination and permutation functions, sum over iterables, product over iterables, and isclose() with two arguments to determine distance between numbers.

import math

c = math.comb(4,2) 

# the number of unordered non-repeating pairs among 4 different things

print(c)

# 4!/((4-2)! * 2!)

GIVES A RESULT OF 4

import math
c = math.perm(4,2)
# ordered non-repeating pairs among 4 different things

print(c)

# 2^4

GIVES A RESULT OF 16



pennies_in_jars = (37, 56, 9, 105)

money = math.fsum(pennies_in_jars)

print(money)

# results in - note that fsum returns a float:

207.0



math.isclose(a, b, relative_tolerance = value, absolute_tolerance = value)

Default value is 1e-09 for relative_tolerance 

absolute_tolerance is needed for when numbers are very close to zero. It's value must be at least zero.

metric: Is abs(a-b) <= max( relative_tolerance * max(abs(a), abs(b)), absolute_tolerance ) ??


isclose() returns a boolean. You must use the exact phrases rel_tol and abs_tol to specify the values in the function call.

example

a = 1.35
b = 1.65

c = abs(a-b) # equals 0.3

rel_tol = 1E-09
abs_tol = 0.4

## 1E-09 * max(1.35, 1.65) = 1E-09 * 1.65 =  1.65E-09

## max(1.65E-09, 0.4) = 0.4

## Finally, is 0.3 < 0.4 # True

result = math. isclose(1.35, 1.65, rel_tol = 1E-09, abs_tol = 0.4)

#result is 

True



Thursday, June 11, 2020

Dates


Dates

import datetime

x = datetime.datetime.now()
print(x)

# results in:

2020-06-11 12:11:37.306782

Using dir() on nested members of an import module shows all "components" including things like __init__().

import datetime

w = dir(datetime)
print(w)

#results in:

 ['MAXYEAR', 'MINYEAR', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_divide_and_round', 'date', 'datetime', 'datetime_CAPI', 'time', 'timedelta', 'timezone', 'tzinfo']

import datetime

x = datetime.datetime(2020, 4, 13)
y = dir(x)
print(y)

#results in:

['__add__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__rsub__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', 'astimezone', 'combine', 'ctime', 'date', 'day', 'dst', 'fromordinal', 'fromtimestamp', 'hour', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'microsecond', 'min', 'minute', 'month', 'now', 'replace', 'resolution', 'second', 'strftime', 'strptime', 'time', 'timestamp', 'timetuple', 'timetz', 'today', 'toordinal', 'tzinfo', 'tzname', 'utcfromtimestamp', 'utcnow', 'utcoffset', 'utctimetuple', 'weekday', 'year']


There is a datetime constructor as a component of the datetime module. The fact that the module and the constructor have the same name may be a bit confusing since you have to repeat that phrase  (datetime.datetime) in order to run the constructor. If you want to avoid the awkward looking repeated phrase sequence, just import only the components that you need like this:

from datetime import datetime

x = datetime.now()

print(x)

# results in:

2020-06-11 16:17:55.482387

The default format for the creation of a datetime object is (year, month, day).

strftime() formats the resulting object string using a single format parameter, denoted by a single upper or lower case letter after a % sign.

Specifying an unlisted code generates no errors but will give either unknown results or the invalid flag repeated back to the screen.

import datetime

x = datetime.datetime(2017, 9, 21)

print(x.strftime("%v"))

#results in:

%v

See all of the strftime format codes.





Monday, June 8, 2020

Scope and Modules

Scope

local and global concepts are used in the standard ways.

Variables, and nested functions which are created within a function for the first-time, can only be local and can only be used within those functions and inner nested functions. You can override that by creating a variable in a function using the global keyword. This is a rare case in python where a variable is actually declared without making an assignment to it.


food = "candy"

def runaway():
    food = "nuts"
    global drink
    # incorrect putting: global drink = "water" all on one line
    drink = "water"

runaway()
print(drink)
print(food)

# results in:

water
candy


 Modules

Modules in python are like C libraries and with the ending of .py file extension

Optionally you can import a module_name with an alias on import which is good for when using modules of other people and you'd rather have them going by a different name to accommodate code that you've already written. Usually, but not necessarily,  the alias is a shortened version of the module name. Note the new keyword as.

 For example:

import module_name as jrq 

You use jrq functions or variables within module_name in your own code.

jrq.eatfood()      # a module function
jrq.hometown    # a module property


There's a built-in function in Python which is dir() which lists all the functions or variables in a module.


listing = dir(module_name)


At a really fine grain level one can pick and choose which elements to import using the from import keyword combo but you're going to have to know the name of the variable (property) or the function in advance. Either that, or get it from a dir() call. 

The format goes like this:

from module_name import function_name 
from module_name import variable_name

If you do it this way you pick and choose functions and variables. 

Don't use the module_name as a base on the function or variable calls; just use the function or variable name as if it were built in globally to your homegrown code.

from module_name import function_name

##### incorrect: module_name.function_name

function_name


To summarize:

1. Import a module by its normal given name, gets you all of the module's functions and properties. Use the module_name as a base when calling on the function or property.

import special_things

gift = special_things.box


2. Import a module and reassign it right away to an alias.
Use the Alias as the base when calling on the function or property.

import special_things as stuff

gift = stuff.box

3. Import just the function or property variable that you want. Do NOT use any module name or alias base for it.

from special_things import box

gift = box








Sunday, June 7, 2020

Iterators

Iterators

An Iterator is an object that has countable sequenced values. The iterator method will not run past the final value if used in a for loop as long as the default built-in functionality of iteration is not overridden by a call to __next__() as explained shortly.

planets = ("Earth", "Venus", "Saturn")
space_travel = iter(planets)

print(next(space_travel))
print(next(space_travel))
print(next(space_travel))
print(next(space_travel))
print(next(space_travel))
print(next(space_travel))

# results in:

Earth
Venus
Saturn

# and the final 3 next calls produce nothing because the end of the 
# tuple was reached.

Strings are also iterable one character at a time.


A call to the iter() member function has to be returned into an object variable. If you want to use a for loop in a more traditional way do it directly on the tuple, list, set or dictionary instead of its iterable object so that it will automatically stop at the last member of the tuple, list, set or dictionary object.


The iterator's built-in next goes by one unit and the built-in iter establishes some type of property associated with the object's self-referencing self identifier. The __iter__() and __next__() function calls override those default functions of an iterable object.


The __next__() function must return the next value unless overriden.

The __iter__()  function must return the self-referencing identity variable for the object, possibly with a property variable attached to it.

Somehow it looks like you're going to need this StopIteration in a raise call as part of an else: clause on the back end of an if conditional clause to get a for loop to stop. It is just the nature of the beast using a next overriding function that it will keep going forever even if the conditional is met.

class OddNumbers:
  def __iter__(oddself):
    oddself.a = 7
    return oddself

  def __next__(oddself):
    if oddself.a <= 30:
      x = oddself.a
      oddself.a += 2
      return x
    else:
      raise StopIteration

# without the else clause the odd numbers would just keep 
# printing out even if oddself was greater than 30. It doesn't
# really make sense compared to the normal way of using if 
# conditionals, but is just the way it is when using the
# __next__() 

Odds = OddNumbers()
iterable_odds = iter(Odds)

for a in iterable_odds:
  print(a)

------------------------------ another example ----------------



class OddNumbers:
  def __iter__(oddself):
    oddself.a = 9
    return oddself

  def __next__(oddself):
    x = oddself.a
    x += 2
    return x

Odds = OddNumbers()
Johns_Odds = iter(Odds)

print(next(Johns_Odds))
print(next(Johns_Odds))
print(next(Johns_Odds))
print(next(Johns_Odds))
print(next(Johns_Odds))

# prints 11 over and again because x is just a reference for oddself.a. 
# If oddself.a itself never gets adjusted then neither will x!!
# in other words, equal sign (=) assignments of an object's self entity is a copy and not by reference.



Saturday, June 6, 2020

Arrays and Inheritance

 Arrays

There is no built-in functionality for arrays in python; just treat them exactly the same way as list: the same properties, same methods, same constructs.


Inheritance


Inheritance: the format is class child(parent) :

Use the pass keyword if you don't want to add any other properties or methods to the child class.

To keep parent inheritance, yet still create new child properties and methods, you need to call the __init__ twice!!


class child(parent):
    def __init__(self, a, b, c):
        parent.__init__(self, d, e, f)

In this case self in the parent call belongs to the child; it's not to be referring to the self in the actual parent class definition. Notice that no colon is used after the parent. 

super() does the same thing as the parent's init call except you can leave out the self-referencing variable parameter in this case. Here also you're not using a trailing colon (:)

class child(parent):
    def __init__(self, a, b, c):
        super()__init__(d, e, f)


For some odd reason the parent parameter list must be repeated in the init call for explicit parent inheritance.

class child(parent):
    def __init__(self, a, b, c):
        parent.__init__()    # incorrect syntax


Properties of the same name in the child will override the parents, same for methods.

Example #1 - hard coded parent property value remains unchanged in the child call 

class Person:
  def __init__(self, fname, lname, year):
    self.firstname = fname
    self.lastname = lname
    self.yeargrad  = 2100
    
  def printname(self):
    print(self.firstname, self.lastname. self.yeargrad)

class Student(Person):
  def __init__(self, fname, lname, year):
    super().__init__(fname, lname, year)
    

x = Student("John", "Quagliano", 1980)
print(x.yeargrad)

# result is:

2100

----------------------------------------



Example #2 - hard coded parent property value is overrdidden  in the child call 
and the child method of the exact same name as the parent's gets invoked instead.



class Person:
  def __init__(self, fname, lname, year):
    self.firstname = fname
    self.lastname = lname
    self.yeargrad  = 2100

  def printname(self):
    print(self.firstname, self.lastname)

class Student(Person):
  def __init__(self, fname, lname, year):
    super().__init__(fname, lname, year)
    self.yeargrad = year

  def printname(self): # same function name as for parent above
    print("Welcome ", self.firstname, self.lastname, " to the class of ", self.yeargrad)
    # notice that the python print built-in function automatically
    # inserts spaces in between string arguments

x = Student("John", " Quagliano", 2019)
x.printname()

# result is:

Welcome John Quagliano to the class of 2019








Friday, June 5, 2020

Lambda, Class and Objects

 Lambda

Lambda functions are anonymous functions and it has to all fit on one line. You can have some mixing of types, for example, int and float, in the lambda's expression.

price = lambda tag : tag + 10
print(price(0.99))

# result is:

10.99

The variable as assigned is the Lambda. You must send in values for all the parameters even if not using it in the expression of the Lambda, though this is poor coding anyhow.


price = lambda dollar_sign, tag : tag + 10
print(price("$", 0.99))
# no error


Lambda looks like a confusing roundabout way to just do traditional functions. I don't see its real purpose.


 Classes and Objects


The class keyword is used to make an object and the object will have methods and properties. After the class is created an object can be defined by calling the class with a pair of empty ().

class groceries:
         meats = "beef"
         breads = "wheat"

dinner = groceries()

print(dinner.meats)

# result is:

beef


A more powerful and practical way of creating classes is to pass in arguments to object's properties.

Don't forget the colon after the class and def lines!!
Don't forget to indent the properties under the init call!!

class groceries:
    def __init__(this_object, protein, carbs):
        this_object.meats = protein
        this_object.breads =  carbs

dinner = groceries("beef", "wheat")

print(dinner.meats)

# result is:

beef


The __init__ it takes the place of a primary list in a function, as would normally  be represented by () with parameters inside on the function def line.

def function(a, b, c) is sort of like def  __init__(self, a,b, c)


Every class has a built-in default self-referencing variable which is the first parameter in the __init__ call. If you try to access properties of the class without using the self-referencing variable you'll get a TypeError that says "0 positional arguments but one was given" 


Using an object's function out of scope will return a null blank value. Meaning, just calling the function name "naked" without its object "owner" returns a blank.


Objects can access other objects' properties by default, but like I just said you must use a self-referencing object variable name for any access to a property, even within a method of the class itself!!

class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age

  def myfunc(self): # this function named myfunc belongs to Class Person
    print("Hello my name is " + p2.name) # using anotherPerson object property - allowed

class anotherPerson:
  def __init__(self, name, age):
    self.name = name
    self.age = age

  def myfunc(self): # this function named myfunc belongs to Class anotherPerson
                               # even though ti has the same name as in the Person class
    print("Hello my name is " + p1.name)

p1 = Person("John", 36)
p2 = anotherPerson("Mary", 22)
p1.myfunc()
p2.myfunc()

# will print Mary in John's place and vice versa


Within a class, the object properties (via init vs via def an internal function) are cross recognized between init and def.



class groceries:
  def __init__(foodobject, fruit, nuts):
    foodobject.sweet = fruit
    foodobject.protein = nuts

# anything can use foodobject's name property
  def anyfunc(anything):
    print("Hello my dessert is " + anything.sweet)

meal = groceries("cherry", "pecan")

meal.anyfunc()

# result is 
My fruit is cherry

snack = groceries("lemon""walnuts")
snack.anyfunc()


# result is 
My fruit is lemon


However, within a class, the use of  that different name for the self-referencing object variable across properties (via init vs via def an internal function) is NOT cross recognized.

class groceries:
  def __init__(foodobject, fruit, nuts):
    foodobject.sweet = fruit
    foodobject.protein = nuts

# the anyfunc using the init's self-referencing object variable foodobject is incorrect.

  def anyfunc(anything):
    print("Hello my dessert is " + 
foodobject.sweet)

snack = groceries("lemon""walnuts")
snack.anyfunc()


# result is an error because foodobject only belongs to __init__
# another way to think about this is that foodobject masquerades as # anything while inside the anyfunc function. The first parameter
# to functions inside of a class is always the self-referencing
# variable.

NameError: name 'foodobject' is not defined.



Use the del keyword to properly remove a property from an object; however, del does not remove it from the class definition.

Use del keyword to remove the entire object and use pass for an empty class.