Introduction to Objects and Functions and a Few Keywords

You don't have to know very much about the Python language internals or even its vocabulary to do highly-productive tasks with it. Believe it or not, in this snippet of code:

import requests
url = "http://stash.compjour.org/pages/hi.html"
response = requests.get(url)
pagetext = response.text
print(pagetext)

There is exactly one special-to-Python keyword that you have to actually know. And what's the rest? It's either user-provided values (such as that URL), or words that refer to the pre-packaged code made by programmers that can be directly imported and used in our code. The trick in reading any given script is knowing which words (and patterns and structures) that you just have to know. And which come from a third-party, in which case, you just have to read their docs (here's the docs for requests refers, for instance).

Python's keywords

If you are overwhelmed by the variety and number of new terms thrown at you in a Python script, take comfort in knowing that there are actually very few keywords that make up the Python language &ndash. For Python 3.x, there are 32 keywords for Python 3.x, and roughly half of those (in bold below) we'll encounter on a regular basis:

and del from nonlocal try
as elif global not while
assert else if or with
break except import pass yield
class False in raise  
continue finally is return  
def for lambda True  

And in these lessons, I skip such concepts as object-oriented programming, so maybe just 12 of these keywords are fundamentally important to memorize (with the others being variants/extensions of those, such as while to for, and elif to if and else):

and False import not True
def for is or  
else if None return  

So this means that for the script at the beginning of this lesson, there is exactly one Python-language keyword in that entire script, which I've bolded below:

import requests
url = "http://stash.compjour.org/pages/hi.html"
response = requests.get(url)
pagetext = response.text
print(pagetext)

So besides the variable names that are made up by us (url, response, pagetext), what are all the other words (requests, get, print)? They refer to either functions, methods, attributes, or modules.

Functions

When a non-keyword (i.e. not something such as if) is immediately followed by an opening parentheses, i.e. (, you should interpret that as a function being executed.

In the following line is the most minimal execution of the print() function, which prints a blank line to the screen:

print()
# [a blank line]

Normally, we want to print something with print, e.g:

print("hello")
# hello

The text value of "hello" is the argument, i.e. an object for print() to act upon, in this case, printing it to the screen.

The print() function can take multiple arguments. In that situation, it just prints each argument consecutively, separated by a space:

print("hello", "mom", "!!!")
# hello mom !!!

Why does print() print those arguments with a space-in-between, rather than unseparated? Because that's how print() was defined by its author. If we want something different, we have to define our own function.

We'll cover the purpose and syntax of defining functions in more detail later; for now, it's just enough to know that it can be done:

def smushprint(a, b, c):
    d = a + b + c
    print(d)

smushprint("hi", "dad", "!!!")
# hidad!!!

(note: this simple proof-of-concept function is seriously flawed; try running it with just one argument)

Functions as labels

If we think of variables as a sort-of label for data values, then think of functions as another kind of label, but for code that is meant to be called, (which I also referred to as executed). Just as it's convenient to give a human-readable/writeable name to a complicated text string for later reference, it's convenient to give a label/nickname to a series of expressions that we intend to execute again.

One consequence of functions being a type of label is that this is something that works (even if it seems nonsensical):

booya = print
booya('kasha')
# kasha

Modules

The import keyword allows your current script (or iPython session) to bring in prepackaged code.

Thus, the following line:

import requests

– allows subsequent code to have access to the module named requests, e.g:

requests.get("http://www.example.com") 

How is requests different from the function named print? Well, for starters, we don't execute the requests module. The following code will cause an error:

requests("http://www.example.com")
# TypeError: 'module' object is not callable

However, just as print is a label/reference to some kind of executable routine, think of the name requests as being a sort of label for a package of code. And so, this is a valid expression:

import requests
demands = requests
demands.get("http://example.com")

Just a sidenote: if you really want to rename something you've imported (which is uncommon), the standard practice is to use the as keyword:

import requests as demands

The type() "function"

By this point, you should be seriously wondering, "How the hell do you keep track of what's a function, a module, a variable, or whatever?". The pragmatic answer is: don't import or introduce a bunch of things you have no clue about. In the case of the requests library, I know that it is a well-loved solution to the tedious process of setting up HTTP connections, so I take the time to read its very thorough documentation and examples.

That said, there is frequent occasion to check on an object's type. And for that, there is the built-in function named type.

The simplest call of the type() function is to pass in an object. The resulting value will be the type of that object:

type(42)
# int
type(42.0)
# float
type("42")
# str
type(str)
# type
type(print)
# builtin_function_or_method

Note: if you try type(type), the return value will be type, even though I've described type() as a function. The exact mechanics here are more nuanced and not worth getting into detail about, especially as I'm sidestepping the depths of object-oriented programming and Python's class mechanism.

Objects and Types

So one takeaway of trying out the type() function is that 42 is a "42" are two different types of objects, int (short for integer) and str (short for string), respectively. In fact, in Python (and in other similarly high-level languages), everything is an object.

But what does that mean exactly? In programming, an object is said to have methods and attributes. OK, what does that mean? In the real-world, if you think of a bicycle as being a kind of object, you might say that for any given instance of a bicycle (such as, "Dan's bicycle"), you can think of it as having attributes of color and weight, and methods of brake() and ring_the_bell().

Back to the programming world. Each object has a set of attributes and methods that belong to it. To refer to these, we append a dot character to an object, then type in the name of the attribute or function.

In the snippet below, I invoke the upper() method (think of methods as similar to functions), which belongs to the object ("hey you") represented by the variable x:

x = "hey you"
x.upper()
# "HEY YOU"

Attributes

Let's take the object "42", which is an instance of the str (a text string) type. The object "42", like all other strings, has an attribute named doc, which contains some documentation for str:

n = "42"
print(n.__doc__)
# str(object='') -> str
# str(bytes_or_buffer[, encoding[, errors]]) -> str
#
# Create a new string object from the given object. If encoding or
# errors is specified...

Methods

Think of a method as a function that belongs to an object. So, using the object "42" again, we can see that it has several methods that can act on it:

n = "42"
n.isnumeric()
# True
n.isalpha()
# False
n.replace('4', 'forty')
# forty2

Tab for autocomplete

This is a tip I should've mentioned at the very beginning of this lesson; even though it is a keyboard-shortcut, it is essential in learning Python and writing complicated programs.

At the iPython prompt, type in:

xxkjsdhfkjsadhf = "hello"

Pretend that somewhere later in your session, you actually need to refer to that variable. And the only thing you remember about it is that it begins with xx.

So type in xx, and then hit the Tab key

iPython should autocomplete the rest of the variable name for you.

Now, with xxkjsdhfkjsadhf spelled-out at the prompt, type a dot character, and then hit the Tab key. What you should then see is a list of all available methods for the xxkjsdhfkjsadhf object (which is the text string "hello").

If you then type f, and hit Tab again, the list of methods will be filtered to those that start with f.

In GIF form:

GIFTK

The list of methods that belong to text string; you, nor any human, can't possibly be expected to remember what they all are. That's the computer's job. So make use of that fact by hitting the Tab key as an instinctive action.

Different types don't (usually) mix

If you test out the attributes and methods for the integer object of 42, you'll see that it has different methods and attributes. For example, it doesn't have isnumeric() (which returns True or False if a text string contains all numbers) because, by definition, an integer is "numeric".

So if "42" and 42 are two different types of objects, what is the result of this expression:

    "42" + 42

Is it 84, 4242, or "4242"? If you run that expression in iPython, you'll get an error which can basically be interpreted as: you are trying to add two different things which makes no sense or has any "correct" answer. Stop and think about it.

No need to go into much more detail about this except to note that many, many of your errors will be "type errors".

USAJobs code

Let's use a more real-world example involving the requests library and the USAJobs API. Refresh your memory of what the following URL looks like in your web browser:

https://data.usajobs.gov/api/jobs?Country=Iraq

Now access it via iPython:

import requests
url = "https://data.usajobs.gov/api/jobs?Country=Iraq"
resp = requests.get(url)
txt = resp.text
print(txt)

Things that should be evident or derivable by you:

  • requests.get() is a method belonging to the requests module
  • text is an attribute of resp.text
  • The value of resp.text itself (which, above, has been assigned to the txt verible), is an object of type str (do type(txt) to check yourself).

One thing that you should wonder about? What exactly is that resp object?

It turns out to be a Response object (which is part of the requests module). Why doesn't resp just contain the raw text what exists at the URL? Because the designer of the requests package thought that it'd be more useful to create an object that held all the various components of a response from the web server, including the metadata that's passed as part of every web request:

type(resp)
# requests.models.Response
resp.status_code
# 200
resp.is_permanent_redirect
# False
resp.ok    
# True

One method we'll find especially useful is the Response object's method, json():

data = resp.json()
type(data)
# dict
print(data['TotalJobs'])
# 6
print(data['Pages'])
# 1

What is a dict object? It's short for "dictionary", and we'll cover that data structure in the next lesson. (note: this lesson is not done…so check out other documentation on lists and functions):