Python learning

Python learning

编码

    # -*- coding: utf-8 -*-

计算

Python 支持的数字类型有:int、float、Decimal(十进制)、Fraction(分数)、复数

    >>> 8 / 5
    1.6
    >>> 17 // 5
    3
    >>> 5 * 2
    10
    >>> 5 ** 2
    25
    >>> 3.5 // 1.7
    2.0

字符串

可以单双引号,特殊字符使用反斜杠转义。

特殊的一些:

  1. 如果不想让反斜杠当做转义输出,可以用原始字符串,方法是在第一个引号前面加上一个rr"C:\some\name"
  2. 字符串文本能够分成多行。一种方法是使用三引号: """...""" 或者 '''...'''。行尾换行符会被自动包含到字符串中,但是可以在行尾加上 \ 来避免这个行为(行尾加上则不换行)
  3. 字符串可以由 + 操作符连接(粘到一起),可以由 * 表示重复,3 * 'un' + 'ium' == 'unununium'
  4. 字符串也可以被截取,索引从0记起,如word[2];索引也可以是负数,这将导致从右边开始计算,word[-1]表示最后一个字符
  5. 字符串还支持切片,如word[2:5]表示获取位置[2,5)的三个字符,如果word是Python,则获取的字符串是tho,word[:5]表示从最开始到第4个位置的字符串,切片也支持负数
  6. 索引过大会引发IndexError,但是切片过大不会报错
  7. Python字符串不可以被更改 — 它们是 不可变的 (整体赋值是可以的)。因此,赋值给字符串索引的位置会导致错误。word[1] = 'j'是错误的!而word = 'aaa'可以

相关链接:

  1. Text Sequence Type — str
  2. String Methods: 转换和查找
  3. String Formatting
  4. String Formatting Operations

列表

同样支持索引和切片、所有的切片操作都会返回一个包含请求的元素的新列表。这意味着下面的切片操作返回列表一个新的(浅)拷贝副本

    >>> sq1 = [1, 2, 3]
    >>> sq2 = sq1
    >>> sq3 = sq1[:]
    >>> sq1
    [1, 2, 3]
    >>> sq2
    [1, 2, 3]
    >>> sq3
    [1, 2, 3]
    >>> sq2[1] = 10
    >>> sq1
    [1, 10, 3]
    >>> sq2
    [1, 10, 3]
    >>> sq3[1] = 100
    >>> sq1
    [1, 10, 3]
    >>> sq3
    [1, 100, 3]

所以,我们知道,要想复制一个列表,需要使用[:]来获取浅拷贝副本,而不能直接赋值。从上面的例子,也可以知道,列表支持对每个元素单独修改

所以,还可以这样:

    >>> sq1
    [1, 10, 3]
    >>> sq1 + [1, 2]
    [1, 10, 3, 1, 2]
    >>> sq1
    [1, 10, 3]
    >>> sq1.append(10)
    >>> sq1
    [1, 10, 3, 10]
    >>> sq1.extend([7, 6, 5])
    >>> sq1
    [1, 10, 3, 10, 7, 6, 5]
    >>> # insert value
    ... sq1[1:1] = [40, 50]
    >>> sq1
    [1, 40, 50, 10, 3, 10, 7, 6, 5]
    >>> # replace value
    ... sq1[1:3] = [60, 70]
    >>> sq1
    [1, 60, 70, 10, 3, 10, 7, 6, 5]
    >>> # remove value
    ... sq1[1:3] = []
    >>> sq1
    [1, 10, 3, 10, 7, 6, 5]

一个例子

    >>> # fibonacci
    ... a, b = 0, 1
    >>> while b < 10:
    ...     print(b)
    ...     a, b = b, a+b
    ...
    1
    1
    2
    3
    5
    8

这里要注意的是:

注意a, b = 0, 1a, b = b, a+b,这是 多重赋值 ,变量在赋值前,右边的表达式从左到右计算,然后再分别赋值给左边的每个变量。所以,将第二个多重赋值改成b, a = a+b, b也没有问题

条件判断:0、空序列(长度为0就算)为false,标准比较操作符与 C 相同: <>==<=>=!=

print()函数可以用,隔开变量输出,并自动会在两个变量之间加一个空格;print('aaa', end=','),end的默认值是\n,改掉这个之后,print不换行,而是以,结尾

流程控制

if语句

    if x < 0:
        print(x)
    elif x == 0:
        print("hello")
    else:
        print("+")

for语句

    words = ['cat', 'window', 'defenestrate']
    for w in words:
        print(w, len(w))

这里需要注意的是,Python 的 for 语句依据任意序列(链表或字符串)中的子项,按它们在序列中的顺序来进行迭代。所以,在迭代过程中修改迭代序列不安全。如果你想要修改你迭代的序列(例如,复制选择项),你可以迭代它的复本

    for w in words[:]:
        if len(w) > 6:
            words.insert(0, w)
    print(words)
    # ['defenestrate', 'cat', 'window', 'defenestrate']

在循环(for, while)中同样可以使用breakcontinue,作用和 C 中相同

循环中,支持else子句,作用是在循环正常结束(break不算)后执行

range()函数

用于生成一个等差级数链表(注意:这不是列表List,是迭代器),以下是例子:

    >>> for i in range(5):
    ...     print(i)
    ...
    0
    1
    2
    3
    4

    range(5, 10)
       5 through 9

    range(0, 10, 3)
       0, 3, 6, 9

    range(-10, -100, -30)
      -10, -40, -70

    >>> a = ['Mary', 'had', 'a', 'little', 'lamb']
    >>> for i in range(len(a)):
    ...     print(i, a[i])
    ...
    0 Mary
    1 had
    2 a
    3 little
    4 lamb

    >>> print(range(10))
    range(0, 10)

    >>> list(range(5))
    [0, 1, 2, 3, 4]

定义函数

关键字def引入一个函数定义,在其后必须跟有函数名和包括形式参数的圆括号

函数体的第一行语句可以是可选的字符串文本,这个字符串是函数的文档字符串,或者称为 docstring。有些工具通过 docstrings 自动生成在线的或可打印的文档,或者让用户通过代码交互浏览;在你的代码中包含 docstrings 是一个好的实践,让它成为习惯吧

    def func():
        """ 这是一个docstring """
        ...

没有return语句的函数会返回None

在函数中引用全局的变量,可以用global语句声明,如:global a

函数参数

默认参数值

    def func(num, str="hello"):
        if num in (1, 2, 3):
            print(num)

重要警告:默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数!!

    def f(a, L=[]):
        L.append(a)
        return L

    print(f(1))
    print(f(2))
    print(f(3))

会输出:

    [1]
    [1, 2]
    [1, 2, 3]

取而代之的方法:

    def f(a, L=None):
        if L is None:
            L = []
        L.append(a)
        return L

关键字参数

    def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
        print("-- This parrot wouldn't", action, end=' ')
        print("if you put", voltage, "volts through it.")
        print("-- Lovely plumage, the", type)
        print("-- It's", state, "!")

    # 接受一个必选参数 (voltage) 以及三个可选参数 (state, action, 和 type)。可以用以下的任一方法调用

    parrot(1000)                                          # 1 positional argument
    parrot(voltage=1000)                                  # 1 keyword argument
    parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
    parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
    parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
    parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

可变参数列表

一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组。在这些可变个数的参数之前,可以有零到多个普通的参数

    def write_multiple_items(file, separator, *args):
        file.write(separator.join(args))

通常,这些可变参数是参数列表中的最后一个(只针对于非关键字参数),因为它们将把所有的剩余输入参数传递给函数。任何出现在*args后的参数是关键字参数,这意味着,他们只能被用作关键字,而不是位置参数

    >>> def concat(*args, sep="/"):
    ...    return sep.join(args)
    ...
    >>> concat("earth", "mars", "venus")
    'earth/mars/venus'
    >>> concat("earth", "mars", "venus", sep=".")
    'earth.mars.venus'

可变关键字参数列表

相当于接收一个字典,必须在可变参数列表(如果有)的后面

    def cheeseshop(kind, *arguments, **keywords):
        print("-- Do you have any", kind, "?")
        print("-- I'm sorry, we're all out of", kind)
        for arg in arguments:
            print(arg)
        print("-" * 40)
        keys = sorted(keywords.keys())
        for kw in keys:
            print(kw, ":", keywords[kw])

    cheeseshop("Limburger", "It's very runny, sir.",
               "It's really very, VERY runny, sir.",
               shopkeeper="Michael Palin",
               client="John Cleese",
               sketch="Cheese Shop Sketch")

    """ result:
    -- Do you have any Limburger ?
    -- I'm sorry, we're all out of Limburger
    It's very runny, sir.
    It's really very, VERY runny, sir.
    ----------------------------------------
    client : John Cleese
    shopkeeper : Michael Palin
    sketch : Cheese Shop Sketch
    """

在传递可变参数的时候可能遇到一种情况是,当要传递的参数已经是一个列表,而函数需要的是可变长参数,可以使用*来拆开。同理,字典拆成关键字参数可以用**

    >>> args = [3, 6]
    >>> list(range(*args))
    [3, 4, 5]
    >>> list(range(3, 6))
    [3, 4, 5]

Lambda形式

通过lambda关键字,可以创建短小的匿名函数。这里有一个函数返回它的两个参数的和: lambda a, b: a+b

lambda 形式可以从外部作用域引用变量:

    >>> def make_incrementor(n):
    ...     return lambda x: x + n
    ...
    >>> f = make_incrementor(42)
    >>> f(0)
    42
    >>> f(1)
    43

pass语句

pass 语句什么也不做。它用于那些语法上必须要有什么语句,但程序什么也不做的场合

    class MyCls:
        pass
    def func():
        pass
    while True:
        pass

各种数据结构

列表方法

    >>> a = [66.25, 333, 333, 1, 1234.5]
    >>> print(a.count(333), a.count(66.25), a.count('x'))
    2 1 0
    >>> a.insert(2, -1)
    >>> a.append(333)
    >>> a
    [66.25, 333, -1, 333, 1, 1234.5, 333]
    >>> a.index(333)
    1
    >>> a.remove(333)
    >>> a
    [66.25, -1, 333, 1, 1234.5, 333]
    >>> a.reverse()
    >>> a
    [333, 1234.5, 1, 333, -1, 66.25]
    >>> a.sort()
    >>> a
    [-1, 1, 66.25, 333, 333, 1234.5]
    >>> a.pop()
    1234.5
    >>> a
    [-1, 1, 66.25, 333, 333]

可以看到,列表自带pop和append方法,所以,可以直接把列表当做堆栈使用

    >>> stack = [3, 4, 5]
    >>> stack.append(6)
    >>> stack.append(7)
    >>> stack
    [3, 4, 5, 6, 7]
    >>> stack.pop()
    7
    >>> stack
    [3, 4, 5, 6]
    >>> stack.pop()
    6
    >>> stack.pop()
    5
    >>> stack
    [3, 4]

使用队列:如果把列表当做队列来使用,效率不高(头部插入和弹出很慢)。可以使用collections.deque

    >>> from collections import deque
    >>> queue = deque(["Eric", "John", "Michael"])
    >>> queue.append("Terry")       # Terry arrives
    >>> queue.append("Graham")      # Graham arrives
    >>> queue.popleft()             # The first to arrive now leaves
    'Eric'
    >>> queue.popleft()             # The second to arrive now leaves
    'John'
    >>> queue                       # Remaining queue in order of arrival
    deque(['Michael', 'Terry', 'Graham'])

列表推导式为从序列中创建列表提供了一个简单的方法

常规的方法如下,副作用是x变量在循环完毕后依然存在,而且长...

    >>> squares = []
    >>> for x in range(10):
    ...     squares.append(x**2)
    ...
    >>> squares
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

使用列表推导式:

    >>> squares = [x ** 2 for x in range(10)]
    >>> squares
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

列表推导式由包含一个表达式的括号组成,表达式后面跟随一个for子句,之后可以有零或多个forif子句。结果是一个列表,由表达式依据其后面的forif子句上下文计算而来的结果构成

另一个例子:

    >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
    [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

更多例子!!

del语句

del语句可以删除列表的对应的索引项 或 变量,与pop()方法的区别在于不返回值,并且可以删除切片和整个变量

    >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
    >>> del a[0]
    >>> a
    [1, 66.25, 333, 333, 1234.5]
    >>> del a[2:4]
    >>> a
    [1, 66.25, 1234.5]
    >>> del a[:]
    >>> a
    []
    >>> del a
    >>> a
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'a' is not defined

元组和序列

我们知道列表和字符串有很多通用的属性,例如索引和切割操作。它们是序列类型中的两种

元组也是一种标准序列类型,一个元组由数个逗号分隔的值组成:

    >>> t = 12345, 54321, 'hello!'
    >>> t[0]
    12345
    >>> t
    (12345, 54321, 'hello!')
    >>> # Tuples may be nested:
    ... u = t, (1, 2, 3, 4, 5)
    >>> u
    ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
    >>> # Tuples are immutable:
    ... t[0] = 88888
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
    >>> # but they can contain mutable objects:
    ... v = ([1, 2, 3], [3, 2, 1])
    >>> v
    ([1, 2, 3], [3, 2, 1])

上面的例子说明,元组在输出的时候总是有括号的,但是输入的时候可以不用。元组元素就像字符串元素,不可变。但是,如果元组中包含可变元素,里面的元素是可变的(如:列表)

特殊的情况,创建零个或一个元素的元组。零个元素直接用(),而一个元素的时候,需要额外加一个逗号,丑但有效1,(1,)

    >>> empty = ()
    >>> singleton = 'hello',    # <-- note trailing comma
    >>> len(empty)
    0
    >>> len(singleton)
    1
    >>> singleton
    ('hello',)

集合

集合(set)是一个无序不重复元素的集,基本功能包括关系测试和消除重复元素。集合对象还支持 union(联合),intersection(交),difference(差)和 sysmmetric difference(对称差集)等数学运算

大括号{...}set()函数可以用来创建集合。注意:想要创建空集合,你必须使用set()而不是{}。后者用于创建空字典

    >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
    >>> print(basket)      # show that duplicates have been removed
    {'orange', 'banana', 'pear', 'apple'}
    >>> 'orange' in basket # fast membership testing
    True
    >>> 'crabgrass' in basket
    False

    >>> # Demonstrate set operations on unique letters from two words
    ...
    >>> a = set('abracadabra')
    >>> b = set('alacazam')
    >>> a          # unique letters in a
    {'a', 'r', 'b', 'c', 'd'}
    >>> a - b      # letters in a but not in b
    {'r', 'd', 'b'}
    >>> a | b      # letters in either a or b
    {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
    >>> a & b      # letters in both a and b
    {'a', 'c'}
    >>> a ^ b      # letters in a or b but not both
    {'r', 'd', 'b', 'm', 'z', 'l'}

以上例子证明,集合支持自动的去重(但乱序)和数学运算

集合也支持推导式(并去重)

    >>> a = {x for x in 'abracadabra' if x not in 'abc'}
    >>> a
    {'r', 'd'}

字典

字典在其他语言中,可能被称为“关联数组”(associative arrays)。字典以关键字为索引,关键字可以是任意不可变类型,通常用字符串或数值。如果元组中只包含字符串和数字,它可以做为关键字,如果它直接或间接的包含了可变对象,就不能当做关键字。不能用列表做关键字,因为列表可以用索引、切割或者append()extend()等方法改变

字典的主要操作是依据键来存储和析取值。也可以用del来删除键:值对(key:value)。如果你用一个已经存在的关键字存储值,以前为该关键字分配的值就会被遗忘。试图从一个不存在的键中取值会导致错误

    >>> tel = {'jack': 4098, 'sape': 4139}
    >>> tel['guido'] = 4127
    >>> tel
    {'sape': 4139, 'guido': 4127, 'jack': 4098}
    >>> tel['jack']
    4098
    >>> del tel['sape']
    >>> tel['irv'] = 4127
    >>> tel
    {'guido': 4127, 'irv': 4127, 'jack': 4098}
    >>> list(tel.keys())
    ['irv', 'guido', 'jack']
    >>> sorted(tel.keys())
    ['guido', 'irv', 'jack']
    >>> 'guido' in tel
    True
    >>> 'jack' not in tel
    False

dict()构造函数可以直接从key-value对中创建字典。此外,字典推导式可以从任意的键值表达式中创建字典

    >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
    {'sape': 4139, 'jack': 4098, 'guido': 4127}
    >>> dict(sape=4139, guido=4127, jack=4098)
    {'sape': 4139, 'jack': 4098, 'guido': 4127}
    >>> {x: x**2 for x in (2, 4, 6)}
    {2: 4, 4: 16, 6: 36}

循环技巧

循环字典,使用item()方法:

    >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
    >>> for k, v in knights.items():
    ...     print(k, v)
    ...
    gallahad the pure
    robin the brave

循环序列,使用enumerate()

    >>> for i, v in enumerate(['tic', 'tac', 'toe']):
    ...     print(i, v)
    ...
    0 tic
    1 tac
    2 toe

同时循环两个或更多序列,使用zip()整体打包:

    >>> questions = ['name', 'quest', 'favorite color']
    >>> answers = ['lancelot', 'the holy grail', 'blue']
    >>> for q, a in zip(questions, answers):
    ...     print('What is your {0}?  It is {1}.'.format(q, a))
    ...
    """ result:
    What is your name?  It is lancelot.
    What is your quest?  It is the holy grail.
    What is your favorite color?  It is blue.
    """

逆向循环,使用reversed()

    >>> for i in reversed(range(1, 10, 2)):
    ...     print(i)
    ...
    9
    7
    5
    3
    1

排序去重后输出,使用sorted()

    >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
    >>> for f in sorted(set(basket)):
    ...     print(f)
    ...
    apple
    banana
    orange
    pear

若要在循环内部修改正在遍历的序列(例如复制某些元素),建议您首先制作副本(!)

    >>> words = ['cat', 'window', 'defenestrate']
    >>> for w in words[:]:  # Loop over a slice copy of the entire list.
    ...     if len(w) > 6:
    ...         words.insert(0, w)
    ...
    >>> words
    ['defenestrate', 'cat', 'window', 'defenestrate']

条件判断

  1. 比较操作符innot in审核值是否在一个区间之内,1 not in [2, 3]
  2. 比较操作符isis not比较两个对象是否相同,[] is ()
  3. 比较操作可以传递,1 <= 3 > 2,比较操作符具有相同的优先级
  4. 短路运算符andor,参数从左向右解析,一旦结果可以确定即停止,短路操作符的返回值通常是最后一个变量0 and 1
  5. notandor同属于逻辑操作符,优先级not>and>or
  6. 总优先级:数值操作 > 比较操作 > 逻辑操作符
  7. C不同,Python在表达式内不能赋值

序列对象可以与相同类型的其他对象作比较,比较操作按字典序进行:首先比较前两个元素,如果不同,就决定了比较的结果;如果相同,就比较后两个元素,依此类推,直到所有序列都完成比较。如果两个元素本身就是同样类 型的序列,就递归字典序比较。如果两个序列的所有子项都相等,就认为序列相等。如果一个序列是另一个序列的初始子序列,较短的一个序列就小于另一个。字符串的字典序按照单字符的ASCII顺序

模块

模块是包括Python定义和声明的文件。文件名就是模块名加上.py后缀。模块的模块名(做为一个字符串)可以由全局变量__name__得到

    # 先在桌面新建一个test.py,定义一个hello()方法
    >>> os.getcwd()
    '/home/my/Desktop'
    >>> import test
    >>> test.hello()
    hello world
    >>> test.__name__
    'test'

除了包含函数定义外,模块也可以包含可执行语句。这些语句一般用来初始化模块。他们仅在 第一次 被导入的地方执行一次

    # 在test.py中加入一个输出语句
    >>> import test
    import mod test successfully!

每个模块都有自己私有的符号表,被模块内所有的函数定义作为全局符号表使用。因此,模块的作者可以在模块内部使用全局变量,而无需担心它与某个用户的全局变量意外冲突

模块可以导入其他的模块。一个(好的)习惯是将所有的import语句放在模块的开始(或者是脚本),这并非强制。被导入的模块名会放入当前模块的全局符号表中

import语句的一个变体直接从被导入的模块中导入命名到本模块的语义表中

    >>> from fibo import fib, fib2
    >>> fib(500)
    1 1 2 3 5 8 13 21 34 55 89 144 233 377

作为脚本来执行模块

只需要添加一个if判定:

    if __name__ == "__main__":
        import sys
        fib(int(sys.argv[1]))

然后用python fibo.py 50即可传入参数并执行

模块的搜索路径

导入一个叫spam的模块时,解释器先在当前目录中搜索名为spam.py的文件。如果没有找到的话,接着会到sys.path变量中给出的目录列表中查找,变量的初始值如下:

  • 输入脚本的目录(当前目录)
  • 环境变量PYTHONPATH表示的目录列表中搜索
  • Python默认安装路径中的搜索
    >>> sys.path
    ['', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages']

需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准模块重名,否则在导入模块时Python会尝试把这些脚本当作模块来加载。这通常会引发错误

dir()函数

内置函数dir()用于按模块名搜索模块定义,它返回一个字符串类型的存储列表

    >>> import fibo, sys
    >>> dir(fibo)
    ['__name__', 'fib', 'fib2']

包通常是使用用“圆点模块名”的结构化模块命名空间。例如,名为 A.B 的模块表示了名为A的包中名为B的子模块。可以避免全局变量之间的相互冲突

包的结构可能是这样的

    sound/              Top-level package
          __init__.py   Initialize the sound package
          formats/      Subpackage for file format conversions
                  __init__.py
                  wavread.py
                  wavwrite.py
                  aiffread.py
                  aiffwrite.py
                  auread.py
                  auwrite.py
                  ...
          effects/      Subpackage for sound effects
                  __init__.py
                  echo.py
                  surround.py
                  reverse.py
                  ...
          filters/      Subpackage for filters
                  __init__.py
                  equalizer.py
                  vocoder.py
                  karaoke.py
                  ...

当导入这个包时,Python通过sys.path搜索路径查找包含这个包的子目录

为了让Python将目录当做内容包,目录中必须包含__init__.py文件。这是为了避免一个含有烂俗名字的目录无意中隐藏了稍后在模块搜索路径中出现的有效模块,比如string。最简单的情况下,只需要一个空的__init__.py文件即可。当然它也可以执行包的初始化代码,或者定义稍后介绍的__all__变量

导入模块的方法:

    # 1
    import sound.effects.echo
    sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
    # 2
    from sound.effects import echo
    echo.echofilter(input, output, delay=0.7, atten=4)
    # 3
    from sound.effects.echo import echofilter
    echofilter(input, output, delay=0.7, atten=4)

from sound.effects import *可能会很慢,或者出现一些意外(强烈不推荐的写法)。但是这种情况难以避免,对于包的作者来说唯一的解决方案就是提供一个明确的包索引:执行from package import *时,如果包中的__init__.py代码定义了一个名为__all__的列表,就会按照列表中给出的模块名进行导入(可以避免导入所有模块)

输入输出

print()函数对于每个参数,自动用空格分开输出,以下是比较优雅的输出方式(不是拼接的):

    >>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
    We are the knights who say "Ni!"

    >>> print('{0} and {1}'.format('spam', 'eggs'))
    spam and eggs
    >>> print('{1} and {0}'.format('spam', 'eggs'))
    eggs and spam

    >>> print('This {food} is {adjective}.'.format(
    ...       food='spam', adjective='absolutely horrible'))
    This spam is absolutely horrible.

    >>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
                                                           other='Georg'))
    The story of Bill, Manfred, and Georg.

    >>> import math
    >>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))
    The value of PI is approximately 3.142.

可以看到,{}用于指代格式化的内容,里面可以规定顺序、关键字等

字段名后允许可选的':'和格式指令。这允许对值的格式化加以更深入的控制

更多例子

旧式的字符串格式化,使用%

    >>> print("Name:%10s Age:%8d Height:%8.2f" % ("Aviad", 25, 1.83))
    Name:     Aviad Age:      25 Height:    1.83

更多例子

文件读写

函数open()返回文件对象,这个函数通常需要两个字符串参数:文件名、打开模式

这里要特别注意的是,对于非文本文件,要在模式后面加上'b',否则会错误当做文本文件,修改一些平台有关的行结束符字符(Python打开文本,会默认会转换成平台相关的行结束符)

    >>> f = open('openfile', 'w');

文件的对象方法:

f.read(size)方法用于读取文件内容,size是可选的数值,如果没有指定size或者指定为负数,就会读取并返回整个文件。当文件大小为当前机器内存两倍时,就会产生问题。反之,会尽可能按比较大的size读取和返回数据。如果到了文件末尾,f.read()会返回一个空字符串''

    >>> f.read()
    'This is the entire file.\n'
    >>> f.read()
    ''
    >>> f.read()
    ''

f.readline()从文件中读取单独一行,字符串结尾会自动加上一个换行符\n,只有当文件最后一行没有以换行符结尾时,这一操作才会被忽略。这样返回值就不会有混淆,如果f.readline()返回一个空字符串,那就表示到达了文件末尾,如果是一个空行,就会描述为'\n',一个只包含换行符的字符串

    >>> f.readline()
    'This is the first line of the file.\n'
    >>> f.readline()
    'Second line of the file\n'
    >>> f.readline()
    ''

高效的读取方法

    >>> for line in f:
    ...     print(line, end='')
    ...

    ''' result:
    This is the first line of the file.
    Second line of the file
    '''

将所有行读到一个列表中

    >>> f.seek(0)
    >>> list(f)
    ['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']
    >>> list(f)
    []
    >>> f.seek(0)
    >>> f.readlines()
    ['dfsafdsa\n', 'dfsafds\n', 'fdsagdfhfdh\n', 'hfgng\n', 'nh\n', 'trh\n', 'trh\n', 'tr\n', '\n', '\n', 'h\n', 'tr\n', 'htrhtr\n', 'h\n', 'tr\n', 'htr\n', 'h\n', 'tr\n', '\n', '\n', '\n', 'htrhtr\n']

f.write(string)方法将string的内容写入文件(是覆盖还是追加,视打开文件的模式而定),Python3.5.2经测试没有返回值。如果想写入非字符串的内容,首先要转换为字符串

    >>> f.write('This is a test\n')

    >>> value = ('the answer', 42)
    >>> s = str(value)
    >>> f.write(s)

文件对象的指针

f.tell()返回一个整数,代表文件对象在文件中的指针位置,该数值计量了自文件开头到指针处的比特数。需要改变文件对象指针话话,使用f.seek(offset,from_what)。指针在该操作中从指定的引用位置移动offset比特,引用位置由from_what参数指定。from_what值为0表示自文件起始处开始,1表示自当前文件指针位置开始,2表示自文件末尾开始(配合的offset是负数)。from_what可以忽略,其默认值为0

    >>> f = open('workfile', 'rb+')
    >>> f.write(b'0123456789abcdef')
    16
    >>> f.seek(5)     # Go to the 6th byte in the file
    5
    >>> f.read(1)
    b'5'
    >>> f.seek(-3, 2) # Go to the 3rd byte before the end
    13
    >>> f.read(1)
    b'd'

当你使用完一个文件时,调用f.close()方法就可以关闭它并释放其占用的所有系统资源。在调用f.close()方法后,试图再次使用文件对象将会自动失败

    >>> f.close()
    >>> f.read()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: I/O operation on closed file
    >>> f
    <closed file 'test.txt', mode 'a+' at 0x7f8cd23ca5d0>

使用with处理文件对象的好习惯

使用关键字with的先进之处在于文件用完后会自动关闭,就算发生异常也没关系。它是try-finally块的简写

    >>> with open('test.txt', 'r') as f:
    ...     read_data = f.read()
    ...
    >>> read_data
    'dfsafdsa\n'
    >>> f.closed
    True

以上的语句相当于,先调用了open()函数,并将返回值赋给f,然后里面的语句是try块里面的内容,finally的内容隐藏在f对象的__exit__方法中,即f.close()。这里需要注意的是,这里出现异常的话,仍然需要自己定义try-except来处理。参考链接

使用json存储格式化数据

需要使用json标准模块,import json

    >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
    >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
    ['foo', {'bar': ['baz', None, 1.0, 2]}]

    >>> x
    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
    >>> f = open("test.txt", "w+")
    >>> f.read()
    ''
    >>> f.tell()
    0
    >>> json.dump(x, f)
    >>> f.seek(0)
    >>> f.read()
    '"[\\"foo\\", {\\"bar\\": [\\"baz\\", null, 1.0, 2]}]"'
    >>> f.seek(0)
    >>> y = json.load(f)
    >>> y
    u'["foo", {"bar": ["baz", null, 1.0, 2]}]'
    >>> z = json.loads(y)
    >>> z[1]["bar"][2]
    1.0

错误和异常

语法错误

语法错误,也被称作解析错误(最常见)

    >>> while True print('Hello world')
      File "<stdin>", line 1
        while True print('Hello world')
                       ^
    SyntaxError: invalid syntax

异常

即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为异常,并且程序不会无条件的崩溃:很快,你将学到如何在Python程序中处理它们。然而,大多数异常都不会被程序处理,像这里展示的一样最终会产生一个错误信息

    >>> 10 * (1/0)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    ZeroDivisionError: int division or modulo by zero
    >>> 4 + spam*3
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    NameError: name 'spam' is not defined
    >>> '2' + 2
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: Can't convert 'int' object to str implicitly

错误信息的最后一行指出发生了什么错误。异常也有不同的类型,异常类型做为错误信息的一部分显示出来

异常处理

try语句按如下方式工作:

  1. 首先,执行try子句 (在tryexcept关键字之间的部分)
  2. 如果没有异常发生,except子句在try语句执行完毕后就被忽略了
  3. 如果在try子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。如果异常匹配于except关键字后面指定的异常类型,就执行对应的except子句。然后继续执行try语句之后的代码
  4. 如果发生了一个异常,在except子句中没有与之匹配的分支,它就会传递到上一级try语句中。如果最终仍找不到对应的处理语句,它就成为一个未处理异常,终止程序运行,显示提示信息

一个except子句可以在括号中列出多个异常的名字去匹配相应的异常;如果省略括号,则表示全部匹配

    import sys

    try:
        f = open('myfile.txt')
        s = f.readline()
        i = int(s.strip())
    except OSError as err:
        print("OS error: {0}".format(err))
    except ValueError:
        print("Could not convert data to an integer.")
    except:
        print("Unexpected error:", sys.exc_info()[0])
        raise

try-except语句可以带有一个else子句,该子句只能出现在所有except子句之后。当try语句没有抛出异常时,需要执行一些代码,可以使用这个子句

    for arg in sys.argv[1:]:
        try:
            f = open(arg, 'r')
        except IOError:
            print('cannot open', arg)
        else:
            print(arg, 'has', len(f.readlines()), 'lines')
            f.close()

使用else子句比在try子句中附加代码要好,因为这样可以避免try-except意外的截获本来不属于它们保护的那些代码抛出的异常

所以,except里面的代码是错误的时候执行的;else里面的代码是正确的时候执行的;finally里面的代码是无论如何都会执行的(后面讲到)

发生异常时,可能会有一个附属值,作为异常的参数存在。这个参数是否存在、是什么类型,依赖于异常的类型。通常可以为except子句指定(as)一个变量,可以直接print出来看

异常处理器不仅仅处理那些在 try 子句中立刻发生的异常,也会处理那些 try 子句中调用的函数内部发生的异常

    >>> def this_fails():
    ...     x = 1/0
    ...
    >>> try:
    ...     this_fails()
    ... except ZeroDivisionError as err:
    ...     print('Handling run-time error:', err)
    ...
    Handling run-time error: int division or modulo by zero

raise语句允许程序员强制抛出一个指定的异常(必须是一个异常实例或异常类)

如果你需要明确一个异常是否抛出,但不想处理它,raise语句可以让你很简单的重新抛出该异常(一些监控工具的实现)

    >>> try:
    ...     raise NameError('HiThere')
    ... except NameError:
    ...     print('An exception flew by!')
    ...     raise
    ...
    ''' result:
    An exception flew by!
    Traceback (most recent call last):
      File "<stdin>", line 2, in ?
    NameError: HiThere
    '''

用户可以自定义异常,异常类通常应该直接或间接从Exception类派生,下面是一个例子,更多例子看这里

    >>> class MyError(Exception):
    ...     def __init__(self, value):
    ...         self.value = value
    ...     def __str__(self):
    ...         return repr(self.value)
    ...
    >>> try:
    ...     raise MyError(2*2)
    ... except MyError as e:
    ...     print('My exception occurred, value:', e.value)
    ...
    ''' result:
    My exception occurred, value: 4
    >>> raise MyError('oops!')
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    __main__.MyError: 'oops!'
    '''

前面说到的finally语句,是无论什么情况都会执行的功能,通常放的是清理行为,如果遇到异常,执行完这段清理语句之后就结束了,下面是一个混合try-except-else-finally的代码块

    >>> def divide(x, y):
    ...     try:
    ...         result = x / y
    ...     except ZeroDivisionError:
    ...         print("division by zero!")
    ...     else:
    ...         print("result is", result)
    ...     finally:
    ...         print("executing finally clause")
    ...
    >>> divide(2, 1)
    result is 2
    executing finally clause
    >>> divide(2, 0)
    division by zero!
    executing finally clause
    >>> divide("2", "1")
    executing finally clause
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "<stdin>", line 3, in divide
    TypeError: unsupported operand type(s) for /: 'str' and 'str'

在真实场景的应用程序中,finally子句用于释放外部资源(文件 或网络连接之类的),无论它们的使用过程中是否出错

有些对象定义了标准的清理行为,无论对象操作是否成功,不再需要该对象的时候就会起作用。下面有两段代码:

    # para 1
    for line in open("myfile.txt"):
        print(line)

    # para 2
    with open("myfile.txt") as f:
        for line in f:
            print(line)

区别在于,第一段代码没有关闭打开的文件,而with语句可以使文件之类的对象确保总能及时准确地进行清理。在第二段语句执行后,文件对象总会被关闭,即使是在处理文件中的数据时出错也一样可以

关于作用域的示例(注意变量的查找顺序),以下例子:

    def scope_test():
        def do_local():
            spam = "local spam"
        def do_nonlocal():
            nonlocal spam
            spam = "nonlocal spam"
        def do_global():
            global spam
            spam = "global spam"
        spam = "test spam"
        do_local()
        print("After local assignment:", spam)
        do_nonlocal()
        print("After nonlocal assignment:", spam)
        do_global()
        print("After global assignment:", spam)

    scope_test()
    print("In global scope:", spam)

类定义语法

类定义就像函数定义,要先执行才能生效

    class ClassName:
        <statement-1>
        .
        .
        .
        <statement-N>

Python的类对象支持属性引用和实例化,如:

    class MyClass:
        """A simple example class"""
        i = 12345
        def f(self):
            return 'hello world'

属性引用: MyClass.i, MyClass.f, MyClass.__doc__
实例化: x = MyClass()

构造函数的定义:

    def __init__(self[, other params]):
        pass

类方法的第一个参数一定是self,在调用的时候省略传入,代指实例本身

Python的实例对象唯一可用的操作是属性引用。和局部变量一样,数据属性不需要声明,第一次使用时它们就会生成

在调用方法的时候,如:x.f()事实上是调用了MyClass.f(x),这就是要在定义类方法的时候加上参数self的原因

类和实例变量

一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法

    class Dog:

        kind = 'canine'         # class variable shared by all instances

        def __init__(self, name):
            self.name = name    # instance variable unique to each instance

    >>> d = Dog('Fido')
    >>> e = Dog('Buddy')
    >>> d.kind                  # shared by all dogs
    'canine'
    >>> e.kind                  # shared by all dogs
    'canine'
    >>> d.name                  # unique to d
    'Fido'
    >>> e.name                  # unique to e
    'Buddy'

这样会出现,可变对象作共享数据的问题

    class Dog:

        tricks = []             # mistaken use of a class variable

        def __init__(self, name):
            self.name = name

        def add_trick(self, trick):
            self.tricks.append(trick)

    >>> d = Dog('Fido')
    >>> e = Dog('Buddy')
    >>> d.add_trick('roll over')
    >>> e.add_trick('play dead')
    >>> d.tricks                # unexpectedly shared by all dogs
    ['roll over', 'play dead']

正确的做法是,将这个列表作为一个实例变量,即放入构造方法中赋值

    class Dog:

        def __init__(self, name):
            self.name = name
            self.tricks = []    # creates a new empty list for each dog

        def add_trick(self, trick):
            self.tricks.append(trick)

    >>> d = Dog('Fido')
    >>> e = Dog('Buddy')
    >>> d.add_trick('roll over')
    >>> e.add_trick('play dead')
    >>> d.tricks
    ['roll over']
    >>> e.tricks
    ['play dead']

类的继承

派生类的定义例子:

    class DerivedClassName(BaseClassName):
        <statement-1>
        .
        .
        .
        <statement-N>

派生类定义的执行过程和基类是一样的。构造派生类对象时,就记住了基类。这在解析属性引用的时候尤其有用:如果在类中找不到请求调用的属性,就搜索基类

如果基类是由别的类派生而来,这个规则会递归的应用上去。方法引用按如下规则解析:搜索对应的类属性,必要时沿基类链逐级搜索,如果找到了函数对象这个方法引用就是合法的

派生类可能会覆盖其基类的方法。因为方法调用同一个对象中的其它方法时没有特权,基类的方法调用同一个基类的方法时,可能实际上最终调用了派生类中的覆盖方法

派生类中调用基类方法,这时需要传入self参数了,BaseClassName.methodname(self, arguments)

判断类或者实例之间的关系

  • 函数isinstance()用于检查实例类型:isinstance(obj, int)只有在obj.__class__int或其它从int继承的类型
  • 函数issubclass()用于检查类继承:issubclass(bool, int)True,因为boolint的子类
  • 然而,issubclass(float, int)False,因为float不是int的子类

Python同样有限的支持多继承形式,例子如下:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>

搜索顺序:如果在DerivedClassName(示例中的派生类)中没有找到某个属性,就会搜索Base1,然后(递归的)搜索其基类,如果最终没有找到,就搜索Base2,以此类推

上面的搜索顺序有一个问题,如果多个Base拥有相同的基类,就会发生重复访问基类的情况,这时,可以用super()来动态改变解析顺序

为了防止重复访问基类,通过动态的线性化算法,每个类都按从左到右的顺序特别指定了顺序,每个祖先类只调用一次

私有变量

Python中不存在只能从对象内部访问的实例变量,一般规定私有变量以一个下划线开头命名,例如_spam。此外,形如__spam,以两个下划线开头,后面至多只有一个下划线的变量,会被替换为_classname__spam

    >>> class Ms1:
    ...     def _update(self):
    ...             print(345)
    ...     def __hello(self):
    ...             print("hello world")
    ...
    >>> m = Ms1()
    >>> m._Ms1__hello()
    hello world
    >>> m.__hello()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Ms1' object has no attribute '__hello'
    >>> m._Ms1__hello()
    hello world
    >>> m._update()
    345

迭代器

大多数容器对象都可以用for遍历,如for elem in [1, 2, 3]。在后台,for语句在容器对象中调用iter(),该函数返回一个定义了__next__()方法的迭代器对象,它在容器中逐一访问元素,当没有后续的元素时,__next__()抛出一个StopIteration异常来通知for语句结束循环

可以使用内建的next()函数来调用__next__()方法

    >>> s = 'abc'
    >>> it = iter(s)
    >>> it
    <iterator object at 0x00A1DB50>
    >>> next(it)
    'a'
    >>> next(it)
    'b'
    >>> next(it)
    'c'
    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
        next(it)
    StopIteration

根据之前所说的原理,可以很容易的为自己的类添加迭代器行为,以下例子:

    class Reverse:
        """Iterator for looping over a sequence backwards."""
        def __init__(self, data):
            self.data = data
            self.index = len(data)
        def __iter__(self):
            return self
        def __next__(self):
            if self.index == 0:
                raise StopIteration
            self.index = self.index - 1
            return self.data[self.index]

    >>> rev = Reverse('spam')
    >>> iter(rev)
    <__main__.Reverse object at 0x00A1DB50>
    >>> for char in rev:
    ...     print(char)
    ...
    m
    a
    p
    s

生成器

生成器是创建迭代器的简单而强大的工具,它们写起来就像是正规的函数,需要返回数据的时候使用yield语句

    def reverse(data):
        for index in range(len(data)-1, -1, -1):
            yield data[index]

    >>> for char in reverse('golf'):
    ...     print(char)
    ...
    f
    l
    o
    g

前面中描述了基于类的迭代器,它能做的每一件事生成器也能做到。因为自动创建了__iter__()__next__()方法,生成器显得如此简洁。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。综上所述,这些功能使得编写一个正规函数成为创建迭代器的最简单方法

更多关于标准库、包管理等的内容见手册

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,914评论 0 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • Python是一种对代码风格很重视的语言,从缩进就能看出这一点,Python强调易于理解。最近在负责代码重构的工作...
    知曰阅读 10,700评论 1 85
  • 前言 ||| 第二章 使用ArcPy编写脚本 Python支持大部分在其他语言中出现的编程结构。在本章内容中,我们...
    muyan阅读 89,805评论 10 55
  • 那一年,2008。南方大雪灾。还在北方冰天雪地里搓手取暖的我,想象着南方的雪到底是多大。 那一年,2008。西...
    情風古陽草阅读 191评论 0 0