Python视频学习(七、★Python高级)

重点回顾:

  1. GIL锁是CPython解释器的问题
  2. copy模块的deepcopy和copy方法对于tuple拷贝的区别
  3. 私有属性的继承问题和重整
  4. 多模块引入问题
  5. Python对象的__mro__ ,以及导致的 super调用顺序,还有 类属性解析顺序
  6. property创建和使用
  7. 各种魔法属性
  8. 上下文管理器的使用,返回对象是__enter__返回值
  9. hasattr, setattr,getattr方法

1. GIL锁

1.1 多任务不同情形下的CPU占用率

使用的是htop程序查看的CPU占用情况。

单线程死循环:

while True:
	pass

在这里插入图片描述

双线程死循环

import threading

def doforever:
	while True:
		pass

t = threading.Thread(target = doforever)
t.start()

while True:
	pass

在这里插入图片描述

双进程死循环

import multiprocessing

def doforever:
	while True:
		pass

t = multiprocessing.Process(target = doforever)
t.start()

while True:
	pass

在这里插入图片描述

1.2 GIL锁的概念特点

GIL(global interpreter lock)是全局解释器锁,它不是Python语言的特性,而是由于历史原因,使得CPython解释器在实现的时候加上的。以前guido尝试过移除GIL锁,但是移除并不是那么简单的,还要加上很多别的控制代码,所以最终的性能反而不如之前好

Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235
The language doesn’t require the GIL – it’s only the CPython virtual machine that has historically been unable to shed it.

GIL锁的特点:

  • 每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。所以就算有多个CPU,一个程序内同一时刻真正执行的线程数还是只有一个。
  • 线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
  • 在Python中如果要真正使用全部的核心资源,应该使用多进程
  • 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁

GIL面试题如下
描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。

1.3 如何避免GIL锁问题

  1. 更换python解释器(只有CPython有GIL锁问题)
  2. 使用别的语言写任务,然后用Python引入:
// libdead_loop.c
void DeadLoop(){
	while(1){
		;
	}
}
gcc libdead_loop.c -shared -o libdead_loop.so

Python调用C语言代码来启动多线程

1.4 何时使用不同的多任务模式:

因为多进程可以利用多个核心,而 多线程和协程,只是实现了阻塞时期的任务切换:

  1. 计算/CPU密集型程序:使用多进程
  2. IO密集型程序: 使用多线程、协程

2. 深浅拷贝

import copy
  1. copy.copy
  2. copy.deepcopy (唯一的深拷贝方法)
  3. 对象.copy
  4. 切片
import copy

a = [1,2,3]
b = copy.copy(a)

id(a)
2732216717384
id(b)
2732218239560
# 成功拷贝
import copy

a = [1,2,3]
b = [4,5,6]
c = [a,b]
d = copy.copy(c)

id(c)
2732216577864
id(d)
2732218221320

a.append(9)
c
[[1, 2, 3, 9], [4, 5, 6]]
d
[[1, 2, 3, 9], [4, 5, 6]]
# 一起受影响,所以这是 浅拷贝
import copy

a = [1,2,3]
b = [4,5,6]
c = [a,b]
d = copy.deepcopy(c)

id(c)
2732216577864
id(d)
2732217733576

a.append(8)
c
[[1, 2, 3, 9, 8], [4, 5, 6]]
d
[[1, 2, 3, 9], [4, 5, 6]]
# d不受影响,所以这是 深拷贝

拷贝元祖:

import copy
a = (1,2)
b = copy.copy(a)
id(a)
2732216884680
id(b)
2732216884680
# 对于不可变类型,因为没有拷贝的必要,所以  copy是指向同一个元素
c = copy.deepcopy(c)
id(c)
2732216884680
# 深拷贝在拷贝只有 不可变对象的元祖时,也是使用的 直接指向
import copy
a = [1,2,3]
b = [4,5,6]
c = (a,b)
d = copy.copy(c)
e = copy.deepcopy(c)
id(c)
2732217828552
id(d)
2732217828552
id(e)
2732218154952
# 对于内部含有可变对象的元祖, deepcopy就会拷贝,而copy不拷贝

a.append(9)
c 
([1, 2, 3, 9], [4, 5, 6])
d
([1, 2, 3, 9], [4, 5, 6])
e
([1, 2, 3], [4, 5, 6])

3. 私有化

几种属性/方法的定义名称:

1.xxxx
公有变量,可以继承,子类和对象可以访问
2._xxxx:
私有变量, 可以继承,子类和对象可以访问;
但是不推荐外部直接使用,而且无法被from abc import xxxx 导入
3.__xxx:
私有变量,不会继承,子类只能通过父类的公有方法来访问,而且有名字重整
4.__xxx__:
魔法属性和方法,可以继承。 比如父类自定义的__init__方法子类也会有,而且会默认调用父类的__init__
5.xx_:
单下划线后置,为的是避免和Python关键字的冲突,不推荐使用

私有属性和名字重整

带有__xxx形式的属性/方法,只有在类中自己添加的时候,才会名字重整。之后自己添加的不是私有属性.

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

    def __hidden(self):
        return self.__name


a = A("Miller")
print(a.__dict__)
# 名字重整的结果
# {'_A__name': 'Miller'}   
print(A.__dict__)
# {'__module__': '__main__', '__init__': <function A.__init__ at 0x000001D5CA59FAE8>, '_A__hidden': <function A.__hidden at 0x000001D5CA59FA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
# 也有名字重整的  __hidden方法

# print(a.__name)  报错

a.__age= 10
print(a.__age)
# 10
# 这个就不是隐藏属性了

自动调用父类构造方法:

class A(object):
    def __init__(self):
        print("哈哈")

class B(A):
    pass

b = B()
# 哈哈
# 自动调用了父亲的构造方法

继承父类构造方法

class A(object):
    def __init__(self,name,age):
        print("哈哈")

class B(A):
    pass

b = B()
# TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
# 报错,所以说子类继承了父类的构造方法

重写父类方法

class A(object):
    def __init__(self,name,age):
        print("哈哈")

class B(A):
    def __init__(self):
        print("你好")

b = B()
# 你好
# 子类重写了父类方法

调用多个父类中的哪个方法

class A(object):
    def __init__(self):
        # self._name = name
        print("哈哈")


class B(object):
    def __init__(self):
        print("嘿嘿")
class C(B,A):
    pass

c = C()
# 嘿嘿
# 继承的时候,谁写在前面,就调用哪个父类的方法

4. 导入模块的问题

4.1 重新导入模块

from imp import reload

reload(模块名)   # 注意,这个模块一定要之前导入过,才能重新导入

应用:可以在运行时修改代码,然后让程序重新带入新代码内容

4.2 多模块开发的问题

开发时经常多模块,公用的内容放在一个模块中:

# common
FLAG = True
MYLIST = []

然后另外2个模块,

  1. 无论是 import common还是 from common import MYLIST, 他们指向的都是同一份 MYLIST
  2. 如果是import common,那么common.FLAG指向的是同一个对象
  3. 如果是from common import FLAG,那么每个模块中维护的FLAG对象不是同一份

5. 再谈python对象

5.1 __class__

每个对象都有一个__class__能够获取他的类对象,从可以调用类的属性:

class A(object):
	count = 10
    def __init__(self,name):
        self.name = name
a = A("哈哈")

print(a.__class__)
print(a.__class__.count)
print(A.__class__)
# <class '__main__.A'>
# 10
# <class 'type'>

在这里插入图片描述

  1. 类属性在内存中只保存一份
  2. 实例属性在每个对象中都要保存一份

5.2 __dict__

__dict__ 属性内部是对象的 属性键值对

print(a.__dict__)
# {'name': '哈哈'}

5.3 ★多继承的MRO

在多继承中,如果直接使用Parent.__init__(args)的方法,会导致交叉的祖先类被多次调用构造方法:

class Parent(object):
    def __init__(self, name):
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init开始被调用')
        self.age = age
        Parent.__init__(self, name)
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init开始被调用')
        self.gender = gender
        Parent.__init__(self, name)
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        Son1.__init__(self, name, age)  # 单独调用父类的初始化方法
        Son2.__init__(self, name, gender)
        print('Grandson的init结束被调用')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)

# 运行结果:
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男

在这里插入图片描述

使用super调用父类方法

  1. super().__init__()
  2. super(类名, self).__init__()

每个类都有一个__mro__,是Python解释器使用C3算法计算的,显示的就是找方法的顺序。

  1. 类中调用super(),实际上是在__mro__显示的元祖列表中去匹配,如果匹配到了类型,则调用的列表中的下一个类型的方法
  2. 类引用类属性和类方法时,也是根据__mro__中的顺序去查找的:
class Parent(object):
    count = 10


class Child(Parent):
    pass


print(Child.count)
c = Child()
print(c.count)
# 10
# 10

#因为是根据 __mro__找到了父类Parent
class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('parent的init开始被调用')
        self.name = name
        print('parent的init结束被调用')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init开始被调用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son1的init结束被调用')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init开始被调用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 为避免多继承报错,使用不定长参数,接受参数
        print('Son2的init结束被调用')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init开始被调用')
        # 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
        # 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
        # super(Grandson, self).__init__(name, age, gender)
        super().__init__(name, age, gender)
        print('Grandson的init结束被调用')

print(Grandson.__mro__)

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
  1. 还能使用super(类名, self).方法, 这个结果是在__mro__中根据这个传入的类名去查找,然后调用匹配顺序的下一个类的方法。 所以super也能够灵活的调用 继承链中的方法

总结:

  1. super().__init__相对于类名.init,在单继承上用法基本无差
  2. 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方3. 会导致方法被执行多次,具体看前面的输出结果
  3. 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
  4. 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
  5. 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因

6. ★property属性

property能够像属性一样的调用方法,比较像别的语言中的getter与setter。

class Foo:
    def func(self):
        pass

    # 定义property属性
    @property
    def prop(self):    # 这个地方必须只写一个self参数,不能多不能少,多了就报错
        pass

# ############### 调用 ###############
foo_obj = Foo()
foo_obj.func()  # 调用实例方法
foo_obj.prop  # 调用property属性

注意:

  1. 定义 时只有一个self参数,不多不少
  2. 调用时不用加括号

@property 这种的,都叫装饰器

另一个例子

class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10 

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val

# ############### 调用 ###############
p = Pager(1)
p.start  # 就是起始值,即:m
p.end  # 就是结束值,即:n

6.1 用装饰器创建

经典类中只有一种@property,而新式类有3种:

  1. @property
  2. @属性名.setter
  3. @属性名.deleter
class Goods:
    """python3中默认继承object类
        以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

# ############### 调用 ###############
obj = Goods()
obj.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数
del obj.price      # 自动执行 @price.deleter 修饰的 price 方法
  1. 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
  2. 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法

6.2 使用类方法创建

新式类和经典类使用这种方法是一样的

步骤:

  1. 先定义好每个方法: getter, setter, deleter
  2. 最后在类的内部用property方法调用传参,得到的返回值即为属性
class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'

    def set_bar(self, value): 
        """必须两个参数"""
        print("setter...")
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'laowang'

    BAR = property(get_bar, set_bar, del_bar, "description...")

obj = Foo()

obj.BAR  # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法

property方法的参数:

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

django中常用property

property的应用——当作getter和setter:

class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

a = Money()
a.money = 100
print(a.money)

7. 魔法属性

7.1 __doc__

class Foo:
    """ 描述类信息,这是用于看片的神奇 """
    def func(self):
        pass

print(Foo.__doc__)
#输出:类的描述信息

7.2 __module____class__

  • __module__ 返回的是对象属于哪个模块,是一个字符串
  • __class__返回的是对象属于哪一个类, 是一个对象
# test.py
# -*- coding:utf-8 -*-

class Person(object):
    def __init__(self):
        self.name = 'laowang'
# main.py
from test import Person

obj = Person()
print(obj.__module__)  # 输出 test 即:输出模块
print(obj.__class__)  # 输出 test.Person 即:输出类

7.3 __init__

初始化方法,而不是构造方法

7.4 __del__

  • 当对象在内存中被释放时,自动触发执行
class Foo:
    def __del__(self):
        pass
f = Foo()
del f   # __del__被调用 

7.5 __call__

能够让对象本身被调用

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__

7.6 __dict__

  • 类或对象中的所有属性

类的方法是属于类自己的属性

class Province(object):
    country = 'China'

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

    def func(self, *args, **kwargs):
        print('func')

# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}

obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}

7.7 __str__

打印对象的时候返回的内容

class Foo:
    def __str__(self):
        return 'laowang'


obj = Foo()
print(obj)
# 输出:laowang

7.8 __getitem__、__setitem__、__delitem__

  • 用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo(object):

    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()

result = obj['k1']      # 自动触发执行 __getitem__
obj['k2'] = 'laowang'   # 自动触发执行 __setitem__
del obj['k1']           # 自动触发执行 __delitem__

7.9 __getslice__、__setslice__、__delslice__

  • 用于切片
class Foo(object):

    def __getslice__(self, i, j):
        print('__getslice__', i, j)

    def __setslice__(self, i, j, sequence):
        print('__setslice__', i, j)

    def __delslice__(self, i, j):
        print('__delslice__', i, j)

obj = Foo()

obj[-1:1]                   # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44]    # 自动触发执行 __setslice__
del obj[0:2]                # 自动触发执行 __delslice__

微信开发包:http://wechat-python-sdk.com/

8. with上下文管理

系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。

8.1 自定义上下文管理器

context manager 就是一个实现了 __enter____exit__的类,可以配合with关键字

class File():

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("will exit")
        self.f.close()

with File('out.txt', 'w') as f:
    print("writing")
    f.write('hello, python')

注意:实际返回的对象并不是__init__返回的对象,而是先调用__init__之后,如果碰到了with,再去调用__enter__方法,将这个方法返回的结果返回。

8.2 contextmanager装饰器实现上下文管理器

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()


with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")
  • yield前面的语句会在 __enter__的时候调用
  • yield后面的语句会在__exit__的时候调用
  • yield返回值就是 方法调用的返回值

9. 闭包

闭包就是函数内嵌套函数,返回值为内部函数,并且通常内部函数引用了外部函数的参数——闭包是一块维护了函数+变量的空间

def linecalc(k, b):
    def func(x):
        return k * x + b
    return func


myfunc1 = linecalc(5, 1)   # y = 5x+1 的线
print(myfunc1(4))   # 21
myfunc2 = linecalc(6, 1)  # y = 6x+1 的线
print(myfunc2(4))   # 25
print(myfunc1(4))   # 21
  • 对象:就是内部维护了 函数+变量
  • 闭包:内部的函数维护的空间中,还包含了函数+变量,就相当于一个轻量级的对象
  • 普通的函数:只有函数代码,并没有维护变量

闭包修改外部变量内容——nonlocal


def outer():
    x = 20
    def inner():
        nonlocal x
        print(x)   # 20
        x = 10
        print(x)   # 10
    inner()
outer()

函数内部好像有 变量预解析:

  • 下面的代码中,x会自动往上层找,找到外面的20来打印
def outer():
    x = 20
    def inner():
        print(x)   # 20

    inner()
  • 下面的情况中,内部定义了x,所以第一句知道此时x没有定义,就报错了
def outer():
    x = 20
    def inner():
        print(x)  # 报错
        x = 10
    inner()
outer()

10 ★★装饰器

不懂装饰器,都不好意思说会python

10.1 python中的装饰器

1 别的语言的装饰器模式

def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")

def decoration():
	print("===验证模块===")
	origin()
	print("===资源清理==="

decoration()

但是对于python来说,这样就需要把以前调用origin的代码全部修改成decoration, 违反了开放封闭原则

2 python的装饰器模式——闭包实现

def decoration(func):
	def new():
		print("===验证模块===")
		func()
		print("===资源清理==="
	return new

def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")


origin()  # 这是原来的origin

origin = decoration(origin)  # 此时origin成了新的new

origin()  # 被装饰之后的origin

3 装饰器语法糖

def decoration(func):
	def new():
		print("===验证模块===")
		func()
		print("===资源清理==="
	return new

@decoration
def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")

origin() # 已经是装饰过的origin了

上面代码中,加上@decoration, 就和10.2中,将 origin的引用指向新的new效果是一样的。

装饰器只能在原函数之前和之后增加功能,无法插入函数内部

10.2 带参数无返回值的装饰器

def mydec(func):
    def inner(paraname):
        print("装饰前")
        func(paraname)
        print("装饰后")
    return inner


@mydec
def demo(name):
    print(f"我的名字是{name}")

demo("高富帅")

不定长参数的装饰器

def mydec(func):
    def inner(*args, **kwargs):
        print("装饰前")
        func(*args, **kwargs)
        print("装饰后")
    return inner


@mydec
def demo(name,*args, **kwargs):
    print(f"我的名字是{name}")
    print(f"======={args}=======")
    print(f"======={kwargs}=======")


demo("高富帅")

10.3 有返回值函数的装饰器

def mydec(func):
    def inner(*args, **kwargs):
        print("装饰前")
        result = func(*args, **kwargs)
        print("装饰后")
        return result
    return inner


@mydec
def demo(name,*args, **kwargs):
    print(f"我的名字是{name}")
    print(f"======={args}=======")
    print(f"======={kwargs}=======")
    return len(args)


a = demo("高富帅",3,4,5)
print(a)

10.4 总结通用写法

  1. 使用 *args, **kwargs 传递参数
  2. 把返回值return

10.5 一个装饰器装饰多个函数

  • 就是给每个函数都加上装饰器, 这样会为每一个函数创建一个闭包
  • 装饰是在设置了@就已经创建好了,而不是函数调用的时候
def mydec(func):
    print("装饰器运行了")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner


@mydec
def demo1(*args, **kwargs):
    return len(args)

@mydec
def demo2(name,age):
    print(f"我叫{name},年龄是{age}")

# 调用demo1
a = demo1("高富帅",3,4,5)
print(a)

# 调用demo2
demo2("张三",18)

结果:

装饰器运行了
装饰器运行了
4
我叫张三,年龄是18

10.6 多个装饰器修饰一个函数

顺序: 离函数定义近的先装饰,离函数定义远的后装饰

def mydec1(func):
    print("装饰器1运行了")

    def inner(*args, **kwargs):
        print("装饰器1的功能")
        return func(*args, **kwargs)

    return inner


def mydec2(func):
    print("装饰器2运行了")

    def inner(*args, **kwargs):
        print("装饰器2的功能")
        return func(*args, **kwargs)

    return inner


@mydec1
@mydec2
def demo(*args, **kwargs):
    return len(args)

demo()

结果:

装饰器2运行了
装饰器1运行了
装饰器1的功能
装饰器2的功能

10.7 类装饰器

  • @类名的含义是: demo = 类名(demo)
  • 所以装饰的类,要有__call__
class Decoration(object):
    def __init__(self,func):
        print("====装饰中====")
        self.func = func

    def __call__(self, *args, **kwargs):
        print("=====运行装饰的功能====")
        return self.func(*args, **kwargs)


@Decoration
def demo(name, *args, **kwargs):
    print(f"运行打印==={name}")
    return name


print(demo("haha"))

使用类方法装饰

class Decoration(object):

    @classmethod
    def mydec(cls, func):
        print("====装饰中====")
        def inner(*args, **kwargs):
            print("====运行装饰功能====")
            return func(*args, **kwargs)
        return inner


@Decoration.mydec
def demo(name, *args, **kwargs):
    print(f"运行打印==={name}")
    return name

使用类的静态方法装饰

class Decoration(object):

    @staticmethod
    def mydec(func):
        print("====装饰中====")
        def inner(*args, **kwargs):
            print("====运行装饰功能====")
            return func(*args, **kwargs)
        return inner


@Decoration.mydec
def demo(name, *args, **kwargs):
    print(f"运行打印==={name}")
    return name


print(demo("haha"))

10.8 装饰器带参数

嵌套三层:

  • 第一层收装饰器的参数
  • 第二层接收被装饰函数
  • 第三层接收被装饰函数的参数
def outter(arg):
    print("=====运行参数传递=====")
    def inner(func):
        print("=====运行装饰函数=====")
        def core(*args, **kwargs):
            print("=====运行装饰功能=====")
            return func(*args, **kwargs)
        return core
    return inner

@outter("hello")
def demo(*args, **kwargs):
    print("程序运行中")

# 结果为
=====运行参数传递=====
=====运行装饰函数=====

demo("123")

# 结果为
=====运行装饰功能=====
程序运行中

调用步骤:

  1. 运行outer,将参数传递作为 outer的参数,然后把这个函数的返回值作为装饰器
  2. 拿到outer的返回值,调用它,将被装饰函数作为参数传入,返回值作为被装饰后的函数
def set_func(level):
	def decoration(func):
		def newfunc(*args, **kwargs):
			if level == 1:
				print("level1级别验证")
			elif level == 2:
				print("level2级别验证")
			return func(*args, **kwargs)
		return newfunc
	return decoration

@set_level(1)
def test1():
	pass

@set_level(2)
def test2():
	pass
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章