Search

The Case of the Missing Declaration Statements in Python

Updated: Sep 2, 2020





It’s important yet sometimes difficult to get a handle on what may be the most fundamental idea in Python programming and is certainly the basis of much of both the conciseness and flexibility of the Python language—dynamic typing, and the polymorphism it implies. This is a slightly long topic, but i wanted to explain this in depth as this is core to understanding Python. Jargons explained -


Dynamic typing

In programming languages, a type system is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program, such as variables, expressions, functions or modules. A programming language is said to be dynamically typed, or just 'dynamic', when the majority of its type checking is performed at run-time as opposed to at compile-time. In dynamic typing, types are associated with values not variables. Compared to static typing, dynamic typing can be more flexible (e.g. by allowing programs to generate types and functionality based on run-time data), though at the expense of fewer apriori guarantees. Dynamic typing may result in runtime type errors—that is, at runtime, a value may have an unexpected type, and an operation nonsensical for that type is applied. This operation may occur long after the place where the programming mistake was made—that is, the place where the wrong type of data passed into a place it should not have. This makes the bug difficult to locate.


Polymorphism

In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types[1] or the use of a single symbol to represent multiple different types. ex when you type x+y in python , the + might concatenate x & y if they are strings or add them if they are numbers.


In Python, we do not declare the specific types of the objects our scripts use. They are naturally applicable in more contexts than we can sometimes even plan ahead for. Because dynamic typing is the root of this flexibility,

and is also a potential stumbling block for most of people working on python, let’s understand this better.

In python we use variables without declaring their existence or their types, and it somehow works!! When we type


   
 >>> x = 10 


in an interactive session or program file, for instance, how does Python know that a should stand for an integer? For that matter, how does Python know what "x" is, at all?


Once you start asking such questions, you’ve crossed over into the domain of Python’s dynamic typing model. In Python, types are determined automatically at runtime, not in response to declarations in your code. This means that you never declare variables ahead of time - a concept that is perhaps simpler to grasp if you keep in mind that it all boils down to variables, objects, and the links between them, I speak about this in detail in my article about

python conceptual hierarchy

When you run an assignment statement such as


    >>> x = 3 

in Python, it works even if you’ve never told Python to use the name a as a variable, or that a should stand for an integer-type object. In the Python language, this all pans out in a very natural way, as follows:


Variable creation

A variable (i.e., name), like x, is created when your code first assigns it a value. Future assignments change the value of the already created name. Technically, Python detects some names before your code runs, but you can think of it as though initial assignments make variables.


Variable types

A variable never has any type information or constraints associated with it. The notion of type lives with objects, not names. Variables are generic in nature; they always simply refer to a particular object at a particular point in time.


Variable use

When a variable appears in an expression, it is immediately replaced with the object that it currently refers to, whatever that may be. Further, all variables must be explicitly assigned before they can be used; referencing unassigned variables results in errors.


Summary -
  1. variables are created when assigned,

  2. Can reference any type of object

  3. Must be assigned before they are referenced.

This means that you never need to declare names used by your script, but you must initialize names before you can update them.


This model is usually easier to understand if you keep clear the distinction between names and objects. Lets understand it better by using an example.



      >>> x = 10


If we type x = 10, to assign a variable a value- Python will perform three distinct steps to carry out the request. These steps reflect the operation of all assignments in the Python language:


1. Create an object to represent the value 10.

2. Create the variable x, if it does not yet exist.

3. Link the variable x to the new object 10.


Imagine this in your head -




Variables and objects are stored in different parts of memory and are associated by links . Variables always link to objects and never to other variables, but larger objects may link to other objects (for instance, a list object has links to the objects it contains).


These links from variables to objects are called references in Python.Whenever the variables are later used , Python automatically follows the variable-to-object links. This is all simpler than the terminology may imply. In concrete terms:


• Variables are entries in a system table, with spaces for links to objects.

• Objects are pieces of allocated memory, with enough space to represent the values for which they stand.

• References are automatically followed pointers from variables to objects.


Each time you generate a new value in your script by running an expression, Python creates a new object - i.e., a chunk of memory, to represent that value. As an optimization, Python internally caches and reuses certain kinds of unchangeable objects, such as small integers and strings (each letter "B" is not really a new piece of memory, leave a note if you want me to write about this). But from a logical perspective, it works as though each expression’s result value is a distinct object and each object is a distinct piece of memory.


Objects have more structure than just enough space to represent their values. Each object also has two standard header fields:

  1. A type designator used to mark the type of the object, and

  2. A reference counter used to determine when it’s OK to reclaim the object.

Lets understand these conceptually -




>>> x = 10                   # x is an integer
>>> x = 'bharath'            # Now x is a string
>>> x = 3.141592             # Now x is a floating point


X starts out as an integer, then becomes a string, and finally becomes a floating-point number. It appears as though the type of X changes from integer to string when we say x = 'bharath'. However, that’s not really what’s happening. In Python, things work more simply. Names have no types; as i said earlier, types live with objects, not names. We’ve simply changed X to reference different objects. Because variables have no type, we haven’t actually changed the type of the variable X; we’ve simply made the variable reference a different type of object. In fact, again, all we can ever say about a variable in Python is that it references a particular object at a particular point in time.


Objects, on the other hand, know what type they are—each object contains a header field that tags the object with its type. The integer object 10, for example, will contain the value 10, plus a designator that tells Python that the object is an integer . The type designator of the 'bharath' string object points to the string type (called str) instead. Because objects know their types, variables don’t have to !

To recap, types are associated with objects in Python, not with variables. In typical code, a given variable usually will reference just one kind of object. Because this isn’t a requirement, though, you’ll find that Python code tends to be much more flexible.

I mentioned that objects have two header fields, a type designator and a reference counter. To understand the latter of these, we need to move on and take a brief look at what happens at the end of an object’s life.


Objects Are Garbage-Collected

Earlier, we assigned the variable a to different types of objects in each assignment. But when we reassign a variable, what happens to the value it was previously referencing? For example, after the following statements, what happens to the object 10?



   >>> x = 10
   >>> x = 'bharath'
   


In Python, whenever a name is assigned to a new object, the space held by the prior object is reclaimed if it is not referenced by any other name or object. This automatic reclamation of objects’ space is known as garbage collection, and makes life much simpler for end users.

To illustrate, consider our earlier example, which sets the name x to a different object on each assignment:



>>> x = 10          # x is an integer
>>> x = 'bharath'   # Now x is a string, space occupied by 10 reclaimed
>>> x = 3.141592    # Now x is a floating point, space occupied by text 'bharath' reclaimed now by pythons garbage collector.


First, notice that x is set to a different type of object each time. Again, though this is not really the case, the effect is as though the type of x is changing over time. Remember, in Python types live with objects, not names. Because names are just generic references to objects, this sort of code works naturally.


Second, notice that references to objects are discarded along the way. Each time x is assigned to a new object, Python reclaims the prior object’s space. For instance, when it is assigned the string 'bharath', the object 10 is immediately reclaimed (assuming it is not referenced anywhere else)—that is, the object’s space is automatically thrown back into the free space pool, to be reused for a future object.


Internally, Python accomplishes this feat by keeping a counter in every object that keeps track of the number of references currently pointing to that object. As soon as and exactly when this counter drops to zero, the object’s memory space is automatically reclaimed. In the preceding listing, we’re assuming that each time x is assigned to a new object, the prior object’s reference counter drops to zero, causing it to be reclaimed. The most immediately tangible benefit of garbage collection is that it means you can use objects liberally without ever needing to allocate or free up space in your script. Python will clean up unused space for you as your program runs.


So far, we have explored what happens as a single variable is assigned references to objects.

To understand what happens when multiple variables reference a same object read my article on Python Shared references and curious case of list sub-setting.


To tie it all and understand what happens when python is asked to execute a code read my article :

what does python do when you ask it to run a script.

If you liked this article , please do share ahead using social icons below. If have specific questions - please do reach out or leave your comments below.


Happy Learning