python语法小细节及小定义(一)

  1. 云栖社区>
  2. 博客列表>
  3. 正文

python语法小细节及小定义(一)

戴千岩 2017-09-26 00:13:59 浏览12 评论0

摘要: 首先,Python是强类型语言,动态类型语言。 那么什么是强类型语言?就是数据类型非常固定的语言,例如说python中的str类型数据和int类型数据不能互相作用。

首先,Python是强类型语言,动态类型语言。

那么什么是强类型语言?就是数据类型非常固定的语言,例如说python中的str类型数据和int类型数据不能互相作用。而c和js就是弱类型语言。

动态类型语言指的是变量类型可变,而C语言的变量类型不可改变,所以是静态类型语言。

输入输出

print()会依次打印每个字符串,遇到作为分隔符的逗号“,”会输出一个空格

# print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出
print('The quick brown fox', 'jumps over', 'the lazy dog')
# print()会依次打印每个字符串,遇到逗号“,”会输出一个空格,因此,输出的字符串是这样拼起来的:
The quick brown fox jumps over the lazy dog

任何计算机程序都是为了执行一个特定的任务,有了输入,用户才能告诉计算机程序所需的信息,有了输出,程序运行后才能告诉用户任务的结果。

输入是Input,输出是Output,因此,我们把输入输出统称为Input/Output,或者简写为IO。

input()和print()是在命令行下面最基本的输入和输出,但是,用户也可以通过其他更高级的图形界面完成输入和输出,比如,在网页上的一个文本框输入自己的名字,点击“确定”后在网页上看到输出信息。



数据类型和变量

# 转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\
print('I\'m \"OK\"!')
I'm "OK"!

如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r”表示”内部的字符串默认不转义

print('\\\t\\')
print(r'\\\t\\')
\   \
\\\t\\

Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。

注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648~2147483647。

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

字符串和编码

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器

# 在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言,例如:
print('包含中文的str')
包含中文的str

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。

Python对bytes类型的数据用带b前缀的单引号或双引号表示:

x = b'ABC'
y = 'ABC'
type(x), type(y)
(bytes, str)

以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

t1 = 'ABC'.encode('ascii')
print(type(t1))
print(t1)
<class 'bytes'>
b'ABC'
t2 = '中文'.encode('utf-8')
print(type(t2))
print(t2)
t2_decode = t2.decode('utf-8')
print(type(t2_decode))
print(t2_decode)
<class 'bytes'>
b'\xe4\xb8\xad\xe6\x96\x87'
<class 'str'>
中文
t3 = "中文"
print(type(t3))
print(t3)
<class 'str'>
中文

Python 3的字符串使用Unicode,直接支持多语言。

str和bytes互相转换时,需要指定编码。最常用的编码是UTF-8。Python当然也支持其他编码方式,比如把Unicode编码成GB2312:

'中文'.encode('gb2312')
b'\xd6\xd0\xce\xc4'
b'\xd6\xd0\xce\xc4'

如果没有特殊业务要求,仅使用UTF-8编码即可。

格式化字符串的时候,可以用Python的交互式命令行测试,方便快捷。

格式化

常见的占位符有:

%d 整数

%f 浮点数

%s 字符串

%x 十六进制整数

# 其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
'%2d-%02d' % (3, 1)
' 3-01'
'%.2f' % 3.1415926
'3.14'

list和tuple

list

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

list元素也可以是另一个list,比如:

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

tuple

另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改

但是,要定义一个只有1个元素的tuple,如果你这么定义:

t = (1)

t

1
定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。

所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

t = (1,)

t

(1,)

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

理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。

高级特性

但是在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

基于这一思想,我们来介绍Python中非常有用的高级特性,1行代码能实现的功能,决不写5行代码。请始终牢记,代码越少,开发效率越高。

切片

取一个list或tuple的部分元素是非常常见的操作

取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
r = []
n = 3
for i in range(n):
    r.append(L[i])

print(r)
['Michael', 'Sarah', 'Tracy']

对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。

对应上面的问题,取前3个元素,用一行代码就可以完成切片:

# L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
L[0:3]
['Michael', 'Sarah', 'Tracy']

类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:

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

迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。

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

最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

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

小结:

任何可迭代对象都可以作用于for循环,包括我们自定义的数据类型,只要符合迭代条件,就可以使用for循环。

列表生成式

List Comprehensions, Python内置的非常简单却强大的可以用来创建list的生成式。

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

但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:

L = []
for x in range(1, 11):
    L.append(x*x)

L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

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

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

[x*x for x in range(1, 5) if x % 2 == 0]
[4, 16]

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

[m + n for m in 'ABC' for n in 'DEF']
['AD', 'AE', 'AF', 'BD', 'BE', 'BF', 'CD', 'CE', 'CF']

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

import os
[d for d in os.listdir('.')]
['.ipynb_checkpoints', 'Python3_语法细节复习.ipynb', '基础面试题复习.ipynb']

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

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

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

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

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

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

使用内建的isinstance函数可以判断一个变量是不是字符串:

x = 'abc'
y = 123
isinstance(x, str)
True
isinstance(y, str)
False

生成器

在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个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 0x000001CF7274AA98>

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a+b
        n = n+1
    return 'done'
f = fib(6)
f
<generator object fib at 0x000001CF7274AD00>

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

g = fib(6)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

* 这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。*

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

from collections import Iterable
isinstance([], Iterable)
True

* 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。 *

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

from collections import Iterator
isinstance((x for x in range(10)), Iterator)
True
isinstance('abc', Iterator)
False
isinstance(iter('abc'), Iterator)
True

你可能会问,为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

* Python的for循环本质上就是通过不断调用next()函数实现的。*

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象
it = iter([1, 2, 3, 4, 5])
# 循环
while True:
    try:
        # 获得下一个值
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

递归函数

def fact(n):
    if n == 1:
        return 1
    return n * fact(n-1)
fact(5)
120

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

* 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 *

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num-1, num * product)

小结

使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

map/reduce

Python内建了map()和reduce()函数。

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

def f(x):
    return x * x

r = map(f, [1, 2, 3, 4, 5, 6])
print(r)
print(list(r))
<map object at 0x000002C4B51114E0>
[1, 4, 9, 16, 25, 36]

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

所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算

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

from functools import reduce

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

reduce(add, [1, 3, 5, 7, 9])
25
# 字典后面直接跟[key]可以取出value值
{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}["7"]
7

配合map()我们就可以写出str转换成int的函数

下面这个例子同时用到了reduce和map

from functools import reduce

def str2int(s):
    # 把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
    return reduce(fn, map(char2num, s))

还可以用lambda函数进一步简化成:

from functools import reduce

def char2num(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

Python内建的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]

把一个序列中的空字符串删掉,可以这么写:

s = '  234 '
s.strip() # 去掉空格?
'234'
def not_empty(s):
    return s 

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
['A', 'B', 'C', '  ']
def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
['A', 'B', 'C']

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

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

sorted 排序算法

排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

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]

我们再看一个字符串排序的例子:

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

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于’Z’ < ‘a’,结果,大写字母Z会排在小写字母a的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给sorted传入key函数,即可实现忽略大小写的排序:

sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

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

sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

返回函数与闭包,闭包与装饰器

返回函数,即函数作为返回值。

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

那么如果要得到结果,需要调用返回得到的函数。举例如下:

# 定义一个求和函数,实现可变参数的求和
def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax
# 不返回求和的结果,而是返回求和的函数
def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
f = lazy_sum(1, 3, 5, 7, 9)
f
<function __main__.lazy_sum.<locals>.sum>

调用函数 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()
True

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

本质上,decorator就是一个返回函数的高阶函数。

decorator可以动态增强函数的功能,即不改变原函数而增加功能。

匿名函数

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

用云栖社区APP,舒服~

【云栖快讯】云栖社区技术交流群汇总,阿里巴巴技术专家及云栖社区专家等你加入互动,老铁,了解一下?  详情请点击

网友评论

戴千岩
文章26篇 | 关注0
关注
是解决用户结构化数据搜索需求的托管服务,支持数据结构、搜索排序、数据处理自由定制。 为您的网... 查看详情
云数据库PPAS版,是阿里云与EnterpriseDB公司合作基于PostgreSQL高度兼... 查看详情
PostgreSQL被业界誉为“最先进的开源数据库”,面向企业复杂SQL处理的OLTP在线事... 查看详情
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效... 查看详情
建站4折

建站4折