Python进阶(迭代,函数式编程,Collections类)

PYTHON进阶

PYTHON迭代

@生成器(Generator)

列表生成器产生的列表很占用内存空间,我们每次在计算使用的时候都是对单个元素进行操作,这样其它元素占用的空间就白白浪费了。所以如果列表内的元素可以按照某种算法推算出来,这样我们就可以在循环过程中不断的推算下一个元素(一次只推算一个),从而避免创建完整的列表而占用大量内存
在Python中我们把一边循环一边计算的机制称为生成器:generator.

生成器创建的语法

列表生成器中括号[]包裹改为小括号()包裹

# 列表生成器
data = [x + 1 for x in range(0, 10)]
print(data)             # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 生成器
g = (x * x for x in range(10))
print(g)                # <generator object <genexpr> at 0x10a4d6318>

生成器的遍历

使用生成器的元素也很简单,直接循环打印出即可

注意:生成器创建完了,只能消费一次。也就是说当遍历过一次后,第二次再遍历时,列表已经为空了

# 使用for循环
for i in g:
    print(i)
    
# 直接使用list打印输出
g = (x for x in range(10))
list(g)                         # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

也可以调用Next函数直到计算出最后一个元素位置,但是这种方法很明显不适用,并且最后会抛出StopIteration的错误

g = (x * x for x in range(10))
next(g)                         # 逐个输出元素,遍历完后会抛出StopIteration异常

所以我们创建一个generator后,基本上永远不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误,generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

@迭代器

什么是可迭代对象(Iterable)?

可用于for循环的对象统称为可迭代对象:Iterable。比如之前学习的集合数据类型,list,tuple,dict, set, str等;还有上面介绍的生成器也是可迭代对象。

什么是迭代器(Iterator)?

可以被next()函数调用并不断返回下一个值的对象称为迭代器。比如生成器就是迭代器的一种,但list、dict、str,却不是Iterator。什么机制咧?迭代器属于未知长度的数据流,惰性序列(只管下一个),

from collections.abc import Iterable,Iterator
a = [1,2,3,4]
g = (i for i in range(10))
isinstance(a, Iterable)             # True
isinstance(a, Iterator)             # False
isinstance(g, Iterator)             # True

# iter方法可以将生成器转化为迭代器
next(iter(a))                           # 1

函数式编程

@高阶函数

变量可以指向函数,函数名也是变量,函数名就是指向函数的变量,比如对于len()这个函数来说,我们可以把len看做是变量

传入函数,既然变量可以指向函数,函数可以接受变量,那么一个函数就可以接受另一个函数作为传入参数

x = len
x([i for i in range(5)])                # 5

def add(x, y, z):
    return z(x) + z(y)
add([1,2,3,4], [4,3,1,5,6], max)        # 10

@map/reduce

map接收两个参数,一个是函数,一个是可迭代对象(Iterable),函数作用在序列的每一个元素上。

map函数返回的依然是可迭代对象(Iterable)。

# 将下面的序列元素与自身相乘
map(lambda x: x * x, [1, 2, 3])         
# 别忘记之前通过list包裹可迭代对象的例子
list(map(lambda x: x * x, [1, 2, 3]))   # [1, 4, 9]

reduce同样也是接收两个参数,一个是函数,一个是可迭代对象(Iterable),不过这个函数必须接受两个参数,reduce把计算结果继续与下一个元素做累积计算。

reduce函数返回的是最终的计算结果。

from functools import reduce
reduce(lambda a,b: a+b, [1,2,3,4,5])        # 15

@filter函数

filter同样也是接收两个参数,一个是函数,一个是可迭代对象(Iterable),不过该函数返回的结果必须是布尔型,filter根据true或者false进行元素的保留和删除

filter函数返回的依然是可迭代对象(Iterable)。

# 对下列数据只保留能被2整除的数
list(filter(lambda x: x % 2 == 0, [1,2,3,4,5]))    # [2, 4]

@匿名函数 Lambda

我们可以不用显示的定义函数名,因此可以使用匿名函数。

匿名函数可以被一个变量保存,也可以不保存直接使用。

# 用变量保存
f = lambda x : x * 2
list(map(f, [1,2,3]))                     # [2, 4, 6]

# 直接使用
list(map(lambda x: x * 2, [1, 2, 3]))     # [2, 4, 6]

@三元运算

  • [true] if [expression] else [false]
print("成年人") if 24 > 18 else print("未成年人")
list(map(lambda x: x ** 2 if x % 2 == 0 else x, [1,2,3,4,5]))

Collections类

@deque

deque和list的用法比较类似,它是队列(先进先出)与栈(先进后出)的实现,可以对有序序列数据进行两端的操作。

deque支持在O(1)的时间复杂度上对序列进行两端的append或者pop。list也可以实现同样的操作,但是它的复杂度是O(N).

from collections import deque

# 创建
d = deque([1,2,3])

# 添加
d.append(4)                 # deque([1, 2, 3, 4])
d.appendleft(0)             # deque([0, 1, 2, 3, 4])


# 删除
d.pop()                     # deque([0, 1, 2, 3])
d.popleft()                 # deque([1, 2, 3])


# 旋转
d.rotate(2)                 # deque([2, 3, 1])

通过魔法函数timeit计算时间,比较list和deque在左侧插入数据的效率

list_a = [x for x in range(1,1000000)]
deque_a = deque(list_a)
%timeit list_a.insert(0, 'a')
%timeit deque_a.appendleft('a')

执行

1.72 ms ± 196 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
234 ns ± 16.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

@Counter与OrderedDict

Counter

A Counter is a dict subclass for counting hashable objects. It is an unordered collection where elements are stored as dictionary keys and their counts are stored as dictionary values. Counts are allowed to be any integer value including zero or negative counts.

from collections import Counter

# 创建
a = list('Jeff')
c = Counter(a)                      # Counter({'J': 1, 'e': 1, 'f': 2})

# 遍历,子类具有父类的全部属性和方法
for k, v in c.items():
    print(k, v)

# 查看Top1的元素
c.most_common(1)                    # [('f', 2)]

OrderedDict

与Counter类似,只不过OrderedDict是有序的,而Counter是无序的

from collections import OrderedDict

# unsorted dictionary
d = {'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2}

# sorted by key
OrderedDict(sorted(d.items(), key=lambda t: t[0]))

# sorted by value
OrderedDict(sorted(d.items(), key=lambda t: t[1]))

# sorted by length of the key string
OrderedDict(sorted(d.items(), key=lambda t: len(t[0])))

r.popitem(last=False)           # ('pear', 1)

字符串处理

@常见处理方法

s = " Hello, World "
len(s)
s.split(' ')
s.lstrip()

# 查找和替换
s.endswith(" ")
s.startswith(" ")
s.find("ll")
s.replace("World", 'Tomorrow')

# 去空格转成大写并按逗号分隔
list(map(lambda t: t.strip(), s.strip().upper().split(",")))    # ['HELLO', 'WORLD']

@格式化

t = '五'
a = 3.1415
print("星期%s" %t)                            # 星期五
# 保留2位小数
print("pi是%.2f" %a)                         # pi是3.14

# format写法(推荐这样写)
a = '2019-12-12'
b = '10'
print("{0}的温度是{1}度".format(a, b))        # 2019-12-12的温度是10度

时间类:datetime

datime模块是专门用于处理时间的类,它是PYTHON的标准库之一,内容复杂且强大,这里只涉及一些常用的函数:

  • 获取时间
  • 字符串与时间的转换
  • 时间的提取
  • 日期之间的计算
# 获取时间
from datetime import datetime
print(datetime.now())                       # 2019-12-13 21:32:25.754216    
# 创建指定的时间
dt = datetime(2019, 12, 13, 20, 11)         # 2019-12-13 20:11:00
print(dt)


# 字符串与时间的转换
s1 = '20170901'
s1 = datetime.strptime(s1, '%Y%m%d')
s2 = "2019/05/03"
s2 = datetime.strptime(s2, '%Y/%m/%d')
s3 = "2019-09-17 20"
s3 = datetime.strptime(s3, '%Y-%m-%d %H')


# 时间的提取
dt.date()                                   
datetime.now().date()                       # datetime.date(2019, 12, 13)
# 属性year,month,day,hour,minute,second
dt.year                                     # 2019


# 日期间的计算
from datetime import datetime, timedelta
print(s3 - s1)                                               # 746 days, 20:00:00
s3 - timedelta(days=1, seconds=86400, hours=12, minutes=60)  # datetime.datetime(2019, 9, 15, 7, 0)

I/O

@读文件

我们的数据一般都是存在文件里的,需要通过程序将文件加载到Python环境中,这样才可以使用python对其进行分析。
PYTHON标准库提供了一些标准的实现,我们一起来看一下。

读文件需要使用 open()函数,其参数为文件名与标识符:

f = open("test2.txt", 'r', encoding="gbk")  # 指定文件编码和模式
data = f.readline()
f.close()
print(data)

‘r‘表示读文件的意思,在获取文件之后,使用read函数读取数据,最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源。但是每次这样写很麻烦,我们可以使用with关键词进行整合:

with open("test2.txt", 'r', encoding="gbk") as handle:
    data = handle.readlines()
print(data)                             # ['hello world\n', '\n', 'Today is 星期二']


list(map(lambda s : s.strip(),data))    # ['hello world', '', 'Today is 星期二']

@写文件

写文件和读文件几乎一致,唯一的区别是标识符需要改为"w"。

第一个实参也是要打开的文件的名称;
第二个实参(‘w‘)告诉Python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r‘)、 写入模式(‘w‘)、 附加模式(‘a‘)或是让你能够读取和写入文件的模式(‘r+‘)。如果
你省略了模式实参, Python将以默认的只读模式打开文件

with open("test2.txt",'w') as handle:
    handle.write("hello world\n")           # 注意,这里手动添加了换行符
    handle.write(" And fate")

如果你要写入的文件不存在,函数open()将自动创建它。然而,以写入(‘w‘)模式打开文件时千万要小心,因为如果指定的文件已经存在, Python将在返回文件对象前清空该文件。

附加到文件

如果你要给文件添加内容,而不是覆盖原有的内容,可以以附加模式打开文件。你以附加模式打开文件时, Python不会在返回文件对象前清空文件,你写入到文件的行都将添加到文件末尾。如果指定的文件不存在,Python将为你创建一个空文件。

with open("test.txt",'a') as handle:
    handle.write("Today is Nice!\n")
    handle.write("We are happy!!\n")

异常处理

Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码(即捕获),程序将继续运行;如果你未对异常进行处理(未捕获),程序将停止,并显示一个traceback,其中包含有关异常的报告。

异常是使用try-except代码块处理的。try-except代码块让Python在执行指定的操作时,一旦发生了异常知道该怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行: 显示你编写的友好的错误消息,而不是令用户迷惑的traceback。

while True:
    first_number = input("First Number: ")
    if first_number == 'q':
        break
    second_number = input("Second Number: ")
    if second_number == 'q':
        break
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError as e:
        print(" 除数不可以为0!!")
    print(answer)

执行

First Number: 10
Second Number: 2
5.0
First Number: 3
Second Number: 5
0.6
First Number: 10
Second Number: 0
 除数不可以为0!!

多个异常

some_list = [1, 2, 3]
try:
    print(some_list[5])
    some_list.remove(4)
# except Exception as e:    # 也可以用Exception替代,不过不建议这样做 
except (IndexError, ValueError) as e:   # 使用tuple封装多个异常类型
    print("Caught again!")
    print(e)

相关推荐