[分享] 趣味微项目,轻松学Python_16-22章读书心得

打破传统   2024-6-25 23:43 楼主

第十六章的内容是随机重排单词中部。作者通过举例我们阅读文字时不会因为单词的打乱而降低阅读速度从而引入本章内容-解扰。

本章中我们要实现一个程序。它对实参给出的文本中的每个单词进行扰码,该扰码只对4个或以上字符的单词作用,只扰乱单词中部的字母,首尾字母保存不变。

主要内容包括:编写scrambler.py、把文本分解成行和单词、捕获组,非捕获组和可选组、编译正则表达式、对一个单词进行扰码,对所有单词进行扰码、处理文本等内容。

通过本章学习我们将学会使用正则表达式把文本拆分成单词、使用random.shufile()函数将list打乱、创建单词的扰码版本,打乱除首尾字母的单词。程序实现如下:

#!/usr/bin/env python3
"""Scramble the letters of words"""

import argparse
import os
import re
import random


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Scramble the letters of words',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('text', metavar='text', help='Input text or file')

    parser.add_argument('-s',
                        '--seed',
                        help='Random seed',
                        metavar='seed',
                        type=int,
                        default=None)

    args = parser.parse_args()

    if os.path.isfile(args.text):
        args.text = open(args.text).read().rstrip()

    return args


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()
    random.seed(args.seed)
    splitter = re.compile("([a-zA-Z](?:[a-zA-Z']*[a-zA-Z])?)")

    for line in args.text.splitlines():
        print(''.join(map(scramble, splitter.split(line))))


# --------------------------------------------------
def scramble(word):
    """For words over 3 characters, shuffle the letters in the middle"""

    if len(word) > 3 and re.match(r'\w+', word):
        middle = list(word[1:-1])
        random.shuffle(middle)
        word = word[0] + ''.join(middle) + word[-1]

    return word


# --------------------------------------------------
def test_scramble():
    """Test scramble"""

    state = random.getstate()
    random.seed(1)
    assert scramble("a") == "a"
    assert scramble("ab") == "ab"
    assert scramble("abc") == "abc"
    assert scramble("abcd") == "acbd"
    assert scramble("abcde") == "acbde"
    assert scramble("abcdef") == "aecbdf"
    assert scramble("abcde'f") == "abcd'ef"
    random.setstate(state)


# --------------------------------------------------
if __name__ == '__main__':
    main()

第十七章内容是使用正则表达式。作者通过“疯狂”填词游戏引出本章的内容。

本章中我们要实现一个程序。它读取位置实参给出的文件,并且找到所有位于尖括号内的占位符,针对每个占位符,我们向用户提示所需词性,然后用户输入的每个值将替换文本中的占位符,当所有占位符都被用户的输入替换后,打印出新文本。

主要内容包括:编写mad.py、使用正则表达式找到尖括号、停止并打印错误、获取值、替换文本、用正则表达式进行替换、不用正则表达式找到占位符等内容。

通过本章学习我们将学会使用sys.exit()暂停程序并提升错误状态、学会使用正则表达式进行贪婪匹配、使用re.findall()为正则表达式找到所有匹配项、使用re.sub()把找到的模式替换成新文件、探索不使用正则表达式的解决方案。实现程序如下:

#!/usr/bin/env python3
"""Mad Libs"""

import argparse
import re
import sys


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Mad Libs',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('file',
                        metavar='FILE',
                        type=argparse.FileType('rt'),
                        help='Input file')

    parser.add_argument('-i',
                        '--inputs',
                        help='Inputs (for testing)',
                        metavar='input',
                        type=str,
                        nargs='*')

    return parser.parse_args()


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()
    inputs = args.inputs
    text = args.file.read().rstrip()
    blanks = re.findall('(<([^<>]+)>)', text)

    if not blanks:
        sys.exit(f'"{args.file.name}" has no placeholders.')

    tmpl = 'Give me {} {}: '
    for placeholder, pos in blanks:
        article = 'an' if pos.lower()[0] in 'aeiou' else 'a'
        answer = inputs.pop(0) if inputs else input(tmpl.format(article, pos))
        text = re.sub(placeholder, answer, text, count=1)

    print(text)


# --------------------------------------------------
if __name__ == '__main__':
    main()

第十八章内容是使用ASCII值对文本进行数字编码。作者通过希伯来数及《圣经教圣经启示录》引出本章的内容。

本章中我们要实现一个程序。把每个单词中的字符对应的数字相加,从而以数值方式对给定文本中的每个单词进行编码。

主要内容包括:编写gematria.py、清洁单词、序数字符值和范围、求和与归约、使用functools.reduce、对单词进行编码、拆解文本、编写word2num()、排序等内容。

通过本章学习我们将了解ord()和chr()函数、探索字符在ASCII表中的组织方式、理解正则表达式中使用的字符范围、使用re.sub()函数、学会如何在不用lambda的情况下写map()、

使用sum()函数,看它与reduce()的关系、学习如何执行不区分大小写的字符串排序。实现程序如下:

#!/usr/bin/env python3
"""Gematria"""

import argparse
import os
import re


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Gematria',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('text', metavar='text', help='Input text or file')

    args = parser.parse_args()

    if os.path.isfile(args.text):
        args.text = open(args.text).read().rstrip()

    return args


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()

    for line in args.text.splitlines():
        print(' '.join(map(word2num, line.split())))


# --------------------------------------------------
def word2num(word):
    """Sum the ordinal values of all the characters"""

    return str(sum(map(ord, re.sub('[^A-Za-z0-9]', '', word))))


# --------------------------------------------------
def test_word2num():
    """Test word2num"""

    assert word2num("a") == "97"
    assert word2num("abc") == "294"
    assert word2num("ab'c") == "294"
    assert word2num("4a-b'c,") == "346"


# --------------------------------------------------
if __name__ == '__main__':
    main()

第十九章的内容是解析 CSV文件,创建文本表输出。作者以自己参加健身俱乐部的例子引出本章的内容。“Workout of the Day”

在本章中我们将实现一个程序。它来帮助我们创建一个随机的每日健身活动清单,这些健身活动是我们必须做的不容反驳。

主要内容包括:编写wod.py、读取带分隔符的文本文件、手动读取CSV文件、用CSV模块进行解析、创建读取CSV文件的函数、选择健身项目、格式化输出、处理无效数据、读取CSV文件、潜在运行错误、使用Pandas.read_csv()解析文件、对表格进行格式化。

通过本章学习我们将学会使用CSV模块解析带分割符的文本文件、强制将文本转换为数字、使用tabulate模块打印表格数据、处理缺失的和格式错误的数据。实现程序如下:

#!/usr/bin/env python3
"""Create Workout Of (the) Day (WOD)"""

import argparse
import csv
import io
import random
from tabulate import tabulate


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Create Workout Of (the) Day (WOD)',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('-f',
                        '--file',
                        help='CSV input file of exercises',
                        metavar='FILE',
                        type=argparse.FileType('rt'),
                        default='inputs/exercises.csv')

    parser.add_argument('-s',
                        '--seed',
                        help='Random seed',
                        metavar='seed',
                        type=int,
                        default=None)

    parser.add_argument('-n',
                        '--num',
                        help='Number of exercises',
                        metavar='exercises',
                        type=int,
                        default=4)

    parser.add_argument('-e',
                        '--easy',
                        help='Halve the reps',
                        action='store_true')

    args = parser.parse_args()

    if args.num < 1:
        parser.error(f'--num "{args.num}" must be greater than 0')

    return args


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()
    random.seed(args.seed)
    wod = []
    exercises = read_csv(args.file)

    for name, low, high in random.sample(exercises, k=args.num):
        reps = random.randint(low, high)
        if args.easy:
            reps = int(reps / 2)
        wod.append((name, reps))

    print(tabulate(wod, headers=('Exercise', 'Reps')))


# --------------------------------------------------
def read_csv(fh):
    """Read the CSV input"""

    exercises = []
    for row in csv.DictReader(fh, delimiter=','):
        low, high = map(int, row['reps'].split('-'))
        exercises.append((row['exercise'], low, high))

    return exercises


# --------------------------------------------------
def test_read_csv():
    """Test read_csv"""

    text = io.StringIO('exercise,reps\nBurpees,20-50\nSitups,40-100')
    assert read_csv(text) == [('Burpees', 20, 50), ('Situps', 40, 100)]


# --------------------------------------------------
if __name__ == '__main__':
    main()

第二十章内容是生成安全且容易记忆的密码。作者通过一部XKCD漫画引出本章的内容易记忆性的密码算法。

本章中我们要使用一个程序。通过随机组合来自输入文件的单词来创建密码。

主要内容包括:编写password.py、创建唯一的单词列表、清洁文本、使用集合、对单词进行过滤、将单词首字母大写、采样和制作密码、l33t-ify、整体运行、ransom()函数、l33t()函数、处理文件、采样和创建密码等内容。

通过本章学习我们将学会把一个或多个输入文件的列表作为位置实参、使用正则表达式删除非单词字符、按照某个最小长度要求来过滤单词、使用单词集(set)来创建唯一表、组合给定数据量的随机选择单词,生成给定数量的密码、选择之前写过的算法的组合来对文本进行编码。实现程序如下:

#!/usr/bin/env python3
"""Password maker, https://xkcd.com/936/"""

import argparse
import random
import re
import string


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Password maker',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('file',
                        metavar='FILE',
                        type=argparse.FileType('rt'),
                        nargs='+',
                        help='Input file(s)')

    parser.add_argument('-n',
                        '--num',
                        metavar='num_passwords',
                        type=int,
                        default=3,
                        help='Number of passwords to generate')

    parser.add_argument('-w',
                        '--num_words',
                        metavar='num_words',
                        type=int,
                        default=4,
                        help='Number of words to use for password')

    parser.add_argument('-m',
                        '--min_word_len',
                        metavar='minimum',
                        type=int,
                        default=3,
                        help='Minimum word length')

    parser.add_argument('-x',
                        '--max_word_len',
                        metavar='maximum',
                        type=int,
                        default=6,
                        help='Maximum word length')

    parser.add_argument('-s',
                        '--seed',
                        metavar='seed',
                        type=int,
                        help='Random seed')

    parser.add_argument('-l',
                        '--l33t',
                        action='store_true',
                        help='Obfuscate letters')

    return parser.parse_args()


# --------------------------------------------------
def main():
    args = get_args()
    random.seed(args.seed)  # <1>
    words = set()

    def word_len(word):
        return args.min_word_len <= len(word) <= args.max_word_len

    for fh in args.file:
        for line in fh:
            for word in filter(word_len, map(clean, line.lower().split())):
                words.add(word.title())

    words = sorted(words)
    passwords = [
        ''.join(random.sample(words, args.num_words)) for _ in range(args.num)
    ]

    if args.l33t:
        passwords = map(l33t, passwords)

    print('\n'.join(passwords))


# --------------------------------------------------
def clean(word):
    """Remove non-word characters from word"""

    return re.sub('[^a-zA-Z]', '', word)


# --------------------------------------------------
def l33t(text):
    """l33t"""

    text = ransom(text)
    xform = str.maketrans({
        'a': '@', 'A': '4', 'O': '0', 't': '+', 'E': '3', 'I': '1', 'S': '5'
    })
    return text.translate(xform) + random.choice(string.punctuation)


# --------------------------------------------------
def ransom(text):
    """Randomly choose an upper or lowercase letter to return"""

    return ''.join(
        map(lambda c: c.upper() if random.choice([0, 1]) else c.lower(), text))


# --------------------------------------------------
if __name__ == '__main__':
    main()

第二十一章的内容是探索状态。作者电源《战争游戏》中井字棋游戏引入本章内容。

本章中我们将实现一个程序。编写一个只玩单回合井字棋的程序。程序接收一个字符串,以代表游戏期间任意时刻的棋盘状态。默认状态是游戏开始,任意玩家行棋之前的空白棋盘。程序可以接收一次行棋添加到棋盘,将打印棋盘的一张图片,并报告本次行棋之后是否有赢家。

主要内容有:编写tictactoe.py、验证用户输入、更改棋盘、打印棋盘、确定赢家、验证实参和变更棋盘、对棋盘进行格式化、找到赢家等。

通过本章学习我们将学会考虑如何使用像字符串和列表这样的元素来表示程序状态的各个方面、在代码中强制实施游戏规则、使用正则表达式来确认初始棋盘、使用and和or将布尔值的组合归约到单个值、使用列表的列表来找获胜的棋盘、使用enumerate()函数来迭代list的索引和值。实现程序如下:

#!/usr/bin/env python3
"""Tic-Tac-Toe"""

import argparse
import re


# --------------------------------------------------
def get_args():
    """Get command-line arguments"""

    parser = argparse.ArgumentParser(
        description='Tic-Tac-Toe',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    parser.add_argument('-b',
                        '--board',
                        help='The state of the board',
                        metavar='board',
                        type=str,
                        default='.' * 9)

    parser.add_argument('-p',
                        '--player',
                        help='Player',
                        choices='XO',
                        metavar='player',
                        type=str,
                        default=None)

    parser.add_argument('-c',
                        '--cell',
                        help='Cell 1-9',
                        metavar='cell',
                        type=int,
                        choices=range(1, 10),
                        default=None)

    args = parser.parse_args()

    if any([args.player, args.cell]) and not all([args.player, args.cell]):
        parser.error('Must provide both --player and --cell')

    if not re.search('^[.XO]{9}$', args.board):
        parser.error(f'--board "{args.board}" must be 9 characters of ., X, O')

    if args.player and args.cell and args.board[args.cell - 1] in 'XO':
        parser.error(f'--cell "{args.cell}" already taken')

    return args


# --------------------------------------------------
def main():
    """Make a jazz noise here"""

    args = get_args()
    board = list(args.board)

    if args.player and args.cell:
        board[args.cell - 1] = args.player

    print(format_board(board))
    winner = find_winner(board)
    print(f'{winner} has won!' if winner else 'No winner.')


# --------------------------------------------------
def format_board(board):
    """Format the board"""

    cells = [str(i) if c == '.' else c for i, c in enumerate(board, start=1)]
    bar = '-------------'
    cells_tmpl = '| {} | {} | {} |'
    return '\n'.join([
        bar,
        cells_tmpl.format(*cells[:3]), bar,
        cells_tmpl.format(*cells[3:6]), bar,
        cells_tmpl.format(*cells[6:]), bar
    ])


# --------------------------------------------------
def find_winner(board):
    """Return the winner"""

    winning = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7],
               [2, 5, 8], [0, 4, 8], [2, 4, 6]]

    for player in ['X', 'O']:
        for i, j, k in winning:
            combo = [board[i], board[j], board[k]]
            if combo == [player, player, player]:
                return player


# --------------------------------------------------
if __name__ == '__main__':
    main()

第二十二章的内容是带有类型提升的交互式版本的井字游戏。本章中作者接上一章的内容完善井字游戏。

主要内容有:编写itictactoe.py、元组对话、具名元组、添加类型提升、用Mypy进行类型验证、更新不可改的结构、向函数定义添加类型提示、使用TypedDict的版本等内容。

通过本章学习我们将学会使用和退出无限循环、向代码中添加类型提示、探索元组,具名元组和类型字典、使用mypy分析代码错误。实现程序如下:

#!/usr/bin/env python3
""" Interactive Tic-Tac-Toe using NamedTuple """

from typing import List, NamedTuple, Optional


class State(NamedTuple):
    board: List[str] = list('.' * 9)
    player: str = 'X'
    quit: bool = False
    draw: bool = False
    error: Optional[str] = None
    winner: Optional[str] = None


# --------------------------------------------------
def main() -> None:
    """Make a jazz noise here"""

    state = State()

    while True:
        print("\033[H\033[J")
        print(format_board(state.board))

        if state.error:
            print(state.error)
        elif state.winner:
            print(f'{state.winner} has won!')
            break
        elif state.quit:
            print('You lose, loser!')
            break
        elif state.draw:
            print("All right, we'll call it a draw.")
            break

        state = get_move(state)


# --------------------------------------------------
def get_move(state: State) -> State:
    """Get the player's move"""

    player = state.player
    cell = input(f'Player {player}, what is your move? [q to quit]: ')

    if cell == 'q':
        return state._replace(quit=True)

    if not (cell.isdigit() and int(cell) in range(1, 10)):
        return state._replace(error=f'Invalid cell "{cell}", please use 1-9')

    cell_num = int(cell)
    if state.board[cell_num - 1] in 'XO':
        return state._replace(error=f'Cell "{cell}" already taken')

    board = state.board
    board[cell_num - 1] = player
    return state._replace(board=board,
                          player='O' if player == 'X' else 'X',
                          winner=find_winner(board),
                          draw='.' not in board,
                          error=None)


# --------------------------------------------------
def format_board(board: List[str]) -> str:
    """Format the board"""

    cells = [str(i) if c == '.' else c for i, c in enumerate(board, 1)]
    bar = '-------------'
    cells_tmpl = '| {} | {} | {} |'
    return '\n'.join([
        bar,
        cells_tmpl.format(*cells[:3]), bar,
        cells_tmpl.format(*cells[3:6]), bar,
        cells_tmpl.format(*cells[6:]), bar
    ])


# --------------------------------------------------
def find_winner(board: List[str]) -> Optional[str]:
    """Return the winner"""

    winning = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7],
               [2, 5, 8], [0, 4, 8], [2, 4, 6]]

    for player in ['X', 'O']:
        for i, j, k in winning:
            combo = [board[i], board[j], board[k]]
            if combo == [player, player, player]:
                return player

    return None


# --------------------------------------------------
if __name__ == '__main__':
    main()

分享中的代码并非全部代码,感兴趣小伙伴可以扫描下面的二维码下载完整的文件。

image.png  

 

回复评论 (2)

一口气学到22章了,赞

点赞  2024-6-27 07:29

很不错,值得点赞,收藏下来,以后观看学习。加油

点赞  2024-7-17 06:49
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复