基础

首先记录pycharm的一些快捷键:

  • shift+F10:运行py文件
  • CTRL:查看类或函数的使用方法
  • CTRL+/进行注释
  • CTRL+P查看当前函数所需要的参数
  • CTRL+D复制上一行的代码
  • CTRL+C退出程序

python的IO

  • 输出:print(),例如print('hello world')print()可以接受多个字符串,字符串之间用,隔开,就可以将字符串连成一串输出。print()会依次打印各个字符串,遇到,就会输出一个空格。
  • 输入:input(),例如name=input()input()有一个字符串参数,例如name = input('please enter your name: '),input的返回值类型是str,会首先输出字符串参数的内容。

python的数据类型

  • 整数:为了清楚,可以在数字中间以_分隔,例如10_000_000_00010000000000是一样的。

  • 字符串:以'"括起来的任意文本。如果'括起来内部还有'这种的话,就要加转义字符\。python可以用r''表示''内部的字符串不转义。

  • 布尔值:只有TrueFalse两种值。可以直接用TrueFalse表示布尔值,也可以通过布尔运算计算出来。布尔值可以用andornot运算。

  • 空值:空值用None表示,不能理解为00是有意义的,而None是一个特殊的空值。

  • 变量:python是动态语言,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。

  • 常量:常用大写变量名表示常量,但是这个常量仍然是一个变量,因为python没有任何机制能够保证常量是常量,只是我们自己心里的一个默认罢了。

  • list列表:list是一种有序的集合,可以随时添加和删除其中的元素。例如:

    >>> classmates = ['Michael', 'Bob', 'Tracy']
    >>> classmates
    ['Michael', 'Bob', 'Tracy']
    • 变量classmates就是一个list。用len()函数可以获得list元素的个数。

      >>> len(classmates)
      3
    • 用索引来访问list中每一个位置的元素,记得索引是从0开始的,最后一个元素的索引是len(classmates) - 1。如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素,以此类推,可以获取倒数第2个、倒数第3个。当索引超出了范围时,Python会报一个IndexError错误。

      >>> classmates[0]
      'Michael'
      >>> classmates[1]
      'Bob'
      >>> classmates[2]
      'Tracy'
      >>> classmates[3]
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      IndexError: list index out of range
      >>> classmates[-1]
      'Tracy'
      >>> classmates[-2]
      'Bob'
      >>> classmates[-3]
      'Michael'
      >>> classmates[-4]
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      IndexError: list index out of range
    • list是一个可变的有序表,所以,可以往list中追加元素到末尾。

      >>> classmates.append('Adam')
      >>> classmates
      ['Michael', 'Bob', 'Tracy', 'Adam']
    • 也可以把元素插入到指定的位置,比如索引号为1的位置。

      >>> classmates.insert(1, 'Jack')
      >>> classmates
      ['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
    • 要删除list末尾的元素,用pop()方法。

      >>> classmates.pop()
      'Adam'
      >>> classmates
      ['Michael', 'Jack', 'Bob', 'Tracy']
    • 要删除指定位置的元素,用pop(i)方法,其中i是索引位置。

      >>> classmates.pop(1)
      'Jack'
      >>> classmates
      ['Michael', 'Bob', 'Tracy']
    • 要把某个元素替换成别的元素,可以直接赋值给对应的索引位置。

      >>> classmates[1] = 'Sarah'
      >>> classmates
      ['Michael', 'Sarah', 'Tracy']
    • list里面的元素的数据类型也可以不同。

      >>> L = ['Apple', 123, True]
    • list元素也可以是另一个list。

      >>> s = ['python', 'java', ['asp', 'php'], 'scheme']
      >>> len(s)
      4

      要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:

      >>> p = ['asp', 'php']
      >>> s = ['python', 'java', p, 'scheme']

      要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组。

    • 如果一个list中一个元素也没有,就是一个空的list,它的长度为0。

      >>> L = []
      >>> len(L)
      0
  • tuple:另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改。

    • >>> classmates = ('Michael', 'Bob', 'Tracy')
      

      classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用`classmates[0]`,`classmates[-1]`,但不能赋值成另外的元素。



      - 当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来。

      ```python
      >>> t = (1, 2)
      >>> t
      (1, 2)
    • 如果要定义一个空的tuple,可以写成()

      >>> t = ()
      >>> t
      ()
    • 但是,要定义一个只有1个元素的tuple,如果这么定义:

      >>> t = (1)
      >>> t
      1
    • 定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

      >>> t = (1,)
      >>> t
      (1,)

      Python在显示只有1个元素的tuple时,也会加一个逗号,

    • 一个”可变的“tuple:

      >>> t = ('a', 'b', ['A', 'B'])
      >>> t[2][0] = 'X'
      >>> t[2][1] = 'Y'
      >>> t
      ('a', 'b', ['X', 'Y'])
    • 这个tuple定义的时候有3个元素,分别是'a''b'和一个list。

tuple1

当我们把list的元素'A''B'修改为'X''Y'后,tuple变为:

tuple2

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的。

Python的整数没有大小限制,Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。

  • 类型转换例如:int(str)

python的计算

  • 一种除法是//计算结果是浮点数。
  • 还有一种除法是//,两个整数的除法仍然是整数。
  • 取余%

字符编码

python的字符串以Unicode编码,对于单个字符编码,ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。python对bytes类型的数据用带b前缀的单引号或双引号表示,例如x=b'ABC'。以Unicode表示的str通过encode()方法可以编码为指定的bytes。要把bytes变为str,就需要用decode()方法。要计算str包含多少个字符,可以用len()函数,len()函数计算的是str的字符数,如果换成byteslen()函数就计算字节数。

格式化字符串

python使用%格式化字符串,有几个%?占位符,后面就跟着几个变量或者值,如果只有一个%?,括号可以省略。例如:

'Hello, %s' % 'world'
'Hi, %s, you have $%d.' % ('Michael', 1000000)

常见占位符:

占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串。有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个%

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

最后一种格式化字符串的方法是使用以f开头的字符串,称之为f-string,它和普通字符串不同之处在于,字符串如果包含{xxx},就会以对应的变量替换:

>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62

上述代码中,{r}被变量r的值替换,{s:.2f}被变量s的值替换,并且:后面的.2f指定了格式化参数(即保留两位小数),因此,{s:.2f}的替换结果是19.62

If相关语句

  • python中的if语句通过缩进表明后面的语句是同一个代码块的,这点和其他语言不同,并且要在条件之后加上一个冒号。例如:

    age = 3
    if age >= 18:
    print('your age is', age)
    print('adult')
    else:
    print('your age is', age)
    print('teenager')
  • python中的else-if语句写法如,这里elif就是else if的缩写:

    age = 3
    if age >= 18:
    print('adult')
    elif age >= 6:
    print('teenager')
    else:
    print('kid')

循环语句

python循环有两种。

  • for…in循环,依次把list或tuple中的每个元素迭代出来.

    names = ['Michael', 'Bob', 'Tracy']
    for name in names:
    print(name)

    for x in...循环就是把每个元素代入变量x,然后执行缩进块的语句,如下例子较为麻烦。

    sum = 0
    for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    sum = sum + x
    print(sum)

    python有range()函数,可以生成整数序列,再通过list()函数可以转换为list

    sum = 0
    for x in range(101): #这是从0-100,也可以写成list(range(101)),一种是列表,一种是序列
    sum = sum + x
    print(sum)
  • while循环

    例如,计算100以内奇数之和:

    sum = 0
    n = 99
    while n > 0:
    sum = sum + n
    n = n - 2
    print(sum)
  • break语句,提前结束循环,用法一致

    n = 1
    while n <= 100:
    if n > 10: # 当n = 11时,条件满足,执行break语句
    break # break语句会结束当前循环
    print(n)
    n = n + 1
    print('END')
  • continue语句,用法与其他语言一致

    n = 0
    while n < 10:
    n = n + 1
    if n % 2 == 0: # 如果n是偶数,执行continue语句
    continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
    print(n)

dict和set

字典dict其实就是map,是key-value的映射,特点是查找速度快。dict的key是不可变对象如字符串、整数,list是可变的

例子:

# 初始化,将数据放入dict
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
# 通过key放入数据,一个key只能对应一个value
>>> d['Adam'] = 67
>>> d['Adam']
67

判断key是否存在

  • 通过in判断

    >>> 'Thomas' in d
    False
  • 通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:

    >>> d.get('Thomas')
    >>> d.get('Thomas', -1) # 指定不存在的话value=-1
    -1

删除key,用pop(key)方法,对应的value也会从dict中删除

>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

set也是一组key的集合,但是不存储value,key不能重复,同样不能放入可变对象。

创建一个set,需要提供list作为输入集合。

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3} #重复元素会自动被过滤

传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果;通过remove(key)方法可以删除元素;set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作

>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.remove(4)
>>> s
{1, 2, 3}
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

函数

调用函数

help(函数名)可以查看函数信息

abs函数是绝对值函数,max函数是最大值函数,它们都是内置函数。若传入的参数书量不对会报TypeError错误,如果传入的参数数量是对的,但是参数类型错误,也会报TypeError错误。

在python中数据类型转换是一种函数,可以直接调用,它是内置的,例如:

>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”,例如:

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

定义函数

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

函数一旦执行到return,就执行完毕了,将结果返回。没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return。

导入函数,可以举例用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名。

空函数:如果想定义一个什么事也不做的空函数,可以用pass语句。pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。pass还可以用在其他语句里,比如:

if age >= 18:
pass

对于函数的传入参数检查,可以使用内置函数isinstance实现:

def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x

python中的函数可以返回多个值:

import math
def move(x, y, step, angle=0):
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.96152422706632 70.0

import math语句表示导入math包,并允许后续代码引用math包里的sincos等函数。但是其实Python函数的返回值仍然是单一值,是一个tuple,但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

函数的参数

位置参数

就是直接给的参数,调用函数时,传入的值按照位置顺序依次赋值给位置参数。

默认参数

可以将参数在定义函数时默认值。设置默认参数时需要注意1.一是必选参数在前,默认参数在后 2.当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。

默认参数有坑:

def add_end(L=[]):
L.append('END')
return L

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。定义默认参数要牢记一点:默认参数必须指向不变对象!

可变参数

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
>>> calc(1, 2)
5
>>> calc()
0

Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数nameage外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数,也可以传入任意个数的关键字参数:

>>> person('Michael', 30)
name: Michael age: 30 other: {}

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

和可变参数类似,可以将传入**dict:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

命名关键字参数

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。调用方式如下:

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

def person(name, age, *args, city, job):
print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。

命名关键字参数可以有缺省值,从而简化调用,由于命名关键字参数city具有默认值,调用时,可不传入city参数:

def person(name, age, *, city='Beijing', job):
print(name, age, city, job)

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。例如:

def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

通过一个tuple和dict,你也可以调用上述函数:

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

高级特性

切片

Python提供了切片(Slice)操作符,能大大简化取一个list或tuple的部分元素这种操作

取前3个元素,用一行代码就可以完成切片:

>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引012,正好是3个元素。

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

>>> L[:3]
['Michael', 'Sarah', 'Tracy']

支持倒数切片:

>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

实现前10个数,每两个取一个:

>>> L[:10:2]
[0, 2, 4, 6, 8]

所有数,每5个取一个:

>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

甚至什么都不写,只写[:]就可以原样复制一个list:

>>> L[:]
[0, 1, 2, 3, ..., 99]

tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:

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

字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串,Python没有针对字符串的截取函数,只需要切片一个操作就可以完成:

>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'

迭代

Python的for循环不仅可以用在listtuple上,还可以作用在其他可迭代对象上。

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b

因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()

由于字符串也是可迭代对象,因此,也可以作用于for循环:

>>> for ch in 'ABC':
... print(ch)
...
A
B
C

Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C

上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:

>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9

列表生成式

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

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?列表生成式可以用一行语句代替循环生成上面的list:

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用。

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

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

还可以使用两层循环,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

for循环其实可以同时使用两个甚至多个变量,比如dictitems()可以同时迭代key和value:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C

因此,列表生成式也可以使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

最后把一个list中所有的字符串变成小写:

>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

以下代码正常输出偶数:

>>> [x for x in range(1, 11) if x % 2 == 0]
[2, 4, 6, 8, 10]

但是,我们不能在最后的if加上else,这是因为跟在for后面的if是一个筛选条件,不能带else,否则如何筛选?

if写在for前面必须加else,否则报错:

>>> [x if x % 2 == 0 for x in range(1, 11)]
File "<stdin>", line 1
[x if x % 2 == 0 for x in range(1, 11)]
^
SyntaxError: invalid syntax

这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。因此,考察表达式:x if x % 2 == 0,它无法根据x计算出结果,因为缺少else,必须加上else

>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

上述for前面的表达式x if x % 2 == 0 else -x才能根据x计算出确定的结果。

可见,在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else

生成器

一边循环一边计算的机制,称为生成器:generator。这样就不必创建完整的list,从而节省大量的空间。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值。当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它

迭代器

可以使用isinstance()判断一个对象是否是可迭代对象:

>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值。可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象:

>>> from collections.abc import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

listdictstrIterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

函数式编程

变量可以指向函数,可以把函数本身赋值给变量。一个变量指向了一个函数,可通过该变量来调用这个函数。

>>> f = abs
>>> f
<built-in function abs>
>>> f(-10)
10

变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。

高阶函数

函数名也是变量

函数名其实就是指向函数的变量。对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数。

如果把abs指向其他对象,会有什么情况发生?

>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

传入函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

一个最简单的高阶函数:

def add(x, y, f):
return f(x) + f(y)

编写高阶函数,就是让函数的参数能够接收别的函数。

map&reduce

map

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个IteratorIterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

reduce

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
>>> str2int('13579')
13579

这个是map和reduce的运用+函数内定义函数这个用法

filter

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

sorted

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

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:

list = [36, 5, -12, 9, -21]

keys = [36, 5, 12, 9, 21]

然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:

keys排序结果 => [5, 9,  12,  21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

>>> sorted([36, 5, -12, 9, -21], key=abs,reverse=True)
[36, -21, -12, 9, 5]

返回函数

如果不需要立刻求和,而是在后面的代码中,根据需要再计算。可以不返回求和的结果,而是返回求和的函数:

def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

调用函数f时,才真正计算求和的结果:

>>> f()
25

我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

f1()f2()的调用结果互不影响。

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

匿名函数

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数lambda x: x * x实际上就是:

def f(x):
return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
return lambda: x * x + y * y

模块

模块使用

if __name__=='__main__':

相当于main函数,只有模块内部才能用,其他的模块是没法调用这个的

作用域

正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,__name__就是特殊变量。类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

面向对象编程

类和实例

class Student(object):
pass

(object),表示该类是从哪个类继承下来的,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

>>> bart = Student()
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

class Student(object):

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

方法__init__前后分别有两个下划线。__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身,相当于this。有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去。

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入。

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

注意:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。

继承和多态

判断一个变量是否是某个类型可以用isinstance()判断:

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):
def run(self):
print('Start...')

获取对象信息

type()

type()返回对应的class类型。

基本类型可以用type()来判断:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

指向函数或类的变量也用type()判断类型:

>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

如果在if中判断两个变量type基本类型是否相同,采用如下形式:

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

判断一个对象是否是函数,就要使用types模块中的常量:

>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

isinstance()

对于class我们使用isintance()函数。isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。

如果继承关系是:

object -> Animal -> Dog -> Husky
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

那么

>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True
>>> isinstance(h, Animal)
True

能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

并且可以判断一个变量是否是某些类型中的一种,例如判断是否是list或者tuple

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

dir()

获取一个对象所有属性和方法,使用dir()函数,返回一个包含字符串的list,比如获得一个str对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

仅仅把属性和方法列出来是不够的,配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()

可以测试该对象的属性:

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

当获取不存在的属性时会抛出错误:

>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

以下是获得对象方法的例子:

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

一个例子如下:

def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None

假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。

在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。

实例属性和类属性

根据类创建的实例可以任意绑定属性,给实例绑定属性的方法是通过实例变量,或者通过self变量,例如:

class Student(object):
def __init__(self, name):
self.name = name

s = Student('Bob')
s.score = 90

如果student类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归student类所有:

class Student(object):
name = 'Student'

当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。

>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

面向对象高级编程

使用__slots__

当我们创建了一个实例后,我们可以给这个实例绑定任何属性和方法。

>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

但是,给一个实例绑定的方法,对另一个实例是不起作用的。为了给所有实例都绑定方法,可以给class绑定方法,给class绑定方法后,所有实例均可调用:

>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score

>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能。

如果想要限制实例的属性,比如只允许对student实例添加name和age属性,就要在定义class的时候定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__