函数式编程和Python新核心特性

函数式编程和Python新核心特性

函数式编程(functional programming)其实是个很古老的概念,诞生距今快60年啦!

最古老的函数式编程语言Lisp

新出现的函数式编程语言:比如Erlang、.Scala、clojure等

热门语言:Python、java、JavaScript、C++等都增加了函数式编程的一些特性。

⚠️函数式编程在某些时刻,非常方便!但不需大家二选一。

⚠️我们通过一些常见的函数式编程的内容,先学习,后体会"函数式编程"。

函数式编程核心(高阶函数、闭包等)

高阶函数和内存分析

函数是一等公民

函数式编程最鲜明的特点就是:函数是一等公民(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数或者作为别的函数的返回值。

一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。

Python内建的高阶函数有mapreducefiltersorted

高阶函数_内存状态分析

python 复制代码
def test1():
    print("test function run!!!")


def test3(a, b):
    print(f"test3,{a},{b}")


def test2(func, *args, **kwargs):
    print("test2 function run!!!")
    func(*args, **kwargs)


a = test1
test2(a)
test2(test3, 10, 20)

"""
test2 function run!!!
test function run!!!
test2 function run!!!
test3,10,20
"""

lambda表达式和匿名函数

详情见Python入门篇的函数和内存分析章节

python 复制代码
f = lambda a, b, c: a + b + c
print(f)
print(id(f))
print(type(f))
print(f(1, 2, 3))

"""
<function <lambda> at 0x000001AF907F7E50>
1853555179088
<class 'function'>
6
"""

g = [lambda a: a * 2, lambda b: b * 4, lambda c: c * 8]
print(g[0](1), g[1](2), g[2](3))
"""
2 8 24
"""

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

偏函数:作用就是把一个函数某些参数固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单。

举例如下:

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换,代码如下:

print(int('12345))

int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

perl 复制代码
#base参数
print('转换为八进制',int('12345',base=8))
print('转换为十六进制',int('12345',16))

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

python 复制代码
def int2(x,base=2):
	return int(x,base)

print(int2('1000000'))  #64
print(int2('1010101'))  #85

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

python 复制代码
import functools

int2 = functools.partial(int, base=2)
print(int2('1000000'))  # 64
print(int2('1010101'))  # 85
print(int2('1000000', base=10))  # 也可以修改base的值

闭包closure

根据字面意思,可以形象地把闭包理解为一个封闭的包裹,这个包裹就是一个函数。当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量(外部函数的局部变量),自由变量可以随着包裹到处游荡。

局部变量:如果名称绑定再一个代码块中,则为该代码块的局部变量,除非声明为nonloca或global

全局变量:如果模块绑定在模块层级,则为全局变量

自由变章:如果变量在一个代码块中被使用但不是在其中定义,则为自由变量(不会被回收)

闭包概念和第一个闭包程序

我们知道,函数作用域是独立的、封闭的,外部的执行环境是访问不了的,但是闭包具有这个能力和权限。

闭包是一个函数,只不过这个函数有超能力,可以访问到另一个函数的作用域。

「函数」和「自由变量」的总和,就是一个闭包。

闭包的特点:

第一,闭包是一个函数,而且存在于另一个函数当中

第二,闭包可以访问到父级函数的变量,且该变量不会销毁

第三,外层函数把内层的这个函数本身当成返回值进行返回

python 复制代码
"""
闭包的特点:
1、存在内外层函数嵌套情况
2、内层函数引用了外层函数的变量或者参数(自由变量)
3、外层函数把内层的这个函数本身当成返回值进行返回
"""


def outer():
    print("outer")
    a = 1

    def inner():
        print("inner")
        # 如果要修改a的值,声明为nonlocal
        nonlocal a
        # 闭包是由于函数内部使用了函数外部的变量。
        # 这个函数对象不销毁,则外部函数的局部变量也不会被销毁。
        print(f"a:{a}")

    return inner


inn = outer()
print("-------")
inn()

"""
outer
-------
inner
a:1
"""

闭包内存分析

  1. 执行完inn=outer()的内存图。outer()栈帧执行完后实际已经消失了,画上去,是为了展现关系。

  2. 执行完inn=outer()的内存图。由于inner()内部函数的调用,outer()栈帧消失后,局部变量a指向的对象1仍然存在。从而形成了"闭包"。

  3. 第一次调用inn(),从而调用内部函数,仍然可以拿到以前局部变量指向的对象1

  4. 第二次调用inn(),仍然可以继续拿到以前局部变量指向的对象1,并将值变为2

闭包可以当成两个部分组成的整体:

  1. 函数
  2. 自由变量

闭包的作用

作用1:隐藏变量,避免全局污染

作用2:可以读取函数内部的变量

同时闭包使用不当,优点就变成了缺点:

缺点1:导致变量不会被垃圾回收机制回收,造成内存消耗

缺点2:不恰当的使用闭包可能会造成内存泄漏的问题

闭包和自由变量

python 复制代码
# coding=utf-8
"""
需求:实现变量a自增
通过自由变量,可以实现递增,也不会污染其他程序
"""
a = 10


def add():
    a = 10

    def increment():
        nonlocal a
        a += 1
        print("a:", a)

    return increment


def print_ten():
    if a == 10:
        print("ten!")
    else:
        print("全局变量a不等于10")


increment = add()
increment()
increment()
print_ten()

"""
a: 11
a: 12
ten!
"""

案例:用闭包实现不修改源码添加功能

python 复制代码
# coding=utf-8
"""
本次内容是装饰器的基础
"""


def outfunc(func):
    def infunc(*args, **kwargs):
        print("日志记录,start")
        func(*args, **kwargs)
        print("日志记录,end")

    return infunc


def fun1():
    print("使用功能1")


def fun2(a, b, c):
    print("使用功能2", a, b, c)


print(id(fun1))
fun1 = outfunc(fun1)
print(id(fun1))
fun1()
fun2 = outfunc(fun2)
fun2(10, 20, 30)


"""
1474019303136
1474084402656
日志记录,start
使用功能1
日志记录,end
日志记录,start
使用功能2 10 20 30
日志记录,end
"""

map函数(内置函数)

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

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

python 复制代码
# coding=utf-8
# map高阶函数使用案例
def f(x):
    return x * x


L = map(f, [1, 2, 3, 4])
print(list(L))
"""
[1, 4, 9, 16]
"""

# map高阶函数使用案例(用匿名函数)
L = map(lambda n: n * n, [1, 2, 3, 4])
print(list(L))
"""
[1, 4, 9, 16]
"""


# map函数传入两个列表
def f2(x, y):
    return x + y


L = map(f2, [1, 2, 3, 4, 5], [6, 7, 8, 9])
print(list(L))
"""
[7, 9, 11, 13]
"""

# map函数传入两个列表(用匿名函数)
L = map(lambda x, y: x + y, [1, 2, 3, 4, 5], [6, 7, 8, 9])
print(list(L))
"""
[7, 9, 11, 13]
"""

reduce函数(位于functools模块)

reduce位于functools模块

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

python 复制代码
# coding=utf-8
# reduce实现对一个序列求和
from functools import reduce


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


sum = reduce(add, [1, 3, 5, 7, 9])
print(sum)

filter函数(内置函数)

内置函数filter()用于过滤序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False,决定保留还是丢弃该元素。

python 复制代码
# coding=utf-8
# filter过滤列表,删除偶数,只保留奇数
def is_odd(n):
    return n % 2 == 1


L = filter(is_odd, [1, 2, 3, 4, 5, 6])
print(list(L))

# filter过滤列表,删除偶数,只保留奇数(用匿名函数实现)
L = filter(lambda n: n % 2 == 1, [1, 2, 3, 4, 5, 6])
print(list(L))


# filter序列中的空字符串删除
def not_empty(s):
    # strip()取出字符串首位指定信息
    return s and s.strip()


L = filter(not_empty, ["a", "b", "", None, " ", "C"])
print(list(L))
# filter序列中的空字符串删除(用匿名函数实现)
L = filter(lambda s: (s and s.strip()), ["a", "b", "", None, " ", "C"])
print(list(L))

"""
[1, 3, 5]
[1, 3, 5]
['a', 'b', 'C']
['a', 'b', 'C']
"""

sorted函数(内置函数)

排序算法,排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。

  1. 如果是数字,我们可以直接比较
  2. 如果是自定义对象呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。通常规定,对于两个元素x和y,如果认为x<y,则返回-1,如果认为x==y,则返回0,如果认为x>y,则返回1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。
python 复制代码
# coding=utf-8
# sorted对list进行排序
sorted1 = sorted([1, 23, -2, -20, 99])
print("升序排列:", list(sorted1))

sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序

python 复制代码
# coding=utf-8
# sorted对list进行排序
sorted1 = sorted([1, 23, -2, -20, 99])
print("升序排序:", list(sorted1))

# sorted函数接收一个key自定义排序
# abs按绝对值排序
sorted2 = sorted([1, 23, -2, -20, 99], key=abs)
print("自定义排序:", list(sorted2))

sorted3 = sorted([1, 23, -2, -20, 99], key=abs, reverse=True)
print("自定义逆序排序:", list(sorted3))

# 字符串排序按照ASCII
sorted4 = sorted(["abc", "ABC", "D", "d"])
print("字符串排序:", list(sorted4))
# 忽略字符串大小写排序
sorted5 = sorted(["abc", "ABC", "D", "d"], key=str.lower)
print("忽略字符串大小写排序:", list(sorted5))
# 忽略字符串大小写反向排序
sorted6 = sorted(["abc", "ABC", "D", "d"], key=str.lower, reverse=True)
print("忽略字符串大小写反向排序:", list(sorted6))

"""
升序排序: [-20, -2, 1, 23, 99]
自定义排序: [1, -2, -20, 23, 99]
自定义逆序排序: [99, 23, -20, -2, 1]
字符串排序: ['ABC', 'D', 'abc', 'd']
忽略字符串大小写排序: ['abc', 'ABC', 'D', 'd']
忽略字符串大小写反向排序: ['D', 'd', 'abc', 'ABC']
"""

sorted对自定义对象排序

python 复制代码
# coding=utf-8
from functools import cmp_to_key


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


def custom_sorted(stu1, stu2):
    if stu1.age < stu2.age:
        return -1
    if stu1.age > stu2.age:
        return 1
    return 0


stu1 = Student(18, "aaa")
stu2 = Student(28, "bbb")
stu3 = Student(21, "ccc")
student_list = sorted([stu1, stu2, stu3], key=lambda x: x.age)
for stu in student_list:
    print(f"{stu.name}-----{stu.age}")

student_list = sorted([stu1, stu2, stu3], key=cmp_to_key(custom_sorted))
for stu in student_list:
    print(f"cmp_to_key排序:{stu.name}-----{stu.age}")

"""
aaa-----18
ccc-----21
bbb-----28
cmp_to_key排序:aaa-----18
cmp_to_key排序:ccc-----21
cmp_to_key排序:bbb-----28
"""

装饰器深入剖析

概念

装饰器来自 Decorator 的直译。什么叫装饰,就是装点、提供一些额外的功能。在 Python 中的装饰器则是提供了一些额外的功能。

装饰器本质上是一个Python函数(其实就是闭包 ),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

装饰器用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

装饰器解决日志问题

闭包解决日志问题

python 复制代码
# coding=utf-8
def mylog(func):
    def infunc(*args, **kwargs):
        print("日志记录,start")
        func(*args, **kwargs)
        print("日志记录,end")

    return infunc


def fun1():
    print("使用功能1")


def fun2(a, b, c):
    print("使用功能2", a, b, c)


fun1 = mylog(fun1)
fun2 = mylog(fun2)

fun1()
fun2(10, 20, 30)

"""
日志记录,start
使用功能1
日志记录,end
日志记录,start
使用功能2 10 20 30
日志记录,end
"""

装饰器解决日志问题(增加参数处理,可以装饰任意多个参数的函数)

python 复制代码
# coding=utf-8
def mylog(func):
    def infunc(*args, **kwargs):
        print("日志记录,start")
        func(*args, **kwargs)
        print("日志记录,end")

    return infunc


@mylog  # 本质 fun1 = mylog(fun1)
def fun1():
    print("使用功能1")


@mylog  # 本质 fun2 = mylog(fun2)
def fun2(a, b, c):
    print("使用功能2", a, b, c)


fun1()
fun2(10, 20, 30)

"""
日志记录,start
使用功能1
日志记录,end
日志记录,start
使用功能2 10 20 30
日志记录,end
"""

多个装饰器

有时候,我们需要多个装饰器修饰一个函数。比如:需要增加日志功能、增加执行效率测试功能。

装饰器函数的执行顺序是分为(被装饰函数)定义阶段和(被装饰函数)执行阶段的,装饰器函数在被装饰函数定义好后立即执行。

在函数定义阶段:执行顺序是从最靠近函数的装饰器开始,自内而外的执行

在函数执行阶段:执行顺序由外而内,一层层执行

python 复制代码
# coding=utf-8
import time


def mylog(func):
    print("mylog start")

    def infunc():
        print("日志记录开始")
        func()
        print("日志记录结束")

    print("mylog end")
    return infunc


def cost_time(func):
    print("cost time start")

    def infunc():
        print("开始计时")
        start = time.time()
        func()
        end = time.time()
        print(f"耗费时间:{end - start}")

    print("cost time start")
    return infunc

# 等价于 mylog(cost_time(fun2))
@mylog
@cost_time
def fun2():
    print("fun2,start")
    time.sleep(3)
    print("fun2,end")


fun2()

"""
cost time start
cost time start
mylog start
mylog end
日志记录开始
开始计时
fun2,start
fun2,end
耗费时间:3.014333486557007
日志记录结束
"""

带参数的装饰器

python 复制代码
# coding=utf-8
def mylog(type):
    def decorator(func):
        def infunc(*args, **kwargs):
            if type == "文件":
                print("文件中:日志记录")
            else:
                print("控制台:日志记录")
            return func(*args, **kwargs)

        return infunc

    return decorator


@mylog("文件")
def fun2(a, b):
    print("使用功能2:", a, b)


if __name__ == '__main__':
    fun2(100, 200)

    
"""
文件中:日志记录
使用功能2: 100 200
"""

wraps装饰器

一个函数不止有他的执行语句,还有着 __name__ (函数名), __doc__(说明文档)等属性,我们之前的例子会导致这些属性改变。

functool.wraps 可以将原函数对象的指定属性赋值给包装函数对象,默认有module、name、doc,或者通过参数选择。

python 复制代码
# coding=utf-8
from functools import wraps


def mylog(func):
    @wraps(func)
    def infunc(*args, **kwargs):
        print("日志记录")
        print("函数文档:", func.__doc__)
        return func(*args, **kwargs)

    return infunc


@mylog    # 等价于 fun2 = mylog(fun2)
def fun2(a, b):
    """强大的功能2"""
    print("使用功能2:", a, b)


if __name__ == '__main__':
    fun2(100, 200)
    print("函数文档--->", fun2.__doc__)

    
"""
日志记录
函数文档: 强大的功能2
使用功能2: 100 200
函数文档---> 强大的功能2
"""

内置装饰器

我们在面向对象学习时,学习过三种装饰器: propertystaticmethodclassmethod 。(详见Python入门的面向对象部分)

property装饰器

property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。

python 复制代码
class Employee:

    def __init__(self, name, salary):
        self.name = name
        self.__salary = salary

    @property                      # 只能读 print(emp1.salary) 相当于属性的调用
    def salary(self):
        print("薪资是:", self.__salary)
        return self.__salary

    @salary.setter                 # 修改使用这个函数 emp1.salary = 50000
    def salary(self, salary): 
        if 0 < salary < 100000:
            self.__salary = salary
        else:
            print("薪资录入错误!只能在0-100000之间")


emp1 = Employee("john", 20000)
emp1.salary = 50000
print(emp1.salary)

emp1.salary = 100000000
print(emp1.salary)

"""
薪资是: 50000
50000

薪资录入错误!只能在0-100000之间
薪资是: 50000
50000
"""

staticmethod装饰器

staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。

python 复制代码
class Student:
    school = "HNU"  # 类属性

    @staticmethod
    def add(a, b):  # 静态方法
        print("{0}+{1}={2}".format(a, b, a + b))
        return a+b


Student.add(30, 40)

classmethod装饰器

classmethod 这个方法是一个类方法。该方法无需实例化,没有 self 参数。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。

python 复制代码
class Student:
    school = "HNU"  # 类属性

    @classmethod
    def printSchool(cls):
        print(cls.school)


Student.printSchool()

类装饰器

上面写的装饰器都是函数来完成的。我们用类也可以实现装饰器。

类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 __call__ 方法。

调用对象, __call__ 方法的使用

ruby 复制代码
class Demo:
	def __call__(self):
	print('我是Demo')
    
    
demo Demo()
demo()		# 直接调用对象,实质是调用了他的__ca11__()

类装饰器的使用案例

python 复制代码
# coding=utf-8
# 类装饰器
class MyLogDecorator():
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("日志记录")
        return self.func(*args, **kwargs)


@MyLogDecorator  # fun2 = MyLogDecorator(fun2)
def fun2():
    print("使用功能2")


if __name__ == '__main__':
    fun2()

    
"""
日志记录
使用功能2
"""

缓存装饰器和计时装饰器综合练习

python 复制代码
# coding=utf-8
import time


class CacheDecorator():
    __cache = {}

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # 如果缓存中有对应的方法名,则直接返回对应的返回值
        if self.func.__name__ in CacheDecorator.__cache:
            return CacheDecorator.__cache[self.func.__name__]
        # 如果缓存中没有对应的方法名,则进行计算,并将结果缓存
        else:
            result = self.func(*args, **kwargs)
            CacheDecorator.__cache[self.func.__name__] = result
            return result


def cost_time(func):
    def infunc(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"耗时:{end - start}")
        return result

    return infunc


@cost_time
@CacheDecorator
def func1_long_time():
    """模拟耗时较长,每次执行返回结果都一样的情况"""
    print("start func1")
    time.sleep(3)
    print("end func1")
    return 999


if __name__ == '__main__':
    print("第一次执行")
    r1 = func1_long_time()
    print("第二次执行")
    r2 = func1_long_time()
    print("打印结果:")
    print(r1)
    print(r2)

"""
第一次执行
start func1
end func1
耗时:3.009289264678955
第二次执行
耗时:0.0
打印结果:
999
999
"""

生成器、迭代器、动态性

生成器

生成器定义

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

为什么要有生成器

列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。

如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大

量的空间。

简单说:

时间换空间!想要得到庞大的数据,又想让它占用空间少,那就用生成器!

延迟计算!需要的时候,再计算出数据!

创建生成器的方式一(生成器表达式)

生成器表达式很简单,只要把一个列表推导式的 [] 改成 () ,就创建了一个生成器(generator):

python 复制代码
# coding=utf-8
L = [x * x for x in range(10)]
print(L)
g = (x * x for x in range(10))
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())

"""
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000002AD5900C430>
0
1
4
9
16
"""

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

创建生成器的方式二(生成器函数)

如果一个函数中包含 yield 关键字,那么这个函数就不再是一个普通函数,调用函数就是创建了一个生成器(generator)对象。

生成器函数:其实就是利用关键字 yield 一次性返回一个结果,阻塞,重新开始

python 复制代码
# coding=utf-8
"""
1.函数有了yield之后,调用它,就会生成一个生成器
2.yield的作用:程序挂起,返回相应的值。下次从下一个语句开始执行。
3.return在生成器中代表生成器终止,直接报错:StopIteration
4.next方法作用:唤醒并继续执行
"""


def demo():
    print("start")
    i = 0
    while i < 3:
        yield i
        print(f"i:{i}")
        i += 1
    print("end")
    return "done"


if __name__ == '__main__':
    a = demo()
    print(a)
    a.__next__()
    a.__next__()
    a.__next__()
    a.__next__()

"""
Traceback (most recent call last):
  File "D:\2022百战Python\函数式编程和核心特性\生成器、迭代器、动态性\生成器函数的创建_yield.py", line 27, in <module>
    a.__next__()
StopIteration: done
<generator object demo at 0x000001B4F30DC430>
start
i:0
i:1
i:2
end
"""

生成器函数的工作原理

原理是这样的:

  1. 生成器函数返回一个迭代器,for循环对这个迭代器不断调用 __next__() 函数,不断运行到下一个yield 语句,一次一次取得每一个返回值,直到没有 yield 语句为止,最终引发 StopIteration 异常。
  2. yield 相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从 yield下一条语句(不是下一行) 开始执行。
  3. send()next() 一样,都能让生成器继续往下走一步(下次遇到 yield 停),但 send() 能传一个值,这个值作为 yield 表达式整体的结果

生成器推导式底层原理也是这样的。

python 复制代码
# coding=utf-8
# send的作用是唤醒并继续执行,发送一个消息到生成器内部
def foo():
    print("start")
    i = 0
    while i < 3:
        temp = yield i
        print(f"temp:{temp}")
        i += 1
    print("end")


g = foo()
print(next(g))  # g.__next__()
print("*" * 10)
print(g.send(100))
print(next(g))

"""
start
0
**********
temp:100
1
temp:None
2
"""

总结

什么是生成器?

生成器仅仅保存了一套生成数值的算法,并且没有让这个算法现在就开始执行,而是我什么时候调它,它什么时候开始

计算一个新的值,并给你返回。

生成器特点:

  1. 生成器函数生成一系列结果。通过 yield 关键字返回一个值后,还能从其退出的地方继续运行,因此可以随时间产生一系列的值。
  2. 生成器和迭代是密切相关的,迭代器都有一个 __next__() 成员方法, 这个方法要么返回迭代的下一项,要么引起异常结束迭代。
  3. 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,Python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用 next() 函数和send() 函数恢复生成器。
  4. 生成器看起来像是一个函数,但是表现得却像是迭代器

迭代器

概念

  1. 迭代是Python最强大的功能之一,是访问集合元素的一种方式。
  2. 迭代器是一个可以记住遍历的位置的对象。
  3. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
  4. 迭代器只能往前不会后退。
  5. 迭代器有两个基本的方法: iter()next()

可迭代对象和迭代器区别

  1. 一个实现了 iter 方法的对象,称为"可迭代对象Ieratable"
  2. 一个实现 了next 方法并且是可迭代的对象,称为"迭代器Iterator"

即:实现了 iter 方法和 next 方法的对象就是迭代器。
⚠️生成器都是 Iterator 对象,但 listdictstr 虽然是 Iterable(可迭代对象) ,却不是 Iterator(迭代器)

python 复制代码
# coding=utf-8
# python3.6之前不加.abc,之后的加
from collections.abc import Iterator
from collections.abc import Iterable

a = isinstance([], Iterable)
a = isinstance([], Iterator)
print(a)

"""
False
"""

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

python 复制代码
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么 listdictstr 等数据类型不是 Iterator

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

所以,生成器一定是迭代器。

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

for循环的本质

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

css 复制代码
for x in [1, 2, 3, 4, 5]:
	pass

本质是:

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

创建一个迭代器

一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()

  1. __iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过StopIteration 异常标识迭代的完成。
  2. __next__() 方法会返回下一个迭代器对象。
python 复制代码
# coding=utf-8
# 创建一个依次返回10,20,30,...这样数字的迭代器
class MyNumbers:
    def __iter__(self):
        self.num = 10
        return self

    def __next__(self):
        if self.num < 40:
            x = self.num
            self.num += 10
            return x
        else:
            raise StopIteration


myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

"""
10
20
30
Traceback (most recent call last):
  File "D:\2022百战Python\函数式编程和核心特性\生成器、迭代器、动态性\创建一个迭代器.py", line 22, in <module>
    print(next(myiter))
  File "D:\2022百战Python\函数式编程和核心特性\生成器、迭代器、动态性\创建一个迭代器.py", line 14, in __next__
    raise StopIteration
StopIteration
"""

动态添加属性和方法

动态编程语言是高级程序设计语言的一个类别,在计算机科学领域已被广泛应用。

它是指在运行时可以改变其结构的语言 :例如新的函数、 对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。

给对象动态的添加属性和方法

ini 复制代码
# coding=utf-8
import types


class Person():
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = Person("jhon", 20)
p2 = Person("parker", 20)

# 动态给对象添加属性
p1.score = 100
print(p1.score)
# 只给p1添加了属性,p2没有
# print(p2.score)


# 动态给对象添加方法
def run(self):
    print(f"{self.name}, running")


# types.MethodType(run,p1)则是告诉解释器,self指的就是p1
p1.run = types.MethodType(run, p1)
p1.run()

type.MethonType的使用

arduino 复制代码
# types.MethodType(run,p1)则是告诉解释器,self指的就是p1

给类动态的添加属性、静态方法以及类方法

ruby 复制代码
# coding=utf-8
class Person():
    def __init__(self, name, age):
        self.name = name
        self.age = age


@staticmethod
def staticfunc():
    print("---static method---")


# 动态给类添加静态方法
Person.staticfunc = staticfunc
Person.staticfunc()


@classmethod
def clsfunc(cls):
    print('---cls method---')


# 动态给类添加类方法
Person.clsfunc = clsfunc
Person.clsfunc()

# 动态给类添加属性
Person.sorce = 100

__slots__ 的作用

  1. __slots__动态添加成员变量、成员方法有限制对动态添加类属性、类方法没有限制
  2. __slots__ 只对本类有限制,不限制子类。
ruby 复制代码
# coding=utf-8
class Person():
    __slots__ = {"name", "age"}

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

    def eat(self):
        print("eat!!!")


if __name__ == '__main__':
    p1 = Person("jhon", 21)
    # p1.gender = "man"
    # AttributeError: 'Person' object has no attribute 'gender'

正则表达式

正则表达式概念

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑(可以用来做检索,截取或者替换操作)。

作用

  1. 给定的字符串是否符合正则表达式的过滤逻辑(称作"匹配")。
  2. 可以通过正则表达式,从字符串中获取我们想要的特定部分。
  3. 还可以对目标字符串进行替换操作。

基本函数

Python语言通过标准库中的re模块支持正则表达式。re模块提供了一些根据正则表达式进行查找、替换、分隔字符串的函数,

这些函数使用一个正则表达式作为第一个参数。

函数 描述
match(pattern,string,flags=0) 根据pattern从string的头部开始匹配字符串,只返回第1次匹配成功的对象;否则,返回None
findall(pattern,string,flags=0) 根据pattern在string中匹配字符串。如果匹配成功,返回包含匹配结果的列表;否则,返回空列表。当pattern中有分组时,返回包含多个元组的列表,每个元组对应1个分组。flags表示规则选项,规则选项用于辅助匹配。
sub(pattern,repl,string,count=0) 根据指定的正则表达式,替换源字符串中的子串。pattern是一个正则表达式,repl是用于替换的字符串,string是源字符串。如果count等于0,则返回string中匹配的所有结果;如果count大于0,则返回前count个匹配结果
subn(pattern,repl,string,count=0) 作用和sub()相同,返回一个二元的元组。第1个元素是替换结果,第2个元素是替换的次数
search(pattern,string,flags=0) 根据pattern在string中匹配字符串,只返回第1次匹配成功的对象。如果匹配失败,返回None
compile(pattern,flags=0) 编译正则表达式pattern,返回1个pattern的对象
split(pattern,string,maxsplit=0) 根据pattern分隔string,maxsplit表示最大的分隔数
escape(pattern) 匹配字符串中的特殊字符,如*、+、?等

match函数的使用

re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回None。语法格式如下:

re.match(pattern, string, flags=0)

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符 - 可选标志

正则表达式修饰符

修饰符 描述
re.I 使匹配对大小写不敏感
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

意义:

  1. re.match是用来进行正则匹配检查的方法,如果字符串开头的0个或多个字符匹配正则表达式模式,则返回相应的match对象。如果字符串不匹配模式,返回None(注意不是空字符串"")
  2. 匹配对象Match Object具有group()方法, 用来返回字符串的匹配部分。具有span()方法,返回匹配字符串的位置(元组存储开始,结束位置)。具有start()end()方法,存储匹配数据的开始和结束位置。(也可以通过对象的dir(对象查看对象的方法))

⚠️注意:

如果想在目标字符串的任意位置查找,需要使用search

scss 复制代码
# coding=utf-8
import re

# 定义正则表达式
pattern = "hello"
# 目标字符串
str = "hello world"
# match函数的使用
result = re.match(pattern, str)
print(result)
print(dir(result))
print("匹配内容:", result.group())
print("匹配字符串的位置: ", result.span())

"""
<re.Match object; span=(0, 5), match='hello'>
['__class__', '__class_getitem__', '__copy__', '__deepcopy__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'end', 'endpos', 'expand', 'group', 'groupdict', 'groups', 'lastgroup', 'lastindex', 'pos', 're', 'regs', 'span', 'start', 'string']
匹配内容: hello
匹配字符串的位置:  (0, 5)
"""
ini 复制代码
# coding=utf-8
import re

# 定义正则表达式
pattern = "hello"
# 目标字符串
str = "Hello world"
# match函数的使用
# 忽略大小写
result = re.match(pattern, str, re.I)
print(result)

常用匹配符

一个正则表达式是由字母、数字和特殊字符(括号、星号、问号等)组成。正则表达式中有许多特殊的字符,这些特殊字符是构成正则表达式的要素。

符号 描述
. 匹配任何一个字符(除了\n)
[] 匹配列表中的字符
\w 匹配字母、数字、下划线,即a-z,A-Z,0-9,_
\W 匹配不是字母、数字、下划线
\s 匹配空白字符,即空格(\n,\t)
\S 匹配不是空白的字符
\d 匹配数字,即0-9
\D 匹配非数字的字符
ini 复制代码
# coding=utf-8
import re

# 常用匹配符.的使用:匹配任意一个字符(除了\n)
pattern = "."
str = "9"
# str = "a"
# str = "B"
# str = "_"
print(re.match(pattern, str))
# 常用匹配符\d的使用:匹配数字
pattern = "\d"
str = "0"
# str = "a"
# str = "B"
# str = "_"
print(re.match(pattern, str))
# 常用匹配符\D的使用:匹配非数字
pattern = "\D"
str = "a"
# str = "B"
# str = "_"
print(re.match(pattern, str))
# 常用匹配符\s的使用:匹配空白字符,即空格(\n,\t)
pattern = "\s"
str = " "
str = "\n"
# str = "\t"
print(re.match(pattern, str))
# 常用匹配符\S的使用:匹配不是空白的字符
pattern = "\S"
str = "a"
# str = "B"
# str = "_"
print(re.match(pattern, str))
# 常用匹配符\w的使用:匹配字母、数字、下划线
pattern = "\w"
str = "a"
# str = "8"
# str = "_"
print(re.match(pattern, str))
# 常用匹配符\W的使用:匹配不是字母、数字、下划线
pattern = "\W"
str = "#"
# str = "@"
# str = "_"
print(re.match(pattern, str))
# []匹配列表中的字符
pattern = "[12345]"
str = "1"
# str = "2"
# str = "3"
print(re.match(pattern, str))
# 匹配手机号码
s = "13456788789"
pattern = "1[35789]\d\d\d\d\d\d\d\d\d"
print("匹配手机号码:", re.match(pattern, s))

"""
<re.Match object; span=(0, 1), match='9'>
<re.Match object; span=(0, 1), match='0'>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='\n'>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='#'>
<re.Match object; span=(0, 1), match='1'>
匹配手机号码: <re.Match object; span=(0, 11), match='13456788789'>
"""

其中,匹配符"[]"可以指定一个范围,例如:"[ok]"将匹配包含"o"或"k"的字符。同时"[]"可以与\w、\s、\d等标记等价。例如,[0-9a-zAZ]等价于\w,[ ^0-9 ] 等价于\D。

限定符

从上面示例中可以看到如果要匹配手机号码,需要形如\d\d\d\d\d\d\d\d\d\d\d这样的正则表达式。其中表现了11次\d,表达方式烦琐。正则表达式作为一门小型的语言,还提供了对表达式的一部分进行重复处理的功能。例如,*可以对正则表达式的某个部分重复匹配多次。这种匹配符号称为限定符。

符号 描述
* 匹配零次或多次
+ 匹配一次或多次
? 匹配一次或零次
{m} 重复m次
{m,n} 重复m到n次,其中n可以省略,表示m到任意次
{m,} 重复至少m次
ini 复制代码
# coding=utf-8
import re

# 限定符 *、+、? 的使用
# * 匹配0次或多次
partten = "\d*"
str = "123abc"
# str = "abc"   # re.Match object; span=(0, 0), match=''>
print(re.match(partten, str))
# + 匹配1次或多次
partten = "\d+"
str = "123abc"
# str = "abc"   # None
print(re.match(partten, str))
# ? 匹配1次或0次
partten = "\d?"
str = "123abc"
# str = "abc"   # re.Match object; span=(0, 0), match=''>
print(re.match(partten, str))
# {m} 重复m次
partten = "\d{3}"
str = "1234abc"
print(re.match(partten, str))
# {m,n} 重复m到n次
partten = "\d{3,5}"
str = "1234abc"
print(re.match(partten, str))
# {m,} 重复至少m次
partten = "\d{3,}"
str = "123456abc"
print(re.match(partten, str))

"""
<re.Match object; span=(0, 3), match='123'>
<re.Match object; span=(0, 3), match='123'>
<re.Match object; span=(0, 1), match='1'>
<re.Match object; span=(0, 3), match='123'>
<re.Match object; span=(0, 4), match='1234'>
<re.Match object; span=(0, 6), match='123456'>
"""

限定符使用实例

python 复制代码
# coding=utf-8
import re

# 匹配出一个字符串首字母为大写字符,后边都是小写字符,这些小写字母可有可无
partten = "[A-Z][a-z]*"
str = "Abc"
print(re.match(partten, str))
# 匹配出有效的变量名
# partten = "[A-Za-z_][A-Za-z_0-9]*"
partten = "[A-Za-z_]\w*"
str = "Abc_"
print(re.match(partten, str))
# 匹配出1-99之间的数字
partten = "[1-9]\d?"
str = "123"
print(re.match(partten, str))
# 匹配出一个随机密码8-20位以内 (大写字母 小写字母 下划线 数字)
partten = "\w{8,20}"
str = "123abc_12D"
print(re.match(partten, str))

"""
<re.Match object; span=(0, 3), match='Abc'>
<re.Match object; span=(0, 4), match='Abc_'>
<re.Match object; span=(0, 2), match='12'>
<re.Match object; span=(0, 10), match='123abc_12D'>
"""

原生字符串

和大多数编程语言相同,正则表达式里使用``作为转义字符,这就可以能造成反斜杠困扰。

假如你需要匹配文本中的字符,那么使用编程语言表示的正则表达式里将需要4个反斜杠:前面两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。

Python里的原生字符串很好地解决了这个问题,使用Python的r前缀。例如匹配一个数字的"\d"可以写成r"\d"。有了原生字符串,再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

python 复制代码
# coding=utf-8
import re

# 反斜杠作为转义
s = "\n123"
print(s)
s = "\n123"
print(s)
# 使用原生字符串 r
s = r"\n123"
print(s)
# 在正则表达式中反斜杠作为转义
s = "\n123"
partten = "\n\d{3}"
print(re.match(partten, s))
# 目标字符串多个反斜杠
s = "\n123"
partten = "\\n\d{3}"
print(re.match(partten, s))
# 使用原生字符串 r
s = "\\n123"
partten = r"\\n\d{3}"
print(re.match(partten, s))

"""

123
\n123
\n123
<re.Match object; span=(0, 4), match='\n123'>
<re.Match object; span=(0, 5), match='\n123'>
<re.Match object; span=(0, 6), match='\\n123'>
"""

边界字符串

字符 功能
^ 匹配字符串开头
$ 匹配字符串结尾
\b 匹配一个单词的边界
\B 匹配非单词的边界

⚠️注意:

^[^m] 中的^的含义并不相同,后者^表示"除了...."的意思

python 复制代码
# coding=utf-8
import re

# 匹配字符串结尾 $
# 匹配一个5-10为的qq邮箱
pattern = "[1-9]\d{4,9}@qq.com$"
qq = "12345@qq.com"
qq = "12345@qq.com.cn"
print(re.match(pattern, qq))
# 匹配字符串开头 ^
pattern = "^hello.*"
s = "hello world"
print(re.match(pattern, s))
# 匹配单词边界 \b
# 左边界
pattern = r".*\bqwe"
s = "abc qweAB"
print(re.match(pattern, s))
# 右边界
pattern = r".*ing\b"
s = "123 runing"
print(re.match(pattern, s))
# 匹配非单词边界 \B
# 左边界
pattern = r".*\Bqwe"
s = "abc aqweBC"
print(re.match(pattern, s))
# 右边界
pattern = r".*ing\B"
s = "123 runingA"
print(re.match(pattern, s))

"""
None
<re.Match object; span=(0, 11), match='hello world'>
<re.Match object; span=(0, 7), match='abc qwe'>
<re.Match object; span=(0, 10), match='123 runing'>
<re.Match object; span=(0, 8), match='abc aqwe'>
<re.Match object; span=(0, 10), match='123 runing'>
"""

search函数

search在一个字符串中搜索满足文本模式的字符串。语法格式如下:

re.search(pattern, string, flags=0)

函数参数与match类似

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符 - 可选标志
python 复制代码
# coding=utf-8
import re

# 定义正则表达式
pattern = "hello"
# 目标字符串
str = "hello world"
# search函数的使用
result = re.search(pattern, str)
print(result)
print("匹配结果:", result.group())

"""
<re.Match object; span=(0, 5), match='hello'>
匹配结果: hello
"""

match和search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

python 复制代码
# coding=utf-8
import re

# 定义正则表达式
pattern = "hello"
# 目标字符串
str = "Hello world, hello Python"
# search函数的使用
result = re.search(pattern, str)
print("search:", result)
r = re.match(pattern, str)
print("match:", r)

"""
search: <re.Match object; span=(13, 18), match='hello'>
match: None
"""

择一匹配(|)的使用

search方法搜索一个字符串,要想搜索多个字符串,如搜索aa、bb和cc,最简单的方法是在文本模式字符串中使用择一匹配符号|。择一匹配符号和逻辑或类似,只要满足任何一个,就算匹配成功。

python 复制代码
# coding=utf-8
import re

# 择一匹配符号(|)的使用
pattern = "aa|bb|cc"
print(re.match(pattern, "aa"))
print(re.match(pattern, "bb"))
print(re.match(pattern, "cc"))
print(re.search(pattern, "where is bb"))

# 匹配0-100之间所有的数字
s = "0"
s = "9"
s = "91"
s = "100"
s = "101"
s = "1000"
pattern = "[1-9]?/d$|100$"
print(re.match(pattern, s))

"""
<re.Match object; span=(0, 2), match='aa'>
<re.Match object; span=(0, 2), match='bb'>
<re.Match object; span=(0, 2), match='cc'>
<re.Match object; span=(9, 11), match='bb'>
None
"""

如果待匹配的字符串中,某些字符可以有多个选择,就需要使用字符集[],也就是一对中括号括起来的字符串。例如,[xyz]表示x、y、z三个字符可以取其中任何一个,相当于x|y|z,所以对单个字符使用或关系时,字符集和择一匹配符的效果是一样的。示例如下:

python 复制代码
import re

m = re.match('[xyz]', 'x')  # 匹配成功
print(m.group())
m = re.match('x|y|z', 'x')  # 匹配成功
print(m.group())
python 复制代码
# 字符集([])和择一匹配符(|)的用法,及它们的差异
import re

# 匹配以第1个字母是a或者b,第2个字母是c或者d,如ac、bc、ad、bd
m = re.match('[ab][cd]', 'aceg')
print(m)
# 匹配以ab开头,第3个字母是c或者d,如abc、abd
m = re.match('ab[cd]', 'abcd')
print(m)
# 匹配ab或者cd
m = re.match('ab|cd', 'cd')
print(m)

分组

如果一个模式字符串中有用一对圆括号括起来的部分,那么这部分就会作为一组,可以通过group方法的参数获取指定的组匹配的字

符串。当然,如果模式字符串中没有任何用圆括号括起来的部分,那么就不会对待匹配的字符串进行分组。

字符 功能
(ab) 将括号中的字符作为一个分组
\num 引用分组num匹配到的字符串
(?p) 分别起组名
(?p=name) 引用别名为name分组匹配到的字符串
python 复制代码
# coding=utf-8
import re

# 匹配座机号码  区号{3,4}-电话号码{5,8}
pattern = "/d{3,4}-[1-9]/d{4,7}$"
s = "010-1234567"
result = re.match(pattern, s)
print(result)

# 使用分组
pattern = r"(\d{3,4})-([1-9]\d{4,7}$)"
s = "010-1234567"
result = re.match(pattern, s)
# print(result)
print(result.group())
print("获取第一个分组结果:", result.group(1))
print("获取第二个分组结果:", result.group(2))
print("使用groups(): ", result.groups())
print("获取第一个分组结果:", result.groups()[0])
print("获取第二个分组结果:", result.groups()[1])


# \num的使用
s = "<html><head>分组的使用</head></html>"
s = "<html><head>分组的使用</body></h1>"
pattern = r"<(.+)><(.+)>.+</\2></\1>"
print(re.match(pattern, s))
# (?P<别名>)
# 引用 (?P=别名)
s = "<html><head>分组的使用</head></html>"
pattern = r"<(?P<k1>.+)><(?P<k2>.+)>.+</(?P=k2)></(?P=k1)>"
print(re.match(pattern, s))

"""
None
010-1234567
获取第一个分组结果: 010
获取第二个分组结果: 1234567
使用groups():  ('010', '1234567')
获取第一个分组结果: 010
获取第二个分组结果: 1234567

None
<re.Match object; span=(0, 31), match='<html><head>分组的使用</head></html>'>
"""

使用分组要了解如下几点:

  1. 只有圆括号括起来的部分才算一组,如果模式字符串中既有圆括号括起来的部分,也有没有被圆括号括起来的部分,那么只会将被圆括号括起来的部分算作一组,其它的部分忽略。
  2. group方法获取指定组的值时,组从1开始,也就是说,group(1)获取第1组的值,group(2)获取第2组的值,以此类推。
  3. groups方法用于获取所有组的值,以元组 形式返回。所以除了使用group(1)获取第1组的值外,还可以使用groups()[0]获取第1组的值。groups()[1]获取第2组以及其它组的值的方式类似。

re模块中其他常用的函数

sub和subn搜索与替换

sub函数和subn函数用于实现搜索和替换功能。这两个函数的功能几乎完全相同,都是将某个字符串中所有匹配正则表达式的部分替

换成其他字符串。用来替换的部分可能是一个字符串,也可以是一个函数,该函数返回一个用来替换的字符串。sub函数返回替换后

的结果,subn函数返回一个元组,元组的第1个元素是替换后的结果,第2个元素是替换的总数。语法格式如下:

re.sub(pattern, repl, string, count=0,flags=0)

参数 描述
pattern 匹配的正则表达式
repl 替换的字符串,也可以是一个函数
string 要被查找替换的原始字符串
count 模式匹配后替换的最大次数,默认0表示替换所有的匹配
python 复制代码
# coding=utf-8
import re

# sub和subn
phone = "2004-959-559 # 这是一个国外电话号码"
# 替换目标字符串中的注释
pattern = "#.*"
result = re.sub(pattern, "", phone)
print(result)
# 删除非数字的字符串 \D
pattern = "\D"
result = re.sub(pattern, "", phone)
print(result)
# 调用subn
result = re.subn(pattern, "", phone)
print(result)
print("替换的结果:", result[0])
print("替换的次数:", result[1])

"""
2004-959-559 
2004959559
('2004959559', 15)
替换的结果: 2004959559
替换的次数: 15
"""

compile函数

compile 函数用于编译正则表达式,生成一个正则表达式(Pattern)对象,供 match()search() 这两个函数使用。语法格式为:

re.compile(pattern[, flags])

参数 描述
pattern 一个字符串形式的正则表达式
flags 可选,表示匹配模式,比如忽略大小写,多行模式等
python 复制代码
# coding=utf-8
import re

# compile函数的使用
s = 'first123 line'
pattern = r'\w+'
regex = re.compile(pattern)  # 匹配至少一个字母或数字
print("使用compile函数:", regex.match(s))
print("直接使用re.match:", re.match(pattern, s))

"""
使用compile函数: <re.Match object; span=(0, 8), match='first123'>
直接使用re.match: <re.Match object; span=(0, 8), match='first123'>
"""

findall函数

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。语法格式如下:

findall(pattern, string, flags=0)

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。如下表列出正则表达式修饰符 - 可选标志
python 复制代码
# findall 函数的使用
pattern = r'\w+'
s = 'first 1 second 2 third 3 _ $$'
o = re.findall(pattern, s)
print(o)

"""
['first', '1', 'second', '2', 'third', '3', '_']
"""

⚠️注意:

match 和 search 是匹配一次, findall 匹配所有

finditer函数

和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。

python 复制代码
# finditer函数的使用
pattern = r'\w+'
s = 'first 1 second 2 third 3 _ $$'
o = re.finditer(pattern, s)
print(o)
for i in o:
    print(i.group())
    
"""
<callable_iterator object at 0x000002598770DE50>
first
1
second
2
third
3
_
"""

split函数

split函数用于根据正则表达式分隔字符串,也就是说,将字符串与模式匹配的子字符串都作为分隔符来分隔这个字符串。split函数返

回一个列表形式的分隔结果,每一个列表元素都是分隔的子字符串。语法格式如下:

re.split(pattern, string[, maxsplit=0,flags=0])

描述
pattern 匹配的正则表达式
string 要匹配的字符串
maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等
python 复制代码
# split函数的使用
pattern = r'\d+'
s = 'a 1 b 2 c 3 d'
print(re.split(pattern, s))
# maxsplit 参数限定分隔的次数,这里限定为1,也就是只分隔一次
print(re.split(pattern, s, maxsplit=1))

"""
['a ', ' b ', ' c ', ' d']
['a ', ' b 2 c 3 d']
"""

贪婪模式和非贪婪模式

贪婪模式 指Python里数量词默认是贪婪的,总是尝试匹配尽可能多的字符非贪婪模式 与贪婪相反,总是尝试匹配尽可能少的字符

可以使用*?+{m,n}后面加上?,使贪婪变成非贪婪。

python 复制代码
# coding=utf-8
import re

s = "This is mytel:133-1234-1234"
print('----------贪婪模式---------')
pattern = r"(.+)(\d+-\d+-\d+)"
result = re.match(pattern, s)
print(result.group())
print("第一个分组匹配结果:", result.group(1))
print("第二个分组匹配结果:", result.group(2))
print('----------非贪婪模式---------')
pattern = r"(.+?)(\d+-\d+-\d+)"
result = re.match(pattern, s)
print(result.group())
print("第一个分组匹配结果:", result.group(1))
print("第二个分组匹配结果:", result.group(2))

print("-------------------------------")
print('贪婪模式')
s = "abc123"
v = re.match(r'abc(\d+)', 'abc123')
print(v.group(1))
print('非贪婪模式')
v = re.match(r'abc(\d+?)', 'abc123')
print(v.group(1))

"""
----------贪婪模式---------
This is mytel:133-1234-1234
第一个分组匹配结果: This is mytel:13
第二个分组匹配结果: 3-1234-1234
----------非贪婪模式---------
This is mytel:133-1234-1234
第一个分组匹配结果: This is mytel:
第二个分组匹配结果: 133-1234-1234
-------------------------------
贪婪模式
123
非贪婪模式
1
"""

Python新特性

formatted字符串字面值

formatted字符串是带有f字符前缀的字符串,可以很方便的格式化字符串

python 复制代码
# coding=utf-8
# 旧版本
name = "jhon"
age = 20
print("name: %s, age: %d" % (name, age))
# 使用format
print("name: {}, age: {}".format(name, age))

# 新版本
print(f"name: {name}, age: {age}")
names = ["java", "python", "c++"]
print(f"name1: {names[0]}, name2: {names[1]}, name3: {names[2]}")
# 表达式
a = 10
b = 20
print(f"a+b运算: {a + b}")
print(f"表达式运算的结果:{3 * (a + b)}")

"""
name: jhon, age: 20
name: jhon, age: 20
name: jhon, age: 20
name1: java, name2: python, name3: c++
a+b运算: 30
表达式运算的结果:90
"""

formatted字符串支持 =

python 复制代码
# formatted字符串支持 =
a = 10
b = 20
print(f"{a=}, {b=}")

# 使用指定的字符填充
# 使用 * 居中填充
name = "jhon"
print("{:*^20}".format(name))
print(f"{name:*^20}")
# 使用 * 居右填充
print(f"{name:*>20}")
# 使用 * 居左填充
print(f"{name:*<20}")

# 对数值变量的格式化
price = 12.235
print("{:.2f}".format(price))
print(f"{price:.2f}")
num = 12
print(f"{num=:.1f}")
pct = 0.789
print("{:.2f}%".format(pct*100))
print(f"{pct*100:.0f}%")

"""
a=10, b=20
*******jhon********
********jhon********
****************jhon
jhon****************
12.23
12.23
num=12.0
78.90%
79%
"""

字符串新方法

方法名 功能描述
str.removeprefix() 如果str以它开头的话,将会返回一个修改过前缀的新字符串,否则它将返回原始字符串
str.removesuffix() 如果str以其结尾,则返回带有修改过后缀的新字符串,否则它将返回原始字符串
python 复制代码
# coding=utf-8
info = 'helloworld'
# 删除前缀
print(info.removeprefix('hello'))
print(info.removeprefix('abc'))
# 删除后缀
print(info.removesuffix('world'))
print(info.removesuffix('World'))

"""
world
helloworld
hello
helloworld
"""

变量类型标注

变量类型注解是用来对变量和函数的参数返回值类型做注解(暗示),帮助开发者写出更加严谨的代码,让调用方减少类型方面的错误,也可以提高代码的可读性和易用性。

但是,变量类型注解语法传入的类型表述能力有限,不能说明复杂的类型组成情况,因此引入了 typing 模块,来实现复杂的类型表达。

常用的数据类型

注意:

  • mypy是Python的可选静态类型检查器
  • 安装mypy模块
  • 使用mypy进行静态类型检查 mypy 执行python文件

基本使用

python 复制代码
# coding=utf-8
# 简化变量类型标注
a: int = 10
b: str = "hello"
c: float = 3.14
d: bool = True
e: bytes = b"world"

# 复杂类型
from typing import List, Set, Dict, Tuple

x: List[int] = [1, 3, 4]
y: Set[str] = {"ab", "cd"}
z: Dict[str, str] = {"name": "jhon", "sex": "male"}
h: Dict[str, object] = {"name": "jhon", "sex": "male", "age": 20}
j: Tuple[int] = (3,)
k: Tuple[int, int, int] = (3, 4, 5)
l: Tuple[int, str, float] = (3, "name", 5.1)
# 定义可变大小的元组,使用省略号
m: Tuple[int, ...] = (2, 3, 4, 5)

简化变量类型标注

python 复制代码
# 3.10新特性可以直接使用 list,tuple,dict,set
n: list[str] = ["a", "b", "c"]
q: tuple[int] = (2,)
p: tuple[int, int] = (2, 3)
# 定义可变大小的元组,使用省略号
i: tuple[int, ...] = (2, 3, 4, 5)
f: set[str] = {"aa", "bb", "cc"}
u: dict[str, object] = {"k1": 1, "k2": "aa", "k3": True}

函数参数返回值添加类型标注

python 复制代码
# coding=utf-8
# 定义一个函数
# 参数 num:int类型
# 返回值:str类型
def num_fun(num: int) -> str:
    return str(num)


# 定义一个函数,两个参数都是int,返回int
def sun_fun(a: int, b: int) -> int:
    return a + b


# 定义一个函数,参数添加默认值
def fun_test(num1: int, num2: float = 12.34) -> float:
    return num1 + num2


print(fun_test(10, 29))  # 30
print(fun_test(10))  # 22.34

# 定义一个变量指向函数
from typing import Callable

# Callable[[参数类型, 参数类型], 返回值类型]
f: Callable[[int, int], int] = sun_fun
print(f(10, 20))  # 30

# 定义函数,产生整数的生成器,每次返回一个
from typing import Iterable


def return_fun(num: int) -> Iterable[int]:
    i = 0
    while i < num:
        yield i
        i += 1


print(return_fun(10))   # <generator object return_fun at 0x00000241595FC430>
for i in return_fun(10):
    print(i)
    
"""
0
1
2
3
4
5
6
7
8
9
"""

混合类型检查改进

联合运算符使用|线来代替了旧版本中Union[]方法,使得程序更加简洁。

python 复制代码
# coding=utf-8
# 旧版本
from typing import Union

def oldFunc(para: Union[int, float]) -> Union[int, float]:
    return para ** 2

# 调用
oldFunc(10)


# 新版本
def newFunc(para: int | float) -> int | float:
    return para + 10

# 调用
print(newFunc(10))
print(newFunc(10.10))

类型别名更改

之前是直接通过不同类型的赋值操作来赋予类型新的名字,在新版本中通过TypeAlias来规定了类型名字的替换。这样操作的优势在于能够让程序开发人员和Python编辑器更加清楚的知道newname是一个变量名还是一个类型的别名,提升程序开发的可靠性。

python 复制代码
# coding=utf-8
# 旧版本
oldname = str


def oldFunc(param: oldname) -> oldname:
    return param + param


oldFunc('oldFunc:百战程序员')

# 新版本
from typing import TypeAlias

newstr: TypeAlias = str
newint: TypeAlias = int


def newFunc(num: newint, msg: newstr) -> newstr:
    return str(num) + msg


print(newFunc(100, "类型别名更改"))

"""
100类型别名更改
"""

二进制表示频率为1的数量统计

通过调用bit_count函数来统计二进制中数字"1"的个数

python 复制代码
# coding=utf-8
# bit_count()函数
# 旧版本
value = 5
# bin()转化为二进制
print(bin(value))
print("出现1的次数:", bin(value).count("1"))
# 新版本
print("出现1的次数:", value.bit_count())

"""
0b101
出现1的次数: 2
出现1的次数: 2
"""

字典的三个方法新增mapping属性

在Python 3.10中,针对于字典的三个方法,itemskeys,和values都增加了一个mapping属性,通过下面的程序可以发现,对三个方法调用mapping属性后都会返回原字典数据

scss 复制代码
# coding=utf-8
mydict = {"一": 1, "二": 2, "三": 3}
print("字典数据:", mydict)
# 旧版本
print(mydict.keys(), mydict.values(), mydict.items())
# 新版本
keys = mydict.keys()
values = mydict.values()
items = mydict.items()
print(keys.mapping, values.mapping, items.mapping)
"""
字典数据: {'一': 1, '二': 2, '三': 3}
dict_keys(['一', '二', '三']) dict_values([1, 2, 3]) dict_items([('一', 1), ('二', 2), ('三', 3)])
{'一': 1, '二': 2, '三': 3} {'一': 1, '二': 2, '三': 3} {'一': 1, '二': 2, '三': 3}
"""

函数zip()新增strict参数

python 复制代码
# coding=utf-8
# 旧版本
keys = ["name", "age", "sex"]
values = ["jhon", 23, "man", 12, "nan"]
data_dict = dict(zip(keys, values))
print("创建的字典对象:", data_dict)
# 新版本
data_dict2 = dict(zip(keys, values, strict=True))
print("新版本添加strict属性:", data_dict2)

"""
创建的字典对象: {'name': 'jhon', 'age': 23, 'sex': 'man'}
Traceback (most recent call last):
  File "D:\2022百战Python\函数式编程和核心特性\python新特性\函数zip()新增strict参数.py", line 8, in <module>
    data_dict2 = dict(zip(keys,values,strict=True))
ValueError: zip() argument 2 is longer than argument 1
"""

对于zip函数加了strict参数,顾名思义strict参数就是要严格的通过参数长度的匹配原则,在以上代码中,keysvalues列表的长度并不一致。旧版本的zip函数会根据长度最短的参数创建字典。新版本的zip函数中,当设定strict参数为True,则要求zip的输入数必须要长度一致,否则报错。

dataclass

使用类

为了支持数据修改, 默认值, 比较等功能。更加好一些的方法是:使用自定义类来实现数据类。

python 复制代码
class Player:
    def __init__(self,name:str,number:int,postion:str,age:int = 18) -> None:
        self.name = name
        self.number = number
        self.postion = postion
        self.age = age
    def __repr__(self) -> str:
        return f'Player: {self.name} {self.number}'
    def __eq__(self, __o: object) -> bool:
        return self.age == __o.age
    def __gt__(self,__o: object) ->bool:
        return self.age > __o.age
    
p1 = Player('SXT',18,'PG',26)
print(p1)

缺点

  • __init__ 方法中重复代码 (示例中每个属性都需要写3遍)
  • 需要自己实现 __repr__ 方法, 和比较方法 __eq__ , __gt__

使用dataclass

dataclass 可以认为是提供了一个简写 __init__ 方法的语法糖,类型注释是必填项 (不限制数据类型时, 添加typing.Any为类型注释),默认值的传递方式和 __init__ 方法的参数格式一致。

python 复制代码
# coding=utf-8
from dataclasses import dataclass


@dataclass
class Player:
    name: str
    postion: str
    age: int


# 创建实例对象
p = Player("jhon", "beijing", 21)
print(p)

"""
Player(name='jhon', postion='beijing', age=21)
"""

优点

可以使用 dataclasses 模块中的其它方法,比如:

  • 转为字典 asdict
  • 转为元组 astuple
dataclass装饰器上的参数

dataclass 装饰器将根据类属性生成数据类和数据类需要的方法。

ini 复制代码
dataclasses.dataclass(*, init = True, repr = True, eq = True, order = False, unsafe_hash = False, frozen = False)
dataclass成员变量额外设置

可以通过 dataclass.filed 方法设置变量的额外功能

  1. defualt:设置默认值

    值为具体的值

  2. default_factory:设置默认值

    值为类型名,程序会根据类型名创建一个空的对象,若使用defualt设置需要手动创建空对象

  3. repr设置生成的 __repr__ 方法中是否加入此属性,默认是True

python 复制代码
# coding=utf-8
from dataclasses import dataclass, field


@dataclass
class Player:
    name: str
    postion: str
    age: int
    sex: str = field(default="man", repr=False)
    # msg: str = field(default="")
    # 创建空对象
    msg: str = field(default_factory=str)


# 创建实例对象
p = Player("jhon", "beijing", 21)
print(p)

"""
Player(name='jhon', postion='beijing', age=21, msg='')
"""
dataclass建立类变量

在类中建立变量,默认是成员变量,若需要设置类变量,需要设置类型为: ClassVar

python 复制代码
# coding=utf-8
from dataclasses import dataclass, field
from typing import ClassVar


@dataclass
class Player:
    name: str
    postion: str
    age: int
    sex: str = field(default="man", repr=False)
    # msg: str = field(default="")
    # 创建空对象
    msg: str = field(default_factory=str)
    # 类变量(类属性)
    country: ClassVar[str]


# 创建实例对象
p = Player("jhon", "beijing", 21)
print(p)
Player.country = "China"
print("类属性:", Player.country)

"""
Player(name='jhon', postion='beijing', age=21, msg='')
类属性: China
"""

字典合并

字典添加两个新的运算符:||=|运算符用于合并字典。|=用于更新字典。

python 复制代码
# coding=utf-8
dict1 = {'a': '1'}
dict2 = {'b': '2'}
# 旧版本
dict1.update(dict2)
print(dict1)
# 新版本
# |合并字典
dict3 = dict1 | dict2
print(dict3)
# |= 更新字典
dict1 |= dict2  # 等价于 dict1 = dict1 | dict2
print(dict1)

"""
{'a': '1', 'b': '2'}
{'a': '1', 'b': '2'}
{'a': '1', 'b': '2'}
"""

match语法

match...case结构化模式匹配,可以匹配字典、类以及其他更复杂的结构。match...case的匹配模式匹配于Java或C++中的switch的使用很相似。语法格式如下:

xml 复制代码
match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>
python 复制代码
# coding=utf-8
status = 404
match status:
    case 200:
        print("访问成功")
    case 404:
        print("访问的资源不存在")
    case 500:
        print("服务器错误")
    case _:
        print("以上都没有匹配成功")

p1 = ("superman", 23, "man")
p2 = ("joker", 21, "woman")
p3 = ("parker", 20, "male")


def func_test(person):
    match person:
        case (name, _, "man"):
            print(f"{name} is man")
        case (name, _, "woman"):
            print(f"{name} is woman")
        case (name, age, sex):
            print(f"{name}, {age}, {sex}")


# 调用函数
func_test(p1)
func_test(p2)
func_test(p3)

"""
访问的资源不存在
superman is man
joker is woman
parker,20,male
"""

上述代码中,case函数通过匹配元组,如果元组第三个参数是female,匹配到第一个case。如果元组第三个参数是male,匹配到第二个case。如果前面两个都不匹配,则输出最后的默认结果。

内存管理

元类

  1. 类也是对象(属于元类的对象)
  2. 使用动态创建类:

语法:type(类名,由父类名称组成的元组(可以为空),包含属性的字典(名称和值))

python 复制代码
# coding=utf-8
"""
元类:
    什么是元类?
        动态创建类
        元类->类
        类->对象
    用途?
        可以动态创建类
    如何使用?
    type()
        1.查看目标对象的数据类型
        2.可以使用type,动态创建类
        语法:
            类 = type(类名,(父类...),{属性,方法})
"""
# 创建一个默认父类,不包含任何属性方法的类
Person = type('Person', (), {})

# 是否可以用Person创建对象
p1 = Person()
print(p1)
# mro()   父类是object
print(Person.mro())


# 传统静态方式创建类
class Animal():
    def __init__(self, color):
        self.color = color

    def eat(self):
        print("动物需要吃东西")


def sleep(self):
    print("狗狗趴着睡觉")


# 使用type动态创建一个类,父类就是Animal
Dog = type('Dog', (Animal,), {'age': 3, 'sleep': sleep})
dog = Dog('Yellow')
print(dog.age)
dog.sleep()
# 是否继承了父类中的特性
# 父类中的属性
print(dog.color)
# 是否继承了父类的方法
dog.eat()

print(Dog.__name__)

"""
<__main__.Person object at 0x00000183883FFFD0>
[<class '__main__.Person'>, <class 'object'>]
3
狗狗趴着睡觉
Yellow
动物需要吃东西
Dog
"""

类装饰器

python 复制代码
"""
类装饰器:
    在不修改函数源代码的前提下,增加新的功能
"""


class AAA():
    def __init__(self, func):
        # print("我是AAA.init()")
        self.__func = func

    # 报错:TypeError: 'AAA' object is not callable
    # 要实现call方法
    def __call__(self, *args, **kwargs):
        print("在这里可以实现新增任意功能")
        self.addFunc()
        self.__func()

    def addFunc(self):
        print("用户权限验证")
        print("日志系统处理")


@AAA
# TypeError: __init__() takes 1 positional argument but 2 were given
# @AAA等价于test1 = AAA(test1)
def test1():
    print("我是功能1")


test1()

"""
在这里可以实现新增任意功能
用户权限验证
日志系统处理
我是功能1
"""

对象池

python 复制代码
"""
对象池
    1.数值类型
        小整数池
            小整数:  [-5,256]
                程序开始时,一次性加载到内存
                LEGB(局部变量,闭包中的变量,全局变量,内建变量)
                全局都是同一个

                id():
                is:

        大整数池
            每创建一个不是小整数范围内的变量,都会被自动存储到大整数池中

    2.字符串
        intern机制
        每个单词(字符串),不夹杂空格或者其他符号,默认开启intern机制,共享内存,靠引用计数决定是否销毁
"""

小整数池

  • 系统默认创建好的,等着你使用。
  • 概述: 整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。Python 对小整数的定义是 [-5, 256] 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,无论这个整数处于LEGB(局部变量,闭包,全局,内建模块)中的哪个位置,所有位于这个范围内的整数使用的都是同一个对象。

大整数池

默认创建出来,池内为空的,创建一个就会往池中存储一个

intern机制

每个单词(字符串),不夹杂空格或者其他符号,默认开启intern机制,共享内存,靠引用计数决定是否销毁

垃圾收集

概述

现在的高级语言如Java,C#等,都采用了垃圾收集机制,而不再是C,C++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。Python里也同Java一样采用了垃圾收集机制,不过不一样的是:Python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。

引用计数机制

python 复制代码
"""
垃圾回收机制:GC机制
    Python:
    1.引用计数机制为主
        如何获取一个对象的引用计数?
            sys.getrefcount(a)
            刚创建对象引用计数为2

        a.增加引用计数操作
            1、如果有新的对象对象使用该对象,+1
            2、装进列表 +1
            3、作为函数参数
        b.减少引用计数操作
            1.如果有新的对象对象使用该对象,新对象不在使用-1
            2.从列表中移除-1
            3.函数调用结束
            4.del 显示销毁

    2.隔代回收为辅助
        循环引用

"""
import sys


class AA():
    # 创建对象开辟内存时调用
    def __new__(cls, *args, **kwargs):
        print("开辟内存空间")
        return super(AA, cls).__new__(cls)

    # 初始化方法
    def __init__(self):
        print("创建对象at:%s" % hex(id(self)))

    # 对象被系统回之前,会调用该方法
    def __del__(self):
        print("%s say bye bye" % hex(id(self)))


def test1(aaa):
    print(aaa)
    print('a的引用计数为:%d' % sys.getrefcount(a))


a = AA()
print('a的引用计数为:%d' % sys.getrefcount(a))
b = a
print('a的引用计数为:%d' % sys.getrefcount(a))
list1 = [a]
print('a的引用计数为:%d' % sys.getrefcount(a))
test1(a)
print('a的引用计数为:%d' % sys.getrefcount(a))
print("*" * 50)

b = 100
print('a的引用计数为:%d' % sys.getrefcount(a))
list1.remove(a)
print('a的引用计数为:%d' % sys.getrefcount(a))

# 1
del a

print("----------------程序结束-----------------")

"""
开辟内存空间
创建对象at:0x1dd4d9dfeb0
a的引用计数为:2
a的引用计数为:3
a的引用计数为:4
<__main__.AA object at 0x000001DD4D9DFEB0>
a的引用计数为:6
a的引用计数为:4
**************************************************
a的引用计数为:3
a的引用计数为:2
0x1dd4d9dfeb0 say bye bye
----------------程序结束-----------------
"""

引用计数机制的优点:

  • 简单
  • 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。 实时性还带来一个好处:处理回收内存的时间分摊到了平时。

引用计数机制的缺点:

  • 维护引用计数消耗资源
  • 循环引用的问题无法解决(DOS窗口,查看内存tasklist,或者内存表,任务管理器)

隔代回收机制

分代回收是用来解决交叉引用(循环引用),并增加数据回收的效率。

原理:通过对象存在的时间不同,采用不同的算法来回收垃圾。 形象的比喻,三个链表,零代链表上的对象(新创建的对象都加入到零代链表),引用数都是一,每增加一个指针,引用加一,随后Python会检测列表中的互相引用的对象,根据规则减掉其引用计数。GC算法对链表一的引用减一,引用为0的,清除,不为0的到链表二,链表二也执行GC算法,链表三一样。存在时间越长的数据,越是有用的数据。

隔代回收触发时间?(GC阈值)

随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。当然,事实并非如此。因为循环引用的原因,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放"浮动的垃圾",并且将剩下的对象移动到一代列表。随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。

通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。

Python处理零代最为频繁,其次是一代然后才是二代。

python 复制代码
"""
垃圾回收机制:
    1.引用计数机制
        相互引用,引用计数无法删除类似的对象
    2.隔代回收机制
        原理:
            随着时间的推进,程序冗余对象逐渐增多,达到一定数量(阈值),系统进行回收

            (0代,1代,2代)

        代码:
        import gc
        gc.get_count()
        gc.get_threshold()   ->(700,10,10)
        gc.disable()

"""
import gc
import time


class AA():
    def __new__(cls, *args, **kwargs):
        print("new")
        return super(AA, cls).__new__(cls)

    def __init__(self):
        print("object:born at %s" % hex(id(self)))

    def __del__(self):
        print("%s 被系统回收" % hex(id(self)))


def start():
    while True:
        a = AA()
        b = AA()
        a.v = b
        b.v = a
        del a
        del b
        print(gc.get_threshold())
        print(gc.get_count())
        time.sleep(0.1)


# 手动关闭垃圾回收机制
# gc.disable()
gc.set_threshold(200, 10, 10)
start()

"""
new
object:born at 0x2442acafeb0
new
object:born at 0x2442acafe80
(20, 10, 10)
(2, 4, 1)
new
object:born at 0x2442acafdc0
new
object:born at 0x2442acafd90
(20, 10, 10)
(4, 4, 1)
new
object:born at 0x2442acafd00
new
object:born at 0x2442acafcd0
(20, 10, 10)
(6, 4, 1)
new
object:born at 0x2442acafc40
new
object:born at 0x2442acafc10
(20, 10, 10)
(8, 4, 1)
new
object:born at 0x2442acafb80
new
object:born at 0x2442acafb50
(20, 10, 10)
(10, 4, 1)
new
object:born at 0x2442acafac0
new
object:born at 0x2442acafa90
(20, 10, 10)
(12, 4, 1)
new
object:born at 0x2442acafa00
new
object:born at 0x2442acaf9d0
(20, 10, 10)
(14, 4, 1)
new
object:born at 0x2442acaf940
new
object:born at 0x2442acaf910
(20, 10, 10)
(16, 4, 1)
new
object:born at 0x2442acaf880
new
object:born at 0x2442acaf850
(20, 10, 10)
(18, 4, 1)
new
object:born at 0x2442acaf7c00x2442acafeb0 被系统回收
0x2442acafe80 被系统回收
0x2442acafdc0 被系统回收
0x2442acafd90 被系统回收
0x2442acafd00 被系统回收
0x2442acafcd0 被系统回收
0x2442acafc40 被系统回收
0x2442acafc10 被系统回收
0x2442acafb80 被系统回收
0x2442acafb50 被系统回收
0x2442acafac0 被系统回收
0x2442acafa90 被系统回收
0x2442acafa00 被系统回收
0x2442acaf9d0 被系统回收
0x2442acaf940 被系统回收
0x2442acaf910 被系统回收
0x2442acaf880 被系统回收
0x2442acaf850 被系统回收
"""
查看引用计数
gc模块的使用
ini 复制代码
# 常用函数:
# 1、获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
gc.get_count()
# 2、获取gc模块中自动执行垃圾回收的频率
gc.get_threshold()
# 3、设置自动执行垃圾回收的频率
gc.set_threshold(threshold0[,threshold1,threshold2])
# 4、python3默认开启gc机制,可以使用该方法手动关闭gc机制
gc.disable()
# 5、手动调用垃圾回收机制回收垃圾
gc.collect()
增加引用计数的条件
ini 复制代码
# 1.创建对象
stu = Student()
# 2.将对象加入列表
list1.append(stu)
# 3.对象被引用
stu2 = stu
# 4.将对象作为参数,传入某个函数
func(stu)
减少对象引用计数的情况
ini 复制代码
# 1.对象被显示销毁
del stu
# 2.对象名指向新的对象
stu = Student()
# 3.从容器中移除,或者显示销毁列表
list1.remove(stu)
list1.pop(stu)
# 4.局部变量对象,作为函数参数
# 函数结束时,引用计数-1
获取某个对象的引用计数
ini 复制代码
import sys
obj = 'Helloworld'
sys.getrefcount(obj)
list1 = []
list.append(obj)
sys.getrefcount(obj)

内建函数

什么叫内建函数:启动python解释器后,默认加载的函数称为内建函数

如何查看内建函数

python 复制代码
# 两种方式:
a. 
dir(__builtins__)
b. 
import builtins
dir(builtins)

常用内建函数

range()

Python range() 函数可创建一个整数列表,一般用在 for 循环中。 语法:range(start, stop[, step])

参数说明:

start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);

stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5)[0, 1, 2, 3, 4]没有5

step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)

返回:range

python 复制代码
"""
range(stop)
    默认从0开始,直到stop-1
    list1 = list(range(10))
print(list1)
range(start[,stop,step])
    start:表示开始
    stop:表示结束(不包括该元素)
        负值
    step:表示迭代的步长
        默认为1
        负值,表示递减

"""
# 创建列表的时候
list1 = list(range(10))
list1 = list(range(0, 10, 1))
list1 = list(range(0, 10, 2))
print(list1)
# 打印[1-10]之间的数据
for i in range(1, 11):
    print(i, end=" ")
print()

for i in range(1, 11, 2):
    print(i, end=" ")
print()

for i in range(10, -1, -1):
    print(i, end=" ")
    print()
    
for i in range(10, -1, -2):
    print(i, end=" ")

from collections import Iterable

print(isinstance(range(10), Iterable))

for i in range(10):
    print(i)
map()

map() 会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,

返回包含每次 function 函数返回值的新列表。

语法:

map(function, iterable, ...)

参数说明:

function:函数

iterable:一个或多个序列

返回:

Python 2.x 返回列表。

Python 3.x 返回迭代器。

python 复制代码
"""
map()
map(func, *iterables) --> map object

    Make an iterator that computes the function using arguments from
    each of the iterables.  Stops when the shortest iterable is exhausted.
"""
from collections import Iterable, Iterator

list1 = [1, 2, 3]
list2 = [4, 5, 6, 7]


def func1(x):
    return 2 * x


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


# 生成一个迭代器
# it1 = map(func1,list1)
it1 = map(lambda x: 2 * x, list1)
print(isinstance(it1, Iterator))
for i in it1:
    print(i, end=' ')
print()
print('-------------------')
# it2 = map(func2,list1,list2)
it2 = map(lambda x, y: x + y, list1, list2)
for i in it2:
    print(i, end=' ')
print()
print('--------------------')
filter()

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

语法:

filter(function, iterable)

参数说明:

function:判断函数。

iterable:可迭代对象。

返回:

返回迭代器。

python 复制代码
"""
filter()
filter(function or None, iterable) --> filter object

    Return an iterator yielding those items of iterable for which function(item)
    is true. If function is None, return the items that are true

"""
list1 = [1, 2, 3, 0, 10, 0, 25]
# 遍历列表中不为0的元素
for i in list1:
    if i != 0:
        print(i, end=' ')
print()
# it1 = filter(None,list1)
# for i in it1:
#     print(i,end=' ')

# it1 = filter(lambda x:x != 0,list1)
# for i in it1:
#     print(i,end=' ')
it1 = filter(lambda x: x >= 5, list1)
for i in it1:
    print(i, end=' ')
reduce()

3.x后,需要先from functools import reduce

reduce() 函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:

用传给reduce中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

语法:

reduce(function, iterable[, initializer])

参数说明:

function:函数,有两个参数

iterable:可迭代对象

initializer:可选,初始参数

返回:

返回函数计算结果

python 复制代码
"""
reduce()

需要先从functools 引入
需求:
    有一个存储了5个随机整数的列表,求列表中所有元素的和
    1.生成随机列表
    2.遍历求和/或者用其他方式求和  reduce()

"""
import random
from functools import reduce

list1 = []
for i in range(5):
    rand_num = random.randint(1, 100)
    list1.append(rand_num)
print(list1)

sum1 = 0
for i in list1:
    sum1 += i
print('和为:%g' % sum1)

result = reduce(lambda x, y: x + y, list1)
print('和为:%g' % result)
sorted()

sorted() 函数对所有可迭代的对象进行排序操作。

sort 与 sorted 区别:

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

语法:

sorted(iterable[, key[, reverse]])

参数说明:

iterable:可迭代对象。

key:主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。

reverse:排序规则,reverse = True 降序 , reverse = False 升序(默认)

返回:

返回重新排序的列表。

scss 复制代码
# coding=utf-8
"""
list.sort()
无返回值,对源数据进行排序
sorted()
有返回值,返回排序备份

sort(iterable,key=None,reverse=False)
Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

"""

list1 = [1, 0, 9, 29, 3]
list2 = sorted(list1)
list3 = sorted(list1, reverse=True)
print(list2)
print(list3)
# 正数从小到大,负数从大到小
list1 = [1, 0, -9, 29, 3, -10, -2, -5]
list2 = sorted(list1, key=lambda x: (x < 0, abs(x)))
print(list2)


# 列表中自定义对象的排序
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu1 = Student('aaa', 19)
stu2 = Student('ccc', 20)
stu3 = Student('bbb', 18)

list1 = [stu1, stu2, stu3]
for stu in list1:
    print(stu.name, stu.age)
print("---------------------")
# list2 = sorted(list1,key=lambda x:x.age)
list2 = sorted(list1, key=lambda x: x.name)
for stu in list2:
    print(stu.name, stu.age)

偏函数

概念:python 中提供一种用于对函数固定属性的函数

定义:

csharp 复制代码
from functools import partial
int3 = partial(int,base=2)
print(int3('1010'))

作用:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

python 复制代码
"""
偏函数的使用:
partial
"""
from functools import partial

str1 = '1010'
result = int(str1, 2)
print(result)


# 重新定义一个函数
def int2(n, base=2):
    return int(n, base)


# result = int(str1,2)
result = int2(str1)
print(result)

# 使用偏函数完成类似的功能
int3 = partial(int, base=2)
# print(type(int3))
print(int3(str1))

wraps

作用:functools.wraps 可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module____name____doc__,或者通过参数选择。

python 复制代码
 """
 wraps函数的使用
 from functools import wraps
 ​
 作用:
 functools.wraps 可以将原函数对象的指定属性
 复制给包装函数对象, 默认有 module、name、doc,或者通过参数选择
 """
 from functools import wraps
 ​
 ​
 def log(func):
     @wraps(func)
     def with_logging(*args, **kwargs):
         print("%s was calling" % func.__name__)
         return func(*args, **kwargs)
 ​
     return with_logging
 ​
 ​
 @log
 # test = log(test)
 def test(x):
     """求x*x的值"""
     return x * x
 ​
 ​
 print(test.__name__)
 print(test.__doc__)
 """
 with_logging
 None
 """
相关推荐
游客52010 分钟前
opencv中的各种滤波器简介
图像处理·人工智能·python·opencv·计算机视觉
Eric.Lee202112 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Dontla17 分钟前
vscode怎么设置anaconda python解释器(anaconda解释器、vscode解释器)
ide·vscode·python
qq_529025291 小时前
Torch.gather
python·深度学习·机器学习
数据小爬虫@1 小时前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
終不似少年遊*2 小时前
pyecharts
python·信息可视化·数据分析·学习笔记·pyecharts·使用技巧
Python之栈2 小时前
【无标题】
数据库·python·mysql
袁袁袁袁满2 小时前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
老大白菜2 小时前
Python 爬虫技术指南
python