《python密码学编程》笔记

  1. 字符串拼接

    >>> 'Hello'
    'Hello'
    >>> 'World'
    'World'
    
    >>> 'Hello' + 'World'
    'HelloWorld'
    
    >>> 2 + 4
    6
    
    >>> 'Hello' + 42
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: cannot concatenate 'str' and 'int' objects
    
    >>> 'Hello' + '42'
    'Hello42'
    
    >>> 'Hello' * 3
    'HelloHelloHello'
    
    >>> spam = 'Abcdef'
    >>> spam = spam * 3
    >>> spam
    'AbcdefAbcdefAbcdef'
    
    >>> spam = spam * 2
    >>> spam
    'AbcdefAbcdefAbcdefAbcdefAbcdefAbcdef'
    
    +运算符可以做加法或字符串连接,*运算符可以做乘法或字符串复制。
    
  2. 打印输出

    >>> print 'Hello!'
    Hello!
    >>> print('Hello!')
    Hello!
    >>> print(42)
    42
    >>> print 42
    42
    
    >>> spam = 'Al'
    >>> print('Hello, ' + spam)
    Hello, Al
    
  3. 转义字符

    ”’
    \ \
    \’ ’
    \” ”
    \n 换行符
    \t 水平制表
    ”’

    print(‘He flew away in a green\teal helicopter.’)
    He flew away in a green\teal helicopter.

  4. 引号和双引号

    >>> print('Hello world')
    Hello world
    >>> print("Hello world")
    Hello world
    

    ”’

    print(‘I asked to borrow Alice\’s car for a week.She said,”Sure.”’)
    I asked to borrow Alice’s car for a week.She said,”Sure.”
    print(“She said,\” I can’t beleve you let him borrow your car.\”“)
    She said,” I can’t beleve you let him borrow your car.”
    ”’

  5. 索引操作

    索引操作和分片操作在列表上的使用方式和它们在字符串值上的一样。这个索引指向的不是字符串里的单个字符,而是列表里的一个项。
    

    正索引:

    >>> spam = 'Hello'
    >>> spam[0]
    'H'
    >>> spam[1]
    'e'
    >>> spam[2]
    'l'
    
    >>> 'Zophie'[2]
    'p'
    >>> eggs = 'Zopie'[2]
    >>> eggs
    'p'
    

    负索引:

    >>> 'Hello'[-1]
    'o'
    >>> 'Hello'[-2]
    'l'
    >>> 'Hello'[-3]
    'l'
    >>> 'Hello'[-4]
    'e'
    >>> 'Hello'[-5]
    'H'
    >>> 'Hello'[0]
    'H'
    >>> 'Hello'[1]
    'e'
    >>> 'Hello'[2]
    'l'
    >>> 'Hello'[3]
    'l'
    >>> 'Hello'[4]
    
  6. 分片操作

    >>> 'Heldy'[0:3]
    'Hel'
    

    得到的结果是从第一个索引到第二个索引,但不包括第二个索引,’Heldy’这个字符串的索引0是H,索引3是d。

    >>> 'Hello world'[0:5]
    'Hello'
    >>> 'Hello world'[6:12]
    'world'
    >>> 'Hello world'[-6:-1]
    ' worl'
    >>> 'Hello world'[6:12][2]
    'r'
    

    分片操作不会在你给它的字符串索引太大时报错。它只会返回它能找到的最大匹配:

    >>> 'Hello'[0:999]
    'Hello'
    >>> 'Hello'[2:999]
    'llo'
    >>> 'Hello'[1000:2000]
    ''
    

    空分片索引:

    省去分片的第一个索引,python会自动认为你想制定索引0为第一个索引
    
    >>> 'Howdy'[:3]
    'How'
    >>> 'Howdy'[0:3]
    'How'
    
    省去分片的第二个索引,python会自动认为你想指定这个字符串的余下部分
    
    >>> 'Howdy'[2:]
    'wdy'
    

    负索引和分片:

    >>> myName = 'Zophie the Fat Cat'
    >>> myName[-7:]
    'Fat Cat'
    >>> myName[11:]
    'Fat Cat'
    >>> myName[:10]
    'Zophie the'
    >>> myName[7:]
    'the Fat Cat'
    

    列表操作:

    >>> animals = ['aardvark','anteater','antelope','albert']
    >>> animals[0]
    'aardvark'
    >>> animals[1]
    'anteater'
    >>> animals[2]
    'antelope'
    >>> animals[3]
    'albert'
    >>> animals[1:3]
    ['anteater', 'antelope']
    
    >>> for spam in ['aardvark','anteater','antelope','albert']:
    ...     print(spam)
    ...
    aardvark
    anteater
    antelope
    albert
    
  7. 函数

    >>> myName = input()
    Albert
    >>> print('It is good to meet you, ' + myName)
    It is good to meet you, Albert
    
  8. 反转加密法

    # Reverse Cipher
    # http://inventwithpython.com/hackinf (BSD Licensed)
    
    message = 'Three can keep a secret, if two of them are dead.'
    translated = ''
    
    i = len(message) - 1
    while i >= 0:
        translated = translated + message[i]    #字符串拼接方法
        i = i - 1
    
    print(translated)
    
    
    解析:
    BSD Licensed:符合自由软件
    len()函数:
    
    >>> len('Hello' + ' ' + 'world!')
    12
    
    使用input()函数:
    
    message = input('Enter message: ')
    
    
    # Reverse Cipher
    # http://inventwithpython.com/hackinf (BSD Licensed)
    # message = 'Three can keep a secret, if two of them are dead.'
    
    message = input('Enter message: ')
    translated = ''
    
    i = len(message) - 1
    while i >= 0:
        translated = translated + message[i]    #字符串拼接方法
        i = i - 1
    
    print(translated)
    
  9. 凯撒加密法(caesarCipher.py)

    # Caeser Cipher
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    '''
    以密钥13为例,将明文按照字母表的顺序循环向后取值并替换,组成密文字符串
    '''
    import pyperclip
    
    # the string to be encrypted/decrypted
    message = 'This is my secret message.'  # 保存加密或解密的字符串
    
    #the encrypted/decrypted key
    key = 13        #保存加密密钥的整数
    
    # tells the program to encrypt or decrypted
    mode = 'encrypt'    #set to 'encrypt' or 'decrypted'    #模式:加密或解密
    
    #every possible symbol that can be encrypted
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'      #字母表
    # LETTERS = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
    
    # stores the encrypted/decrypted form of the message
    translated = ''
    
    #capitalize the string in message
    message = message.upper()
    
    #run the encryption/decryption code on each symbol in the message string
    for symbol in message:
        if symbol in LETTERS:
            # get the encrypted (or decrypted) numble for this aymbol
            num = LETTERS.find(symbol)  #get the numble of the aymbol
            if mode == 'encrypt':
                num = num + key
            elif mode == 'decrypt':
                num = num - key
    
            # handle the wrap-around if num is larger than the length of LETTERS or less than 0
            if num >= len(LETTERS):
                num = num - len(LETTERS)
            elif num < 0:
                num = num + len(LETTERS)
    
            # add encrypted/decrypted numble's synbol at the end of translated
            translated = translated + LETTERS[num]
    
        else:
            #just add the symbol without encrtpting/decrypting
            translated = translated + symbol
    # print the encrypted/decrypted string to the screen
    print(translated)
    
    #copy the encrypted/decrypted string to the clipboard
    pyperclip.copy(translated)
    '''
    

    大小写转换方法:

    >>> 'Hello world!'.upper()
    'HELLO WORLD!'
    >>> 'Hello world!'.lower()
    'hello world!'
    >>> 'Hello world!'.upper().lower()
    'hello world!'
    >>> 'Hello world!'.lower().upper()
    'HELLO WORLD!'
    >>> fizz = 'Hello world!'
    >>> fizz.upper()
    'HELLO WORLD!'
    >>> fizz
    'Hello world!'
    

    循环:

    >>> for letter in 'Howdy':
    ...     print (letter)
    ...
    H
    o
    w
    d
    y
    
    >>> i = 0
    >>> while i < len('Howdy'):
    ...     letter = 'Howdy'[i]
    ...     print(letter)
    ...     i = i+1
    ...
    H
    o
    w
    d
    y
    
  10. 凯撒加密法破译程序(caesarHacker.py)

    # Caesar Cipher Hacker
    # http://inventwithpython.com/hacking (BSD Licensed)
    #
    message = 'GUVF VF ZL FRPERG ZRFFNTR.'
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    # loop through every possible key
    for key in range(len(LETTERS)):
    
        # It is important to set translated to the blank string so that the
        # previous iteration's value for translated is cleared.
        translated = ''
    
        # The rest of the program is the same as the original Caesar program:
    
        # run the encryption/decryption code on each symbol in the message
        for symbol in message:
            if symbol in LETTERS:
                num = LETTERS.find(symbol) # get the number of the symbol
                num = num - key
    
                # handle the wrap-around if num is 26 or larger or less than 0
                if num < 0:
                    num = num + len(LETTERS)
    
                # add number's symbol at the end of translated
                translated = translated + LETTERS[num]
    
            else:
                # just add the symbol without encrypting/decrypting
                translated = translated + symbol
    
        # display the current key being tested, along with its decryption
        print('Key #%s: %s' % (key, translated))
    
  11. in 和 not in 运算符

    >>> 'hello' in 'hello world'
    True
    >>> 'hello' in 'Hello world'
    False
    >>> 'hello' not in 'Hello world'
    True
    >>> 'D' in 'ABCDEF'
    True
    
  12. find() 字符串方法

    有匹配值时,返回位置

    >>> 'hello'.find('e')
    1
    >>> 'hello'.find('o')
    4
    >>> 'hello'.find('l')
    2
    >>> fizz = 'hello'
    >>> fizz.find('h')
    0
    

    无匹配值时,返回-1

    >>> 'hello'.find('x')
    -1
    >>> 'hello'.find('H')
    -1
    

    匹配值超过一个字符时,返回第一个索引

    >>> 'hello'.find('ello')
    1
    >>> 'hello'.find('lo')
    3
    >>> 'hello hello'.find('lo')
    3
    

    range()函数

    >>> for i in range(6):
    ...     print(i)
    ...
    0
    1
    2
    3
    4
    5
    
    >>> for i in range(2,6):
    ...     print(i)
    ...
    2
    3
    4
    5
    
  13. 格式化输出

    >>> 'Hello %s!' % ('world')
    'Hello world!'
    >>> 'Hello ' + 'world' + '!'
    'Hello world!'
    >>>
    >>> 'The %s ate the %s that ate %s.' % ('dog','cat','rat')
    'The dog ate the cat that ate rat.'
    
    >>> '%s had %s pies.' % ('Alice',42)
    'Alice had 42 pies.'
    >>> 'Alice' + ' had ' + 42 + ' pies. '
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: must be str, not int
    
  14. 换位加密法加密程序(transpositionEncrypt.py)

    # Transposition Cipher Encryption
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    '''
    第一步是画出一行个数等于密钥的格子
    第二步是把你希望加密的消息写入格子,每个格子写入一个字符
    我们把最后一行的最后两个格子涂成灰色是为了提醒我们忽略它们
    
    以密钥8为例,将明文以n行8列排列
        每列从上到下按序取值,共8列组成密文字符串
    
    加密的步骤如下:
        1. 数一下消息里的字符个数。
        2. 画一行个数等于密钥的格子。(比如说,密钥是12,格子就有12个。)
        3. 从左到右开始填充格子,每个格子填一个字符。
        4. 当你用完格子还有字符剩下时,再加一行格子。
        5. 把最后一行剩下不用的格子涂成灰色。
        6. 从最上角开始往下写出字符。当你到达这一行的底部后,移到右边那一列。跳过任何灰色的格子。这就是密文。
    
    4行8列的矩阵
    '''
    
    import pyperclip
    
    def main():
        myMessage = 'Common sense is not so common.'
        myKey = 8
    
        ciphertext = encryptMessage(myKey, myMessage)
    
        # Print the encrypted string in ciphertext to the screen, with
        # a | (called "pipe" character) after it in case there are spaces at
        # the end of the encrypted message.
        print(ciphertext + '|')
    
        # Copy the encrypted string in ciphertext to the clipboard.
        pyperclip.copy(ciphertext)
    
    
    def encryptMessage(key, message):
        # Each string in ciphertext represents a column in the grid.
        ciphertext = [''] * key
    
        # Loop through each column in ciphertext.
        for col in range(key):
            pointer = col
    
            # Keep looping until pointer goes past the length of the message.
            while pointer < len(message):
                # Place the character at pointer in message at the end of the
                # current column in the ciphertext list.
                ciphertext[col] += message[pointer]
    
                # move pointer over
                pointer += key
    
        # Convert the ciphertext list into a single string value and return it.
        return ''.join(ciphertext)
    
    
    # If transpositionEncrypt.py is run (instead of imported as a module) call
    # the main() function.
    if __name__ == '__main__':
        main()
    
  15. import pyperclip

    换位加密法程序和凯撒加密法程序一样会把加密之后的文字复制到剪贴板。
    因此,我们得先导入pyperclip模块,以便它可以调用pyperclip.copy()。
    
  16. 实参和形参

    “传入”函数调用的实参值是复步/给形参的。因此,如果形参被修改,提供实参值的变量不会改变。
    
  17. 列表操作

    使用list()函数把区间对象转换成列表

    >>> myList = list(range(10))
    >>> myList
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    >>> for i in range(10):
    ...     myList = myList + [i]
    ...
    >>> myList
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
    >>> myList = list('Hello world!')
    >>> myList
    ['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!']
    

    重新赋值列表里的项:

    >>> animals = ['aardvark','anteater','antelope','albert']
    >>> animals
    ['aardvark', 'anteater', 'antelope', 'albert']
    >>> animals[2] = 9999
    >>> animals
    ['aardvark', 'anteater', 9999, 'albert']
    

    虽然你可以重新赋值列表里的项,但是你不能重新赋值字符串值里的字符。

    >> 'Hello world!'[6]
    'w'
    >>> 'Hello world!'[6] = 'x'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'str' object does not support item assignment
    

    要改变字符串里的字符, 可以使用分片

    >>> spam = 'Hello world!'
    >>> spam[:6]
    'Hello '
    >>> spam[7:]
    'orld!'
    >>> spam = spam[:6] + 'x' + spam[7:]
    >>> spam
    'Hello xorld!'
    

    列表值还可以包含其他列表值

    >>> spam = [['dog','cat'],[1,2,3]]
    >>> spam[0]
    ['dog', 'cat']
    >>> spam[1]
    [1, 2, 3]
    >>> spam[0][0]
    'dog'
    >>> spam[0][1]
    'cat'
    >>> spam[1][0]
    1
    >>> spam[1][1]
    2
    >>> spam[0][1][0]
    'c'
    >>> spam[0][1][1]
    'a'
    >>> spam[0][1][2]
    't'
    

    在列表上使用len()和in运算符

    >>> animals = ['aardvark','anteater','antelope','albert']
    >>> len(animals)
    4
    >>> 'albert' in animals
    True
    >>> 'albert' not in animals
    False
    

    列表连接和赋值

    >>> ['hello'] + ['world']
    ['hello', 'world']
    >>> ['hello'] * 5
    ['hello', 'hello', 'hello', 'hello', 'hello']
    

    spam += 2 这条语句所做的事和spam = spam + 2 完全相同,但是要输入的东西更短。+=和整数一起使用能做加法,和字符串一起使用能做字符串连接,和列表一起使用能做列表连接

    >>> spam = 40
    >>> spam += 2
    >>> spam
    42
    >>> spam = 'Hello'
    >>> spam += ' world'
    >>> spam
    'Hello world'
    >>> spam = ['dog']
    >>> spam += ['cat']
    >>> spam
    ['dog', 'cat']
    
  18. join()字符串方法

    join()方法接受一个字符串列表,并返回一个字符串。

    >>> eggs = ['dogs','cats','moose']
    >>> ''.join(eggs)
    'dogscatsmoose'
    >>> ' '.join(eggs)
    'dogs cats moose'
    >>> 'XYZ'.join(eggs)
    'dogsXYZcatsXYZmoose'
    >>> ''.join(eggs).upper().join(eggs)
    'dogsDOGSCATSMOOSEcatsDOGSCATSMOOSEmoose'
    
  19. 既可以作为一个程序单独运行,也可以作为一个模块导入另一个程序

    if __name__ == '__main__':
        main()
    

    我们这样设置代码的原因是,虽然Python会在程序运行时会把name设为 ‘main‘,但如果我们的程序被一个不同的Python程序导入,它会把它设为字符串’transpositionEncrypt’.这正是我们的程序如何得知它是作为程序运行还是作为模块导入到另一个程序的。

  20. 换位加密法解密程序(transpositionDecrypt.py)

    # Transposition Cipher Decryption
    # http://inventwithpython.com/hacking (BSD Licensed)
    
    '''
    解密这个密文的第一步是计算需要画多少个格子。为了得到这个数字,将密文消息的长度除以密钥,然后向上取整。我们的密文的长度是30个字符(和明文完全一样),密钥是8,因此计算30除以8得3.75。
        这意味着我们需要画一个4列(我们刚算好的数字)8行(密钥)的网格。
    我们需要计算的第二个东西是最右边那一列有多少个格子要涂灰。拿格子的总数(32)减去密文的长度(30), 32-30 = 2,因此涂灰最右边那一列下面两个格子。
        从最上角开始向右填,就像我们在加密时做的那样。
    解密的步骤如下:
        1. 将消息的长度除以密钥并向上取整计算需要的列数。
        2. 画出一定数量的格子。列数已在第一步计算,行数和密钥一样。
        3. 将格子的个数(行数乘以列数)减去密文消息的长度计算需要涂灰格子的个数。
        4. 涂灰最右边那一列下面的格子,个数已在第三步计算。
        5. 填入密文的字符,从最上面那一行开始,从左向右填入。跳过任何灰色格子。
        6. 要获取明文,从最左边那一列开始,从上往下读取,然后移到下一列顶部。
    
    4列8行的矩阵
    '''
        import math, pyperclip
    
        def main():
            myMessage = 'Cenoonommstmme oo snnio. s s c'
            myKey = 8
    
            plaintext = decryptMessage(myKey, myMessage)
    
            # Print with a | (called "pipe" character) after it in case
            # there are spaces at the end of the decrypted message.
            print(plaintext + '|')
    
            pyperclip.copy(plaintext)
    
    
        def decryptMessage(key, message):
            # The transposition decrypt function will simulate the "columns" and
            # "rows" of the grid that the plaintext is written on by using a list
            # of strings. First, we need to calculate a few values.
    
            # The number of "columns" in our transposition grid:
            # 列的长度往高了取值
            numOfColumns = math.ceil(len(message) / key)
            # The number of "rows" in our grid will need:
            # 8位密钥,8行矩阵
            numOfRows = key
            # The number of "shaded boxes" in the last "column" of the grid:
            # 计算网格里灰色格子的个数(解密的第三步),它是列数乘以行数,减去消息的长度。
            numOfShadedBoxes = (numOfColumns * numOfRows) - len(message)
    
            # Each string in plaintext represents a column in the grid.
            # 通过列表复制,我们可以将一个包含一个空字符串的列表乘以numOfColumns,得到一个包含多个空字符串的列表。
            plaintext = [''] * numOfColumns
    '''
    plaintext变量包含一个字符串列表。这个列表里的每个字符串都是这个网格的一列。对于这次解密,我们希望plaintext最终的值是这样:
    >>> plaintext = ['Common s_, 'ense is ', 'not so c', 'ommon.']
    >>> plaintext[0]
    'Common s'
    表格里是4列8行
    密文:Cenoonommstmme oo snnio. s s c
    '''
            # The col and row variables point to where in the grid the next
            # character in the encrypted message will go.
            col = 0
            row = 0
    
            for symbol in message:
                plaintext[col] += symbol
                col += 1 # point to next column
    
                # If there are no more columns OR we're at a shaded box, go back to
                # the first column and the next row.
                if (col == numOfColumns) or (col == numOfColumns - 1 and row >= numOfRows - numOfShadedBoxes):
                    col = 0
                    row += 1
    
            return ''.join(plaintext)
    
    
        # If transpositionDecrypt.py is run (instead of imported as a module) call
        # the main() function.
        if __name__ == '__main__':
            main()
    
  21. math.ceil()、math.floor()和 round()函数

    如果你希望把这个数字四舍五入到最近的整数,你可以使用round()函数。

    >>> round(4.2)
    4
    >>> round(4.8)
    5
    >>> round(4.5)
    4
    >>> round(4.0)
    4
    >>> round(22 / 5)
    4
    

    如果你只希望向上取整,那就使用math.ceilG函数,它表示“上限”。
    这些函数在math模块里,因此你需要在调用它们之前导入math模块。

    >>> import math
    >>> math.floor(4.0)
    4
    >>> math.floor(4.2)
    4
    >>> math.floor(4.8)
    4
    

    如果你只希望向下取整,那就使用mathiloorO函数。
    这些函数在math模块里,因此你需要在调用它们之前导入math模块。

    >>> import math
    >>> math.ceil(4.0)
    4
    >>> math.ceil(4.2)
    5
    >>> math.ceil(4.8)
    5
    
  22. 换位加密法测试程序(transpositionTest.py)

    # Transposition Cipher Test
    # http://inventwithpython.com/hacking (BSD Licensed)
    
    import random, sys, transpositionEncrypt, transpositionDecrypt
    
    def main():
        random.seed(42) # set the random "seed" to a static value
    
        for i in range(20): # run 20 tests
            # Generate random messages to test.
    
            # The message will have a random length:
            message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40)
    
            # Convert the message string to a list to shuffle it.
            # 为了打乱一个字符串值里的字符,我们得先使用list()把这个字符串转换成列表,然后使
            # 用shuffle()打乱列表里的项,接着使用join()字符串方法把它转换回字符串值。
            # 我们使用这个技术打乱message变量里的字符。这样我们就可以测试很多不同的消息,
            # 以防万一我们的换位加密法只能加密和解密某些消息,但不能处理其他的。
            message = list(message)
            random.shuffle(message)
            message = ''.join(message) # convert list to string
    
            # 因为message里的字符串可能非常长,所以我们通过分片操作只显示message的前50个字符。
            print('Test #%s: "%s..."' % (i+1, message[:50]))
    
            # Check all possible keys for each message.
            # 凯撒加密法的密钥是从0到25的整数,而换位加密法的密钥在1和消息的长度之间
            for key in range(1, len(message)):
                encrypted = transpositionEncrypt.encryptMessage(key, message)
                decrypted = transpositionDecrypt.decryptMessage(key, encrypted)
    
                # If the decryption doesn't match the original message, display
                # an error message and quit.
                # 正常情况下,一旦执行到达最下面,没有更多代码需要执行,我们的程序就会退出。然而,
                # 我们可以通过调用sys.exitG函数让程序提前退岀。当sys.exit()被调用时,程序会立刻中止。
                if message != decrypted:
                    print('Mismatch with key %s and message %s.' % (key, message))
                    print(decrypted)
                    sys.exit()
    
        print('Transposition cipher test passed.')
    
    
    # If transpositionTest.py is run (instead of imported as a module) call
    # the main() function.
    if __name__ == '__main__':
        main()
    
  23. 伪随机数和random.seedO函数

    由Python的random.randint()函数产生的数字并不是真正随机的。它们是根据伪随机数生成算法产生的,这个算法广为人知,它所产生的数字是可预测的
    

    可以通过调用random.seed()函数重设Python的随机种子

    >>> import random
    >>> random.seed(42)
    >>> for i in range(5):
    ...     print(random.randint(1,10))
    ...
    2
    1
    5
    4
    4
    >>> random.seed(42)
    >>> for i in range(5):
    ...     print(random.randint(1,10))
    ...
    2
    1
    5
    4
    4
    
    >>> random.seed(43)
    >>> for i in range(5):
    ...     print(random.randint(1,10))
    ...
    1
    5
    3
    8
    6
    >>> random.seed(42)
    >>> for i in range(5):
    ...     print(random.randint(1,10))
    ...
    2
    1
    5
    4
    4
    

    关于在Python里使用os.urandom()函数产生真随机数字的详细内容可以在http://invpy.com/random 找到。
    Python documentation on the random module.
    Python documentation on os.urandom
    random.randintO函数接受两个整数参数,返回那两个整数之间(包括那两个整数本身)的一个随机整数

    >>> import random
    >>> random.randint(1,20)
    5
    >>> random.randint(1,20)
    4
    >>> random.randint(1,20)
    18
    >>> random.randint(1,20)
    3
    >>> random.randint(1,20)
    
    >>> message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' * random.randint(1,5)
    >>> message
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    >>> message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' * random.randint(1,5)
    >>> message
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    >>> message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ' * random.randint(1,5)
    >>> message
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUVWXYZ '
    
  24. 引用

    变量的引用:引用的是变量的值

    >>> spam = 42
    >>> cheese = spam
    >>> spam = 10
    >>> spam
    10
    >>> cheese
    42
    

    列表的引用:引用的是列表的指针

    >>> spam = [0,1,2,3,4,5]
    >>> cheese = spam
    >>> spam
    [0, 1, 2, 3, 4, 5]
    >>> cheese
    [0, 1, 2, 3, 4, 5]
    >>> cheese[1] = 'Hello!'
    >>> spam
    [0, 'Hello!', 2, 3, 4, 5]
    >>> cheese
    [0, 'Hello!', 2, 3, 4, 5]
    
  25. copy.deepcopy()函数

    列表复制,普通模式是赋值对列表的引用,想要复制列表值本身,则需要调用copy.deepcopy()函数

    >>> spam = [0,1,2,3,4,5]
    >>> import copy
    >>> cheese = copy.deepcopy(spam)    #复制并创建一个新的列表变量
    >>> cheese[1] = 'Hello!'
    >>> spam
    [0, 1, 2, 3, 4, 5]
    >>> cheese
    [0, 'Hello!', 2, 3, 4, 5]
    
  26. random.shuffl()函数

    random.shuffl()函数也在random模块里。它接受一个列表参数,接着随机重新排列这个列表里的项。

    shuffleG函数就地修改列表值(因为shuffle()通过传给它的列表引用值直接修改列表)
    我们只需执行 random.shuffle (spam),而不是执行 spam = random.shuffle (spam)。
    
    >>> import random
    >>> spam = [0,1,2,3,4,5,6,7,8,9]
    >>> spam
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> random.shuffle(spam)
    >>> spam
    [1, 6, 5, 0, 9, 3, 8, 2, 7, 4]
    >>> random.shuffle(spam)
    >>> spam
    [6, 8, 2, 0, 3, 4, 5, 9, 1, 7]
    

    可以使用list()函数把字符串或区间对象转换成列表值

    >>> import random
    >>> eggs = list('Hello')
    >>> eggs
    ['H', 'e', 'l', 'l', 'o']
    >>> random.shuffle(eggs)
    >>> eggs
    ['o', 'e', 'l', 'H', 'l']
    >>> eggs = ''.join(eggs)
    >>> eggs
    'oelHl'
    
    >>> import random
    >>> spam = 'Hello world!'
    >>> spam = list(spam)
    >>> random.shuffle(spam)
    >>> spam = ''.join(spam)
    >>> spam
    'olH!deowrl l'
    
  27. 换位加密法文件加密程序(transpositionFileCipher.py)

    # Transposition Cipher Encrypt/Decrypt File
    # http://inventwithpython.com/hacking (BSD Licensed)
    '''
    将整个文件进行加密
    '''
    import time, os, sys, transpositionEncrypt, transpositionDecrypt
    
    def main():
    
        # 以下四行将决定这是加密程序
        inputFilename = 'frankenstein.txt'
        # inputFilename = 'frankenstein.encrypted.txt'
        # BE CAREFUL! If a file with the outputFilename name already exists,
        # this program will overwrite that file.
        outputFilename = 'frankenstein.encrypted.txt'
        # outputFilename = 'frankenstein.decrypted.txt'
        myKey = 10
        myMode = 'encrypt' # set to 'encrypt' or 'decrypt'
    
        '''
        以下四行的更改将导致加密程序直接变成解密程序
        inputFilename = 'frankenstein.encrypted.txt'
        # BE CAREFUL! If a file with the outputFilename name already exists,
        # this program will overwrite that file.
        outputFilename = 'frankenstein.decrypted.txt'
        myKey = 10
        myMode = 'decrypt' # set to 'encrypt' or 'decrypt'
        '''
    
        # If the input file does not exist, then the program terminates early.
        # 我们使用os.path.exists()函数确认inputFilename里的文件名真的存在。否则,我们没
        # 有要加密或解密的文件。在这种情况下,我们会向用户显示消息,然后退出程序。
        if not os.path.exists(inputFilename):
            print('The file %s does not exist. Quitting...' % (inputFilename))
            sys.exit()
    
        # If the output file already exists, give the user a chance to quit.
        if os.path.exists(outputFilename):
            print('This will overwrite the file %s. (C)ontinue or (Q)uit?' % (outputFilename))
            response = input('> ')
            if not response.lower().startswith('c'):
                sys.exit()
    
        # Read in the message from the input file
        fileObj = open(inputFilename)
        content = fileObj.read()
        fileObj.close()
    
        print('%sing...' % (myMode.title()))
    
        # Measure how long the encryption/decryption takes.
        # 可以使用这个time.time()函数判断我们的程序运行了多久。
        startTime = time.time()
        if myMode == 'encrypt':
            translated = transpositionEncrypt.encryptMessage(myKey, content)
        elif myMode == 'decrypt':
            translated = transpositionDecrypt.decryptMessage(myKey, content)
        totalTime = round(time.time() - startTime, 2)
        print('%sion time: %s seconds' % (myMode.title(), totalTime))
    
        # Write out the translated message to the output file.
        outputFileObj = open(outputFilename, 'w')
        outputFileObj.write(translated)
        outputFileObj.close()
    
        # 因为myMode应该包含字符串'encrypt’或者'decrypt、调用title()字符串方法将显示Encrypting...?或者'Decrypting".’。
        print('Done %sing %s (%s characters).' % (myMode, inputFilename, len(content)))
        print('%sed file is %s.' % (myMode.title(), outputFilename))
    
    
    # If transpositionCipherFile.py is run (instead of imported as a module)
    # call the main() function.
    if __name__ == '__main__':
        main()
    
  28. read()文件对象方法

    打开文件:

    >>> fo = open('spam.txt','r')
    >>> content = fo.read()
    >>> print(content)
    Hello world!
    
    >>> print('Hello\nworld!')
    Hello
    world!
    

    关闭文件:

    >>> fo.close()
    
  29. os.path.exists()函数

    os.path.exists()函数有一个字符串参数,表示文件名,如果这个文件已经存在就返回True,如果不存在就返回False
    os.path.exists()函数在path模块里,path模块本身在os模块里。但是,如果我们导入os模块,path模块也会被导入。

    '''
    读取文件总是无害的,但在写入文件时我们需要当心。如果我们以写人模式调用open()
    函数,但文件名已经存在,那个文件将被删除,为新的文件让路。这意味着,如果我们把重
    要文件的名字传给open()函数,可能会不小心删除这个重要文件。使用os.path.exists()函数,
    我们可以检查某个文件名是否已经存在。
    '''
    
    >>> import os
    >>> os.path.exists('abcdef')
    False
    >>> os.path.exists('112233.txt')
    False
    >>> os.path.exists('spam.txt')
    
  30. startswith()和 endswith()字符串方法

    如果字符串参数可以在这个字符串开头找到,startswith()方法会返回True。

    >>> 'Hello'.startswith('h')
    False
    >>> 'Hello'.startswith('H')
    True
    >>> 'Hello'.startswith('l')
    False
    >>> 'Hello world!'.startswith('Hello wo')
    True
    >>> spam = 'Albert'
    >>> spam.startswith('Al')
    True
    

    endswith()字符串方法可以用来检查一个字符串值是否以另一个字符串值结尾。

    >>> 'Hello world!'.endswith('world!')
    True
    >>> 'Hello world!'.endswith('world')
    False
    >>> 'Hello world!'.endswith('wor')
    False
    >>> 'Hello world!'.endswith('!')
    True
    

    title()字符串方法会返回一个字符串的“标题形式”。标题形式的每个单词的首字母大写,其他字母小写

    >>> 'hello'.title()
    'Hello'
    >>> 'HELLO'.title()
    'Hello'
    >>> 'heLLO'.title()
    'Hello'
    >>> 'hello world! HOW ARE YOU?'.title()
    'Hello World! How Are You?'
    
  31. time 模块和 time.time()函数

    time.time()函数会返回一个浮点值,表示自1970年1月1日起的秒数。

    >>> import time
    >>> time.time()
    1515761331.6953547
    >>> time.time()
    1515761336.7378328
    
  32. 通过编程检测英文(detectEnglish.py)

    # 检测参数字符串或语句是否是英文单词
    '''
    >>> import detectEnglish
    >>> detectEnglish.isEnglish('Is this sentence English text?')
    True
    '''
    # Detect English module
    # http://inventwithpython.com/hacking (BSD Licensed)
    
    # To use, type this code:
    #   import detectEnglish
    #   detectEnglish.isEnglish(someString) # returns True or False
    # (There must be a "dictionary.txt" file in this directory with all English
    # words in it, one word per line. You can download this from
    # http://invpy.com/dictionary.txt)
    UPPERLETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \t\n'
    
    # dictionaryFile.read()方法调用会读取整个文件,并把它作为一个很大的字符串值返回。在这
    # 个字符串上,我们调用split()方法,使用换行符对它进行分割。这个split()调用会返回一个
    # 列表值,包含字典文件里的每个单词(因为在这个字典文件里一个单词占一行)。
    def loadDictionary():
        dictionaryFile = open('dictionary.txt')
        englishWords = {}
        for word in dictionaryFile.read().split('\n'):
            englishWords[word] = None
            # 字典可以在被赋值时进行累加创建
        dictionaryFile.close()
        return englishWords
    
    ENGLISH_WORDS = loadDictionary()
    
    # getEnglishCount()函数接受一个字符串参数,返回一个浮点值,表示已经识别出多少个英文单词
    def getEnglishCount(message):
        message = message.upper()
        message = removeNonLetters(message)
        possibleWords = message.split()
    
        if possibleWords == []:
            return 0.0 # no words at all, so return 0.0
    
        matches = 0
    
        # getEnglishCount()返回的浮点值在0.0和1.0之间。为了计算这个数字,我们将
        # possibleWords里已被识别为英文的单词个数除以possibleWords里的单词总数。
        for word in possibleWords:
            if word in ENGLISH_WORDS:
                matches += 1
        return float(matches) / len(possibleWords)
        # len(possibleWords)是 lenpossibleWords 里的字符串的个数
    
    # 从这个字符串移除非字母字符,如数字和标点
    def removeNonLetters(message):
        lettersOnly = []
        for symbol in message:
            if symbol in LETTERS_AND_SPACE:
                lettersOnly.append(symbol)
        return ''.join(lettersOnly)
    
    # 第二和第三个参数(wordPercentage和
    # letterPercentage)旁边都有等号和值。这些叫做默认参数值。带有默认参数值的参数是可选
    # 的。如果函数调用不向这些参数传递参数值,就会使用默认参数值。
    def isEnglish(message, wordPercentage=20, letterPercentage=85):
        # By default, 20% of the words must exist in the dictionary file, and
        # 85% of all the characters in the message must be letters or spaces
        # (not punctuation or numbers).
        # 通过把message传给getEnglishCount()计算message里已被识别的英文单词的比例,那个函数为我们做除法并返回一个在0.0和1.0之间的浮点值。
        # 记住,>=比较运算符计算表达式得到一个布尔值
        wordsMatch = getEnglishCount(message) * 100 >= wordPercentage
        # 调用removeNonLetters (message)。这会返回一个字符串,里面的数字和标点字符都被移除了
        numLetters = len(removeNonLetters(message))
        # 为了计算英文单词的比例,你将英文单词个数除以单词总数再乘以100
        # 通过获取numLetters里的整数的浮点版本并将它除以len (message)确定字母的比例
        messageLettersPercentage = float(numLetters) / len(message) * 100
        # 检查messageLettersPercentage里的比例是否大于或等于letterPercentage参数。
        # 这个表达式计算得到的布尔值会保存在lettersMatch里。
        lettersMatch = messageLettersPercentage >= letterPercentage
        # 如果wordsMatch和lettersMatch变
        # 量都是True,那么isEnglish()会返回True,表示这个消息参数是英文
        return wordsMatch and lettersMatch
    
    '''
    >>> import detectEnglish
    >>> detectEnglish.isEnglish('Is this sentence English text?')
    True
    '''
    
  33. split()方法通过查找空格字符确定每个单词的开始和结束。

    其他人已经在一个文本文件里输入了几乎所有的英文单词。这
    些文本文件叫做字典文件。因此,我们只要写一个函数检查字符串里的这些单词是否存在于
    那个文件的某个地方就行了。
    

    splito字符串方法返回字符串列表。每个字符串之间的“分割”会在有空格的地方发生。

    >>> 'My very energetic mother ]ust served us Nutella.'.split()
    ['My', 'very', 'energetic', 'mother', ']ust', 'served', 'us', 'Nutella.']
    
    >>> 'helloXXXworldXXXhowXXXareXXyou?'.split('XXX')
    ['hello', 'world', 'how', 'areXXyou?']
    >>>
    
  34. 字典和字典数据类型

    >>> emptyList = []
    >>> emptyDictionary = {}
    
    >>> spam = {'key1':'This is a value','key2':42}
    >>> spam['key1']
    'This is a value'
    >>> spam['key2']
    42
    

    字典引用的是指针

    >>> spam = {'hello':42}
    >>> eggs = spam
    >>> eggs['hello'] = 99
    >>> eggs
    {'hello': 99}
    >>> spam
    {'hello': 99}
    

    添加或修改字典里的项

    >>> spam = {42:'hello'}
    >>> print(spam[42])
    hello
    >>> spam[42] = 'goodbye'
    >>> print(spam[42])
    goodbye
    

    字典里也可以包括其他字典

    >>> foo = {'fizz': {'name': 'A1', 'age': 144}, 'moo':['a', 'brown', 'cow']}
    >>> foo['fizz']
    {'name': 'A1', 'age': 144}
    >>> foo['fizz']['name']
    'A1'
    >>> foo['moo']
    ['a', 'brown', 'cow']
    >>> foo['moo'][1]
    'brown'
    

    在字典上使用len()函数

    >>> spam = {}
    >>> len(spam)
    0
    >>> spam['name'] = 'Al'
    >>> spam['pet'] = 'Zophie the cat'
    >>> spam['age'] = 89
    >>> len(spam)
    3
    

    in运算符也可以用来检查某个键是否存在于一个字典里

    in运算符是检查一个键是否存在于这个字典里,而不是检查一个值,记住这点很重要
    >>> eggs = {'foo':'milk','bar':'bread'}
    >>> 'foo' in eggs
    True
    >>> 'blah blah blah' in eggs
    False
    >>> 'milk' in eggs
    False
    >>> 'bar' in eggs
    True
    >>> 'bread' in eggs
    False
    

    在字典上使用for循环

    >>> spam = {'name':'Al','age':99}
    >>> for k in spam:
    ...     print(k)
    ...     print(spam[k])
    ...     print('=========')
    ...
    name
    Al
    =========
    age
    99
    =========
    

    字典在很多方面很像列表,但有几个重要的区别:

    1. 字典的项没有固定顺序。字典不像列表那样有“第一项”或“最后一项”。
    2. 字典不能使用+运算符连接。如果你希望添加新的项,只需在进行索引操作时使用新的键就行了。
        比如说,foo['a new key'] = 'a string'0
    3. 列表只有整数索引值,范围从0到列表长度减1。但字典能有任何键。
        如果你有一个字典保存在一个spam变量里,那么你可以把一个值保存到spam[3]里,
        而不必先保存 spam[0]、spam[l]或 spam[2]的值。
    
  35. float()、int()和str()函数以及整数除法

    >>> float(42)
    42.0
    >>> int(42.0)
    42
    >>> int(42.7)
    42
    >>> int("42")
    42
    >>> int('42')
    42
    >>> str(42)
    '42'
    >>> str(42.7)
    '42.7'
    

    当除法运算的两个数都是整数时,
    Python 2会做整数除法。这意味着结果会被向下取整。因此,使用Python 2, 22/7会得到3。
    但是,如果其中一个值是浮点值,Python 2会做常规除法: 22.0 / 7会得到3.142857142857143。
    这就是第36行调用floats的原因。这个调用是为了让代码向后兼容以前版本。
    Python 3总是做常规除法,不管这些值是浮点值还是整数值。

  36. append()列表方法

    >>> spam = [2,3,4,5,6,7]
    >>> spam
    [2, 3, 4, 5, 6, 7]
    >>> spam = spam + [42]
    >>> spam
    [2, 3, 4, 5, 6, 7, 42]
    

    从技术的角度来说,使用appendG方法比把一个值放入一个列表然后使用+运算符添加更快

    appendO方法就地修改列表纳人新值
    >>> eggs = []
    >>> eggs.append('hovercraft')
    >>> eggs
    ['hovercraft']
    >>> eggs.append('eels')
    >>> eggs
    ['hovercraft', 'eels']
    >>> eggs.append(42)
    >>> eggs
    ['hovercraft', 'eels', 42]
    
  37. 译换位加密法(transpositionHacker.py)

    # Transposition Cipher Hacker
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import pyperclip, detectEnglish, transpositionDecrypt
    
    def main():
        # You might want to copy & paste this text from the source code at
        # http://invpy.com/transpositionHacker.py
        # 三引号字符串也叫做多行字符串,因为它们里面也能包含实际的换行
        # 这个字符串值可以跨越多行。左三引号之后的一切都被看做是这个字符串的一部
        # 分,直到右三引号为止。多行字符串可以使用三个双引号字符或者三个单引号字符。
        # 多行字符串对于把很大的字符串放入程序的源代码很有用
        myMessage = """Cb b rssti aieih rooaopbrtnsceee er es no npfgcwu  plri ch nitaalr eiuengiteehb(e1  hilincegeoamn fubehgtarndcstudmd nM eu eacBoltaeteeoinebcdkyremdteghn.aa2r81a condari fmps" tad   l t oisn sit u1rnd stara nvhn fsedbh ee,n  e necrg6  8nmisv l nc muiftegiitm tutmg cm shSs9fcie ebintcaets h  aihda cctrhe ele 1O7 aaoem waoaatdahretnhechaopnooeapece9etfncdbgsoeb uuteitgna.rteoh add e,D7c1Etnpneehtn beete" evecoal lsfmcrl iu1cifgo ai. sl1rchdnheev sh meBd ies e9t)nh,htcnoecplrrh ,ide hmtlme. pheaLem,toeinfgn t e9yce da' eN eMp a ffn Fc1o ge eohg dere.eec s nfap yox hla yon. lnrnsreaBoa t,e eitsw il ulpbdofgBRe bwlmprraio po  droB wtinue r Pieno nc ayieeto'lulcih sfnc  ownaSserbereiaSm-eaiah, nnrttgcC  maciiritvledastinideI  nn rms iehn tsigaBmuoetcetias rn"""
    
        # 这个函数接受一个字符串参数:要破译的已被加密的密文消息。
        hackedMessage = hackTransposition(myMessage)
    
        if hackedMessage == None:
            print('Failed to hack encryption.')
        else:
            # 可以破译:
            print('Copying hacked message to clipboard:')
            # 输出解密之后的文字
            print(hackedMessage)
            # 复制到剪贴板
            pyperclip.copy(hackedMessage)
    
    # 这个函数接受一个字符串参数:要破译的已被加密的密文消息。
    def hackTransposition(message):
        print('Hacking...')
    
        # Python programs can be stopped at any time by pressing Ctrl-C (on
        # Windows) or Ctrl-D (on Mac and Linux)
        print('(Press Ctrl-C or Ctrl-D to quit at any time.)')
    
        # brute-force by looping through every possible key
        # 换位加密法的密钥范围是1和消息长度之间的整数
        # for循环会使用每个密钥运行这个函数的破译部分。
        for key in range(1, len(message)):
            print('Trying key #%s...' % (key))
    
            # 从当前测试的密钥获得解密之后的输岀,并把它保存到decryptedText变量里。
            decryptedText = transpositionDecrypt.decryptMessage(key, message)
    
            # decryptedText里的字符串会传给写好的detectEnglish.isEnglish()函数。
            if detectEnglish.isEnglish(decryptedText):
                # Check with user to see if the decrypted key has been found.
                print()
                print('Possible encryption hack:')
                print('Key %s: %s' % (key, decryptedText[:100]))
                print()
                print('Enter D for done, or just press Enter to continue hacking:')
                # 程序在第41行执行时暂停,等候用户输入D并按Enter键或者什么都不输入直接按Enter键。
                # 这个输入会以字符串的形式保存在response里。
                response = input('> ')
    
                if response.strip().upper().startswith('D'):
                    return decryptedText
    
        return None
    
    if __name__ == '__main__':
        main()
    
  38. strips)字符串方法返回字符串首尾空白去掉之后的版本。

    >>> '      Hello   '.strip()
    'Hello'
    >>> 'Hello         '.strip()
    'Hello'
    >>> 'Hello      XX   '.strip()
    'Hello      XX'
    
    # 们也可以给stripO方法传递一个字符串参数,告诉它应该从字符串的首尾移除哪些字
    # 符而不是移除空白。空白字符(whitespace character)是指空格字符(space character)、
    # 制表符以及换行符。
    # strip('abc')里的abc匹配时无序,abc是或者(or)关系
    
    >>> 'Helloxxxxxxx'.strip('x')
    'Hello'
    >>> 'aaaaaHELLOaaa'.strip('a')
    'HELLO'
    >>> 'ababaHELLoab'.strip('ab')
    'HELLo'
    >>> 'ababaHELLoab'.strip('a')
    'babaHELLoab'
    >>> 'abccbacbbbccaXYZabccbaXYZcccaaabbb'.strip('abc')
    'XYZabccbaXYZ'
    
  39. 取模运算

    # 可以把取模运算符看做是“余数”运算符。21 + 5 = 4余1。于是,21%5 = 1。这适用
    # 于正数,但不适用于负数。-21+5 =-4余-1。但取模运算符的结果不会是负数。如果把余数
    # -1看做和5-1—样,将会得到4,这正是-21 % 5的计算结果:
    
    >>> 21 % 5
    1
    >>> -21 % 5
    4
    
  40. GCD:最大公约数(又名最大公因数)

    # 欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。
    # 基本算法:设a=qb+r,其中a,b,q,r都是整数,则gcd(a,b)=gcd(b,r),即gcd(a,b)=gcd(b,a%b)。
    
        def gcd(a, b):
            while a != 0:
                a, b = b % a, a
            return b
    
    # 如果gcd (a,b) =1,那么数字a和b互质。
    
  41. 多重赋值

    >>> spam, eggs = 42, 'Hello'
    >>> spam
    42
    >>> eggs
    'Hello'
    >>> a,b,c,d = ['Alice','Bob','Carol','David']
    >>> a
    'Alice'
    >>> b
    'Bob'
    >>> c
    'Carol'
    >>> d
    'David'
    

    通过多重赋值交换值

    >>> spam = 'hello'
    >>> eggs = 'goodbye'
    >>> spam,eggs = eggs,spam
    >>> spam
    'goodbye'
    >>> eggs
    'hello'
    

    在欧几里得算法的实现里使用这种交换技术。

  42. 乘数加密法

    # 在凯撒加密法里,加密和解密符号涉及把它们转换成数字,加上或减去密钥,然后把新的数字转换回符号。
    # 如果我们在加密时不是加上密钥而是乘以呢?这会导致“回调”问题,不过取模运算符
    # 可以解决这个问题。比如说,我们使用只包含大写字母的符号集和密钥7
    # 要找出符号F使用密钥7会加密成什么,将它的数字(5)乘以7,再对26取模(处理26
    # 符号集的‘‘回调”)。然后使用那个数字的符号。(5x7)取模26 = 9, 9是符号I的数字。
    # 因此,F在乘数加密法里使用密钥7会加密成J。
    
  43. 乘数加密法 + 凯撒加密法 = 仿射加密法

    # 乘数加密法的一个缺点是字母A总是映射到字母A。这是因为A的数字是0,而0乘以
    # 任何东西总是得到0。我们可以添加第二个密钥,在乘数加密法的乘积和取模完成之后执行
    # 凯撒加密法,这样就能修复这个问题了。
    # 这种加密法叫做仿射加密法。仿射加密法有两个密钥。“密钥A”是字母的数字将要乘
    # 以的整数。在这个数字对26取模之后,“密钥B”将会加到这个数字。这个和也会对26取模,
    # 就像原来的凯撒加密法一样。
    # 这意味着仿射加密法可能的密钥是乘数加密法的26倍。它也能确保字母A并不总是加
    # 密到字母A。
    
    # 乘数加密法的密钥和仿射加密法的密钥A存在两个问题。密钥A不能使用任何数字。
    # 比如说,如果你选择密钥8,
    # 这个映射根本不能使用!字母C和P都加密成了 Q。当我们在密文里碰到Q时,我们
    # 如何得知是哪个加密的?同样的问题也存在于加密A和N,F和S等。
    # 因此,一些密钥可以在仿射加密法里使用,另一些则不行。判断哪些密钥数字可以使用
    # 的秘密在这:
    在仿射加密法里,密钥A数字和符号集的大小必须互质。也就是说,gcd (密钥,符号集的大小)==1。
    
    密钥7可以用做仿射加密法密钥,因为gcd(7, 26)返回1。
    密钥8不能使用,因为gcd (8, 26)是2。
    
  44. 模逆

    #两个数字a和m的模逆(modular inverse) i满足(a * i) % m = 10
    #
    #在凯撒加密法里,我们使用加法来加密,使用减法来解密。在仿射加密法里,我们使用
    #乘法来加密。你可能以为我们需要使用除法才能解密仿射加密法。但如果你自己试一下,你
    #很快就会发现这是不行的。要解密仿射加密法,我们需要乘以这个密钥的模逆。
    #仿射加密法的加密密钥和解密密钥是两个不同的数字。加密密钥可以选择任何数字,只
    #要它和26 (也就是符号集的大小)互质就行了。如果我们选择密钥7作为仿射加密法的加密
    #密钥,那么解密密钥将是7取模26的模逆,
    #因此,仿射加密法的解密密钥是15。要解密密文字母,我们将字母的数字乘以15,然后
    #取模26。这将是原来的明文字母的数字。
    #
    #模逆和最大公约数一样都有算法可以找出。欧几里得的扩展算法(Extended Algorithm)
    #可以用来找出一个数字的模逆:
    
        def findModInverse(a,m):
            if gcd(a,m) != 1:
                return None     # 如果 a 和 m 不互质,则不存在模逆
            u1,u2,u3 = 1,0,a
            v1,v2,v3 = 0,1,m
            whilev3 != 0:
                q = u3 // v3    # // 是整数除法运算符
                v1,v2,v3,u1,u2,u3 = (u1 - q * v1),(u2 - 1 * v2),(u3 - q * v3),v1,v2,v3
            return u1 % m
    
  45. // 整数除法运算符

    使用//整数除法运算符的表达式总是计算得到一个整数,而不是一个浮点数。

    >>> 41 // 7
    5
    >>> 41 / 7
    5.857142857142857
    >>> 10 // 5
    2
    >>> 10 / 5
    2.0
    
  46. Cryptomath 模块

    # Cryptomath Module
    # http://inventwithpython.com/hacking (BSD Licensed)
    '''
    >>> import cryptomath
    >>> cryptomath.gcd(24,32)
    8
    >>> cryptomath.gcd(37,41)
    1
    >>> cryptomath.findModInverse(7,26)
    15
    >>> cryptomath.findModInverse(8953851,26)
    17
    
    '''
    def gcd(a, b):
        # Return the GCD of a and b using Euclid's Algorithm
        while a != 0:
            a, b = b % a, a
        return b
    
    def findModInverse(a, m):
        # Returns the modular inverse of a % m, which is
        # the number x such that a*x % m = 1
    
        if gcd(a, m) != 1:
            return None # no mod inverse if a & m aren't relatively prime
    
        # Calculate using the Extended Euclidean Algorithm:
        u1, u2, u3 = 1, 0, a
        v1, v2, v3 = 0, 1, m
        while v3 != 0:
            q = u3 // v3 # // is the integer division operator
            v1, v2, v3, u1, u2, u3 = (u1 - q * v1), (u2 - q * v2), (u3 - q * v3), v1, v2, v3
        return u1 % m
    
  47. 仿射加密法加密程序(affineCipher.py)

    # 仿射加密法需要两个密钥:一个用于乘数加密法相乘,另一个用于凯撒加密法相加。
    # 对于仿射加密法程序,我们将使用单个整数密钥。我们将通过某种简单的数学运算把这
    # 个密钥分成两个密钥,我们把它们称为密钥A和密钥B。
    
    # Affine Cipher
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    # sys模块导入exit()函数
    import sys, pyperclip, cryptomath, random
    # 保存在SYMBOLS变量里的字符串是符号集。符号集包含可以加密的所
    

    # 有字符。需要加密的消息里的任何字符如果不在SYMBOLS里,就会不做加密直接添加到密文。
    SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
    abcdefghijklmnopqrstuvwxyz{|}~”“” # note the space at the front

    def main():
        # 要加密的明文
        myMessage = """"A computer would deserve to be called intelligent if it could deceive a human into believing that it was human." -Alan Turing"""
        # 如果要使用随机密钥,把 myKey 更换为 myKey = getRandomKey()
        myKey = 2023
        myMode = 'encrypt' # set to 'encrypt' or 'decrypt'
    
        if myMode == 'encrypt':
            translated = encryptMessage(myKey, myMessage)
        elif myMode == 'decrypt':
            translated = decryptMessage(myKey, myMessage)
        print('Key: %s' % (myKey))
        print('%sed text:' % (myMode.title()))
        print(translated)
        pyperclip.copy(translated)
        print('Full %sed text copied to clipboard.' % (myMode))
    
    # 要把密钥A和密钥B组合起来,变回一个密钥,将密钥A乘以符号集的大小,再加上
    # 密钥B: (21 *95) +28,计算得到2023
    def getKeyParts(key):
        keyA = key // len(SYMBOLS)
        keyB = key % len(SYMBOLS)
        # 元组值类似于列表,它是一个可以保存其他值的值,可以通过索引或者分片来访问。但
        # 是,元组里的值不能修改。元组值没有appendG方法。元组使用小括号而不是中括号来写。
        # 第27行返回的值是一个元组。
        return (keyA, keyB)
    
    #使用仿射加密法加密会将一个字符在SYMBOLS里的索引乘以密钥A,再加上密钥B。
    #但是,如果keyA是1,加密之后的文字将会非常弱,因为将这个索引乘以1并未改变它。
    #类似地,如果keyB是0,加密之后的文字也会非常弱,因为将这个索引加上0并未改变它。
    #如果keyA是1,且keyB是0,那么“加密之后的”消息就会和原来的消息完全一样。也就
    #是根本没有加密!
    def checkKeys(keyA, keyB, mode):
        if keyA == 1 and mode == 'encrypt':
            sys.exit('The affine cipher becomes incredibly weak when key A is set to 1. Choose a different key.')
        if keyB == 0 and mode == 'encrypt':
            sys.exit('The affine cipher becomes incredibly weak when key B is set to 0. Choose a different key.')
        if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1:
            sys.exit('Key A must be greater than 0 and Key B must be between 0 and %s.' % (len(SYMBOLS) - 1))
        # 密钥A必须与符号集的大小互质。这意味着keyA和len (SYMBOLS)的最大公
        # 约数必须等于1。
        if cryptomath.gcd(keyA, len(SYMBOLS)) != 1:
            sys.exit('Key A (%s) and the symbol set size (%s) are not relatively prime. Choose a different key.' % (keyA, len(SYMBOLS)))
            '''
            sys.exit()函数有一个
            可选的字符串参数,会在终止这个程序之前输出到屏幕。这可以用于在这个程序退出之前在
            屏幕上显示错误消息。
            '''
    
    def encryptMessage(key, message):
        keyA, keyB = getKeyParts(key)
        checkKeys(keyA, keyB, 'encrypt')
        ciphertext = ''
        for symbol in message:
            if symbol in SYMBOLS:
                # encrypt this symbol
                # 检查是否在符号集里
                symIndex = SYMBOLS.find(symbol)
                # 对字符加密并取模
                ciphertext += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)]
            else:
                # 不在符号集里,直接添加在末尾
                ciphertext += symbol # just append this symbol unencrypted
        return ciphertext
    
    
    def decryptMessage(key, message):
        keyA, keyB = getKeyParts(key)
        checkKeys(keyA, keyB, 'decrypt')
        plaintext = ''
        # 解密过程不是乘以密钥A,而是乘以密钥A的模逆
        modInverseOfKeyA = cryptomath.findModInverse(keyA, len(SYMBOLS))
    
        # 在encryptMessage()函数里,符号索引乘以密钥A,然后加上密钥B。在decryptMessage()
        # 里的第65行,符号索引首先减去密钥B,然后乘以模逆。接着,这个数字会对符号集的大小
        # len (SYMBOLS)取模
        for symbol in message:
            if symbol in SYMBOLS:
                # decrypt this symbol
                symIndex = SYMBOLS.find(symbol)
                plaintext += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)]
            else:
                plaintext += symbol # just append this symbol undecrypted
        return plaintext
    
    # 生成随机密钥
    def getRandomKey():
        while True:
            # 码把keyA和keyB的随机数字限制在2和符号集的大小之间。这样
            # 就不会出现密钥A或密钥B等于无效的0或1的情况了。
            keyA = random.randint(2, len(SYMBOLS))
            keyB = random.randint(2, len(SYMBOLS))
            if cryptomath.gcd(keyA, len(SYMBOLS)) == 1:
                # 就将keyA乘以符号集的大小再加上keyB,把这两个密钥合成一个密钥
                return keyA * len(SYMBOLS) + keyB
    
    
    # If affineCipher.py is run (instead of imported as a module) call
    # the main() function.
    if __name__ == '__main__':
        main()
    
    '''
    Key: 2023
    Encrypted text:
    fX<*h>}(rTH<Rh()?<?T]TH=T<rh<tT<*_))T?<ISrT))I~TSr<Ii<Ir<*h()?<?T*TI=T<_<4(>_S<ISrh<tT)IT=IS~<r4_r<Ir<R_]<4(>_SEf<0X)_S<k(HIS~
    '''
    
  48. 仿射加密法解密程序(affineDecrypt.py)

    # Affine Decrypt
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import sys, pyperclip, cryptomath, random
    SYMBOLS = """ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~""" # note the space at the front
    
    
    def main():
        # 要加密的明文
        myMessage = """"fX<*h>}(rTH<Rh()?<?T]TH=T<rh<tT<*_))T?<ISrT))I~TSr<Ii<Ir<*h()?<?T*TI=T<_<4(>_S<ISrh<tT)IT=IS~<r4_r<Ir<R_]<4(>_SEf<0X)_S<k(HIS~" -Alan Turing"""
        myKey = 2023
        myMode = 'decrypt' # set to 'encrypt' or 'decrypt'
    
        if myMode == 'encrypt':
            translated = encryptMessage(myKey, myMessage)
        elif myMode == 'decrypt':
            translated = decryptMessage(myKey, myMessage)
        print('Key: %s' % (myKey))
        print('%sed text:' % (myMode.title()))
        print(translated)
        pyperclip.copy(translated)
        print('Full %sed text copied to clipboard.' % (myMode))
    
    
    def getKeyParts(key):
        keyA = key // len(SYMBOLS)
        keyB = key % len(SYMBOLS)
        return (keyA, keyB)
    
    
    def checkKeys(keyA, keyB, mode):
        if keyA == 1 and mode == 'encrypt':
            sys.exit('The affine cipher becomes incredibly weak when key A is set to 1. Choose a different key.')
        if keyB == 0 and mode == 'encrypt':
            sys.exit('The affine cipher becomes incredibly weak when key B is set to 0. Choose a different key.')
        if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1:
            sys.exit('Key A must be greater than 0 and Key B must be between 0 and %s.' % (len(SYMBOLS) - 1))
        if cryptomath.gcd(keyA, len(SYMBOLS)) != 1:
            sys.exit('Key A (%s) and the symbol set size (%s) are not relatively prime. Choose a different key.' % (keyA, len(SYMBOLS)))
    
    
    def encryptMessage(key, message):
        keyA, keyB = getKeyParts(key)
        checkKeys(keyA, keyB, 'encrypt')
        ciphertext = ''
        for symbol in message:
            if symbol in SYMBOLS:
                # encrypt this symbol
                symIndex = SYMBOLS.find(symbol)
                ciphertext += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)]
            else:
                ciphertext += symbol # just append this symbol unencrypted
        return ciphertext
    
    
    def decryptMessage(key, message):
        keyA, keyB = getKeyParts(key)
        checkKeys(keyA, keyB, 'decrypt')
        plaintext = ''
        modInverseOfKeyA = cryptomath.findModInverse(keyA, len(SYMBOLS))
    
        for symbol in message:
            if symbol in SYMBOLS:
                # decrypt this symbol
                symIndex = SYMBOLS.find(symbol)
                plaintext += SYMBOLS[(symIndex - keyB) * modInverseOfKeyA % len(SYMBOLS)]
            else:
                plaintext += symbol # just append this symbol undecrypted
        return plaintext
    
    
    def getRandomKey():
        while True:
            keyA = random.randint(2, len(SYMBOLS))
            keyB = random.randint(2, len(SYMBOLS))
            if cryptomath.gcd(keyA, len(SYMBOLS)) == 1:
                return keyA * len(SYMBOLS) + keyB
    
    
    # If affineCipher.py is run (instead of imported as a module) call
    # the main() function.
    if __name__ == '__main__':
        main()
    
    '''
    Key: 2023
    Decrypted text:
    L"A computer would deserve to be called intelligent if it could deceive a human into believing that it was human." -Alan TuringL^HRKO9^eYtf9x
    '''
    
  49. 破译仿射加密法(affineHacker.py)

    # Affine Cipher Hacker
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import pyperclip, affineCipher, detectEnglish, cryptomath
    
    # 把SILENT_MODE变量设为True,
    # 程序会进入静默模式,不再输岀这些消息。这会极大地加速程序的运行。
    SILENT_MODE = False
    
    def main():
        # You might want to copy & paste this text from the source code at
        # http://invpy.com/affineHacker.py
        myMessage = """U&'<3dJ^Gjx'-3^MS'Sj0jxuj'G3'%j'<mMMjS'g{GjMMg9j{G'g"'gG'<3^MS'Sj<jguj'm'P^dm{'g{G3'%jMgjug{9'GPmG'gG'-m0'P^dm{LU'5&Mm{'_^xg{9"""
    
        hackedMessage = hackAffine(myMessage)
    
        if hackedMessage != None:
            # The plaintext is displayed on the screen. For the convenience of
            # the user, we copy the text of the code to the clipboard.
            print('Copying hacked message to clipboard:')
            print(hackedMessage)
            pyperclip.copy(hackedMessage)
        else:
            print('Failed to hack encryption.')
    
    
    def hackAffine(message):
        print('Hacking...')
    
        # Python programs can be stopped at any time by pressing Ctrl-C (on
        # Windows) or Ctrl-D (on Mac and Linux)
        print('(Press Ctrl-C or Ctrl-D to quit at any time.)')
    
        # brute-force by looping through every possible key
        # 用来暴力破译密文的密钥的整数范围是从0到符号集大小的二次方
        # 我们之所以这样相乘是因为密钥A有len (affineCipher.SYMBOLS)个可能的整数,密
        # 钥B也有len (affineCipher.SYMBOLS)个可能的整数。要得到可能密钥的整个范围,我们将这些值相乘
        for key in range(len(affineCipher.SYMBOLS) ** 2):
            # 这个函数调用的返回值是一个元组,包含了两个整数(一个是密钥A,另一个是密钥B)
            # 因为hackAffine()只需要密钥A,所以函数调用后面的[0]会从返回值中获取返回元组的第一个整数。
            # 比如说,元组(42, 22) [0],然后得到42。
            keyA = affineCipher.getKeyParts(key)[0]
            if cryptomath.gcd(keyA, len(affineCipher.SYMBOLS)) != 1:
                continue
    
            #如果密钥A和符号集的大小不是互质,那么第35行的条件将会是Tme,第36行的
            #continue语句将会执行。这会导致程序的执行跳回循环的开始,进人下一次迭代。如果这个
            #密钥无效,程序会跳过第38行的deoyptMessageO调用,继续下一个密钥。
            decryptedText = affineCipher.decryptMessage(key, message)
            if not SILENT_MODE:
                print('Tried Key %s... (%s)' % (key, decryptedText[:40]))
    
            if detectEnglish.isEnglish(decryptedText):
                # Check with the user if the decrypted key has been found.
                print()
                print('Possible encryption hack:')
                print('Key: %s' % (key))
                print('Decrypted message: ' + decryptedText[:200])
                print()
                print('Enter D for done, or just press Enter to continue hacking:')
                response = input('> ')
    
                if response.strip().upper().startswith('D'):
                    return decryptedText
        return None
    
    
    # If affineHacker.py is run (instead of imported as a module) call
    # the main() function.
    #我们希望affineHacker.py文件既可以自己运行,又可以作为模块导入。
    #如果affineHacker.py作为程序运行,那么特殊的__name__变量将会设为字符串main '
    #(而不是’affineHacker’)。在这种情况下,我们希望调用main()函数。
    if __name__ == '__main__':
        main()
    
  50. 有很酷的“打字机”效果,每次一个字符慢慢输出文字(typerwriter.py)

    import sys, random, time
    def print(message, end='\n'):
        for c in message:
            sys.stdout.write(c)
            sys.stdout.flush()
            time.sleep(random.randint(40, 100)/ 1000)
        sys.stdout.write(end)
    
  51. **指数运算符

    >>> 2 ** 6
    64
    >>> 4**2
    16
    >>> 2**4
    16
    >>> 2 **4
    16
    
  52. 当ccmtimie语句执行时,程序的执行立刻跳到循环的开始,进入下一次迭代。

    >>> for i in range(3):
    ...     print(i)
    ...     print('Hello!')
    ...
    0
    Hello!
    1
    Hello!
    2
    Hello!
    
    >>> for i in range(3):
    ...     print(i)
    ...     continue
    ...     print('Hello!')
    ...
    0
    1
    2
    
  53. 简单替代加密法(simpleSubCipher.py)

    '''
    要实现简单替代加密法,随机选择字母来加密字母表的每个字母。每个字母只用一次。
    密钥最终会是字母表的26个字母按照随机顺序排列的字符串。密钥共有403 291 461 126 605635 584 000 000种可能的顺序。
    (要了解这个数字是怎么计算出来的,可以参见http://invpy.com/factorial)。
    
    让我们先用纸笔实现简单替代加密法。比如说,我们要用密钥VJZBGNFEPLITMXDW
    KQUCRYAHSO加密消息“Attack at dawn.”。首先写出字母表的字母,然后在它下面写下密钥。
    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
    VJZBGNFEPLITMXDWKQUCRYAHSO
    要加密消息,从上面那行的明文找到字母,用下面那行的字母替代它。A加密成V,T
    加密成C,C加密成Z,如此类推。因此,消息“Attack at dawn.”加密成“Vccvzi vc bvax.”。
    要解密,从下面那行的密文找到字母,用上面那行的字母替代它。V解密成A,C解密
    成T,Z解密成C,如此类推
    
    简单替代加密法的优势是拥有大量可能的密钥。劣势是密钥有26个字符的长度,很
    难记住。如果你把密钥写下来,需要确保这个密钥不会被其他人看到!
    
    如果字母在明文里是小写的,它在密文里也是小写的;如果字母在明文里是大写
    的,它在密文里也是大写的。简单替代加密法并不加密空格或标点符号
    
    简单替代加密法的密钥字符串值只有在它包含了符号集里的每个字符且没有重复或缺失
    的情况下才有效。我们可以按照字母表顺序对一个字符串值和符号集分别进行排序,并检查
    它们是否相等,这样就能判断这个字符串值是不是一个有效的密钥了
    
    
    '''
    # Simple Substitution Cipher
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import pyperclip, sys, random
    
    
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    def main():
        myMessage = 'If a man is offered a fact which goes against his instincts, he will scrutinize it closely, and unless the evidence is overwhelming, he will refuse to believe it. If, on the other hand, he is offered something which affords a reason for acting in accordance to his instincts, he will accept it even on the slightest evidence. The origin of myths is explained in this way. -Bertrand Russell'
        # 密钥是26个字母打乱顺序的结果
        myKey = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
        myMode = 'encrypt' # set to 'encrypt' or 'decrypt'
    
        checkValidKey(myKey)
    
        if myMode == 'encrypt':
            translated = encryptMessage(myKey, myMessage)
        elif myMode == 'decrypt':
            translated = decryptMessage(myKey, myMessage)
        print('Using key %s' % (myKey))
        print('The %sed message is:' % (myMode))
        print(translated)
        pyperclip.copy(translated)
        print()
        print('This message has been copied to the clipboard.')
    
    
    # checkValidKey()函数(稍后解释)确保密钥适用于加密和解密
    # 函数, 如果不适用就显示错误消息并退出程序
    def checkValidKey(key):
        # 把LETTERS 常量(也就是字符串 VVBCDEFGHIJKLMNOPQRSTUVWXYZ')传给 list()
        # 它返回列表[’A',’B,’C’,’D’,E,,F,,’G’,’H’,I,’J,,’K’,,L’,’M’,’N’,’O,’P’,'Q',’R’,,S,,T,,U,,V,’W’,X,,Y,Z,]
        keyList = list(key)
        lettersList = list(LETTERS)
        # 就像append()列表方法,SOrt()列表方法就地修改列表,没有返回值
        keyList.sort()
        lettersList.sort()
        if keyList != lettersList:
            sys.exit('There is an error in the key or symbol set.')
    
    # 包装器函数只是包装了其他函数的代码,并返回被包装函数的返回值
    # 在这里,encryptMessage()和 decryptMessage()(包装器函数)调用 translateMessage()(被
    # 包装函数),并返回translateMessage()的返回值。
    def encryptMessage(key, message):
        return translateMessage(key, message, 'encrypt')
    
    
    def decryptMessage(key, message):
        return translateMessage(key, message, 'decrypt')
    
    
    def translateMessage(key, message, mode):
        translated = ''
        charsA = LETTERS
        charsB = key
        # translateMessage()里的代码总是把charsA (上面那行)里的字符换成charsB (中
        # 间那行)里相同索引上的字符。因此,当第52行交换charsA和charsB的值时,
        # translateMessage()里的代码将执行解密流程而不是加密流程
        if mode == 'decrypt':
            # For decrypting, we can use the same code as encrypting. We
            # just need to swap where the key and LETTERS strings are used.
            charsA, charsB = charsB, charsA
    
        # loop through each symbol in the message
        for symbol in message:
            # if语句确保symbol.upper()在charsA里
            if symbol.upper() in charsA:
                # encrypt/decrypt the symbol
                # 如果这个符号的大写形式在charsA里(记住,key和LETTERS里面都只有大写
                # 字符),我们将在charsA里查找symbol的大写形式的索引
                symIndex = charsA.find(symbol.upper())
                # 如果字符是大写就将对应的密文换成大写加在尾部
                # 如果字符是小写就将对应的密文换成小写加在尾部
                if symbol.isupper():
                    translated += charsB[symIndex].upper()
                else:
                    translated += charsB[symIndex].lower()
            else:
                # symbol is not in LETTERS, just add it
                # 如果symbol是数字或者标点符号,如5或?,则对这个字符串无影响
                translated += symbol
    
        return translated
    
    # 输入一个字符串,让它包含字母表的每个字符且每个字母只包含一次,这是比较困难的。
    # 为了帮助用户,getRandomKeyG函数会返回一个有效的密钥以供使用
    def getRandomKey():
        key = list(LETTERS)
        random.shuffle(key)
        return ''.join(key)
    
    # 如果simpleSubCipher.py是作为程序运行而不是作为模块导入其他程序,程序底部的
    # 第76和77行将调用main()。
    if __name__ == '__main__':
        main()
    
    '''
    加密空格和标点
    LETTERS = r""" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'abcdefghijklmnopqrstuvwxyz{j}~"""
    使用的密钥也必须包含所有字符
    myKey = r"""^_'abcdefgxyz{| !"#$%&6789:;<=>?@ABCZ[\45tuvwEFGHrs'()*+Dhijklmnopq123RSTUVWXY,-./0IJKLMNOPQ]}~"""
    使用三引号的原始字符串(raw string)不用转义引号和\斜杠字符,使输入更容易。
    第58到62行区别对待大小写字母的代码可以换成这两行:
    symIndex = charsA.find(symbol)
    translated += charsB[symIndex]
    '''
    
  54. 简单替代加密法解密程序(simpleSubDecrypt.py)

    # Simple Substitution Cipher
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于 两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import pyperclip, sys, random
    
    
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    
    def main():
        myMessage = 'Sy l nlx sr pyyacao l ylwj eiswi upar lulsxrj isr sxrjsxwjr, ia esmm rwctjsxsza sj wmpramh, lxo txmarr jia aqsoaxwa sr pqaceiamnsxu, ia esmm caytra jp famsaqa sj. Sy, px jia pjiac ilxo, ia sr pyyacao rpnajisxu eiswi lyypcor l calrpx ypc lwjsxu sx lwwpcolxwa jp isr sxrjsxwjr, ia esmm lwwabj sj aqax px jia rmsuijarj aqsoaxwa. Jia pcsusx py nhjir sr agbmlsxao sx jisr elh. -Facjclxo Ctrramm'
        myKey = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
        # myMode = 'encrypt' # set to 'encrypt' or 'decrypt'
        myMode = 'decrypt'
    
        checkValidKey(myKey)
    
        if myMode == 'encrypt':
            translated = encryptMessage(myKey, myMessage)
        elif myMode == 'decrypt':
            translated = decryptMessage(myKey, myMessage)
        print('Using key %s' % (myKey))
        print('The %sed message is:' % (myMode))
        print(translated)
        pyperclip.copy(translated)
        print()
        print('This message has been copied to the clipboard.')
    
    
    def checkValidKey(key):
        keyList = list(key)
        lettersList = list(LETTERS)
        keyList.sort()
        lettersList.sort()
        if keyList != lettersList:
            sys.exit('There is an error in the key or symbol set.')
    
    
    def encryptMessage(key, message):
        return translateMessage(key, message, 'encrypt')
    
    
    def decryptMessage(key, message):
        return translateMessage(key, message, 'decrypt')
    
    
    def translateMessage(key, message, mode):
        translated = ''
        charsA = LETTERS
        charsB = key
        if mode == 'decrypt':
            # For decrypting, we can use the same code as encrypting. We
            # just need to swap where the key and LETTERS strings are used.
            charsA, charsB = charsB, charsA
    
        # loop through each symbol in the message
        for symbol in message:
            if symbol.upper() in charsA:
                # encrypt/decrypt the symbol
                symIndex = charsA.find(symbol.upper())
                if symbol.isupper():
                    translated += charsB[symIndex].upper()
                else:
                    translated += charsB[symIndex].lower()
            else:
                # symbol is not in LETTERS, just add it
                translated += symbol
    
        return translated
    
    
    def getRandomKey():
        key = list(LETTERS)
        random.shuffle(key)
        return ''.join(key)
    
    if __name__ == '__main__':
        main()
    
  55. isupper()方法会返回True

    '''
    如果:
        1. 这个字符串至少有一个大写字母。
        2. 这个字符串里没有任何小写字母。
        islowerG方法会返回True,如果:
        1. 这个字符串至少有一个小写字母。
        2. 这个字符串里没有任何大写字母。
    '''
    
    >>> 'HELLO'.isupper()
    True
    >>> 'HELLO WORLD 123'.isupper()
    True
    >>> 'HELLO'.isupper()
    True
    >>> 'hELLO'.isupper()
    False
    >>> 'hELLO'.lower()
    'hello'
    >>> 'hELLO'.islower()
    False
    >>> 'hello'.islower()
    True
    >>> '123'.islower()
    False
    >>> '123'.isupper()
    False
    >>> ''.isupper()
    False
    >>> ''.islower()
    False
    >>> ' '.islower()
    False
    >>> '5'.lower()
    '5'
    >>> '?'.lower()
    '?'
    
  56. 计算单词模式(makeWordPatterns.py)

    # Makes the wordPatterns.py File
    # http://inventwithpython.com/hacking (BSD Licensed)
    
    # Creates wordPatterns.py based on the words in our dictionary
    # text file, dictionary.txt. (Download this file from
    # http://invpy.com/dictionary.txt)
    
    
    import pprint
    
    
    def getWordPattern(word):
        # Returns a string of the pattern form of the given word.
        # e.g. '0.1.2.3.4.1.2.3.5.6' for 'DUSTBUSTER'
        word = word.upper()
        # nNum保存新的字母找到时要用的下一个数字。
        nNum = 0
        # letterNums保存一个字典,它的键是单个字母的字符串,它的值是那个字母的整数
        # 数字。随着我们在单词里找到新字母,这个字母和它的数字会保存在letterNums里。
        letterNums = {}
        # wordPattern将是从这个函数返回的字符串
        wordPattern = []
    
        # 通过检查letter是不是letterNums字典的键来判断letter之前是否出现过
        for letter in word:
            # 如果我们没有见过这个字母,第21行会把这个字母作为键,把nNum的字符串形式
            # 作为这个键的值添加到letterNums字典
            if letter not in letterNums:
                letterNums[letter] = str(nNum)
                nNum += 1
            # letterNums[letter] 的结果是键对应的值
            # 的lettefNumstletter】的计算结果是letter变量里的字母所用的整数,这会追加
            # 到wordPattern末尾
            wordPattern.append(letterNums[letter])
        return '.'.join(wordPattern)
    
    
    def main():
        # allPatterns里保存的值是我们将要写到wordPatterns.py文件的东西。它是一个字典,
        # 它的键是单词模式的字符串(如0.1.2.3.0.4.5或’0.1.1.2’),键的值是匹配那个模式的英文单词
        # 的字符串列表。
        allPatterns = {}
    
        fo = open('dictionary.txt')
        # 根据\n换行符进行分割,并返回一个字符串列表
        # wordList变量里的每个字符串将是一个英文单词。
        wordList = fo.read().split('\n')
        fo.close()
    
        for word in wordList:
            # Get the pattern for each string in wordList.
            # 例如0.0.1.1.1
            pattern = getWordPattern(word)
            # 如果0.0.1.1.1不在allPattern里,将添加键和值
            if pattern not in allPatterns:
                # allPatterns[pattern]是值,创建并添加键值
                # 举例:
                    '''
                    >>> all = {}
                    >>> all['11'] = ['one']
                    >>> all['22'] = ['two']
                    >>> all
                    {'11': ['one'], '22': ['two']}
                    '''
                allPatterns[pattern] = [word]
            # 如果pattern已在allPatterns里,我们就不必创建这个列表了。只会把这个单词添加到已经存在的列表值。
            else:
                # allPatterns[pattern]是值,给值追加
                allPatterns[pattern].append(word)
    
        # This is code that writes code. The wordPatterns.py file contains
        # one very, very large assignment statement.
        fo = open('wordPatterns.py', 'w')
        fo.write('allPatterns = ')
        fo.write(pprint.pformat(allPatterns))
        fo.close()
    
    
    if __name__ == '__main__':
        main()
    
    '''
    运行结果:
        运行这个程序不会输出任何东西到屏幕。相反,它会静静地创建一个wordPatterns.py
        文件,放在和makeWordPatterns.py相同的文件夹里
            allPatterns = {'0.0.1': ['EEL'],
                '0.0.1.2': ['EELS', '00ZE'],
                ?0.0.1.2.0': ['EERIE'],
                '0.0.1.2.3': ['AARON', 'LLOYD', 1 OOZED 1],
                ".the rest has been cut for brevity...
        当我们把allPatterns的值写到wordPatterns.py文件时,我们将会使用pprint模块防止它全部挤在一行上。
    
        makeWordPatterns.py 程序创建 wordPatterns.py。我们的 Python 程序创建一个 Python
        程序!完整的wordPatterns.py程序只是allPatterns变量的一个(很大的)赋值语句。虽然
        这个赋值语句在文件里跨越多行,却被看做“一行代码”,因为Python知道如果一行以逗号
        结束,它其实还在字典值中间,所以会忽略下一行的缩进,把它看做上一行的一部分。(这是
        Python重要的缩进规则的罕见例外。)
    
        allPatterns变量包含一个字典值,其中的键是从字典文件里的英文单词创建出来的所有
        单词模式。键的值是匹配那个模式的英文单词的字符串列表。当wordPatterns.py作为模块
        导入时,我们的程序将能查找匹配任何给定单词模式的所有英文单词。
    
    '''
    
  57. 计算单词模式

    英语里的什么单词匹配这种模式? “Puppy”是一个匹配这种模式的单词。它有5个字
    母的长度(P、U、P、P、Y),使用3个不同的字母(P、U、Y),匹配相同模式(P对应第
    1、3和4个字母,U对应第2个字母,Y对应第5个字母)。“Mommy”、“Bobby”、“lulls”、
    “nanny”和“Lilly”都匹配这种模式。(“Lilly”是一个人名,不要和花名“Lily”搞混了。
    因为“Lilly”可以出现在英文消息里,所以它可能是一个匹配这种模式的单词。)如果我们
    有很多时间,我们可以仔细查阅整本字典,找出所有匹配这种模式的单词。更好的做法是,
    我们可以让计算机帮我们查阅字典
    
    本书的单词模式是一组数字,数字之间用句点分隔,它告诉我们密文或明文单词的字母模式。
    给密词(cipherword)创建单词模式很容易:第1个字母获得数字0,后面每个不同字
    母第1次出现都会获得下一个数字。比如说:
        “cat”的单词模式是0.1.2。
        “catty”的单词模式是0.1.2.2.3。
        “roofer” 的单词模式是0.1.1.2.3.0。
        “blimp”的单词模式是0.1.2.3.4。
        “classification” 的单词模式是0.1.2.3.3.4.5.4.0.2.6.4.7.8。
    明文单词和它的密词总是有着相同的单词模式,不管使用哪个简单替代密钥进行解密。
    
    密文单词    HGHHU
    单词模式    01002
    候选单词    puppy mommy bobby lulls nanny lilly
    
    密字          H G U
    潜在解密字母  P u y
                    m 0 y
                    b 0 y
                    l U s
                    n a y
                    1 i y
    
    我们可以从这个表格创建一个密字映射(cipherletter mapping):
        密文字母H的潜在解密字母是P、M、B、L和N。
        密文字母G的潜在解密字母是U、O、A和I。
        密文字母U的潜在解密字母是Y和S。
        H、G和U之外的其他密文字母都没有潜在解密字母。
    
    当我们在Python代码里表示密指映射时,我们会使用字典值:
    {'A':[],'B':[],'C':[],'D';[],'E':[],'F':[],'G':['U','O','A','I'],'H':['P','B','L','N'],'I':[],'J':[],'K':[],'L':[M],'N':[],'O':[],'P':[],'Q':[],'R':[],'S':[],'T':[],'U':['Y','S'],'V':[],'W':[],'X':[],'Y':[],'Z':[]}
    
    如果我们把密字的潜在解密字母的数目减至只有一个字母,那么我们就知道那个密字解
    密成什么了。即使我们没有解开全部26个密字,我们还是可能破译大多数密文的密字。
    但是,我们必须先找出字典文件里的每个单词的模式,把它们放在一个列表里排序好,
    以便获取匹配特定密词的单词模式的所有候选单词
    
    >>> import wordPatterns
    >>> wordPatterns.allPatterns['0.1.2.1.1.3.4']
    ['BAZAARS', 'BESEECH', 'REDEEMS', 'STUTTER']
    >>> wordPatterns.allPatterns['0.1.2.2.3.2.4.1.5.5']
    ['CANNONBALL']
    >>> wordPatterns.allPatterns['0.1.0.1.0.1']
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: '0.1.0.1.0.1'
    >>> '0.1.0.1.0.1' in wordPatterns.allPatterns
    False
    
    
    #模式'0.1.0.1.0.1'并不在这个字典里。这就是为什么表达式wordPatterns.allPatterns
    #[Ul.0.1.0.1’]会出错(因为 allPatterns 里没有U1.0.1.0.1?这个键),也是为什么Ul.O.l.O.l1 in
    #wordPatterns.allPatterns 计算结果是 False
    
  58. pprint模块

    # pprint模块包含美化输出(pretty print)值的函数
    # pprint模块有一个函数叫pprint()。传给pprint.pprint()的值会“美化输出”到屏幕上,
    # 更易阅读:
    >>> someListOfListVar  = [['ant'], ['baboon','badger','bat','bear','beaver'],['came','cat','clam','cobra','cougar','coyote','crow'],['deer','dog','donkey','duck'],['eagle'],['ferret','fox','frog'],['goat']]
    >>> print(someListOfListVar)
    [['ant'], ['baboon', 'badger', 'bat', 'bear', 'beaver'], ['came', 'cat', 'clam', 'cobra', 'cougar', 'coyote', 'crow'], ['deer', 'dog', 'donkey', 'duck'], ['eagle'], ['ferret', 'fox', 'frog'], ['goat']]
    >>>
    >>> import pprint
    >>> pprint.pprint(someListOfListVar)
    [['ant'],
     ['baboon', 'badger', 'bat', 'bear', 'beaver'],
     ['came', 'cat', 'clam', 'cobra', 'cougar', 'coyote', 'crow'],
     ['deer', 'dog', 'donkey', 'duck'],
     ['eagle'],
     ['ferret', 'fox', 'frog'],
     ['goat']]
    >>>
    >>> prettifiedString = pprint.pformat(someListOfListVar)
    >>> print(prettifiedString)
    [['ant'],
     ['baboon', 'badger', 'bat', 'bear', 'beaver'],
     ['came', 'cat', 'clam', 'cobra', 'cougar', 'coyote', 'crow'],
     ['deer', 'dog', 'donkey', 'duck'],
     ['eagle'],
     ['ferret', 'fox', 'frog'],
     ['goat']]
    
  59. 在Python里使用列表创建字符串

    以前的字符串连接方法:

    building = ''
    for c in 'Hello world!':
        building += c
    print(building)
    

    这似乎是完成这项工作的一种简单方法。但是,对于Python而言,连接字符串是非常
    低效的。其中的技术原因已经超出本书的范围,但是,以空列表而不是空字符串开始,接着
    使用appendO列表方法而不是字符串连接会更快

    在你创建完字符串列表之后,你可以使用join()方法把字符串列表转换层单个字符串值

    building = []
    for c in 'Hello world!':
        building.append(c)
    building = ''.join(building)
    print(building)
    

    在你的代码里使用这种方法创建字符串将得到更快的程序、

  60. 破译简单替代加密法(simpleSubHacker.py)

    '''
    破译简单替代加密法非常容易,有5个步骤:
        1. 找出密文里的每个密词的单词模式。
        2. 找出每个密词可以解密成哪些英文单词。
        3. 使用密词的候选单词列表为每个密词创建一个密字映射。(密字映射只是一个字典值。)
        4. 计算所有密字映射的交集,得到一个交集密字映射。
        5. 从交集密字映射移除任何已经破译的字母。
           密文里的密词越多,我们可以用来计算交集的密字映射就越多。
           我们计算交集的密字映射越多,每个密字的潜在解密字母的数目就越少。
           这意味着密文消息越长,我们就越可能破译和解密它。
    
    '''
    
    # Simple Substitution Cipher Hacker
    # http://inventwithpython.com/hacking (BSD Licensed)
    # pyperclip.py 依赖于两个软件xclip、xsel
    # 安装方法:apt-get install xclip xsel
    
    import os, re, copy, pprint, pyperclip, simpleSubCipher, makeWordPatterns
    
    # 判断目标文件是否存在于当前目录
    if not os.path.exists('wordPatterns.py'):
        makeWordPatterns.main() # create the wordPatterns.py file
    import wordPatterns
    
    LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    # re.compileO函数是新的。这个函数编译(也就是创建)一个新的正则表达式模式对象,
    # 也可以简单说成“正则对象”或“模式对象”。正则表达式是字符串,它们定义了匹配某些字
    # 符串的特定模式
    # 我们只是使用它们从字符串移除不是大写字母或空格的字符
    nonLettersOrSpacePattern = re.compile('[^A-Z\s]')
    
    def main():
        message = 'Sy l nlx sr pyyacao l ylwj eiswi upar lulsxrj isr sxrjsxwjr, ia esmm rwctjsxsza sj wmpramh, lxo txmarr jia aqsoaxwa sr pqaceiamnsxu, ia esmm caytra jp famsaqa sj. Sy, px jia pjiac ilxo, ia sr pyyacao rpnajisxu eiswi lyypcor l calrpx ypc lwjsxu sx lwwpcolxwa jp isr sxrjsxwjr, ia esmm lwwabj sj aqax px jia rmsuijarj aqsoaxwa. Jia pcsusx py nhjir sr agbmlsxao sx jisr elh. -Facjclxo Ctrramm'
    
        # Determine the possible valid ciphertext translations.
        print('Hacking...')
        # ,hackSimpleSub()会返回密字映射(具体来说就是移除了已破译字母的交集密字
        # 映射,就像我们在交互式Shell实践里创建的那个一样)。返回的密字映射将传给
        # decryptWithCipherletterMapping()来解密 message 里的密文。
        letterMapping = hackSimpleSub(message)
    
        # Display the results to the user.
        print('Mapping:')
        # 为保存在letterMapping里的密字映射是一个字典,所以我们使用pprint.pprint() “美
        # 化输出”函数在屏幕上显示它
        pprint.pprint(letterMapping)
        print()
        print('Original ciphertext:')
        print(message)
        print()
        print('Copying hacked message to clipboard:')
        hackedMessage = decryptWithCipherletterMapping(message, letterMapping)
        pyperclip.copy(hackedMessage)
        print(hackedMessage)
    
    # getBlankCipherletterMapping()函数会返回一个密字映射。密字映射只是一个字典,字
    # 典的键是26个大写单字母字符串,字典的值是单个大写字母字符串(如^或’Q')的列表。
    def getBlankCipherletterMapping():
        # Returns a dictionary value that is a blank cipherletter mapping.
        return {'A': [], 'B': [], 'C': [], 'D': [], 'E': [], 'F': [], 'G': [], 'H': [], 'I': [], 'J': [], 'K': [], 'L': [], 'M': [], 'N': [], 'O': [], 'P': [], 'Q': [], 'R': [], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': [], 'Y': [], 'Z': []}
    '''
    >>> import simpleSubHacker
    >>> letterMapping1 = simpleSubHacker.getBlankCipherletterMapping()
    >>> letterMapping1
    {'A': [], 'B': [], 'C': [], 'D': [], 'E': [], 'F': [], 'G': [], 'H': [], 'I': [], 'J': [], 'K': [], 'L': [], 'M': [], 'N': [], 'O': [], 'P': [], 'Q': [], 'R': [], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': [], 'Y': [], 'Z': []}
    '''
    
    # 把字母添加到密字映射
    def addLettersToMapping(letterMapping, cipherword, candidate):
        # The letterMapping parameter is a "cipherletter mapping" dictionary
        # value that the return value of this function starts as a copy of.
        # The cipherword parameter is a string value of the ciphertext word.
        # The candidate parameter is a possible English word that the
        # cipherword could decrypt to.
    
        # This function adds the letters of the candidate as potential
        # decryption letters for the cipherletters in the cipherletter
        # mapping.
        '''
            addLettersToMapping()函数会确保候选单词里的每个字母都能映射到密词里的某个字
            母。它检查candidate里的每个字母,并把cipherword里的对应字母添加到letterMapping,
            如果那里没有的话。
            比如说,如果'PUPPY’是’HGHHU’密词的候选单词,addLettersToMapping()函数会修
            改letterMapping,把T'添加到'H’键的潜在解密字母列表。接着,这个函数会修改’G’键,把
            V添加到它的列表。
            如果这个字母已经在潜在解密字母列表里,addLettersToMappingO不会把字母添加到
            列表。我们会跳过把’P'添加到’H'键两次,因为它已经添加过了。最后,这个函数会改变TJ’
            键,让Y在它的潜在解密字母列表里。
        '''
        # 为了防止修改传给letterMapping参数的原来的字典值,第49行会复制letterMapping
        # 里的字典,并让这个副本成为letterMapping的新值。
        letterMapping = copy.deepcopy(letterMapping)
        for i in range(len(cipherword)):
            if candidate[i] not in letterMapping[cipherword[i]]:
                letterMapping[cipherword[i]].append(candidate[i])
        return letterMapping
    
    # 计算两个密字映射的交集
    '''
        如果一个密字映射里的密字的潜在解密字母列表是空的,这意味着这个密字可能解密成
        任何字母。在这种情况下,密字映射交集只是其他映射的潜在解密字母列表的副本。
        换句话说,如果mapA的潜在解密字母列表是空的,那就把映射交集的列表设为mapB
        的列表的副本。或者,如果mapB的列表是空的,那就把映射交集的列表设为mapA的列表
        的副本。
        (如果两个映射的列表都是空的,那么第65行会把mapB的空列表复制到映射交集。这
        是我们希望的效果:如果两个列表都是空的,那么映射交集也是空列表。)
    '''
    def intersectMappings(mapA, mapB):
        # To intersect two maps, create a blank map, and then add only the
        # potential decryption letters if they exist in BOTH maps.
        intersectedMapping = getBlankCipherletterMapping()
        for letter in LETTERS:
    
            # An empty list means "any letter is possible". In this case just
            # copy the other map entirely.
            if mapA[letter] == []:
                intersectedMapping[letter] = copy.deepcopy(mapB[letter])
            elif mapB[letter] == []:
                intersectedMapping[letter] = copy.deepcopy(mapA[letter])
            else:
                # If a letter in mapA[letter] exists in mapB[letter], add
                # that letter to intersectedMapping[letter].
                for mappedLetter in mapA[letter]:
                    if mappedLetter in mapB[letter]:
                        # 会把这个共有的字母添加到intersectedMappingfletter]的潜在解密字母列表。
                        intersectedMapping[letter].append(mappedLetter)
    
        return intersectedMapping
        '''
            一旦第60行开始的for循环完成,intersectedMapping里的密字映射将只包含同时存在
            于mapA和mapB的潜在解密字母列表里的潜在解密字母。完整的交集密字映射在第75行返回。
        '''
    
    
    # 从密字映射移除已经破译的字母
    # removeSolvedLettersFromMapping()函数接受一个密字映射,并从其他密字的列表移除这些已经破译的潜在解密字母
    '''
        removeSolvedLettersFromMapping()函数查找 letterMapping 参数里有且只有一个潜
        在解密字母的密字。这些密字已经破译:这个密字必须解密成那个潜在解密字母。这意味着
        包含这个已经破译的字母的其他密字可以把这个字母从它们的潜在解密字母列表移除了。
        这可能产生连锁反应,因为当一个潜在解密字母从其他潜在解密字母列表移除时,可能
        产生新的已经破译的密字。在这种情况下,这个程序会在整个密字映射之上再次循环执行已
        经破译的字母的移除操作。
    '''
    def removeSolvedLettersFromMapping(letterMapping):
        # Cipher letters in the mapping that map to only one letter are
        # "solved" and can be removed from the other letters.
        # For example, if 'A' maps to potential letters ['M', 'N'], and 'B'
        # maps to ['N'], then we know that 'B' must map to 'N', so we can
        # remove 'N' from the list of what 'A' could map to. So 'A' then maps
        # to ['M']. Note that now that 'A' maps to only one letter, we can
        # remove 'M' from the list of letters for every other
        # letter. (This is why there is a loop that keeps reducing the map.)
        '''
            第87行会复制letterMapping里的密字映射,这样在函数里对它所做的修改就不会影响
            到函数之外的字典值了。第88行创建loopAgain,这个变量保存一个布尔值,告诉我们代码
            是否找到新的已经破译的字母,需要再次循环。第88行把loopAgain变量设为Tme,这样
            程序的执行就进人第89行的while循环。
        '''
        letterMapping = copy.deepcopy(letterMapping)
        loopAgain = True
        while loopAgain:
            '''
                在循环的最开始,第91行会把loopAgain设为False。这个代码假设这是经过第89行
                的while循环的最后一次迭代。仅当我们在这次迭代里找到新的已经破译的密字时才会把
                loopAgain 变量设为 True。
            '''
            # First assume that we will not loop again:
            loopAgain = False
    
            # solvedLetters will be a list of uppercase letters that have one
            # and only one possible mapping in letterMapping
            '''
                代码的下一部分创建有且仅有一个潜在解密字母的密字列表。我们会把这些密字字符串
                放入solvedLetters列表。第95行的solvedLetters变量以空列表开始。
                第96行的for循环遍历26个可能的密字,查看那个密字在密字映射里的潜在解密字母
                列表(也就是 letterMapping[cipherletter]) 0
                如果这个列表的长度是1 (在第97行检查),那么我们就知道这个密字只会解密成一个
                字母,因此这个密字已经破译了。我们会在第98行把这个字母(是潜在解密字母,不是密字)
                添加到solvedLetters列表。已经破译的字母总是在letterMapping[cipherletter][0],因为在
                letterMapping[cipherletter]是一个只有一个字符串值的潜在解密字母列表,这个字符串值在
                列表的索引0上。
            '''
            solvedLetters = []
            for cipherletter in LETTERS:
                if len(letterMapping[cipherletter]) == 1:
                    solvedLetters.append(letterMapping[cipherletter][0])
    
            # If a letter is solved, than it cannot possibly be a potential
            # decryption letter for a different ciphertext letter, so we
            # should remove it from those other lists.
            '''
                在前面第96行开始的for循环完成之后,solvedLetters变量将包含所有密字破译出
                来的字母。第103行的for循环会遍历26个可能的密字,并查看密字映射的潜在解密字母
                列表。
                对于每个检查的密字,第104行都会遍历solvedLetters里的字母来检查它们存在于
                letterMapping[cipherletter]的潜在解密字母列表里。第105行检查潜在解密字母列表是否没
                有破译(也就是len (letterMapping[cipherletter])不等于1)并且已经破译的字母存在于
                潜在解密字母列表里。如果这个条件是Tme,那么第106行将把s里的已经破译的字母从这
                个潜在解密字母列表里移除。
            '''
            for cipherletter in LETTERS:
                for s in solvedLetters:
                    if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]:
                        letterMapping[cipherletter].remove(s)
                        if len(letterMapping[cipherletter]) == 1:
                            # A new letter is now solved, so loop again.
                            '''
                            如果这个移除操作恰好导致这个潜在解密字母列表变成只有一个字母在里面,那么第
                            109行将会把loopAgain变量设为True,以便代码在下一次迭代里检查密字映射里的这个新
                            的已经破译的字母。
                            '''
                            loopAgain = True
        return letterMapping
    
    # 破译简单替代加密法
    '''
        破译简单替代加密法消息的步骤吗:对于每个密词,
        根据密词的单词模式获取所有候选单词,然后把这些候选单词添加到密字映射。接着取出每
        个密词的密字映射并计算它们的交集。
        intersectedMap变量将会保存各个密词的密字映射的交集。在hackSimpleSub()函数的
        开始,将会创建一个空密字映射。
    '''
    def hackSimpleSub(message):
        intersectedMap = getBlankCipherletterMapping()
        # nonLettersOrSpacePattern正则对象匹配任何不是字母和空白字符的字符
        # 串。sub()方法会返回message变量的非字母和非空格的字符换成空字符串之后的字符串。这
        # 实际上返回message移除所有标点符号和数字字符之后的字符串。
        '''
            >>> import re
            >>> nonLetterOrSpacePattern = re.compile('[^A-Z\S]')
            >>> message = 'Hello,this is my 1st test message.'
            >>> message = nonLetterOrSpacePattern.sub('',message.upper())
            >>> message
            'HELLO,THISISMY1STTESTMESSAGE.'
            >>> cipherwordList = message.split()
            >>> cipherwordList
            ['HELLO,THISISMY1STTESTMESSAGE.']
        '''
        cipherwordList = nonLettersOrSpacePattern.sub('', message.upper()).split()
        # 第116行的for循环把message列表里的每个字符串赋值给cipherword变量。在这个
        # 循环里,我们将得到密词的候选单词,把候选单词添加到密字映射,然后计算这个映射和
        # intersectedMap 的交集。
        for cipherword in cipherwordList:
            # Get a new cipherletter mapping for each ciphertext word.
            # 第118行将从getBlankCipherletterMappingO获取一个新的空密字映射,并把它保存到newMap变量里
            newMap = getBlankCipherletterMapping()
    
            # 调用makeWordPattern模块的getWordPattern()函数来获得这个密词的单词模式
            '''
            >>> import makeWordPatterns
            >>> wordPat = makeWordPatterns.getWordPattern('OLQIHXIRCKGNZ')
            >>> wordPat
            '0.1.2.3.4.5.3.6.7.8.9.10.11'
            '''
            # 要找到当前密词的候选单词,我们在第120行调用makeWordPatterns模块的
            # getWordPattern().如果这个密词的单词模式不在wordPatterns.allPatterns字典的键里,那
            # 么不管这个密词解密成什么都不会在我们的字典文件里。在这种情况下,第122行的continue
            # 语句会跳回第116行,处理列表里的下一个密词。
            wordPattern = makeWordPatterns.getWordPattern(cipherword)
            if wordPattern not in wordPatterns.allPatterns:
                continue # This word was not in our dictionary, so continue.
    
            # Add the letters of each can
    
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值