Python is powerful but could be tricky
What keep in mind to avoid the tricky and get the power
Python is an interpreted, high-level, general-purpose programming language. It is dynamically typed and garbage-collected. It supports multiple programming paradigms, including procedural, object-oriented, and functional programming. But the real reason for his overwhelming success is: Python is often described as a "batteries included" language due to its comprehensive standard library.
This looks impressive but sometime it could be tricky. Let's see:
>>> l = [1, 2, 3] >>> m = l >>> l[0] = 'x' >>> m ['x', 2, 3]
Above, the first line creates the object (list) and the 'l' variable to point to the list. The next line creates the m variable that points to the same list. Next, the l variable is used to modify the list first element. But, when we print the m variable content: surprise! The result is the first element addressed by m was modified too!
Mutable, Immutable... everything is an object!
The following table shows which classes are mutable and which are not!
Yes, everything is an object. Let's dive in.
a = "banana" b = "banana"
The result in memory is:
This tells us that both a and b refer to the same object.
Since strings are immutable, Python optimizes resources by making two names that refer to the same string value refer to the same object. This is the same case for the other immutable objects.
We can verify this by testing whether two names have the same value using ==:
>>> a == b True
We can test whether two names refer to the same object using the is operator:
>>> a is b True
Because the same string has two different names, a and b, we say that it is aliased. Changes made with one alias affect the other. This is known as Aliasing. The first example is about aliasing too, but using lists.
Therefore, the mutable object changes and the immutable object does not.
Others interesting and useful commands are:
- The built-in function id() returns the identity of an object as an integer.
- The built-in function type() returns the type of an object.
Examples:
>>> x = "Holberton" >>> y = "Holberton" >>> id(x) 140135852051111 >>> id(y) 140135852051111 >>> print(x is y) True >>> a = 50 >>> type(a) <class: ‘int’> >>> b = "Holberton" >>> type(b) <class: 'string'> >>> a = 98 >>> b = a + 1 >>> print(a is b) False >>> id(a) 10108192 >>> id(b) 10108224
Add is not Append. :o
""" Append """ >>> l = [1, 2, 3] >>> m = l >>> l.append(4) >>> print(m) [1, 2, 3, 4] """ Add """ >>> l = [1, 2, 3] >>> m = l >>> l = l + [4] >>> print(m) [1, 2, 3]
While Append modifies the object, Add creates a new object. ;))
Addressing the same object from different frames (scopes)
""" Function double stuff """ def double_stuff(a_list): for index, value in enumerate(a_list): a_list[index] = 2 * value """ Calling the function """ >>> from double_stuff import double_stuff >>> things = [2, 5, 'Spam', 9.5] >>> double_stuff(things) >>> things [4, 10, 'SpamSpam', 19.0]
What we have above: The parameter a_list and the variable things are aliases for the same object.
If a function modifies a list parameter, the caller sees the change.
>>> def assign_value(n, v): ... n = v ... >>> l = [1, 2, 3] >>> m = [4, 5, 6] >>> assign_value(l, m) >>> print(l) [1, 2, 3]
The object never changes inside the function it is only an assignation, thus the caller never sees the change.
Hold on: Assignment and reference?
The assignment is fundamental to Python; it is how the objects created by expressions are preserved. We create and change variables primarily with the assignment statement.
- variable = expression
Variable names (often just called "names") are references to the actual object. When a name is on the right-hand side of an equation, the object that it references is automatically looked up and used in the equation. The result of the expression on the right-hand side is an object. The name on the left-hand side of the equation becomes a reference to this (possibly new) object. Therefore, this variable name is a pointer to the object, thus a reference.
>>> """ Example ... """ >>> shares = 150 >>> shares 150 >>> id(shares) 10109856 >>> type(shares) <class 'int'> >>> >>> price = 3 + 5.0/8.0 >>> price 3.625 >>> id(price) 139974558169848 >>> type(price) <class 'float'> >>> >>> value = shares * price >>> value 543.75 >>> id(value) 139974558169728 >>> type(value) <class 'float'>
We have an object, the number 150, which we assign to the variable shares. We have an expression 3+5.0/8.0, which creates a floating-point object, which is referenced by the "price" variable. We have another expression, shares * price, which creates a floating-point object, that is referenced by the variable name "value".
>>> """ Example 1 ... """ >>> a = [1, 2, 3] >>> id(a) 139974550068296 >>> >>> b = a >>> id(b) 139974550068296 >>> a is b True >>> >>> """ Example 2 ... """ >>> a = [1, 2, 3] >>> id(a) 139974550068296 >>> >>> b = a[:] >>> id(b) 139974516664328 >>> a is b False
In the examples above we have two different situations. First, two assignments that reference the same list object. Second, two assignments that reference two different list-objects but, the elements of list2 object are references to object list1's elements.
According to this, it is necessary to be careful with the expression on the right side to make the correct assignment.
Tuples are immutable ... but
Python tuples have a surprising trait: they are immutable, but their values may change. This may happen when a tuple holds a reference to any mutable object, such as a list.
If we have two idem tuples:
>>> ines = ('2000-10-23', ['poetry', 'pretend-fight']) >>> cloe = ('2000-10-23', ['poetry', 'pretend-fight']) >>> ines == cloe True >>> ines is cloe False >>> id(ines), id(cloe) (4313018120, 4312991048)
Now, change the content of someone!
>>> skills = ines[1] >>> skills.append('rap') >>> ines ('2000-10-23', ['poetry', 'pretend-fight', 'rap'])
What happened? What is immutable is the physical content of a tuple, consisting of the object references only. The value of the list referenced by ines[1] changed, but the referenced object id is still the same. A tuple has no way of preventing changes to the values of its items.
>>> ines == cloe False
Optimizing
The special attribute __slots__ allows you to explicitly state which instance attributes you expect your object instances to have, with the expected results:
- faster attribute access.
- space savings in memory.
""" Example """ #!/usr/bin/python3 class LockedClass: __slots__ = ['first_name']
Yes, it is a list of strings :))
On the other hand, we have NSMALLPOSINTS the NSMALLNEGINTS for performance optimization. Those macros are equivalent to the integer range from -5 to 256 included zero to get the 262 ints commonly used. This number of int objects is created when Python interpreter starts and let to anticipate performance issues because of demanding resources for int objects.