post / 2023.06.27

Python Basics

Python Basics

This article merges four earlier Python notes, removes duplicated and irrelevant material, and keeps only what is useful as a day-to-day reference. Examples target Python 3.

Variables and Objects

  • Variables need no type declaration, but they must be assigned before use, otherwise a NameError is raised.
  • Python has no const and no mechanism preventing reassignment; a “constant” is only a naming convention (all caps).
  • = assigns; == compares.
>>> age
NameError: name 'age' is not defined

A useful mental model: objects are the data, variables are labels stuck onto objects.

a = "Jack"   # create the string object "Jack", then stick the label a onto it
b = a        # stick label b onto the same object; this is not a copy
id(a) == id(b)   # True, both refer to the same object
a = "Ross"   # a different object gets the label a; b still refers to "Jack"
  • An object can carry many labels, but a variable refers to exactly one object at a time.
  • The variable itself has no type; the type belongs to the object. So a = 1 followed by a = "1" is perfectly legal.

Assignment and Slicing

= is right-associative, so a = b = c = 1 makes all three names refer to the integer object 1.

a, b, c = 1, 2, 3   # unpacking assignment
age = 17 + 1        # evaluate the right side first

Slicing works on strings, tuples, and lists with the syntax obj[a:b:c]:

FormMeaning
obj[a:b]Indices in $[a, b)$, excluding b; omit a to start at 0, omit b to run to the end
obj[a:b:c]Take every c-th element; c defaults to 1
negative cWalk backwards
obj[::-1]Reverse (returns a new object; the original is unchanged)
>>> name = "polaris"
>>> name[1:3]
'ol'
>>> name[::-3]
'sap'
>>> name[::-1]
'siralop'

Slicing also copies: y = x[:] gives a shallow copy, so sorting y does not affect x.

Strings

  1. Single and double quotes are equivalent; there is no separate single-character type.
  2. Triple quotes allow multi-line strings.
  3. To include the same quote character inside a string, escape it: \".

Three ways to format

name, age = "Bronya", 18

print('My wife is %s, she is %d.' % (name, age))          # % formatting, the oldest
print('My wife is {}, she is {}.'.format(name, age))      # .format()
print(f'My wife is {name}, she is {age}.')                # f-string, recommended
  • % formatting takes a tuple or dict and substitutes positionally or by name.
  • In .format(), {} may use positional indices {0} or keywords {name}.
  • f-strings require Python 3.6+ and are the most readable; all three support width, alignment, and precision specifiers.

Numbers and Booleans

TypeNotes
intArbitrary-precision integer; supports 0x hex, 0o octal, 0b binary literals
floatFloating point; scientific notation such as 1.23e-6
complexComplex numbers, written a + bj or complex(a, b)
boolTrue is really 1 and False is really 0
NoneTypeThe null value; None must be capitalised
  • Mixing an int with a float promotes the int: 1 + 2.0 gives 3.0.
  • Floats carry rounding error: 0.1 + 0.2 == 0.3 is False (the actual value is 0.30000000000000004). Use the decimal module when exact arithmetic is required.
  • round() uses banker’s rounding (round-half-to-even): round(0.5) is 0, round(1.5) is 2, round(2.5) is 2.

Falsy values: 0, 0.0, None, empty string, empty list, empty tuple, empty dict, empty set, False. Everything else is truthy, including any non-empty string (even the string "False").

Operators

Arithmetic

OperatorMeaningExample
+ - *addition, subtraction, multiplication
/true division; the result is always a float6 / 3 → 2.0
//floor division, rounds down7 // 2 → 3, -7 // 2 → -4
%modulo (remainder)7 % 2 → 1
**power, right-associative2 ** 2 ** 3 → 256
-unary minus, binds weaker than **-2 ** 2 → -4

The rest

CategoryOperators
Comparison== != < <= > >=; can be chained: a > b == c means a > b and b == c
Logicaland or not
Membershipin not in
Identityis is not; compares whether two names refer to the same object (compares id, not value)
Bitwise& | ^ ~ << >>
Augmented assignment+= -= *= //= **= &= |= ^= <<= >>=

and / or return one of their operands, not True / False:

>>> True and 0 + 3
3
>>> False or 31
31
>>> 0 or 0
0

Precedence (highest to lowest)

() → indexing [] → attribute . → ** → unary ~ + - → * / % // → + - → << >> → & → ^ → | → comparisons (<, ==, in, is, all one level) → not → and → or → assignment

If you cannot remember it, just add parentheses; that is the cheapest fix.

Input, Output, and Built-in Functions

print(self, *args, sep=' ', end='\n', file=None)   # sep between items, end after the last
age = input("Enter your age: ")   # the return value is always a string
int(age)                          # convert explicitly when you need a number

Common built-ins:

FunctionPurpose
id(x)memory address (identity) of the object
type(x)the object’s type, e.g. <class 'int'>
len(x)number of elements
abs() round() bool()absolute value, rounding, boolean conversion
enumerate(seq, start=0)index and value together while looping
range() zip() sum() max() min() sorted()common sequence helpers

Type-conversion functions: int(x, base), float(), complex(), str(), repr(), chr(), ord(), hex(), oct(), eval(), list(), tuple(), set(), frozenset(), dict().

Conversion is not unconditional; it depends on whether the source data carries enough information to express the target type. int("123") works, int("Hello") raises an error; a list can become a set, but not a single number.

Implicit conversion follows a precision order: bool < int < float < complex, moving toward the more precise type in mixed expressions.

galgames = ['Cafe', 'Senren Banka', 'Tenshi Sou Sou']
for index, galgame in enumerate(galgames, 1):
    print(f'Playing game number {index}: {galgame}')

Conditionals and Loops

if condition1:
    ...
elif condition2:
    ...
else:
    ...

for iterates over anything iterable (lists, dicts, strings, tuples, generators, …):

for x in sequence:
    ...

for i in range(5):
    ...
  • break leaves only the innermost loop.
  • continue skips the rest of this iteration and goes to the next.
  • for ... else / while ... else: the else runs when the loop completes normally; it is skipped if the loop was left via break.
while condition:
    ...

When writing a while, always confirm that the body eventually changes the condition or triggers a break; otherwise you have an infinite loop.

The Four Container Types

ContainerLiteralOrderedMutableDuplicatesElement requirement
list[1, 2]yesyesyesany
tuple(1, 2)yesnoyesany
dict{k: v}insertion orderyeskeys uniquekeys must be hashable
set{1, 2}noyesno (auto-deduplicated)must be hashable

list

a = []                  # empty list
a.append(x)             # append to the end
a.insert(i, x)          # insert at index i, shifting later elements right
a.extend(b)             # append each element of b
a.pop()                 # remove and return the last element
a.pop(i)                # remove and return the element at index i
a.remove(x)             # remove the first element equal to x; errors if absent
a.index(x)              # index of the first element equal to x
a.count(x)              # how many elements equal x
a.reverse()             # reverse in place
a.sort(key=..., reverse=False)   # sort in place; returns None
del a[i]                # delete the element at index i
del a[1:3]              # delete a slice

Sorting example:

random = [(2, 2), (3, 4), (4, 1), (1, 3)]
random.sort(key=lambda e: e[1])   # sort by the second element of each tuple
# [(4, 1), (2, 2), (1, 3), (3, 4)]

Note: sort() accepts only key and reverse. The Python 2 cmp parameter was removed.

tuple

  • Tuples are immutable: no insert, update, or delete.
  • A one-element tuple needs a trailing comma: (1,) is a tuple, (1) is just the integer.
  • Conversion: list(t) yields a list, tuple(l) yields a tuple. The original object is unchanged; a new object is returned.

dict

profile = {'name': 'Guzheng', 'age': 19}
profile2 = dict(name='Guzheng', age=19)              # with = the keys are unquoted
profile3 = dict([('name', 'Guzheng'), ('age', 19)])  # built from key-value pairs
profile4 = {i: i ** 2 for i in range(2, 5)}          # {2: 4, 3: 9, 4: 16}

Access and update:

profile['name']                 # raises KeyError if the key is absent
profile.get('gender')           # returns None if absent
profile.get('gender', 'male')   # returns a default if absent
profile.setdefault('gender', 'male')   # writes the pair if absent, then returns it
profile['gender'] = 'male'      # add or update
del profile['age']
profile.pop('age')

The key distinction: get() is read-only and never modifies the dict; only setdefault() writes when the key is missing.

Test for a key with in / not in. has_key() is the Python 2 spelling and was removed in Python 3.

set

s = {1, 2, 2, 3}     # deduplicated automatically to {1, 2, 3}
s = set()            # the only way to write an empty set; {} is an empty dict
OperationMethodOperator
uniona.union(b)a | b
intersectiona.intersection(b)a & b
difference (in a, not in b)a.difference(b)a - b
symmetric differencea.symmetric_difference(b)a ^ b
  • Add: add(x) (only hashable/immutable values), update(seq) (accepts a string, list, tuple, or set; given a dict it takes only the keys).
  • Remove: remove(x) errors if absent; discard(x) does not; pop() removes and returns an arbitrary element and takes no argument; clear() empties the set.
  • Test: x in s, a.isdisjoint(b) (True when they share nothing), b.issubset(a).
  • Sets are unordered and unindexed: you cannot read or modify elements by position, only add and remove.

Common misconception: pop() removes an arbitrary element, not a random one. Storage order follows hash values, so it looks random but is not under your control.

Iterators and Generators

Iterable: anything you can loop over with for. It qualifies if either holds:

  1. it implements __iter__() returning an iterator instance;
  2. it has no __iter__() but implements __getitem__() and supports indexing from 0 upward.

Iterator: what iter(iterable) returns. Call next() repeatedly to pull values; when exhausted it raises StopIteration.

from collections.abc import Iterable, Iterator

isinstance([], Iterable)        # True, a list is iterable
isinstance([], Iterator)        # False, but a list is not itself an iterator
isinstance(iter([]), Iterator)  # True

In other words, a list is iterable but not an iterator: a list can be traversed repeatedly, while an iterator moves forward only once.

A generator is “an iterator written as a function”:

# list comprehension produces a list
alist = [i for i in range(5)]     # [0, 1, 2, 3, 4]

# parentheses produce a generator
agen = (i for i in range(5))      # <generator object ...>, lazily evaluated

Note: (i for i in ...) is a generator expression, not a tuple comprehension. Python has no tuple comprehension. To get a tuple, write tuple(i for i in ...).

yield versus return: when execution reaches yield, the function pauses and hands out a value without ending; the next request resumes right after the pause, until the function finishes. A bare yield produces None.

def generator():
    i = 0
    while i < 5:
        i += 1
        yield i

gen = generator()
next(gen)   # 1

for v in generator():
    print(v)   # 1 2 3 4 5

The value of a generator is laziness: it never computes every result at once, so it uses far less memory than a list when handling large data streams.

Other Notes

Comments

# a single-line comment

'''
Triple quotes are technically a "multi-line string", not a comment:
the interpreter really does create a string object, it is simply an unused expression.
'''

Because it is a string rather than a comment, dropping triple quotes into the middle of code breaks the syntax and often raises an error. The correct habit is to use # for comments.

Source encoding

  • Python 3 source files are UTF-8 by default, so you normally do not need an encoding declaration.
  • Only Python 2 required a header line, which is now essentially obsolete:
# -*- coding: UTF-8 -*-

Identifiers

  • Composed of letters, digits, and underscores, cannot start with a digit, and are case-sensitive.
  • Python 3 permits non-ASCII (including Chinese) names, but this is discouraged.
  • Underscore conventions:
    • _name: single leading underscore, meaning “internal use”; from module import * will not import it.
    • __age: double leading underscore, triggering name mangling to avoid clashes in subclasses.
    • __init__: leading and trailing double underscores mark a built-in special method; do not invent such names yourself.

Author: 孤筝

Title: Python Basics

Published: 2023-06-27

Updated: 2026-10-05

License: CC BY-NC-SA 4.0

When will I have a drink and discuss the details again?

Search