Откроем текстовый файл на чтение (когда второй аргумент не указан, файл открывается именно на чтение).
f=open('text.txt')
f,type(f)
Получился объект f одного из файловых типов. Что с ним можно делать? Можно его использовать в for цикле, каждый раз будет возвращаться очередная строка файла (включая '\n' в конце; в конце последней строки текстового файла '\n' может и не быть).
for s in f:
    print(s)
Теперь файл нужно закрыть.
f.close()
Такой стиль работы с файлом (f=open(...); работа с f; f.close()) на самом деле не рекомендуется. Гораздо правильнее использовать оператор with. Он гарантирует, что файл будет закрыт как в том случае, когда исполнение тела with нормально дошло до конца, так и тогда, когда при этом произошло исключение, и мы покинули тело with аварийно.
В операторе with может использоваться любой объект класса, реализующего методы __enter__ и __exit__. Обычно это объект-файл, возвращаемый функцией open.
with open('text.txt') as f:
    for s in f:
        print(s[:-1])
Метод f.read(n) читает n символов (когда файл близится к концу и прочитать именно n символов уже невозможно, читает меньше; в самый последний раз он читает 0 символов и возвращает ''). Прочитаем файл по 1 символу.
with open('text.txt') as f:
    while True:
        c=f.read(1)
        if c=='':
            break
        else:
            print(c)
Вызов f.read() без аргумента читает файл целиком (что не очень разумно, если в нём много гигабайт).
with open('text.txt') as f:
    s=f.read()
s
f.readline() читает очередную строку (хотя проще использовать for s in f:).
with open('text.txt') as f:
    while True:
        s=f.readline()
        if s=='':
            break
        else:
            print(s)
Метод f.readlines() возвращает список строк (опять же его лучше не применять для очень больших файлов).
with open('text.txt') as f:
    l=f.readlines()
l
Теперь посмотрим, чем же оператор with лучше, чем пара open - close.
def a(name):
    global f
    f=open(name)
    s=f.readline()
    n=1/0
    f.close()
    return s
a('text.txt')
f.closed
f.close()
Произошло исключение, мы покинули функцию до строчки close, и файл не закрылся.
def a(name):
    global f
    with open(name) as f:
        s=f.readline()
        n=1/0
    return s
a('text.txt')
f.closed
Теперь всё в порядке.
Чтобы открыть файл на запись, нужно включить второй аргумент 'w'.
f=open('newtext.txt','w')
f.write('aaa\n')
f.write('bbb\n')
f.write('ccc\n')
f.close()
Метод write возвращает число записанных символов.
Опять же, лучше использовать with.
with open('newtext.txt','w') as f:
    f.write('aaa\n')
    f.write('bbb\n')
    f.write('ccc\n')
!cat newtext.txt
Эта функция копирует старый текстовый файл в новый. Если строки нужно как-нибудь обработать, в последней строчке вместо line будет стоять что-нибудь вроде f(line).
def copy(old_name,new_name):
    with open(old_name) as old,open(new_name,'w') as new:
        for line in old:
            new.write(line)
copy('text.txt','newtext.txt')
!cat newtext.txt
Если в программе используется какой-нибудь ресурс, который обязательно надо освободить после использования (например, сетевое соединение или соединение с базой данных), то лучше написать класс, реализующий методы __enter__ и __exit__, и использовать этот ресурс в блоке with.
class Connection:
    
    def __init__(self):
        self.opened=False
    
    def __enter__(self):
        print('Открываем')
        self.opened=True
    
    def __exit__(self,ex_type,ex_value,ex_traceback):
        if ex_value:
            print(f'Exception {ex_value}')
        print('Закрываем')
        self.opened=False
def f(x):
    with Connection() as conn:
        x=1/x
    return x
f(1)
f(0)
В интерактивной сессии (или в программе, запущенной с командной строки) можно попросить пользователя что-нибудь ввести. Аргумент функции input - это приглашение для ввода (prompt). Можно использовать просто input(), тогда приглашения не будет. Но это неудобно, т.к. в этом случае трудно заметить, что программа чего-то ждёт.
s=input('Введите целое число ')
s
n=int(s)
n
Питон - интерпретатор, поэтому он может во время выполнения программы интерпретировать строки как куски исходного текста на языке питон. Так, функция eval интерпретирует строку как выражение и вычисляет его (в текущем контексте - подставляя текущие значения переменных).
s=input('Введите выражение ')
s
eval(s)
А функция exec интерпретирует строку как оператор и выполняет его. Оператор может менять значения переменных в текущем пространстве имён.
s=input('Введите оператор ')
s
exec(s)
x
Строка s может быть результатом длинного и сложного вычисления. Но лучше таких фокусов не делать, так как программа фактически становится самомодифицирующейся. Такие программы очень сложно отлаживать.
Для работы с путями к файлам и директориям в стандартной библиотеке существует модуль pathlib. Объект класса Path представляет собой путь к файлу или директории.
from pathlib import Path
Path() возвращает текущую директорию.
p=Path()
p
Очень полезный метод resolve приводит путь к каноническому виду.
p.resolve()
Путь может быть записан в совершенно идиотском виде; resolve его исправит.
p=Path('.././/book')
p=p.resolve()
p
Статический метод cwd возвращает текущую директорию (current working directory).
Path.cwd()
Если p - путь к директории, то можно посмотреть все файлы в ней.
for f in p.iterdir():
    print(f)
Если p - путь к директории, то p/'fname' - путь к файлу fname в ней (он, конечно, тоже может быть директорией).
p2=p/'b101_numbers.ipynb'
p2
Существует ли такой файл?
p2.exists()
Является ли он симлинком, директорией, файлом?
p2.is_symlink(),p2.is_dir(),p2.is_file()
Части пути p2.
p2.parts
Родитель - директория, в которой находится этот файл.
p2.parent,p2.parent.parent
Имя файла, его основа и суффикс.
p2.name,p2.stem,p2.suffix
Метод stat возвращает всякую ценную информацию о файле.
s=p2.stat()
s
Например, его размер в байтах.
s.st_size
Я написал полезную утилиту для поиска одинаковых файлов. Ей передаётся произвольное число аргументов - директорий и файлов. Она рекурсивно обходит директории, находит размер всех файлов (симлинки игнорируются) и строит словарь, сопоставляющий каждому размеру список файлов, имеющих такой размер. Это простой этап, не требующий чтения (возможно больших) файлов. После этого файлы из тех списков, длина которых $>1$, сравниваются функцией cmp из библиотечного модуля filecmp (что, конечно, требует их чтения).
!cat dup.py
!ls test
!ls test/sub
!./dup.py test
В питоне можно работать с переменными окружения как с обычным словарём.
from os import environ
environ['PATH']
environ['ABCD']
environ['ABCD']='abcd'
environ['ABCD']
Мы не просто добавили пару ключ-значение в словарь, а действительно добавили новую переменную к текущему окружению. Если теперь вызвать из питона какую-нибудь внешнюю программу, то она эту переменную увидит. Эта переменная исчезнет, когда закончится выполнение текущей программы на питоне (или интерактивная сессия).