Python Note 9
Python Note 9
28/01/2024
Computer logic
Have you noticed that the conditions we've used so far have been very simple, not to say, quite primitive? The conditions we use in
real life are much more complex. Let's look at this sentence:
If we have some free time, and the weather is good, we will go for a walk.
We've used the conjunction and , which means that going for a walk depends on the simultaneous fulfilment of these two
conditions. In the language of logic, such a connection of conditions is called a conjunction. And now another example:
If you are in the mall or I am in the mall, one of us will buy a gift for Mom.
The appearance of the word or means that the purchase depends on at least one of these conditions. In logic, such a compound is
called a disjunction.
It's clear that Python must have operators to build conjunctions and disjunctions. Without them, the expressive power of the
language would be substantially weakened. They're called logical operators.
and
One logical conjunction operator in Python is the word and. It's a binary operator with a priority that is lower than the one
expressed by the comparison operators. It allows us to code complex conditions without the use of parentheses like this one:
The result provided by the and operator can be determined on the basis of the truth table.
If we consider the conjunction of A and B , the set of possible values of arguments and corresponding values of the conjunction
looks as follows:
or
A disjunction operator is the word or . It's a binary operator with a lower priority than and (just like + compared to * ). Its truth
table is as follows:
Argument A Argument B A or B
False False False
False True True
True False True
True True True
not
In addition, there's another operator that can be applied for constructing conditions. It's a unary operator performing a logical
negation. Its operation is simple: it turns truth into falsehood and falsehood into truth.
This operator is written as the word not , and its priority is very high: the same as the unary + and - . Its truth table is simple:
Logical expressions
Let's create a variable named var and assign 1 to it. The following conditions are pairwise equivalent:
# Example 1:
print(var > 0)
print(not (var <= 0))
# Example 2:
print(var != 0)
print(not (var == 0))
The result of their operations is one of these values: False or True . This means that this snippet will assign the value True to
the j variable if i is not zero; otherwise, it will be False .
i = 1
j = not not i
Bitwise operators
However, there are four operators that allow you to manipulate single bits of data. They are called bitwise operators.
They cover all the operations we mentioned before in the logical context, and one additional operator. This is the xor (as
in exclusive or) operator, and is denoted as ^ (caret).
Let us add an important remark: the arguments of these operators must be integers; we must not use floats here.
The difference in the operation of the logical and bit operators is important: the logical operators do not penetrate into the
bit level of its argument. They're only interested in the final integer value.
Bitwise operators are stricter: they deal with every bit separately. If we assume that the integer variable occupies 64 bits (which
is common in modern computer systems), you can imagine the bitwise operation as a 64-fold evaluation of the logical operator for
each pair of bits of the arguments. This analogy is obviously imperfect, as in the real world all these 64 operations are performed at
the same time (simultaneously).
We'll now show you an example of the difference in operation between the logical and bit operations. Let's assume that the
following assignments have been performed:
i = 15
j = 22
If we assume that the integers are stored with 32 bits, the bitwise image of the two variables will be as follows:
i: 00000000000000000000000000001111
j: 00000000000000000000000000010110
The assignment is given:
log = i and j
We are dealing with a logical conjunction here. Let's trace the course of the calculations. Both variables i and j are not zeros, so
will be deemed to represent True . Consulting the truth table for the and operator, we can see that the result will be True . No other
operations are performed.
log: True
Now the bitwise operation - here it is:
bit = i & j
The & operator will operate with each pair of corresponding bits separately, producing the values of the relevant bits of the result.
Therefore, the result will be as follows:
i 00000000000000000000000000001111
j 00000000000000000000000000010110
bit = i & j 00000000000000000000000000000110
These bits correspond to the integer value of six.
Let's look at the negation operators now. First the logical one:
logneg = not i
The logneg variable will be set to False - nothing more needs to be done.
bitneg = ~i
It may be a bit surprising: the bitneg variable value is -16 . This may seem strange, but isn't at all. If you wish to learn more, you
should check out the binary numeral system and the rules governing two's complement numbers.
i 00000000000000000000000000001111
bitneg = ~i 11111111111111111111111111110000
Each of these two-argument operators can be used in abbreviated form. These are the examples of their equivalent notations:
x = x & y x &= y
x = x | y x |= y
x = x ^ y x ^= y
You can also make a sequence of instructions depending on the state of your bit i here it is:
2. Reset your bit - you assign a zero to the bit while all the other bits must remain unchanged; let's use the same property of the
conjunction as before, but let's use a slightly different mask - exactly as below:
11111111111111111111111111110111
Note that the mask was created as a result of the negation of all the bits of the_mask variable. Resetting the bit is simple, and looks
like this (choose the one you like more):
x | 1 = 1
x | 0 = x
You're now ready to set your bit with one of the following instructions:
4. Negate your bit - you replace a 1 with a 0 and a 0 with a 1 . You can use an interesting property of the xor operator:
x ^ 1 = ~x
x ^ 0 = x
You already apply this operation very often and quite unconsciously. How do you multiply any number by ten? Take a look:
12345 × 10 = 123450
As you can see, multiplying by ten is in fact a shift of all the digits to the left and filling the resulting gap with zero.
12340 ÷ 10 = 1234
The same kind of operation is performed by the computer, but with one difference: as two is the base for binary numbers (not
10), shifting a value one bit to the left thus corresponds to multiplying it by two; respectively, shifting one bit to the
right is like dividing by two (notice that the rightmost bit is lost).
The shift operators in Python are a pair of digraphs: << and >> , clearly suggesting in which direction the shift will act.
The priority of these operators is very high. You'll see them in the updated table of priorities, which we'll show you at the end of this
section.
17 68 8
Note:
17 >> 1 → 17 // 2 (17 floor-divided by 2 to the power of 1) → 8 (shifting to the right by one bit is the same as integer
division by two)
17 << 2 → 17 * 4 (17 multiplied by 2 to the power of 2) → 68 (shifting to the left by two bits is the same as integer
multiplication by four)
Priority Operator
1 ~, +, - unary
2 **
3 * , / , // , %
4 +, - binary
5 << , >>
7 == , !=
8 &
9 |
Key takeaways
and → if both operands are true, the condition is true, e.g., (True and True) is True ,
or → if any of the operands are true, the condition is true, e.g., (True or False) is True ,
not → returns false if the result is true, and returns true if the result is false, e.g., not True is False .
2. You can use bitwise operators to manipulate single bits of data. The following sample data:
will be used to illustrate the meaning of bitwise operators in Python. Analyze the examples below:
& does a bitwise and, e.g., x & y = 0 , which is 0000 0000 in binary,
| does a bitwise or, e.g., x | y = 31 , which is 0001 1111 in binary,
˜ does a bitwise not, e.g., ˜ x = 240 *, which is 1111 0000 in binary,
^ does a bitwise xor, e.g., x ^ y = 31 , which is 0001 1111 in binary,
>> does a bitwise right shift, e.g., y >> 1 = 8 , which is 0000 1000 in binary,
<< does a bitwise left shift, e.g., y << 3 = , which is 1000 0000 in binary,
* -16 (decimal from signed 2's complement) -- read more about the Two's complement operation.
var1 = int(input())
var2 = int(input())
var3 = int(input())
var4 = int(input())
var5 = int(input())
var6 = int(input())
:
:
If you don't think that this is a complicated task, then take a piece of paper and write a program that:
You should find that you don't even have enough paper to complete the task.
So far, you've learned how to declare variables that are able to store exactly one given value at a time. Such variables are
sometimes called scalars by analogy with mathematics. All the variables you've used so far are actually scalars.
Think of how convenient it would be to declare a variable that could store more than one value. For example, a hundred, or a
thousand or even ten thousand. It would still be one and the same variable, but very wide and capacious. Sounds appealing?
Perhaps, but how would it handle such a container full of different values? How would it choose just the one you need?
在编程中,标量(scalar)变量是指可以存储单个值的变量。这个概念与数学中的标量类似,数学中的标量是指只有大小没有方向的量,比如一个单独的数字。在编程语言中,标量可以是整数、浮点数、字符或布尔值等。
python
複製
integer_scalar = 10 # 整数标量
float_scalar = 3.14 # 浮点数标量
char_scalar = 'A' # 字符标量 (在 Python 中实际上是一个长度为 1 的字符串)
boolean_scalar = True # 布尔标量
每个变量都可以存储一个数据值。如果你需要存储多个值,你就会使用复合数据类型,如列表、元组、集合或字典。标量与复合数据类型的区别在于,复合数据类型可以存储多个值(它们可以包含标量或其他复合类型),而标量变量只能存储一个单一
的值。
What if you could just number them? And then say: give me the value number 2; assign the value number 15; increase the value
number 10000.
We'll show you how to declare such multi-value variables. We'll do this with the example we just suggested. We'll write
a program that sorts a sequence of numbers. We won't be particularly ambitious - we'll assume that there are exactly five
numbers.
Let's create a variable called numbers ; it's assigned with not just one number, but is filled with a list consisting of five values (note:
the list starts with an open square bracket and ends with a closed square bracket; the space between the brackets is
filled with five numbers separated by commas).
numbers = [10, 5, 7, 2, 1]
Let's say the same thing using adequate terminology: numbers is a list consisting of five values, all of them numbers. We can
also say that this statement creates a list of length equal to five (as in there are five elements inside it).
The elements inside a list may have different types. Some of them may be integers, others floats, and yet others may be lists.
Python has adopted a convention stating that the elements in a list are always numbered starting from zero. This means that
the item stored at the beginning of the list will have the number zero. Since there are five elements in our list, the last of them is
assigned the number four. Don't forget this.
You'll soon get used to it, and it'll become second nature.
Before we go any further in our discussion, we have to state the following: our list is a collection of elements, but each
element is a scalar.
Indexing lists
Let's assign a new value of 111 to the first element in the list. We do it this way:
numbers = [10, 5, 7, 2, 1]
print("Original list content:", numbers) # Printing original list content.
numbers[0] = 111
print("New list content: ", numbers) # Current list content.
And now we want the value of the fifth element to be copied to the second element - can you guess how to do it?
numbers = [10, 5, 7, 2, 1]
print("Original list content:", numbers) # Printing original list content.
numbers[0] = 111
print("\nPrevious list content:", numbers) # Printing previous list content.
The value inside the brackets which selects one element of the list is called an index, while the operation of selecting an element
from the list is known as indexing.
We're going to use the print() function to print the list content each time we make the changes. This will help us follow each step
more carefully and see what's going on after a particular list modification.
Note: all the indices used so far are literals. Their values are fixed at runtime, but any expression can be the index, too. This
opens up lots of possibilities.
Each of the list's elements may be accessed separately. For example, it can be printed:
Assuming that all of the previous operations have been completed successfully, the snippet will send 111 to the console.
As you can see in the editor, the list may also be printed as a whole - just like here:
print(numbers) # Printing the whole list.
As you've probably noticed before, Python decorates the output in a way that suggests that all the presented values form a list. The
output from the example snippet above looks like this:
[111, 1, 7, 2, 1]
The length of a list may vary during execution. New elements may be added to the list, while others may be removed from it.
This means that the list is a very dynamic entity.
If you want to check the list's current length, you can use a function named len() (its name comes from length).
The function takes the list's name as an argument, and returns the number of elements currently stored inside the list (in
other words - the list's length).
Look at the last line of code in the editor, run the program and check what value it will print to the console. Can you guess?
You have to point to the element to be removed - it'll vanish from the list, and the list's length will be reduced by one.
Look at the snippet below. Can you guess what output it will produce? Run the program in the editor and check.
del numbers[1]
print(len(numbers))
print(numbers)
You can't access an element which doesn't exist - you can neither get its value nor assign it a value. Both of these
instructions will cause runtime errors now:
print(numbers[4])
numbers[4] = 1
Add the snippet above after the last line of code in the editor, run the program and check what happens.
Note: we've removed one of the list's elements - there are only four elements in the list now. This means that element number four
doesn't exist.
print(numbers[-1])
The example snippet will output 1 . Run the program and check.
Similarly, the element with an index equal to -2 is the one before last in the list.
print(numbers[-2])
The last accessible element in our list is numbers[-4] (the first one) - don't try to go any further!
A method is a specific kind of function - it behaves like a function and looks like a function, but differs in the way in which it
acts, and in its invocation style.
A function doesn't belong to any data - it gets data, it may create new data and it (generally) produces a result.
A method does all these things, but is also able to change the state of a selected entity.
A method is owned by the data it works for, while a function is owned by the whole code.
This also means that invoking a method requires some specification of the data from which the method is invoked.
It may sound puzzling here, but we'll deal with it in depth when we delve into object-oriented programming.
result = function(arg)