Python 100Days 学习记录 Day16~20
https://github.com/jackfrued/Python-100-Days 我学习的网址,以下记录了我自身不太熟悉的知识点。
Day16-函数使用进阶
一、高阶函数
函数可以作为函数的参数,函数也可以作为函数的返回值。把一个函数作为其他函数的参数或返回值的用法,我们通常称之为“高阶函数”
1. 函数作为函数的参数
def calc(init_value, op_func, *args, **kwargs):
items = list(args) + list(kwargs.values())
result = init_value
for item in items:
if type(item) in (int, float):
result = op_func(result, item)
return result
def add(x, y):
return x + y
def mul(x, y):
return x * y
print(calc(0, add, 1, 2, 3, 4, 5)) # 15
print(calc(1, mul, 1, 2, 3, 4, 5)) # 120
import operator
print(calc(0, operator.add, 1, 2, 3, 4, 5)) # 15
print(calc(1, operator.mul, 1, 2, 3, 4, 5)) # 120
2.filter和map函数
我们前面提到过的filter和map函数就是高阶函数,前者可以实现对序列中元素的过滤,后者可以实现对序列中元素的映射
def is_even(num):
"""判断num是不是偶数"""
return num % 2 == 0
def square(num):
"""求平方"""
return num ** 2
old_nums = [35, 12, 8, 99, 60, 52]
new_nums = list(map(square, filter(is_even, old_nums)))
print(new_nums) # [144, 64, 3600, 2704]
当然,要完成上面代码的功能,也可以使用列表生成式,列表生成式的做法更为简单优雅。
old_nums = [35, 12, 8, 99, 60, 52] new_nums = [num ** 2 for num in old_nums if num % 2 == 0] print(new_nums) # [144, 64, 3600, 2704]
3.sorted
sorted可以实现对容器型数据类型(如:列表、字典等)元素的排序。我们之前讲过list类型的sort方法,它实现了对列表元素的排序,sorted函数从功能上来讲跟列表的sort方法没有区别,但它会返回排序后的列表对象,而不是直接修改原来的列表,这一点我们称为函数的无副作用设计,也就是说调用函数除了产生返回值以外,不会对程序的状态或外部环境产生任何其他的影响
old_strings = ['in', 'apple', 'zoo', 'waxberry', 'pear'] new_strings = sorted(old_strings, key=len) print(new_strings) # ['in', 'zoo', 'pear', 'apple', 'waxberry']
二、Lambda 函数(匿名函数)
lambda 函数只能有一行代码,代码中的表达式产生的运算结果就是这个匿名函数的返回值。之前的代码中,我们写的is_even和square函数都只有一行代码,我们可以考虑用 lambda 函数来替换掉它们,代码如下所示。
old_nums = [35, 12, 8, 99, 60, 52] new_nums = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, old_nums))) print(new_nums) # [144, 64, 3600, 2704]
通过上面的代码可以看出,定义 lambda 函数的关键字是lambda,后面跟函数的参数,如果有多个参数用逗号进行分隔;冒号后面的部分就是函数的执行体,通常是一个表达式,表达式的运算结果就是 lambda 函数的返回值,不需要写return 关键字。
三、偏函数
偏函数是指固定函数的某些参数,生成一个新的函数,这样就无需在每次调用函数时都传递相同的参数。在 Python 语言中,我们可以使用functools模块的partial函数来创建偏函数。例如,int函数在默认情况下可以将字符串视为十进制整数进行类型转换,如果我们修修改它的base参数,就可以定义出三个新函数,分别用于将二进制、八进制、十六进制字符串转换为整数,代码如下所示。
import functools
int2 = functools.partial(int, base=2)
int8 = functools.partial(int, base=8)
int16 = functools.partial(int, base=16)
print(int('1001')) # 1001
print(int2('1001')) # 9
print(int8('1001')) # 513
print(int16('1001')) # 4097
Day17-函数高级应用
一、装饰器
Python 语言中,装饰器是“用一个函数装饰另外一个函数并为其提供额外的能力”的语法现象。
import random
import time
def download(filename):
"""下载文件"""
print(f'开始下载{filename}.')
time.sleep(random.random() * 6)
print(f'{filename}下载完成.')
def upload(filename):
"""上传文件"""
print(f'开始上传{filename}.')
time.sleep(random.random() * 8)
print(f'{filename}上传完成.')
start = time.time()
download('MySQL从删库到跑路.avi')
end = time.time()
print(f'花费时间: {end - start:.2f}秒')
start = time.time()
upload('Python从入门到住院.pdf')
end = time.time()
print(f'花费时间: {end - start:.2f}秒')
通过上面的代码,我们可以在下载和上传文件时记录下耗费的时间,但不知道大家是否注意到,上面记录时间、计算和显示执行时间的代码都是重复代码。有编程经验的人都知道,重复的代码是万恶之源.在 Python 语言中,装饰器就是解决这类问题的最佳选择。通过装饰器语法,我们可以把跟原来的业务(上传和下载)没有关系计时功能的代码封装到一个函数中,如果upload和download函数需要记录时间,我们直接把装饰器作用到这两个函数上即可。
import random
import time
def download(filename):
"""下载文件"""
print(f'开始下载{filename}.')
time.sleep(random.random() * 6)
print(f'{filename}下载完成.')
def upload(filename):
"""上传文件"""
print(f'开始上传{filename}.')
time.sleep(random.random() * 8)
print(f'{filename}上传完成.')
import time
def record_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.2f}秒')
return result
return wrapper
download = record_time(download)
upload = record_time(upload)
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
在 Python 中,使用装饰器很有更为便捷的语法糖(编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强,我们将其称之为“语法糖”或“糖衣语法”),可以用@装饰器函数将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同。
import random
import time
def record_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.2f}秒')
return result
return wrapper
@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.random() * 6)
print(f'{filename}下载完成.')
@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.random() * 8)
print(f'{filename}上传完成.')
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
如果在代码的某些地方,我们想去掉装饰器的作用执行原函数,那么在定义装饰器函数的时候,需要做一点点额外的工作。Python 标准库functools模块的wraps函数也是一个装饰器,我们将它放在wrapper函数上,这个装饰器可以帮我们保留被装饰之前的函数,这样在需要取消装饰器时,可以通过被装饰函数的__wrapped__属性获得被装饰之前的函数。
import random
import time
from functools import wraps
def record_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.2f}秒')
return result
return wrapper
@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.random() * 6)
print(f'{filename}下载完成.')
@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.random() * 8)
print(f'{filename}上传完成.')
# 调用装饰后的函数会记录执行时间
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
# 取消装饰器的作用不记录执行时间
download.__wrapped__('MySQL必知必会.pdf')
upload.__wrapped__('Python从新手到大师.pdf')
二、递归调用
Python 中允许函数嵌套定义,也允许函数之间相互调用,而且一个函数还可以直接或间接的调用自身。函数自己调用自己称为递归调用。
函数调用会通过内存中称为“栈”(stack)的数据结构来保存当前代码的执行现场,函数调用结束后会通过这个栈结构恢复之前的执行现场。栈是一种先进后出的数据结构,这也就意味着最早入栈的函数最后才会返回,而最后入栈的函数会最先返回。
如果我们使用官方的 Python 解释器(CPython),默认将函数调用的栈结构最大深度设置为1000层。如果超出这个深度,就会发生RecursionError错误。
除此以外,我们还可以使用 Python 标准库中functools模块的lru_cache函数来优化上面的递归代码。lru_cache函数是一个装饰器函数,我们将其置于上面的函数fib3之上,它可以缓存该函数的执行结果从而避免在递归调用的过程中产生大量的重复运算,这样代码的执行性能就有“飞一般”的提升。
from functools import lru_cache
def fib1(n): if n in (1, 2): return 1 return fib1(n - 1) + fib1(n - 2) def fib2(n): a, b = 0, 1 for _ in range(n): a, b = b, a + b return a
@lru_cache()
def fib3(n):
if n in (1, 2):
return 1
return fib3(n - 1) + fib3(n - 2)
print(fib1(40)) #102334155 执行时间21.78秒 print(fib2(40)) #102334155 执行时间0.00秒
print(fib3(40)) #102334155 执行时间0.00秒
Day18-面向对象编程入门
定义、初始化、创建及使用
在 Python 语言中,我们可以使用class关键字加上类名来定义类,写在类里面的函数我们通常称之为方法,方法就是对象的行为,也就是对象可以接收的消息。方法的第一个参数通常都是self,它代表了接收这个消息的对象本身。
class Student:
def __init__(self, name, age):
"""初始化方法"""
self.name = name
self.age = age
def study(self, course_name):
print(f'{self.name}正在学习{course_name}.')
def play(self):
print(f'{self.name}正在玩游戏.')
stu1 = Student('骆昊', 44)
stu2 = Student('王大锤', 25)
stu1.study("数学") #骆昊正在学习数学.
Student.study(stu2, "英语") #王大锤正在学习英语.
Day19-面向对象编程进阶
一、可见性和属性装饰器
对象的属性通常会被设置为私有(private)或受保护(protected)的成员,简单的说就是不允许直接访问这些属性。可以用__name表示一个私有属性,_name表示一个受保护属性。
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
def study(self, course_name):
print(f'{self.__name}正在学习{course_name}.')
stu = Student('王大锤', 20)
stu.study('Python程序设计')
# (仍然可以访问到私有属性)
print(stu._Student__name) #王大锤
print(stu.__name) # AttributeError: 'Student' object has no attribute '__name'
二、动态属性
在 Python 中,我们可以动态为对象添加属性,这是 Python 作为动态类型语言的一项特权。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('王大锤', 20)
stu.sex = '男' # 给学生对象动态添加sex属性
如果不希望在使用对象时动态的为对象添加属性,可以使用 Python 语言中的__slots__魔法。对于Student类来说,可以在类中指定__slots__ = ('name', 'age'),这样Student类的对象只能有name和age属性,如果想动态添加其他属性将会引发异常。
三、静态方法和类方法
除了对象方法之外,类中还可以有静态方法和类方法,这两类方法是发给类的消息,二者并没有实质性的区别。
staticmethod装饰器声明了is_valid方法是Triangle类的静态方法,如果要声明类方法,可以使用classmethod装饰器(如上面的代码15~18行所示)。
对象方法、类方法、静态方法都可以通过“类名.方法名”的方式来调用,区别在于方法的第一个参数到底是普通对象还是类对象,还是没有接受消息的对象。静态方法通常也可以直接写成一个独立的函数,因为它并没有跟特定的对象绑定。
我们可以给上面计算三角形周长和面积的方法添加一个property装饰器(Python 内置类型),这样三角形类的area就变成了两个属性,不再通过调用方法的方式来访问,而是用对象访问属性的方式直接获得.
class Triangle(object):
"""三角形"""
def __init__(self, a, b, c):
"""初始化方法"""
self.a = a
self.b = b
self.c = c
@staticmethod
def is_valid(a, b, c):
"""判断三条边长能否构成三角形(静态方法)"""
return a + b > c and b + c > a and a + c > b
@classmethod
def is_valid2(cls, a, b, c):
"""判断三条边长能否构成三角形(类方法)"""
return a + b > c and b + c > a and a + c > b
def perimeter(self):
"""计算周长"""
return self.a + self.b + self.c
@property
def area(self):
"""计算面积"""
p = self.perimeter() / 2
return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5
if __name__ == '__main__':
print(Triangle.is_valid(3, 2, 5)) # False
print(Triangle.is_valid2(3, 4, 5)) # True
t = Triangle(3, 4, 5)
print(f'周长: {t.perimeter()}') # 周长: 12
print(f'面积: {t.area}') # 面积: 6.0
四、继承和多态
面向对象的编程语言支持在已有类的基础上创建新类,从而减少重复代码的编写。提供继承信息的类叫做父类(超类、基类),得到继承信息的类叫做子类(派生类、衍生类)。
class Person:
"""人"""
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(f'{self.name}正在吃饭.')
def sleep(self):
print(f'{self.name}正在睡觉.')
class Student(Person):
"""学生"""
def __init__(self, name, age):
super().__init__(name, age)
def study(self, course_name):
print(f'{self.name}正在学习{course_name}.')
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self.title = title
def teach(self, course_name):
print(f'{self.name}{self.title}正在讲授{course_name}.')
浙公网安备 33010602011771号