慕课python3.5学习笔记

本文章中有部分代码为 python2 慕课 python 入门 慕课 python 进阶

布尔值

  • 布尔值可以用 and、or 和 not 运算。
  • and 运算是与运算,只有所有都为 True,and 运算结果才是 True。
  • or 运算是或运算,只要其中有一个为 True,or 运算结果就是 True。
  • not 运算是非运算,它是一个单目运算符,把 True 变成 False,False 变成 True。
  • 空值是 Python 里一个特殊的值,用 None 表示。None 不能理解为 0,因为 0 是有意义的,而 None 是一个特殊的空值。

定义动态变量

(较类似指针#具体内容可以查看这个网址从 C/C++软件工程师角度看 Python 的动态类型

  a=1
  print (a)
  • 同一个变量可以反复赋值,而且可以是不同类型的变量

    • 当我们写:a = 'ABC'时,Python 解释器干了两件事情:

      1. 在内存中创建了一个'ABC'的字符串;
      2. 在内存中创建了一个名为 a 的变量,并把它指向'ABC'。
    • 也可以把一个变量 a 赋值给另一个变量 b,这个操作实际上是把变量 b 指向变量 a 所指向的数据

  • 这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。

静态语言

  • 静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。

python 字符串

  • Python 字符串用\进行转义。

    • 要表示字符串 Bob said "I'm OK".

      'Bob said \"I\'m OK\".'
      
    • \n 表示换行

    • \t 表示一个制表符

    • \ 表示 \ 字符本身

  • 注意:转义字符 \ 不计入字符串的内容中。

  • 如果一个字符串包含很多需要转义的字符,对每一个字符都进行转义会很麻烦。为了避免这种情况,我们可以在字符串前面加个前缀 r ,表示这是一个 raw 字符串,里面的字符就不需要转义了。
    例如:

    r'\(~_~)/ \(~_~)/'
    
    • 但是 r'...'表示法不能表示多行字符串,也不能表示包含'和 "的字符串
  • 如果要表示多行字符串,可以用'''...'''表示:

    '''Line 1
      Line 2
      Line 3'''
    
    • 上面这个字符串的表示方法和下面的是完全一样的:

      'Line 1\nLine 2\nLine 3'

  • 还可以在多行字符串前面添加 r ,把这个多行字符串也变成一个 raw 字符串,但是要注意'''之后没有空格即使是这样(r''''xx''''):

    r'''Python is created by "Guido".
      It is free and easy to learn.
      Let's start learn Python in imooc!'''
    

Unicode

  • Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题了。

  • python3 默认 unicode,so,不用 u'..'

  • Python2 添加了对 Unicode 的支持,以 Unicode 表示的字符串用 u'...'表示,
    比如:

    print u'中文'
    中文
    
    • 注意:python2 不加 u ,中文就不能正常显示
  • Unicode 字符串除了多了一个 u 之外,与普通字符串没啥区别,转义字符和多行表示法仍然有效:

    • 转义:

      u'中文\n日文\n韩文'
      
    • 多行:

      u'''第一行
        第二行'''
      
    • raw+多行:

      ur'''Python的Unicode字符串支持"中文",
        "日文",
        "韩文"等多种语言'''
      
  • 如果中文字符串在 Python 环境下遇到 UnicodeDecodeError,这是因为.py 文件保存的格式有问题。可以在第一行添加注释

    # -*- coding: utf-8 -*-
    
    • 目的是告诉 Python 解释器,用 UTF-8 编码读取源代码。然后用 Notepad++ 另存为... 并选择 UTF-8 格式保存。

and,or,not

  • 在 Python 中,布尔类型还可以与其他数据类型做 and、or 和 not 运算,
    请看下面的代码:

    a = True
    print a and 'a=T' or 'a=F'
    
    • 计算结果不是布尔类型,而是字符串 'a=T',这是为什么呢?

    • 因为 Python 把 0、空字符串''和 None 看成 False,其他数值和非空字符串都看成 True,所以: True and 'a=T' 计算结果是 'a=T' 继续计算 'a=T' or 'a=F' 计算结果还是 'a=T'

    • 要解释上述结果,又涉及到 and 和 or 运算的一条重要法则:
      短路计算:

      1. 在计算 a and b 时,如果 a 是 False,则根据与运算法则,整个结果必定为 False,因此返回 a;如果 a 是 True,则整个计算结果必定取决与 b,因此返回 b。

      2. 在计算 a or b 时,如果 a 是 True,则根据或运算法则,整个计算结果必定为 True,因此返回 a;如果 a 是 False,则整个计算结果必定取决于 b,因此返回 b。

    所以 Python 解释器在做布尔运算时,只要能提前确定计算结果,它就不会往后算了,直接返回结果。
    
  • 非运算:把 True 变为 False,或者把 False 变为 True:

    • not True # ==> False
    • not False # ==> True

List

  • Python 内置的一种数据类型是列表:list。list 是一种有序的集合,可以随时添加和删除其中的元素。

    >>> classmates = ['Michael', 'Bob', 'Tracy']
    >>> classmates # 打印classmates变量的内容
    ['Michael', 'Bob', 'Tracy']
    
  • 由于 Python 是动态语言,所以 list 中包含的元素并不要求都必须是同一种数据类型,我们完全可以在 list 中包含各种数据:

    >>> L = ['Michael', 100, True]
    
  • 一个元素也没有的 list,就是空 list:

    >>> empty_list = []
    
  • -1 索引表示最后一个元素

    >>> L = ['Adam', 'Lisa', 'Bart']
    >>> print L[-1]
    Bart
    Bart同学表示躺枪。
    
    • 类似的,倒数第二用 -2 表示,倒数第三用 -3 表示,倒数第四用 -4 表示:

      >>> print L[-2]
      Lisa
      >>> print L[-3]
      Adam
      >>> print L[-4]
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      IndexError: list index out of range
      
      • L[-4] 报错了,因为倒数第四不存在,一共只有 3 个元素。
      • 使用倒序索引时,也要注意不要越界。
    • 现在,班里有 3 名同学:

      >>> L = ['Adam', 'Lisa', 'Bart']
      
    • 今天,班里转来一名新同学 Paul,如何把新同学添加到现有的 list 中呢?

      1. 第一个办法是用 list 的 append() 方法,把新同学追加到 list 的末尾:

        >>> L = ['Adam', 'Lisa', 'Bart']
        >>> L.append('Paul')
        >>> print L
        ['Adam', 'Lisa', 'Bart', 'Paul']
        
        • append()总是把新的元素添加到 list 的尾部。
      2. list 的 insert()方法,它接受两个参数,第一个参数是索引号,第二个参数是待添加的新元素:

        >>> L = ['Adam', 'Lisa', 'Bart']
        >>> L.insert(0, 'Paul')
        >>> print L
        ['Paul', 'Adam', 'Lisa', 'Bart']
        
        • L.insert(0, 'Paul') 的意思是,'Paul'将被添加到索引为 0 的位置上(也就是第一个),而原来索引为 0 的 Adam 同学,以及后面的所有同学,都自动向后移动一位。
    • Paul 同学刚来几天又要转走了,那么我们怎么把 Paul 从现有的 list 中删除呢?

      • 要把 Paul 踢出 list,我们就必须先定位 Paul 的位置。由于 Paul 的索引是 3,因此,用 pop(3)把 Paul 删掉:

        >>> L.pop(3)
        'Paul'
        >>> print L
        ['Adam', 'Lisa', 'Bart']
        
      • 如果 Paul 同学排在最后一个,我们可以用 list 的 pop()方法删除:

        >>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
        >>> L.pop()
        'Paul'
        >>> print L
        ['Adam', 'Lisa', 'Bart']
        
        • pop()方法总是删掉 list 的最后一个元素,并且它还返回这个元素,所以我们执行 L.pop() 后,会打印出 'Paul'。
    • 假设现在班里仍然是 3 名同学:

      >>> L = ['Adam', 'Lisa', 'Bart']
      
    • 现在,Bart 同学要转学走了,碰巧来了一个 Paul 同学,要更新班级成员名单,

      • 我们可以先把 Bart 删掉,再把 Paul 添加进来。

      • 另一个办法是直接用 Paul 把 Bart 给替换掉:

        >>> L[2] = 'Paul'
        >>> print L
        >>> L = ['Adam', 'Lisa', 'Paul']
        
      • 对 list 中的某一个索引赋值,就可以直接用新的元素替换掉原来的元素,list 包含的元素个数保持不变。

      • 由于 Bart 还可以用 -1 做索引,因此,下面的代码也可以完成同样的替换工作:

        >>> L[-1] = 'Paul'
        

创建 tuple

  • tuple 是另一种有序的列表,中文翻译为“ 元组 ”。tuple 和 list 非常类似,但是,tuple 一旦创建完毕,就不能修改了。

    • 同样是表示班里同学的名称,用 tuple 表示如下:

      >>> t = ('Adam', 'Lisa', 'Bart')
      
  • 创建 tuple 和创建 list 唯一不同之处是用( )替代了[ ]。

    • 现在,这个 t 就不能改变了,tuple 没有 append()方法,也没有 insert()和 pop()方法。所以,新同学没法直接往 tuple 中添加,老同学想退出 tuple 也不行。
  • 获取 tuple 元素的方式和 list 是一模一样的,我们可以正常使用 t[0],t[-1]等索引方式访问元素,但是不能赋值成别的元素,
    不信可以试试:

    >>> t[0] = 'Paul'
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
    
    • 创建包含 1 个元素的 tuple 呢?来试试:

      >>> t = (1)
      >>> print t
      1
      
      • 好像哪里不对!t 不是 tuple ,而是整数 1。为什么呢?
      • 因为()既可以表示 tuple,又可以作为括号表示运算时的优先级,结果 (1) 被 Python 解释器计算出结果 1,导致我们得到的不是 tuple,而是整数 1。
  • 因为用()定义单元素的 tuple 有歧义,所以 Python 规定,单元素 tuple 要多加一个逗号“,”,这样就避免了歧义:

    >>> t = (1,)
    >>> print t
    (1,)
    
    • Python 在打印单元素 tuple 时,也自动添加了一个“,”,为了更明确地告诉你这是一个 tuple。

    • 多元素 tuple 加不加这个额外的“,”效果是一样的:

      >>> t = (1, 2, 3,)
      >>> print t
      (1, 2, 3)
      
  • 可变”的 tuple

    • 前面我们看到了 tuple 一旦创建就不能修改。现在,我们来看一个“可变”的 tuple:

      >>> t = ('a', 'b', ['A', 'B'])
      
    • 注意到 t 有 3 个元素:'a','b'和一个 list:['A', 'B']。list 作为一个整体是 tuple 的第 3 个元素。list 对象可以通过 t[2] 拿到:

      >>> L = t[2]
      
    • 然后,我们把 list 的两个元素改一改:

      >>> L[0] = 'X'     
      >>> L[1] = 'Y'
      
    • 再看看 tuple 的内容:

      >>> print t
      ('a', 'b', ['X', 'Y'])
      
    • 不是说 tuple 一旦定义后就不可变了吗?怎么现在又变了?

    1. 定义的时候 tuple 包含的 3 个元素: 当我们把 list 的元素'A'和'B'修改为'X'和'Y'后 表面上看,tuple 的元素确实变了,但其实变的不是 tuple 的元素,而是 list 的元素。
    2. tuple 一开始指向的 list 并没有改成别的 list,所以,tuple 所谓的“不变”是说,tuple 的每个元素,指向永远不变。 即指向'a',就不能改成指向'b',指向一个 list,就不能改成指向其他对象,但指向的这个 list 本身是可变的!

Python 代码的缩进规则

  • Python 代码的缩进规则。具有相同缩进的代码被视为代码块,上面的 3,4 行 print 语句就构成一个代码块(但不包括第 5 行的 print)。如果 if 语句判断为 True,就会执行这个代码块。 缩进请严格按照 Python 的习惯写法:4 个空格,不要使用 Tab,更不要混合 Tab 和空格,否则很容易造成因为缩进引起的语法错误。

if else

  • 利用 if ... else ... 语句,我们可以根据条件表达式的值为 True 或者 False ,分别执行 if 代码块或者 else 代码块。

    • 注意: else 后面有个“:”
  • 用 if ... 多个 elif ... else ... 的结构:

    if age >= 18:
       print 'adult'
    elif age >= 6:
       print 'teenager'
    elif age >= 3:
       print 'kid'
    else:
       print 'baby'
    
    • elif 意思就是 else if。这样一来,我们就写出了结构非常清晰的一系列条件判断。

for

  • Python 的 for 循环就可以依次把 list 或 tuple 的每个元素迭代出来:

    L = ['Adam', 'Lisa', 'Bart']
    for name in L:
        print name
    
    • 注意: name 这个变量是在 for 循环中定义的,意思是,依次取出 list 中的每一个元素,并把元素赋值给 name,然后执行 for 循环体(就是缩进的代码块)。

while 循环

  • 和 for 循环不同的另一种循环是 while 循环,while 循环不会迭代 list 或 tuple 的元素,而是根据表达式判断循环是否结束。 比如要从 0 开始打印不大于 N 的整数

    n= 10
    x = 0
    while x < n:
        print x
        x = x + 1
    
    • while 循环每次先判断 x < n,如果为 True,则执行循环体的代码块,否则,退出循环。
  • while True 就是一个死循环 True 要大写

break

  • break 退出循环

continue

  • continue 跳过后续循环代码,继续下一次循环。

dict

  • Python 用 dict 表示“名字”-“成绩”的查找表如下:

    _key:value_
    d = {
      'Adam': 95,
      'Lisa': 85,
      'Bart': 59
    }
    
  • 我们把名字称为 key,对应的成绩称为 value,dict 就是通过 key 来查找 value。

  • 花括号 {} 表示这是一个 dict,然后按照 key: value, 写出来即可。最后一个 key: value 的逗号可以省略。

  • 由于 dict 也是集合,len() 函数可以计算任意集合的大小:

    >>> len(d)
    3
    
  • 可以简单地使用 d[key] 的形式来查找对应的 value,这和 list 很像,不同之处是,list 必须使用索引返回对应的元素,而 dict 使用 key:

    >>> print d['Adam']
    95
    >>> print d['Paul']
    Traceback (most recent call last):
    File "index.py", line 11, in <module>
        print d['Paul']
    KeyError: 'Paul'
    
  • 注意: 通过 key 访问 dict 的 value,只要 key 存在,dict 就返回对应的 value。如果 key 不存在,会直接报错:KeyError。

    • 要避免 KeyError 发生,有两个办法:
      1. 第一种是先判断一下 key 是否存在,用 in 操作符:

        if 'Paul' in d:
            print d['Paul']
        

        如果 'Paul' 不存在,if 语句判断为 False,自然不会执行 print d['Paul'] ,从而避免了错误。

      2. 第二种是使用 dict 本身提供的一个 get 方法,在 Key 不存在的时候,返回 None:

        >>> print d.get('Bart')
        59
        >>> print d.get('Paul')
        None
        
  • dict 的特点

    • dict 的第一个特点是查找速度快,无论 dict 有 10 个元素还是 10 万个元素,查找速度都一样。而 list 的查找速度随着元素增加而逐渐下降。 不过 dict 的查找速度快不是没有代价的,dict 的缺点是占用内存大,还会浪费很多内容,list 正好相反,占用内存小,但是查找速度慢。 由于 dict 是按 key 查找,所以,在一个 dict 中,key 不能重复。

    • dict 的第二个特点就是存储的 key-value 序对是没有顺序的!这和 list 不一样:

      d = {
        'Adam': 95,
        'Lisa': 85,
        'Bart': 59
      }
      

      当我们试图打印这个 dict 时

      >>> print d
      {'Lisa': 85, 'Adam': 95, 'Bart': 59}
      

      打印的顺序不一定是我们创建时的顺序,而且,不同的机器打印的顺序都可能不同,这说明 dict 内部是无序的,不能用 dict 存储有序的集合。

    • dict 的第三个特点是作为 key 的元素必须不可变,Python 的基本类型如字符串、整数、浮点数都是不可变的,都可以作为 key。但是 list 是可变的,就不能作为 key。 可以试试用 list 作为 key 时会报什么样的错误。

      • 不可变这个限制仅作用于 key,value 是否可变无所谓:

        {
          '123': [1, 2, 3],  # key 是 str,value是list
          123: '123',  # key 是 int,value 是 str
          ('a', 'b'): True  # key 是 tuple,并且tuple的每个元素都是不可变对象,value是 boolean
        }
        
      • 最常用的 key 还是字符串,因为用起来最方便。

  • 更新 dict

    • dict 是可变的,也就是说,我们可以随时往 dict 中添加新的 key-value。比如已有 dict:

      d = {
        'Adam': 95,
        'Lisa': 85,
        'Bart': 59
      }
      
    • 要把新同学'Paul'的成绩 72 加进去,用赋值语句:

      >>> d['Paul'] = 72
      
      • 如果 key 已经存在,则赋值会用新的 value 替换掉原来的 value

set

  • set 持有一系列元素,set 的元素没有重复,而且是无序的。

  • 创建 set 的方式是调用 set() 并传入一个 list,list 的元素将作为 set 的元素:

    >>> s = set(['A', 'B', 'C'])
    
    • 因为 set 不能包含重复的元素,所以,当我们传入包含重复元素的 list 会怎么样呢?

      >>> s = set(['A', 'B', 'C', 'C'])
      >>> print s
      set(['A', 'C', 'B'])
      >>> len(s)
      3
      
      • 结果显示,set 会自动去掉重复的元素,原来的 list 有 4 个元素,但 set 只有 3 个元素。
  • set 的特点

    • set 的内部结构和 dict 很像,唯一区别是不存储 value,因此,判断一个元素是否在 set 中速度很快。 set 存储的元素和 dict 的 key 类似,必须是不变对象,因此,任何可变对象是不能放入 set 中的。 最后,set 存储的元素也是没有顺序的。

      • 如果事先创建好一个 set,包含'MON' ~ 'SUN':
        weekdays = set(['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'])
        再判断输入是否有效,只需要判断该字符串是否在 set 中;

        x = '???' # 用户输入的字符串
        if x in weekdays:
            print 'input ok'
        else:
            print 'input error'
        

        这样一来,代码就简单多了。

  • 更新 set

    • 由于 set 存储的是一组不重复的无序元素,因此,更新 set 主要做两件事:

      • 一是把新的元素添加到 set 中,二是把已有元素从 set 中删除。
    • 添加元素时,用 set 的 add()方法:

      • 如果添加的元素已经存在于 set 中,add()不会报错,但是不会加进去了:
    • 删除 set 中的元素时,用 set 的 remove()方法:

      • 如果删除的元素不存在 set 中,remove()会报错:
    • 所以用 add()可以直接添加,而 remove()前需要判断。

编写函数

  • 在 Python 中,定义一个函数要使用 def 语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。

    • 我们以自定义一个求绝对值的 my_abs 函数为例:

      def my_abs(x):
          if x >= 0:
              return x
          else:
              return -x
      
      • 如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 None
        return None 可以简写为 return。
  • 返回多值

    import math
    def move(x, y, step, angle):
        nx = x + step * math.cos(angle)
        ny = y - step * math.sin(angle)
        return nx, ny
    
    • 这样我们就可以同时获得返回值:

      >>> x, y = move(100, 100, 60, math.pi / 6)
      >>> print x, y
      >>> 151.961524227 70.0
      
    • 但其实这只是一种假象,Python 函数返回的仍然是单一值:

      >>> r = move(100, 100, 60, math.pi / 6)
      >>> print r
      >>> (151.96152422706632, 70.0)
      
    • 用 print 打印返回结果,原来返回值是一个 tuple!

    • 但是,在语法上,返回一个 tuple 可以省略括号,而多个变量可以同时接收一个 tuple,按位置赋给对应的值,所以,Python 的函数返回多值其实就是返回一个 tuple,但写起来更方便。

  • 汉诺塔问题:

    • 汉诺塔

      A B C
      =| | |
      ==| | |
      ===| | |
      ----- ---- --->
      def move(n, a, b, c):
          if n ==1:
              print a, '-->', c
              return
          move(n-1, a, c, b)
          print a, '-->', c
          move(n-1, b, a, c)
      
    • move(n, a, b, c)表示的是有 n 个盘子,将要由 a 柱子转移到 b 柱子上面去

    • move(盘子数,起始地,中转地,目的地)

    • def move(n, a, b, c):

    • 如果 a 柱子上面只有一个盘子,则直接移到 c 柱子上面去并输出路径,结束递归

      if n == 1:
          print a, '-->', c
          return
      
    • 表示的是将由上到下的 n-1 个盘子从 a 柱子上面移到 b 柱子上面去

      (a上 的n-1个,a---->b)
      move(n-1        ,a, c, b)
      
    • 输出最下面的那个盘子从 a 移到 c 的路径,(a 上的第 n 个,a---->c)

      print a, '-->', c
      
    • 将 b 柱子上面的 n-1 个盘子移动到 c 柱子上面

      (b上n-1个,b---->c)
      move(n-1     ,b, a, c)
      
  • 定义默认参数

    • 定义函数的时候,还可以有默认参数。

    • 我们来定义一个计算 x 的 N 次方的函数并且计算平方的次数最多,我们就可以把 n 的默认值设定为 2:

      def power(x, n=2):
          s = 1
          while n > 0:
              n = n - 1
              s = s * x
          return s
      
    • 这样一来,计算平方就不需要传入两个参数了:

      >>> power(5)
      25
      
    • 由于函数的参数按从左到右的顺序匹配,所以默认参数只能定义在必需参数的后面:

  • 定义(个数)可变参数

    • 如果想让一个函数能接受任意个参数,我们就可以定义一个可变参数:

      def fn(*args):
          print args
      
    • 可变参数的名字前面有个 * 号,我们可以传入 0 个、1 个或多个参数给可变参数:

      >>> fn()
      ()
      >>> fn('a')
      ('a',)
      >>> fn('a', 'b')
      ('a', 'b')
      
    • Python 解释器会把传入的一组参数组装成一个 tuple 传递给可变参数, 因此,在函数内部,直接把变量 args 看成一个 tuple 就好了
      例如:

      def average(*args):
          sum=0.0
          if len(args) == 0:#使用的是args而不是*args
              return sum
          for n in args:
              sum=sum+n
          return sum/len(args)
      

对 list 进行切片

  • 取一个 list 的部分元素是非常常见的操作。比如,一个 list 如下:

    >>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
    
  • 取前 N 个元素,也就是索引为 0-(N-1)的元素,可以用循环:

    >>> r = []
    >>> n = 3
    >>> for i in range(n):
    ...     r.append(L[i])
    
    • 对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python 提供了切片(Slice)操作符,能大大简化这种操作。
  • 对应上面的问题,取前 3 个元素,用一行代码就可以完成切片:
    例:
    注意:索引与索引所代表的元素的区别

  • L[起始索引 a:b],索引 b - 索引 a = 算上索引 a 所代表的一共取多少个元素(即由索引 a 所对应的开始取(b-a)个元素)

    >>> L[0:3]
    ['Adam', 'Lisa', 'Bart']
    
    • L[0:3]表示,从索引 0 开始取,直到索引 3 为止,但不包括索引 3。即索引 0,1,2,正好是 3 个元素。

    • L[0:3]表示:由 0 开始一共取 3-0 个

    • 如果第一个索引是 0,还可以省略:

      >>> L[:3]
      ['Adam', 'Lisa', 'Bart']
      
    • 也可以从索引 1 开始,取出 2 个元素出来:

      >>> L[1:3]
      ['Lisa', 'Bart']
      
    • 只用一个 : ,表示从头到尾:

      >>> L[:]
      ['Adam', 'Lisa', 'Bart', 'Paul']
      
      • 因此,L[:]实际上复制出了一个新 list。
  • 切片操作还可以指定第三个参数:

    >>> L[::2]
    ['Adam', 'Bart']
    
    • 第三个参数表示每 N 个取一个,上面的 L[::2] 会每两个元素取出一个来,也就是隔一个取一个。 即:

      2:   0101010101
            0123456789
            0 2 4 6 8
      
      • 所以:2 是 01,逢 0 就取

        3: 0120120120
            0123456789
            0 3 6 9
        
      • 所以:3 是 012,逢 0 就取

  • 如果把 list 换成 tuple,切片操作完全相同,只是切片的结果也变成了 tuple。

  • 倒序切片

        0 ,  1,  2,  3, 4,  5,  6, 7,  8,  9,
         -10, -9, -8, -7,-6, -5, -4,-3, -2, -1,一个邪恶的未知元素索引
    
    • 所以,如果想表示最后一个元素的话,就必须 L[-1:]

      because,-1+1=0,and 0 是最开始的地方,so,L[-1:](不可以写 0)。但是,在大脑中计算时可以想象为 0) 其他方面和正常切片差不多

      >>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
      >>> L[-2:]
      ['Bart', 'Paul']
      >>> L[:-2]#如果前方为空即为由起点开始不可以0进行计算,但是可以写为L[0:-2]
      ['Adam', 'Lisa']
      >>> L[-3:-1]
      ['Lisa', 'Bart']
      >>> L[-4:-1:2]
      ['Adam', 'Bart']
      
    • 利用倒序切片对 1 - 100 的数列,取出最后 10 个 5 的倍数

      L[4::5][-10:]
      

      记住倒数第一个元素的索引是-1

对字符串切片

  • 字符串 'xxx'和 Unicode 字符串 u'xxx'也可以看成是一种 list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

    >>> 'ABCDEFG'[:3]
    'ABC'
    >>> 'ABCDEFG'[-3:]
    'EFG'
    >>> 'ABCDEFG'[::2]
    'ACEG'
    
  • Python 没有针对字符串的截取函数,只需要切片一个操作就可以完成。

迭代

  • 迭代操作就是对于一个集合,无论该集合是有序还是无序,我们用 for 循环总是可以依次取出集合的每一个元素。

    注意: 集合是指包含一组元素的数据结构,我们已经介绍的包括:

    1. 有序集合:list,tuple,str 和 unicode;
    2. 无序集合:set
    3. 无序集合并且具有 key-value 对:dict
  • 而迭代是一个动词,它指的是一种操作,在 Python 中,就是 for 循环。

  • 迭代与按下标访问数组最大的不同是,后者是一种具体的迭代实现方式,而前者只关心迭代结果,根本不关心迭代内部是如何实现的。

  • 索引迭代

    • Python 中,迭代永远是取出元素本身,而非元素的索引。

    • 对于有序集合,元素确实是有索引的。有的时候,我们确实想在 for 循环中拿到索引,怎么办?

      • 方法是使用 enumerate() 函数:

        >>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
        >>> for index, name in enumerate(L):
        ...     print index, '-', name
        ...
        0 - Adam
        1 - Lisa
        2 - Bart
        3 - Paul
        
        • 使用 enumerate() 函数,我们可以在 for 循环中同时绑定索引 index 和元素 name。但是,这不是 enumerate() 的特殊语法。实际上,enumerate() 函数把: ['Adam', 'Lisa', 'Bart', 'Paul']

        • 变成了类似:[(0, 'Adam'), (1, 'Lisa'), (2, 'Bart'), (3, 'Paul')] 把 list 每一个元素与对应的索引绑在了一起因此,迭代的每一个元素实际上是一个 tuple

          for t in enumerate(L):
              index = t[0]
              name = t[1]
              print index, '-', name
          
          • 如果我们知道每个 tuple 元素都包含两个元素,for 循环又可以进一步简写为:

            for index, name in enumerate(L):
            print index, '-', name

            这样不但代码更简单,而且还少了两条赋值语句。

          • 可见,索引迭代也不是真的按索引访问,而是由 enumerate() 函数自动把每个元素变成 (index, element) 这样的 tuple,再迭代,就同时获得了索引和元素本身。

  • 迭代 dict 的 value

    • 如果我们希望迭代 dict 对象的 value,应该怎么做?

    • dict 对象有一个 values() 方法,这个方法把 dict 转换成一个包含所有 value 的 list,这样,我们迭代的就是 dict 的每一个 value:

      >>> d = {
      ... 'Adam': 95,
      ... 'Lisa': 85,
      ... 'Bart': 59
      ...  }
      >>> print d.values()
      [85, 95, 59]
      
      >>> for v in d.values():
      ...     print v
      
      85
      95
      59
      
    • 如果仔细阅读 Python 的文档,还可以发现,dict 除了 values()方法外,还有一个 itervalues()python3 无此语法 方法,用 itervalues() 方法替代 values() 方法,迭代效果完全一样:

      d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
      print d.itervalues()(python3无此语法)
      <dictionary-valueiterator object at 0x106adbb50>
      
      for v in d.itervalues()(python3无此语法):
          print v
      85
      95
      59
      
    1. values() 方法实际上把一个 dict 转换成了包含 value 的 list。
    2. 但是 itervalues() (python3 无此语法)方法不会转换,它会在迭代过程中依次从 dict 中取出 value,所以 itervalues() 方法比 values() 方法节省了生成 list 所需的内存。
    • 如果一个对象说自己可迭代,那我们就直接用 for 循环去迭代它, 可见,迭代是一种抽象的数据操作,它不对迭代对象内部的数据有任何要求。
  • 迭代 dict 的 key 和 value

    -我们了解了如何迭代 dict 的 key 和 value,那么,在一个 for 循环中,能否同时迭代 key 和 value?
    答案是肯定的。

    • 首先,我们看看 dict 对象的 items() 方法返回的值:

      >>> d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
      >>> print d.items()
      [('Lisa', 85), ('Adam', 95), ('Bart', 59)]
      
    • 可以看到,items() 方法把 dict 对象转换成了包含 tuple 的 list,我们对这个 list 进行迭代,可以同时获得 key 和 value:

      >>> for key, value in d.items():
      ...     print key, ':', value
      ...
      Lisa : 85
      Adam : 95
      Bart : 59
      
    • 和 values() 有一个 itervalues() 类似, items() 也有一个对应的 iteritems(),iteritems() 不把 dict 转换成 list,而是在迭代过程中不断给出 tuple,所以, iteritems() 不占用额外的内存。

生成列表

  • 要生成 list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],我们可以用 range(1, 11):

    >>> range(1, 11)
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
  • 但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?

    1. 方法一是循环:

      >>> L = []
      >>> for x in range(1, 11):
      ...    L.append(x * x)
      ...
      >>> L
      [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
      
    2. 但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的 list:

      >>> [x * x for x in range(1, 11)]
      [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
      
  • 提示:range(1, 100, 2) 可以生成 list [1, 3, 5, 7, 9,...],与切片类似 这种写法就是 Python 特有的列表生成式。利用列表生成式,可以以非常简洁的代码生成 list。

  • 写列表生成式时,把要生成的元素 x * x 放到前面,后面跟 for 循环,就可以把 list 创建出来,十分有用,多写几次,很快就可以熟悉这种语法

复杂表达式

  • 使用 for 循环的迭代不仅可以迭代普通的 list,还可以迭代 dict。

    • 假设有如下的 dict:

      d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
      
    • 完全可以通过一个复杂的列表生成式把它变成一个 HTML 表格:

      tds = ['<tr><td>%s</td><td>%s</td></tr>' % (name, score) for name, score in d.items()]
      print '<table>'
      print '<tr><th>Name</th><th>Score</th><tr>'
      print '\n'.join(tds)
      print '</table>'
      
    • 注:字符串可以通过 % 进行格式化,用指定的参数替代 %s。字符串的 join()方法可以把一个 list 拼接成一个字符串。

    • 把打印出来的结果保存为一个 html 文件,就可以在浏览器中看到效果了:

      <table>
      <tr><th>Name</th><th>Score</th><tr>
      <tr><td>Lisa</td><td>85</td></tr>
      <tr><td>Adam</td><td>95</td></tr>
      <tr><td>Bart</td><td>59</td></tr>
      </table>
      

条件过滤

  • 列表生成式的 for 循环后面还可以加上 if 判断。例如:

    • 如果我们想要偶数的平方,可以加上 if 来筛选:

      >>> [x * x for x in range(1, 11) if x % 2 == 0]
      [4, 16, 36, 64, 100]
      

多层表达式

  • for 循环可以嵌套,因此,在列表生成式中,也可以用多层 for 循环来生成列表。

    • 对于字符串 'ABC' 和 '123',可以使用两层循环,生成全排列:

      >>> [m + n for m in 'ABC' for n in '123']
      ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
      
  • 翻译成循环代码就像下面这样:

    L = []
    for m in 'ABC':
        for n in '123':
            L.append(m + n)
    
  • list 如下:

    L = ['Adam', 'Lisa', 'Paul', 'Bart']
    

    Paul 的索引是 2,Bart 的索引是 3,如果我们要把 Paul 和 Bart 都删掉, 请解释下面的代码为什么不能正确运行:

    L.pop(2)
    L.pop(3)
    

    怎样调整代码可以把 Paul 和 Bart 都正确删除掉?

    1. 当首先删除索引为 2 的 Paul 时,L 变成了:

      ['Adam', 'Lisa', 'Bart']
      

      这时,注意到 Bart 的索引已经从原来的 3 变成 2 了!

  • 参考代码:

    L = ['Adam', 'Lisa', 'Paul', 'Bart']
    L.pop(3)#由后到前
    L.pop(2)
    print L
    

set,list,tuple,dict

set

  • 添加元素时,用 set 的 add()方法
  • 删除 set 中的元素时,用 set 的 remove()方法

list

  • append()总是把新的元素添加到 list 的尾部。
  • insert()方法,它接受两个参数,第一个参数是索引号,第二个参数是待添加的新元素
  • 用 pop(索引)把索引对应的元素删掉,并且它还返回这个元素
  • pop()方法总是删掉 list 的最后一个元素,并且它还返回这个元素

tuple

  • tuple 一旦创建完毕,就不能修改了。

dict

  • 用 pop(索引)把索引对应的元素删掉,并且它还返回这个元素 dict[key]=value

  • 注意 python 内层函数可以访问外层函数中定义 的变量,但不能重新赋值(rebind)

高阶函数

  • 把函数作为参数
    例:

    import math
    def add(x, y, f):
        return f(x) + f(y)
    print add(25, 9,math.sqrt)
    
  • map()函数

    • map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list, 并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

    • 例如,对于list [1, 2, 3, 4, 5, 6, 7, 8, 9] 如果希望把 list 的每个元素都作平方,就可以用 map()函数:

    • 因此,我们只需要传入函数 f(x)=x*x,就可以利用 map()函数完成这个计算

      def fun(x):
          return x*x
      
      print map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])#python2的map()
      print(list(map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])))#python3的map()
      
  • reduce()函数

    • reduce()函数也是 Python 内置的一个高阶函数。reduce()函数接收的参数和 map()类似, 一个函数 f,一个 list,但行为和 map()不同,reduce()传入的函数 f 必须接收两个参数, reduce()对 list 的每个元素反复调用函数 f,并返回最终结果值。

    • 例如,编写一个 f 函数,接收 x 和 y,返回 x 和 y 的和:

      def f(x, y):
          return x + y
      
    • 调用 reduce(f, [1, 3, 5, 7, 9])时,reduce 函数将做如下计算:
      先计算头两个元素:f(1, 3),结果为 4;
      再把结果和第 3 个元素计算:f(4, 5),结果为 9;
      再把结果和第 4 个元素计算:f(9, 7),结果为 16;
      再把结果和第 5 个元素计算:f(16, 9),结果为 25;
      由于没有更多的元素了,计算结束,返回结果 25。

    • 上述计算实际上是对 list 的所有元素求和。虽然 Python 内置了求和函数 sum(),但是,利用 reduce()求和也很简单。

    • reduce()还可以接收第 3 个可选参数,作为计算的初始值。如果把初始值设为 100,计算:

      reduce(f, [1, 3, 5, 7, 9], 100)###python2
      
      import functools###python3
      functools.reduce(f, [1, 3, 5, 7, 9], 100)
      
    • 结果将变为 125,因为第一轮计算是:

    • 计算初始值和第一个元素:f(100, 1),结果为 101。

  • filter()函数

    • filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个 list, 这个函数 f 的作用是对每个元素进行判断,返回 True 或 False,

    • filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新 list。

    • 例如,要从一个 list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,
      首先,要编写一个判断奇数的函数:

      def is_odd(x):
          return x % 2 == 1
      
    • 然后,利用 filter()过滤掉偶数:

      filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
      
    • 结果:[1, 7, 9, 17]

    • 利用 filter(),可以完成很多有用的功能,例如,删除 None 或者空字符串:

      def is_not_empty(s):
          return s and len(s.strip()) > 0
          filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])
      
    • 结果:['test', 'str', 'END']

    • 注意: s.strip(rm) 删除 s 字符串中开头、结尾处的 rm 序列的字符。

    • 当 rm 为空时,默认删除空白符(包括'\n', '\r', '\t', ' '),如下:

      a = '     123'
      a.strip()
      
    • 结果:'123'

      a='\t\t123\r\n'
      a.strip()
      
    • 结果:'123'

  • 自定义排序函数

    • Python 内置的 sorted()函数可对 list 进行排序:

      >>> sorted([36, 5, 12, 9, 21])
      [5, 9, 12, 21, 36]
      
    • 但 sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,

    • 比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面, 返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

    • 因此,如果我们要实现倒序排序,只需要编写一个 reversed_cmp 函数:

      def reversed_cmp(x, y):
          if x > y:
          return -1
      if x < y:
          return 1
      return 0
      
    • 这样,调用 sorted() 并传入 reversed_cmp 就可以实现倒序排序:

      >>> sorted([36, 5, 12, 9, 21], reversed_cmp)
      [36, 21, 12, 9, 5]
      
    • python3

      >>> import functools
      >>> sorted([36, 5, 12, 9, 21], key=functools.cmp_to_key(reversed_cmp))
      在python3中其实可以
      >>> sorted([36, 5, 12, 9, 21], reverse=True)#记住True的T要大写
      可以用key=abs来排列绝对值
      
    • sorted()也可以对字符串进行排序,字符串默认按照 ASCII 大小来比较:

      >>> sorted(['bob', 'about', 'Zoo', 'Credit'])
      ['Credit', 'Zoo', 'about', 'bob']
      

      'Zoo'排在'about'之前是因为'Z'的 ASCII 码比'a'小。

    • python3

      >>> sorted(['bob', 'about', 'Zoo', 'Credit'])
      如果想忽略大小写可以调用upper或lower函数
      >>> sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.upper)
      
  • 返回函数

    • Python 的函数可以返回函数!

    • 定义一个函数 f(),我们让它返回一个函数 g,可以这样写:

      def f():
          print 'call f()...'
      def g():
          print 'call g()...'
      return g # 返回函数g
      
    • 返回函数可以把一些计算延迟执行。

    • 由于可以返回函数,我们在后续代码里就可以决定到底要不要调用该函数

  • 闭包

    • 内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
    • 闭包的特点是返回的函数还引用了外层函数的局部变量,
    • 所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变
    • 例如:具体细节请看错题 2
  • 匿名函数

    • 以 map()函数为例,计算 f(x)=x2 时,除了定义一个 f(x)的函数外, 还可以直接传入匿名函数:

      >>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])###python2
      [1, 4, 9, 16, 25, 36, 49, 64, 81]
      
      >>> list( map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))###python3
      [1, 4, 9, 16, 25, 36, 49, 64, 81]
      
    • 通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:

      def f(x):
          return x * x
      
    • 关键字 lambda 表示匿名函数,冒号前面的 x 表示函数参数。

    • 匿名函数有个限制,就是只能有一个表达式,不写 return,返回值就是该表达式的结果。

    • 返回函数的时候,也可以返回匿名函数:

      >>> myabs = lambda x: -x if x < 0 else x
      >>> myabs(-1)
      1
      >>> myabs(1)
      1
      
  • 装饰器 decorator

    • 例:

      def f(x):
          return x*2
      
      def new_fn(f):#装饰器函数
          def fn(x):
              print f._name_+'say 'hello''
              return f(x)
          return fn
      
    • 所以调用 new_fn(f)时,

      • 1.定义了 fn(x)
      • 2.return fn 即调用了 fn
      • so,
        • 3.print
        • 4.return f(x)
        • 5.f=new_fn(f)
        • 6.print f1(5)#f1 的原始定义函数被彻底隐藏了
    • python 内置的@语法就是为了简化装饰器调用

  • 编写无参数 decorator

    • 使用 decorator 用 Python 提供的 @ 语法, 这样可以避免手动编写 f = decorate(f) 这样的代码。

    • 一个@log 的定义:

      def log(f):
          def fn(x):
              print 'call ' + f.__name__ + '()...'
              return f(x)
          return fn
      
    • 对于阶乘函数

      @log
      def factorial(n):
          return reduce(lambda x,y: x*y, range(1, n+1))
      print factorial(10)
      

      结果:

      call factorial()...
      3628800
      
    • 但是,对于参数不是一个的函数,调用将报错:

      @log
      def add(x, y):
          return x + y
      print add(1, 2)
      
    • 因为 add() 函数需要传入两个参数,但是 @log 写死了只含一个参数的返回函数。

    • 要让 @log 自适应任何参数定义的函数,可以利用 Python 的 *args 和 **kw, 保证任意个数的参数总是能正常调用:

      def log(f):
          def fn(*args, \*\*kw):
              print 'call ' + f.**name** + '()...'
              return f(*args, \*\*kw)
          return fn
      

    现在,对于任意函数,@log 都能正常工作。

  • 编写带参数 decorator

    • 考察上一节的 @log 装饰器:

      def log(f):
          def fn(x):
              print 'call ' + f.__name__ + '()...'
              return f(x)
          return fn
      
    • 发现对于被装饰的函数,log 打印的语句是不能变的(除了函数名)。

    • 如果有的函数非常重要,希望打印出'[INFO] call xxx()...',有的函数不太重要, 希望打印出'[DEBUG] call xxx()...',这时,log 函数本身就需要传入'INFO'或'DEBUG'这样的参数,
      类似这样:

      @log('DEBUG')
      def my_func():
          pass
      
    • 所以,带参数的 log 函数首先返回一个 decorator 函数,再让这个 decorator 函数接收 my_func 并返回新函数:

      def log(name):
          def log_decorator(fun):
              def wrapper(*args, **kw):
                  print '[%s] %s()...' % (name, fun.__name__)
                  return f(*args, **kw)
              return wrapper
          return log_decorator
      
      @log('DEBUG')
      def test():
          pass
      print test()
      

      执行结果:

      >>> test()
      [DEBUG] test()...
      None
      
  • 完善 decorator

    • 有 decorator 的情况下,打印函数名:

      def log(f):
          def wrapper(*args, **kw):
              print 'call...'
              return f(*args, **kw)
          return wrapper
      
      @log
      def f2(x):
          pass
      print f2.__name__
      
      >>> f2._name_
      wrapper
      
    • 可见,由于 decorator 返回的新函数函数名已经不是'f2',而是@log 内部定义的'wrapper'。 这对于那些依赖函数名的代码就会失效。decorator 还改变了函数的__doc__等其它属性。 如果要让调用者看不出一个函数经过了@decorator 的“改造”, 就需要把原函数的一些属性复制到新函数中:

    • 例:

      def log(f):
          def wrapper(*args, **kw):
              print 'call...'
              return f(*args, **kw)
          wrapper.__name__ = f.__name__
          wrapper.__doc__ = f.__doc__#############这里的__是两个_
          return wrapper
      
    • 这样写 decorator 很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上, 所以 Python 内置的 functools 可以用来自动化完成这个“复制”的任务:

    • 例:

      import functools
      def log(f):
          @functools.wraps(f)###记住@functools.wraps()的位置在刚把函数当作参数的函数的第一行
          def wrapper(*args, **kw):
              print 'call...'
              return f(*args, **kw)
          return wrapper
      
    • 最后需要指出,由于我们把原函数签名改成了(*args, **kw), 因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:

      def log(f):
          @functools.wraps(f)
          def wrapper(x):
              print 'call...'
              return f(x)
          return wrapper
      
    • 也可能改变原函数的参数名,因为新函数的参数名始终是 'x', 原函数定义的参数名不一定叫 'x'。

  • 偏函数

    • 当一个函数有很多参数时,调用者就需要提供多个参数。 如果减少参数个数,就可以简化调用者的负担。 比如:

    • 假设要转换大量的二进制字符串,每次都传入 int(x, base=2)非常麻烦, 于是,我们想到,可以定义一个 int2()的函数,默认把 base=2 传进去:

      def int2(x, base=2):
          return int(x, base)
      

      这样,我们转换二进制就非常方便了:

      >>> int2('1000000')
      64
      >>> int2('1010101')
      85
      
    • functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义 int2(), 可以直接使用下面的代码创建一个新的函数 int2:

      >>> import functools
      >>> int2 = functools.partial(int, base=2)##(函数,参数)
      >>> int2('1000000')
      64
      >>> int2('1010101')
      85
      
    • 我们在 sorted 这个高阶函数中传入自定义排序函数就可以实现忽略大小写排序。 请用 functools.partial 把这个复杂调用变成一个简单的函数:

      • 要固定 sorted()的 cmp 参数,需要传入一个排序函数作为 cmp 的默认值。
        • 参考代码:

          import functools
          sorted_ignore_case = functools.partial(sorted, cmp=lambda s1, s2: cmp(s1.upper(), s2.upper()))
          print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])
          

模块和包

#test.py	<-------------------------------自身模块名test
import math    <--------------------------引用math模块
print (math.pow(2,10))   <-------------调用math模块的函数


包{
    模块{
        函数
    }
}

bao.mo.fun

#test.py	<-------------------------------自身模块名test
import  bao.mo    <-----------------------引用math模块
print (bao.mo.fun(2,10))   <------------调用math模块的函数
  • 在文件系统中
    包就是文件夹
    模块就是 xxx.py 文件
  • 但是包和普通文件夹的区别是:
    包下都会有__init__.py 文件
    注意:每层都必须有

导入模块

>>> import math
>>> math.pow(2, 0.5) # pow是函数
1.4142135623730951

>>> math.pi # pi是变量
3.141592653589793
  • 如果我们只希望导入用到的 math 模块的某几个函数,而不是所有函数,可以用下面的语句:

    from math import pow, sin, log
    
  • 这样,可以直接引用 pow, sin, log 这 3 个函数,但 math 的其他函数没有导入进来:

    >>> pow(2, 10)
    1024.0
    >>> sin(3.14)
    0.0015926529164868282
    
  • 如果遇到名字冲突怎么办?比如 math 模块有一个 log 函数,logging 模块也有一个 log 函数, 如果同时使用,如何解决名字冲突?

  • 如果使用 import 导入模块名,由于必须通过模块名引用函数名,因此不存在冲突

  • 如果使用 from...import 导入 log 函数,势必引起冲突。这时,可以给函数起个“别名”来避免冲突:

    from math import log
    from logging import log as logger   # logging的log现在变成了logger
    print log(10)   # 调用的是math的log
    logger(10, 'import from logging')   # 调用的是logging的log
    
  • from 在前

动态导入模块

  • 如果导入的模块不存在,Python 解释器会报 ImportError 错误: 有的时候,两个不同的模块提供了相同的功能, 比如 StringIO 和 cStringIO 都提供了 StringIO 这个功能。
    • 同样的功能,StringIO 是纯 Python 代码编写的,而 cStringIO 部分函数是 C 写的, 因此 cStringIO 运行速度更快。

      try:
          from cStringIO import StringIO
      except ImportError:
          from StringIO import StringIO
      
      • 上述代码先尝试从 cStringIO 导入,如果失败了(比如 cStringIO 没有被安装),再尝试从 StringIO 导入。 这样,如果 cStringIO 模块存在,则我们将获得更快的运行速度,如果 cStringIO 不存在, 则顶多代码运行速度会变慢,但不会影响代码的正常执行。

使用__future__

  • Python 的新版本会引入新的功能,但是,实际上这些功能在上一个老版本中就已经存在了。 要“试用”某一新的特性,就可以通过导入__future__模块的某些功能来实现

    • 例如,Python 2.7 的整数除法运算结果仍是整数:

      >>> 10 / 3
      3
      
    • 但是,Python 3.x 已经改进了整数的除法运算,“/”除将得到浮点数,“//”除才仍是整数:

      >>> 10 / 3
      3.3333333333333335
      >>> 10 // 3
      3
      
    • 要在 Python 2.7 中引入 3.x 的除法规则,导入__future__的 division:

      >>> from __future__ import division
      >>> print 10 / 3
      3.3333333333333335
      
  • 当新版本的一个特性与旧版本不兼容时,该特性将会在旧版本中添加到__future__中, 以便旧的代码能在旧版本中测试新特性。

  • 在 Python 3.x 中,字符串统一为 unicode,不需要加前缀 u,而以字节存储的 str 则必须加前缀 b。

安装第三方模块

pip install xxx.py
python
>>> import xxx

map,reduce,filter,sorted

  • python2:map(fun,alist)

  • python3:list(map(fun,alist))

    • fun(x)函数分别作用于对 list 每一个元素
      • 例:

        def fun(x):
            return x*x
        print map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])		###python2的map()
        print(list(map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])))	###python3的map()
        
  • reduce(fun,alist,intializer) (intializer:初始值)

    • 传入的函数 fun(x,y)必须接收两个参数
    • reduce()对 list 的每两个个元素反复调用函数 f,即(0,1)返回 a,(a,2)返回 b,(b,3),(c,4),(d,5)。。。
      • 例:

        # 时间线---------------->------------------->
        def f(x, y):		# x , y =    1.0 ,1	               2.a ,2
            return x + y	# x+y   =    a	                       b
                            #                                      所以此时b=0+1+2
        reduce(f, [1, 3, 5, 7, 9], 100) # python2
        import functools###python3
        functools.reduce(f, [1, 3, 5, 7, 9], 100)
        结果将变为125,
        因为第一轮计算是:计算初始值和第一个元素:f(100, 1)。
        
  • filter(fun,alist)

    • 接收一个函数 f 和一个 list,这个函数 f 的作用是对每个元素进行判断,返回 True 或 False, filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新 list。
      • 例:去除偶数

        def is_odd(x):
            return x % 2 == 1
        
        filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
        # 结果:[1, 7, 9, 17]
        
  • python2:sorted(aliist,fun)

  • python3:sorted(alist,key,reverse) #reverse:颠倒

    • 可以接收一个比较函数来实现自定义排序, 比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面, 返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。 sorted(alist)函数还可以直接进行排序

      >>> sorted([36, 5, 12, 9, 21])`
      [5, 9, 12, 21, 36]
      
      • 例:倒序

        def reversed_cmp(x, y):
            if x > y:
                return -1
            if x < y:
                return 1
            return 0
        
        >>> sorted([36, 5, 12, 9, 21], reversed_cmp)
        [36, 21, 12, 9, 5]
        
        ###python3###
        >>> import functools
        >>> sorted([36, 5, 12, 9, 21], key=functools.cmp_to_key(reversed_cmp))
        # 在python3中其实可以
        >>> sorted([36, 5, 12, 9, 21], reverse=True)#记住True的T要大写
        

面向对象编程

  • 类:

    class xxx(object):
        ...
    
  • 实例:

    aclass=xxx()
    
  • 创建实例属性

    • 由于 Python 是动态语言,对每一个实例,都可以直接给他们的属性赋值,

      • 例如,给 xiaoming 这个实例加上 name、gender 和 birth 属性:

        xiaoming = Person()
        xiaoming.name = 'Xiao Ming'
        xiaoming.gender = 'Male'
        xiaoming.birth = '1990-1-1'
        
    • 实例的属性可以像普通变量一样进行操作:

      xiaohong.grade = xiaohong.grade + 1
      
  • 初始化实例属性

    • 在定义 Person 类时,可以为 Person 类添加一个特殊的__init__()方法, 当创建实例时,__init__()方法被自动调用,我们就能在此为每个实例都统一加上以下属性:

      class Person(object):
          def __init__(self, name, gender, birth):
              self.name = name
              self.gender = gender
              self.birth = birth
      
    • __init__() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法), 后续参数则可以自由指定,和定义函数没有任何区别。

    • 除了可以直接使用 self.name = 'xxx'设置一个属性外,还可以通过 setattr(self, 'name', 'xxx') 设置属性 相应地,创建实例时,就必须要提供除 self 以外的参数:

      xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
      xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')
      
    • 有了__init__()方法,每个 Person 实例在创建时, 都会有 name、gender 和 birth 这 3 个属性,并且,被赋予不同的属性值,访问属性使用.操作符:

      print xiaoming.name
      # 输出 'Xiao Ming'
      print xiaohong.birth
      # 输出 '1992-2-2'
      
    • 要特别注意的是,初学者定义__init__()方法常常忘记了 self 参数:

        >>> class Person(object):
        ...     def __init__(name, gender, birth):
        ...         pass
        ...
        >>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        TypeError: __init__() takes exactly 3 arguments (4 given)
      
      • 这会导致创建失败或运行不正常,因为第一个参数 name 被 Python 解释器传入了实例的引用, 从而导致整个方法的调用参数位置全部没有对上。
  • 访问限制

    • Python 对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__), 该属性就无法被外部访问。

      • 例:

        class Person(object):
            def __init__(self, name):
                self.name = name
                self._title = 'Mr'
                self.__job = 'Student'
                self.__a__ = 0
        p = Person('Bob')
        print p.name
        # => Bob
        print p._title
        # => Mr
        print b.__a__
        # => 0
        print p.__job
        # => Error
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        AttributeError: 'Person' object has no attribute '__job'
        
    • 可见,只有以双下划线开头的"__job"不能直接被外部访问。

    • 但是,如果一个属性以__xxx__的形式定义,那它就又可以被外部访问了, 以__xxx__定义的属性在 Python 的类中被称为特殊属性,有很多预定义的特殊属性可以使用, 通常我们不要把普通属性用__xxx__定义。

  • 创建类属性

    • 类是模板,而实例则是根据类创建的对象。

    • 绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象, 如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且, 所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有, 互相独立,而类属性有且只有一份。

    • 定义类属性可以直接在 class 中定义:

      class Person(object):
          address = 'Earth'
          def __init__(self, name):
              self.name = name
      
    • 因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问:

      print Person.address
      # => Earth
      
    • 对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:

      p1 = Person('Bob')
      p2 = Person('Alice')
      print p1.address
      # => Earth
      print p2.address
      # => Earth
      
    • 由于 Python 是动态语言,类属性也是可以动态添加和修改的:

      Person.address = 'China'
      print p1.address
      # => 'China'
      print p2.address
      # => 'China'
      
    • 因为类属性只有一份,所以,当 Person 类的 address 改变时,所有实例访问到的类属性都改变了。

      class Person(object):
          count=0
          def __init__(self,name):
              self.name=name
              Person.count+=1###########
      
      p1 = Person('Bob')
      print Person.count
      
  • 类属性和实例属性名字冲突怎么办

    • 修改类属性会导致所有实例访问到的类属性全部都受影响, 但是,如果在实例变量上修改类属性会发生什么问题呢?

      class Person(object):
          address = 'Earth'
          def __init__(self, name):
              self.name = name
        
      p1 = Person('Bob')
      p2 = Person('Alice')
      
      print 'Person.address = ' + Person.address
        
      p1.address = 'China'
      print 'p1.address = ' + p1.address
      
      print 'Person.address = ' + Person.address
      print 'p2.address = ' + p2.address
      
      • 结果如下:

        Person.address = Earth
        p1.address = China
        Person.address = Earth
        p2.address = Earth
        
    • 我们发现,在设置了 p1.address = 'China' 后,p1 访问 address 确实变成了 'China', 但是,Person.address 和 p2.address 仍然是'Earch',怎么回事?

      • 原因是 p1.address = 'China'并没有改变 Person 的 address, 而是给 p1 这个实例绑定了实例属性 address ,对 p1 来说, 它有一个实例属性 address(值是'China'),而它所属的类 Person 也有一个类属性 address,
      • 所以:
        • 访问 p1.address 时,优先查找实例属性,返回'China'。

        • 访问 p2.address 时,p2 没有实例属性 address,但是有类属性 address,因此返回'Earth'。

        • 可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。 当我们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 'Earth'了:

          del p1.address
          print p1.address
          # => Earth
          
        • 可见,千万不要在实例上修改类属性,它实际上并没有修改类属性, 而是给实例绑定了一个实例属性。

  • 定义实例方法

    • 虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。 除了可以定义实例的属性外,还可以定义实例的方法。

    • 实例的方法就是在类中定义的函数,它的第一个参数永远是 self, 指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:

      class Person(object):
      
          def __init__(self, name):
              self.__name = name
      
          def get_name(self):
              return self.__name
      

      get_name(self) 就是一个实例方法,它的第一个参数是 self

    • 调用实例方法必须在实例上调用:

      p1 = Person('Bob')
      print p1.get_name()  # self不需要显式传入
      # => Bob
      class Person(object):
          def __init__(self, name, score):
              self.__name = name
              self.__score = score
          def get_grade(self):
              if self.__score >= 80:
                  return 'A'
              if self.__score >= 60:
                  return 'B'
              return 'C'
      
      p1 = Person('Bob', 90)
      p2 = Person('Alice', 65)
      p3 = Person('Tim', 48)
      
      print p1.get_grade()
      print p2.get_grade()
      print p3.get_grade()
      
  • 方法也是属性

    • 我们在 class 中定义的实例方法其实也是属性,它实际上是一个函数对象:

      class Person(object):
          def __init__(self, name, score):
              self.name = name
              self.score = score
          def get_grade(self):
              return 'A'
      
      p1 = Person('Bob', 90)
      print p1.get_grade
      # => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
      print p1.get_grade()
      # => A
      
      • 也就是说,p1.get_grade 返回的是一个函数对象,但这个函数是一个绑定到实例的函数, p1.get_grade() 才是方法调用。
    • 因为方法也是一个属性,所以,它也可以动态地添加到实例上, 只是需要用 types.MethodType() 把一个函数变为一个方法:

      import types
      def fn_get_grade(self):
          if self.score >= 80:
              return 'A'
          if self.score >= 60:
              return 'B'
          return 'C'
      
      class Person(object):
          def __init__(self, name, score):
              self.name = name
              self.score = score
      
      p1 = Person('Bob', 90)
      p1.get_grade = types.MethodType(fn_get_grade, p1, Person)
      print p1.get_grade()
      # => A
      p2 = Person('Alice', 65)
      print p2.get_grade()
      # ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
      # 因为p2实例并没有绑定get_grade
      

      给一个实例动态添加方法并不常见,直接在 class 中定义要更直观。

    • 函数调用不需要传入 self,但是方法调用需要传入 self。

  • 定义类方法

    • 和属性类似,方法也分实例方法和类方法。

      • 在 class 中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。
        • 要在 class 中定义类方法,需要这么写:

          class Person(object):
              count = 0
              @classmethod
              def how_many(cls):
                  return cls.count
              def __init__(self, name):
                  self.name = name
                  Person.count = Person.count + 1
          
          print Person.how_many()
          p1 = Person('Bob')
          print Person.how_many()
          

          通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。 类方法的第一个参数将传入类本身,通常将参数名命名为 cls, 上面的 cls.count 实际上相当于 Person.count。

    • 因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。

  • 继承

    • is 关系 即继承:student is person

      class Student(Person):
          def __init(self,aaa):
              super(Student,self).__init__(aaaa)
      
    • has 关系 即组合:student has book

      class Student(object):
          def __init__(self,book_name):
              self.book=Book(book_name)
      
    • 继承一个类

      • 如果已经定义了 Person 类,需要定义新的 Student 和 Teacher 类时,可以直接从 Person 类继承:

        class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
        
      • 定义 Student 类时,只需要把额外的属性加上,例如 score:

        class Student(Person):
            def __init__(self, name, gender, score):
                super(Student, self).__init__(name, gender)
                self.score = score
        
    • 一定要用 super(Student, self).init(name, gender) 去初始化父类, 否则,继承自 Person 的 Student 将没有 name 和 gender。

    • 函数 super(Student, self)将返回当前类继承的父类,即 Person ,然后调用__init__()方法, 注意 self 参数已在 super()中传入,在__init__()中将隐式传递,不需要写出(也不能写)。

判断类型

  • 函数 isinstance()可以判断一个变量的类型, 既可以用在 Python 内置的数据类型如 str、list、dict, 也可以用在我们自定义的类,它们本质上都是数据类型。

  • 假设有如下的 Person、Student 和 Teacher 的定义及继承关系如下:

    class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
    class Student(Person):
        def __init__(self, name, gender, score):
            super(Student, self).__init__(name, gender)
            self.score = score
    
    class Teacher(Person):
        def __init__(self, name, gender, course):
            super(Teacher, self).__init__(name, gender)
            self.course = course
    
    p = Person('Tim', 'Male')
    s = Student('Bob', 'Male', 88)
    t = Teacher('Alice', 'Female', 'English')
    
    • 当我们拿到变量 p、s、t 时,可以使用 isinstance 判断类型:

      >>> isinstance(p, Person)
      True    # p是Person类型
      >>> isinstance(p, Student)
      False   # p不是Student类型
      >>> isinstance(p, Teacher)
      False   # p不是Teacher类型
      
      • 这说明在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法。
    • 我们再考察 s :

      >>> isinstance(s, Person)
      True    # s是Person类型
      >>> isinstance(s, Student)
      True    # s是Student类型
      >>> isinstance(s, Teacher)
      False   # s不是Teacher类型
      
      • s 是 Student 类型,不是 Teacher 类型,这很容易理解。 但是,s 也是 Person 类型,因为 Student 继承自 Person, 虽然它比 Person 多了一些属性和方法,但是,把 s 看成 Person 的实例也是可以的。
      • 这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。

多态

  • 类具有继承关系,并且子类类型可以向上转型看做父类类型, 如果我们从 Person 派生出 Student 和 Teacher ,并都写了一个 whoAmI() 方法:

    class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
        def whoAmI(self):
            return 'I am a Person, my name is %s' % self.name
    
    class Student(Person):
        def __init__(self, name, gender, score):
            super(Student, self).__init__(name, gender)
            self.score = score
        def whoAmI(self):
            return 'I am a Student, my name is %s' % self.name
    
    class Teacher(Person):
        def __init__(self, name, gender, course):
            super(Teacher, self).__init__(name, gender)
            self.course = course
        def whoAmI(self):
            return 'I am a Teacher, my name is %s' % self.name
    
  • 在一个函数中,如果我们接收一个变量 x,则无论该 x 是 Person、Student 还是 Teacher,都可以正确打印出结果:

    def who_am_i(x):
        print x.whoAmI()
    
    p = Person('Tim', 'Male')
    s = Student('Bob', 'Male', 88)
    t = Teacher('Alice', 'Female', 'English')
    
    who_am_i(p)
    who_am_i(s)
    who_am_i(t)
    
    运行结果:
    
    I am a Person, my name is Tim
    I am a Student, my name is Bob
    I am a Teacher, my name is Alice
    
  • 这种行为称为多态。也就是说,方法调用将作用在 x 的实际类型上。 s 是 Student 类型,它实际上拥有自己的 whoAmI()方法以及从 Person 继承的 whoAmI 方法, 但调用 s.whoAmI()总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找, 直到在某个父类中找到为止。

  • 由于 Python 是动态语言,所以,传递给函数 who_am_i(x)的参数 x 不一定是 Person 或 Person 的子类型。 任何数据类型的实例都可以,只要它有一个 whoAmI()的方法即可:

    class Book(object):
        def whoAmI(self):
            return 'I am a book'
    
  • 这是动态语言和静态语言(例如 Java)最大的差别之一。动态语言调用实例方法, 不检查类型,只要方法存在,参数正确,就可以调用。

多重继承

  • 除了从一个父类继承外,Python 允许从多个父类继承,称为多重继承。

  • 多重继承的继承链就不是一棵树了,它像这样:

    class A(object):
        def __init__(self, a):
            print 'init A...'
            self.a = a
    
    class B(A):
        def __init__(self, a):
            super(B, self).__init__(a)
            print 'init B...'
    
    class C(A):
        def __init__(self, a):
            super(C, self).__init__(a)
            print 'init C...'
    
    class D(B, C):
        def __init__(self, a):###
            super(D, self).__init__(a)###
            print 'init D...'
    
  • 看下图:

    爷   	         A
                   /   \
                  /     \
    父	         B       C
                  \     /
                   \   /
    孙               D
    
    
    像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。
    
  • 多重继承通过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:

    >>> d = D('d')
    init A...
    init C...
    init B...
    init D...
    
  • 多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。

  • 通过多重继承,请定义“会打篮球的学生”和“会踢足球的老师”。

    • 多重继承需要从两个或更多的类派生。

    • 参考代码:

      class Person(object):
          pass
      
      class Student(Person):
          pass
      
      class Teacher(Person):
          pass
      
      class SkillMixin(object):
          pass
      
      class BasketballMixin(SkillMixin):
          def skill(self):
              return 'basketball'
      
      class FootballMixin(SkillMixin):
          def skill(self):
              return 'football'
      
      class BStudent(Student, BasketballMixin):
          pass###只有在父类需要参数时,调用super(BSTudent,self).__init(xxx,xxx)
      
      class FTeacher(Teacher, FootballMixin):
          pass###
      
      s = BStudent()
      print s.skill()
      
      t = FTeacher()
      print t.skill()
      

获取对象信息

  • 拿到一个变量,除了用 isinstance() 判断它是否是某种类型的实例外, 还有没有别的方法获取到更多的信息呢?

    • 例如,已有定义:

      class Person(object):
          def __init__(self, name, gender):
              self.name = name
              self.gender = gender
      
      class Student(Person):
          def __init__(self, name, gender, score):
              super(Student, self).__init__(name, gender)
              self.score = score
          def whoAmI(self):
              return 'I am a Student, my name is %s' % self.name
      
  • 首先可以用 type() 函数获取变量的类型,它返回一个 Type 对象:

    >>> type(123)
    <type 'int'>
    >>> s = Student('Bob', 'Male', 88)
    >>> type(s)
    <class '__main__.Student'>
    
  • 其次,可以用 dir() 函数获取变量的所有属性:

    >>> dir(123)   # 整数也有很多属性...
    ['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...]
    
    >>> dir(s)
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__','__hash__', '__init__',
    '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__','__sizeof__', '__str__',
    '__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']
    
  • 对于实例变量,dir()返回所有实例属性,包括__class__这类有特殊意义的属性。 注意到方法whoAmI也是 s 的一个属性。

  • 如何去掉__xxx__这类的特殊属性,只保留我们自己定义的属性?回顾一下 filter()函数的用法。

  • dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性, 就需要用 getattr() 和 setattr( )函数了:

    >>> getattr(s, 'name')  # 获取name属性
    'Bob'
    
    >>> setattr(s, 'name', 'Adam')  # 设置新的name属性
    
    >>> s.name
    'Adam'
    
    >>> getattr(s, 'age')  # 获取age属性,但是属性不存在,报错:
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      AttributeError: 'Student' object has no attribute 'age'
    
    >>> getattr(s, 'age', 20)  # 获取age属性,如果属性不存在,就返回默认值20:
    20
    
  • 对于 Person 类的定义:

    class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
  • 希望除了 name 和 gender 外,可以提供任意额外的关键字参数, 并绑定到实例,请修改 Person 的 __init__()定 义,完成该功能。

  • 传入**kw 即可传入任意数量的参数,并通过 setattr() 绑定属性。

    • 参考代码:

      class Person(object):
          def __init__(self, name, gender, **kw):
              self.name = name
              self.gender = gender
              for k, v in kw.iteritems():###
                  setattr(self, k, v)
      
      p = Person('Bob', 'Male', age=18, course='Python')
      print p.age
      print p.course
      

特殊方法

    • 打印 用于 print 的__str__
    • 长度 用于 len 的__len__
    • 比较 用于 cmp 的__cmp__
  • 特殊方法定义在 class 中

  • 不需要直接调用

  • Python 的某些函数或操作符会调用对应的特殊方法

  • 正确实现特殊方法

    • 有关联性的特殊方法都必须实现
      • 如果定义了__getattr__方法,就必须要定义__setattr__方法和__delattr__方法
  • __str__和__repr__

    • 如果要把一个类的实例变成 str,就需要实现特殊方法__str__():

      class Person(object):
          def __init__(self, name, gender):
              self.name = name
              self.gender = gender
          def __str__(self):
              return '(Person: %s, %s)' % (self.name, self.gender)
      
    • 现在,在交互式命令行下用 print 试试:

      >>> p = Person('Bob', 'male')
      >>> print p
      (Person: Bob, male)
      
    • 但是,如果直接敲变量 p:

      >>> p
      <main.Person object at 0x10c941890>
      
      
      似乎__str__() 不会被调用。
      
    • 因为 Python 定义了__str__()和__repr__()两种方法,__str__()用于显示给用户, 而__repr__()用于显示给开发人员。

    • 有一个偷懒的定义__repr__的方法:

      class Person(object):
          def __init__(self, name, gender):
              self.name = name
              self.gender = gender
          def __str__(self):
              return '(Person: %s, %s)' % (self.name, self.gender)
          __repr__ = __str__
      
  • __cmp__

    • 对 int、str 等内置数据类型排序时,Python 的 sorted() 按照默认的比较函数 cmp 排序, 但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 cmp():

      class Student(object):
          def __init__(self, name, score):
              self.name = name
              self.score = score
          def __str__(self):
              return '(%s: %s)' % (self.name, self.score)
          __repr__ = __str__
      
          def __cmp__(self, s):
              if self.name < s.name:
                  return -1
              elif self.name > s.name:
                  return 1
              else:
                  return 0
      
    • 上述 Student 类实现了__cmp__()方法,__cmp__用实例自身 self 和传入的实例 s 进行比较, 如果 self 应该排在前面(就是 self 比 s 小),就返回 -1,如果 s 应该排在前面, 就返回 1,如果两者相当,返回 0。

    • Student 类实现了按 name 进行排序:

      >>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
      >>> print sorted(L)
      [(Alice: 77), (Bob: 88), (Tim: 99)]
      
    • 注意: 如果 list 不仅仅包含 Student 类,则 cmp 可能会报错:

      L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
      print sorted(L)
      
    • 请思考如何解决:个人认为在__cmp__中加入 if 判断是否为 Student,isinstance(self,Student)

      # python3
      import functools
      class Student(object):
          def __init__(self, name, score):
              self.name = name
              self.score = score
          def __str__(self):
              return '(%s: %s)' % (self.name, self.score)
          __repr__ = __str__
      
          def __cmp__(self, s):
              if self.name < s.name:
                  return -1
              elif self.name > s.name:
                  return 1
              else:
                  return 0
      
      L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
      print(sorted(L,key=functools.cmp_to_key(__cmp__)))
      
    • 请修改 Student 的 cmp 方法,让它按照分数从高到底排序,分数相同的按名字排序。

      class Student(object):
      
          def __init__(self, name, score):
              self.name = name
              self.score = score
      
          def __str__(self):
              return '(%s: %s)' % (self.name, self.score)
      
          __repr__ = __str__
      
          def __cmp__(self, s):
              if False == isinstance(s, Student):
                  return -1
              return -cmp(self.score, s.score) or cmp(self.name, s.name)
      
      L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
      print sorted(L)
      
  • __len__

    • 如果一个类表现得像一个 list,要获取有多少个元素,就得用 len() 函数。

    • 要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。

    • 例如,我们写一个 Students 类,把名字传进去:

      class Students(object):
          def __init__(self, *args):
              self.names = args
          def __len__(self):
              return len(self.names)
      
    • 只要正确实现了__len__()方法,就可以用 len()函数返回 Students 实例的“长度”:

      >>> ss = Students('Bob', 'Alice', 'Tim')
      >>> print len(ss)
      3
      
    • 斐波那契数列是由 0, 1, 1, 2, 3, 5, 8...构成。

      • 请编写一个 Fib 类,Fib(10)表示数列的前 10 个元素,print Fib(10) 可以打印出数列的前 10 个元素, len(Fib(10))可以正确返回数列的个数 10。

      • 需要根据 num 计算出斐波那契数列的前 N 个元素。

      • 参考代码:

        class Fib(object):
            def __init__(self, num):
                a, b, L = 0, 1, []
                for n in range(num):
                    L.append(a)
                    a, b = b, a + b
                self.numbers = L
        
            def __str__(self):
                return str(self.numbers)
        
            __repr__ = __str__
        
            def __len__(self):
                return len(self.numbers)
        
        f = Fib(10)
        print f
        print len(f)
        

数学运算

  • Python 提供的基本数据类型 int、float 可以做整数和浮点的四则运算以及乘方等运算。

但是,四则运算不局限于 int 和 float,还可以是有理数、矩阵等。

要表示有理数,可以用一个 Rational 类来表示分数:

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
  • p、q 都是整数,表示有理数 p/q。

  • 如果要让 Rational 进行+-*/运算,需要正确实现

  • 加法运算 + 调用__add__

  • 减法运算 - 调用__sub__

  • 乘法运算 * 调用__mul__

  • 除法运算 / 调用__div__

  • 如果运算结果是 6/8,在显示的时候需要归约到最简形式 3/4。

    • 参考代码:

      def gcd(a, b):
          if b == 0:
              return a
          return gcd(b, a % b)
      
      class Rational(object):
          def __init__(self, p, q):
              self.p = p
              self.q = q
          def __add__(self, r):
              return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
          def __sub__(self, r):
              return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
          def __mul__(self, r):
              return Rational(self.p * r.p, self.q * r.q)
          def __div__(self, r):
              return Rational(self.p * r.q, self.q * r.p)
          def __str__(self):
              g = gcd(self.p, self.q)
              return '%s/%s' % (self.p / g, self.q / g)
          __repr__ = __str__
      
      r1 = Rational(1, 2)
      r2 = Rational(1, 4)
      print r1 + r2
      print r1 - r2
      print r1 * r2
      print r1 / r2
      
      3/4
      1/4
      1/8
      2/1
      

类型转换

  • Rational 类实现了有理数运算,但是,如果要把结果转为 int 或 float 怎么办?
    • 考察整数和浮点数的转换:

      >>> int(12.34)
      12
      >>> float(12)
      12.0
      
    • 如果要把 Rational 转为 int,应该使用:

      r = Rational(12, 5)
      n = int(r)
      
    • 要让 int()函数正常工作,只需要实现特殊方法__int__():

      class Rational(object):
          def __init__(self, p, q):
                self.p = p
                self.q = q
          def __int__(self):
              return self.p // self.q
      
      结果如下:
      
      >>> print int(Rational(7, 2))
      3
      >>> print int(Rational(1, 3))
      0
      
    • 同理,要让 float()函数正常工作,只需要实现特殊方法__float__()。

      def __float__(self):
          return 1.0*self.p/self.q
      

@property

  • 考察 Student 类:

    class Student(object):
        def __init__(self, name, score):
            self.name = name
            self.score = score
    
  • 当我们想要修改一个 Student 的 scroe 属性时,可以这么写:

    s = Student('Bob', 59)
    s.score = 60
    
  • 但是也可以这么写:

    s.score = 1000
    

    显然,直接给属性赋值无法检查分数的有效性。

    • 如果利用两个方法:

      class Student(object):
          def __init__(self, name, score):
              self.name = name
              self.__score = score
          def get_score(self):
              return self.__score
          def set_score(self, score):
              if score < 0 or score > 100:
                  raise ValueError('invalid score')#########
              self.__score = score
      

    这样一来,s.set_score(1000) 就会抛异常。

  • 但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。

  • 有没有两全其美的方法?----有。

  • 因为 Python 支持高阶函数,在函数式编程中我们介绍了装饰器函数, 可以用装饰器函数把 get/set 方法“装饰”成属性调用:

     class Student(object):
         def __init__(self, name, score):
             self.name = name
             self.__score = score
         @property
         def score(self):
             return self.__score
         @score.setter
         def score(self, score):
             if score < 0 or score > 100:
                 raise ValueError('invalid score')####
             self.__score = score
    
    • 注意: 第一个 score(self)是 get 方法,用@property 装饰, 第二个 score(self, score)是 set 方法,用@score.setter 装饰, @score.setter 是前一个@property 装饰后的副产品(有 get 就得有 set),也就是 score 的判断语句。

    • 现在,就可以像使用属性一样设置 score 了(即不用 get_score 和 set_score,也可以对输入的 score 进行判断):

      >>> s = Student('Bob', 59)
      >>> s.score = 60
      >>> print s.score
      60
      >>> s.score = 1000
      Traceback (most recent call last):
      ...
      ValueError: invalid score
      
    • 说明对 score 赋值实际调用的是 set 方法。

__slots__

  • 由于 Python 是动态语言,任何实例在运行期都可以动态地添加属性。

  • 如果要限制添加的属性,例如,Student 类只允许添加 name、gender 和 score 这 3 个属性, 就可以利用 Python 的一个特殊的__slots__来实现。

  • 顾名思义,__slots__是指一个类允许的属性列表:

    class Student(object):
        __slots__ = ('name', 'gender', 'score')
        def __init__(self, name, gender, score):
            self.name = name
            self.gender = gender
            self.score = score
    
  • 现在,对实例进行操作:

    >>> s = Student('Bob', 'male', 59)
    >>> s.name = 'Tim' # OK
    >>> s.score = 99 # OK
    >>> s.grade = 'A'
      Traceback (most recent call last):
        ...
      AttributeError: 'Student' object has no attribute 'grade'
    
  • __slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性, 使用__slots__也能节省内存。

  • 子类的__slots__只需要包含父类不包含的属性即可。

__call__

  • 在 Python 中,函数其实是一个对象:

    >>> f = abs
    >>> f.__name__
    'abs'
    >>> f(-123)
    123
    
  • 由于 f 可以被调用,所以,f 被称为可调用对象。

  • 所有的函数都是可调用对象。

  • 一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。

    • 我们把 Person 类变成一个可调用对象:

      class Person(object):
          def __init__(self, name, gender):
              self.name = name
              self.gender = gender
      
          def __call__(self, friend):
              print 'My name is %s...' % self.name
              print 'My friend is %s...' % friend
      
    • 现在可以对 Person 实例直接调用:

      >>> p = Person('Bob', 'male')
      >>> p('Tim')
      My name is Bob...
      My friend is Tim...
      
    • 单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在 Python 中,函数也是对象, 对象和函数的区别并不显著

    • 改进一下前面定义的斐波那契数列

      class Fib(object):
          def __call__(self, num):
              a, b, L = 0, 1, []
              for n in range(num):
                  L.append(a)
                  a, b = b, a + b
              return L
      
      f = Fib()
      print f(10)
      

典型错误:

  • 1

    def calc_prod(lst):
        def g(lst):
            s=1
            for n in lst:
                s*=n
            return s
        return g
    
    f = calc_prod([1, 2, 3, 4])
    print (f())
    
    • 程序的第二行中的 g 函数:g(lst)表示创建一个新的 lst 参数,但是,在程序中并没有传入因此会报错

    • 修改:

      def calc_prod(lst):
          def g():
              s=1
              for n in lst:
                  s*=n
              return s
          return g
      
      f = calc_prod([1, 2, 3, 4])
      print (f())
      
  • 2

    • 希望一次返回 3 个函数,分别计算 1x1,2x2,3x3:

      def count():
          fs = []
          for i in range(1, 4):
              def f():
                   return i*i
              fs.append(f)
          return fs
      
      f1, f2, f3 = count()
      
    • 你可能认为调用 f1(),f2()和 f3()结果应该是 1,4,9,但实际结果全部都是 9

    • 原因就是当执行完 count()函数后, i 的值就变成了 3。由于 f1、f2、f3 并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:

      >>> f1()
      >>> 9 # 因为 f1 现在才被调用,而此时的 i=3
      
  • 闭包的特点是返回的函数还引用了外层函数的局部变量, 所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。

    • 修改:

      • 考察下面的函数 f:

        def f(j):
            def g():
                return j*j
            return g
        
        
        它可以正确地返回一个闭包g,g所引用的变量j不是循环变量,因此将正常执行。
        
    • 在 count 函数的循环内部,如果借助 f 函数,就可以避免引用循环变量 i。

      def count():
          fs = []
          for i in range(1, 4):
              def f(j):
      #------------------------------------------------------------------------------
                  def g():
                      return j*j
                  return g
              k=f(i)
              fs.append(k)#如果此处写fs.append(f)的话,则每次调用f时需要传入参数
                      #但是fs.append(k),中的k函数已经是传入值后的f了,例如k=f(1)
      #-----------------------------------------------------------------------------
          return fs
      f1, f2, f3 = count()
      
      • 当执行完 count()函数后, i 的值就变成了 3。

      • f1、f2、f3 也没有被调用,但是,程序中的 k 并不受 i 的影响,因为 k 函数已经是传入值后的 f 了,

      • 例如 k=f(1),k 已经确定了

      • 当 f1 被调用时:

        >>> f1()
        9
        
      • 当执行完 count()函数后, i 的值就变成了 3。

      • f1、f2、f3 也没有被调用,但是,程序中的 k 并不受 i 的影响,因为 k 函数已经是传入值后的 f 了,

      • 例如 k=f(1),k 已经确定了

      • 当 f1 被调用时:

        >>> f1()
        9
        
posted @ 2016-10-17 13:16  twfb  阅读(707)  评论(0编辑  收藏  举报