Lesson A4 – Objects

This section should give a little more insight into what Python objects are and how you can handle them. We used the term object already, but it may still appear somewhat cryptic. We will only touch on the very basics and use simplifications here and there. The whole topic itself is pretty advanced. If you want to dive deeper into the fundamentals you may want to start here.

Variables

We have used variables already in the previous lessons. We can assign a value to a variable in Python using the equal sign (=).

[1]:
# Assign to a variable
number = 13.5

Note: Values are assigned to variables always from right to left using the equal sign (=).

Now, number is a variable holding the floating point value 13.5. When we assign a value to a variable, we define the variable. The value is now stored in the RAM (random access memory) of your computer at a specific location or address. We can think of the variable name as a label pointing to the address in memory where the value is stored. We can access the stored value via the variable label. We can also print out the address of the value in memory (its id), although this is hardly ever used in practice.

[2]:
print(number)
print(f"The value {number} is stored at {id(number)} (address in memory)")
13.5
The value 13.5 is stored at 139712208698320 (address in memory)

Every variable we want to use must be defined. Doing anything with a variable that has not been defined yet, causes a NameError.

[3]:
print(unknown)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-401545cb8ea8> in <module>
----> 1 print(unknown)

NameError: name 'unknown' is not defined

You can delete variables (or rather make Python forget about it) by using the del statement.

[4]:
del number

Different kinds of values, need to be stored differently in memory, which is the reason why we for example distinguish between floats and integers that take different amounts of space in memory. In Python we have the luxury that the kind of the value can be figured out by Python itself. In many other programming languages you would need to state the kind (declare the variable) before you could use a variable.

Advanced: In Python we define variables, meaning we directly assign a value to them, when we create them. It is not possible to just declare a variable without defining. This can be very useful. In other languages you can create a variable and the corresponding point to a memory address, without being forced to write a suitable value into this memory address. Calling the variable then returns whatever happens to be stored (e.g. from a previous calculation) at this memory location. This can cause software bugs that are incredibly hard to trace. Instead of declarations, Python only knows so-called type annotations, that do not reserve memory.

>>> x: int
>>> print(x)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-fc17d851ef81> in <module>
----> 1 print(x)

NameError: name 'x' is not defined

Objects

In principle this could be all there is to variables. But Python is a modern object-oriented programming language and everything in Python is an object, including data like numbers. The variable number we defined above does not only point to a floating point value in memory, it points to a floating point object that contains a reference to the floating point value besides a bunch of other things. You can think of an object as a bundle of variables (= object attributes) and functions (= object methods). We can access these attributes via dot-notation. A floating point object has for example an attribute .real holding the real part of the floating point number. It might seem silly, but it also has an attribute .imag for the imaginary part

[5]:
number = 13.5

# The floating point object `number` has an attribute `.real`
print(number.real)
# And an attribute `.imag`
print(number.imag)
13.5
0.0

Floating point objects also come with a handful of useful methods:

[6]:
# Is a float a whole number?
number.is_integer()
[6]:
False
[7]:
another_number = 15.0
another_number.is_integer()
[7]:
True
[8]:
# Return a rational number as a fraction
number.as_integer_ratio()
[8]:
(27, 2)

\(13.5 = \frac{27}{2}\)

Other types of objects, come with their own specific set of available attributes.

Note: Objects carry attributes that can be accessed via object.attribute (dot-notation). We can call object methods via object.method().

Of what type is an object?

You may ask the question now: And how can we know that an object is of a certain kind in Python? The answer is, we can use a function, the function type(), that tells us the type of an object. How Python deals with types is very flexible. All the more it becomes important that you know about the types you are dealing with.

[9]:
x = "Good morning, Berlin!"
print(type(x))
<class 'str'>
[10]:
number = 13.5
print(type(number))
<class 'float'>

We can say the string object x is of type str and the float object number is of type float. Objects of type string have different attributes than objects of type float.

When we talk about objects and types in Python, we also come across the term class. Let’s discuss the difference between a class and an object. You can think of a class as the packing list or the blueprint for a particular object. In Python 3 the type of an object and the class of an object can be used interchangeably as synonyms. Take this examples:

[11]:
y = 1234
print(type(y))
<class 'int'>

By defining the variable y, Python automatically recognises that the data value 1234 should be represented as an integer (object of type int). It takes the value and bundles it together with everything that makes an integer according to the int class (all its attributes) in an object we can now refer to by the variable y. We can also say, the new object is an instance of the integer class int. It is an object build after the building plan given by the class.

Note: How an object is represented in Python and what you can do with it, is specified by its type (its class). Python can automatically infer the type of objects.

How do I know which attributes and methods come with an object?

Sometimes, you may want to know which attributes are currently there to access from an object. This can be achieved with the dir function. With that we can get a list of all the attributes attached to an object (don’t be alarmed by the length of the list):

[12]:
dir(x)
[12]:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

We can see that a string object like x has a rather large amount of attributes attached to it. The dir() function gives you the super power to discover all the available attributes.

How do I know what all these attributes and methods do?

To get more information on an objects and its attributes, use the help function. It is particularly helpful, when used on function objects like object methods.

[13]:
help(number.is_integer)
Help on built-in function is_integer:

is_integer(...) method of builtins.float instance
    Return True if the float is an integer.

[14]:
help(x.lower)
Help on built-in function lower:

lower(...) method of builtins.str instance
    S.lower() -> str

    Return a copy of the string S converted to lowercase.

This will print in-depth background information on what you have passed as an argument. Note that we passed in only the name of a function to help(). We did not call the function we wanted to know more about. It tells you, how you can use this object, which can be incredible valuable, when you are in doubt about what to do with it.

Note: Use help() to get a helpful description of objects (especially functions) in Python.

Don’t be scared of, by the way, by the strange looking elements in the list with the double underscores before and after the attribute name, like __str__(). These are special methods, we normally do not need to call directly, but have a special Python-internal meaning in different situations.

How do I know which objects are currently available?

You can also call the dir() function without an explicit argument:

[15]:
dir()
[15]:
['In',
 'Out',
 '_',
 '_12',
 '_6',
 '_7',
 '_8',
 '__',
 '___',
 '__builtin__',
 '__builtins__',
 '__doc__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_dh',
 '_i',
 '_i1',
 '_i10',
 '_i11',
 '_i12',
 '_i13',
 '_i14',
 '_i15',
 '_i2',
 '_i3',
 '_i4',
 '_i5',
 '_i6',
 '_i7',
 '_i8',
 '_i9',
 '_ih',
 '_ii',
 '_iii',
 '_oh',
 'another_number',
 'exit',
 'get_ipython',
 'number',
 'quit',
 'x',
 'y']

The call of dir() returns a list of all the objects defined right now in the current Python session. Besides a lot of weird elements you do not need to care about, we can find in this list the element 'number', referring to the object number, and others that we have defined e.g. in the section Variables.

Note: Use dir() to investigate what variables are defined in the current scope/session or associated to a specific object.

In the current session, many more objects are available that we have not defined ourself. The functions we have used so far, like print, are so-called builtin functions. Builtin functions are always defined in a standard Python session, as indicated by the element '__builtin__' in the above list we get from dir(). Calling dir on the __builtin__ object, reveals the print function among many other builtin objects.

[16]:
dir(__builtin__)
[16]:
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'DeprecationWarning',
 'EOFError',
 'Ellipsis',
 'EnvironmentError',
 'Exception',
 'False',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'FutureWarning',
 'GeneratorExit',
 'IOError',
 'ImportError',
 'ImportWarning',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'KeyboardInterrupt',
 'LookupError',
 'MemoryError',
 'ModuleNotFoundError',
 'NameError',
 'None',
 'NotADirectoryError',
 'NotImplemented',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PendingDeprecationWarning',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'ResourceWarning',
 'RuntimeError',
 'RuntimeWarning',
 'StopAsyncIteration',
 'StopIteration',
 'SyntaxError',
 'SyntaxWarning',
 'SystemError',
 'SystemExit',
 'TabError',
 'TimeoutError',
 'True',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'UnicodeWarning',
 'UserWarning',
 'ValueError',
 'Warning',
 'ZeroDivisionError',
 '__IPYTHON__',
 '__build_class__',
 '__debug__',
 '__doc__',
 '__import__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'abs',
 'all',
 'any',
 'ascii',
 'bin',
 'bool',
 'bytearray',
 'bytes',
 'callable',
 'chr',
 'classmethod',
 'compile',
 'complex',
 'copyright',
 'credits',
 'delattr',
 'dict',
 'dir',
 'display',
 'divmod',
 'enumerate',
 'eval',
 'exec',
 'filter',
 'float',
 'format',
 'frozenset',
 'get_ipython',
 'getattr',
 'globals',
 'hasattr',
 'hash',
 'help',
 'hex',
 'id',
 'input',
 'int',
 'isinstance',
 'issubclass',
 'iter',
 'len',
 'license',
 'list',
 'locals',
 'map',
 'max',
 'memoryview',
 'min',
 'next',
 'object',
 'oct',
 'open',
 'ord',
 'pow',
 'print',
 'property',
 'range',
 'repr',
 'reversed',
 'round',
 'set',
 'setattr',
 'slice',
 'sorted',
 'staticmethod',
 'str',
 'sum',
 'super',
 'tuple',
 'type',
 'vars',
 'zip']

Advanced: The __builtin__ object is a so-called module in Python. It is a place in which certain things, like functions, are defined. The module is loaded (imported), when you start your Python session.

Remember that you can use help() to learn more about a built-in function.

[17]:
help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

[18]:
help(dir)
Help on built-in function dir in module builtins:

dir(...)
    dir([object]) -> list of strings

    If called without an argument, return the names in the current scope.
    Else, return an alphabetized list of names comprising (some of) the attributes
    of the given object, and of attributes reachable from it.
    If the object supplies a method named __dir__, it will be used; otherwise
    the default dir() logic is used and returns:
      for a module object: the module's attributes.
      for a class object:  its attributes, and recursively the attributes
        of its bases.
      for any other object: its attributes, its class's attributes, and
        recursively the attributes of its class's base classes.