当前位置:   article > 正文

python入门12——面向对象(进阶02):装饰器、元类metaclass_python 装饰器和元类的区别

python 装饰器和元类的区别

一、装饰器

装饰器(Decorators)是 Python 的一个重要部分。简单地说:它们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。
在这里插入图片描述

1. 装饰器基础
一切皆对象

首先我们来理解下 Python 中的函数:

def hi(name="zhai"):
    return "hi " + name

print(hi())  # output: 'hi zhai'

# 我们甚至可以将一个函数赋值给一个变量,比如
greet = hi
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。我们尝试运行下这个
print(greet())  # output: 'hi zhai'

# 如果我们删掉旧的hi函数,看看会发生什么!
del hi
print(hi())  # outputs: NameError
print(greet())  # outputs: 'hi zhai'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
在函数中定义函数

以上这些就是函数的基本知识了。我们来让你的知识更进一步。在 Python 中我们可以在一个函数中定义另一个函数:

def hi():
    print("-------hi()-------")

    def greet():
        return "-------greet()-------"

    def welcome():
        return "-------welcome()-------"

    print(greet())
    print(welcome())
    print("now you are back in the hi() function")

hi()
# -------hi()-------
# -------greet()-------
# -------welcome()-------
# now you are back in the hi() function
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

以上实例可以看出无论何时调用hi(), greet()和welcome()都将同时被调用。但是然reet()和welcome()函数在hi()函数之外是不能访问的,显示未定义该函数,如下:

greet()
# NameError: name 'greet' is not defined
  • 1
  • 2

那现在我们知道了可以在函数中定义另外的函数。也就是说:我们可以创建嵌套的函数。现在你需要再多学一点,就是函数也能返回函数。

在函数中返回函数

其实并不需要在一个函数里去执行另一个函数,我们也可以将其作为输出返回出来:

def hi(name="yasoob"):
    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    if name == "yasoob":
        return greet
    else:
        return welcome

a = hi()
print(a)  # <function hi.<locals>.greet at 0x000001C325A0B280>
print(a())  # now you are in the greet() function
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

再次看看这个代码。在 if/else 语句中我们返回 greet 和 welcome,而不是 greet() 和 welcome()。当我们把一对小括号放在后面,这个函数就会执行;然而如果不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。
当我们写下 a = hi(),hi() 会被执行,而由于 name 参数默认是 yasoob,所以函数 greet 被返回了。如果我们把语句改为 a = hi(name = "zhai"),那么 welcome 函数将被返回。

将函数作为参数传给另一个函数
def hi():
    return "hi zhai!"

def doSomethingBeforeHi(func):
    print("------在hi()之前做些工作------")
    print(func())

doSomethingBeforeHi(hi)  # 把hi()函数作为参数传递到doSomethingBeforeHi函数中
# ------在hi()之前做些工作------
# hi zhai!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上述例子,能让我们体会到最简单的装饰器的作用,即装饰器让你在一个函数的前后去执行代码。
我们可以用@符号,使用一个简短的方式来生成一个被装饰的函数,如下:


def doSomethingBeforeHi(func):
    print("------在hi()之前做些工作------")
    return func

@doSomethingBeforeHi  # 作用与hi=doSomethingBeforeHi(hi)相同
def hi():
    print ("------hi()------")
# ------在hi()之前做些工作------
# 即使该方法被装饰后,也不会影响该函数的正常执行:
hi()  # ------hi()------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

以上内容参考@https://www.runoob.com/w3cnote/python-func-decorators.html

2. 类的装饰器

上述用@符号修饰函数的操作同样适用于修饰类,具体操作如下:

def deco(obj):
    print("--------deco---------")
    return obj

@deco  # Foo = deco(Foo)
class Foo:
    pass
# --------deco---------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

将deco方法中的输出语句改成print("--------deco---------", obj)可以查看出此时obj对象是Foo类对象,即输出--------deco--------- <class '__main__.Foo'>。为了更直观的体会@deco的作用,我们将上述代码进一步扩充,如下所示:

def deco(obj):
    print("--------deco---------", obj)
    obj.x = "zhang"  # 会被加到Foo对象的属性字典中去
    obj.y = "yi"
    obj.z = "xing"
    return obj

@deco  # Foo = deco(Foo)
class Foo:
    pass

print(Foo.__dict__)  # 查看属性字典
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

运行结果如下:
在这里插入图片描述
如果想要对deco中的属性函数进行动态修改该怎么办呢?其实我们可以采用之前学过的函数嵌套的方式操作,将deco嵌套在一个可以指定参数的Typed方法内,具体代码如下所示:

def Typed(**kwargs):
    def deco(obj):
        print('----->', obj)
        print("类名----->", obj)
        return obj

    print('======>', kwargs)  # 首先执行该句
    return deco  # 再执行deco方法中的操作

@Typed(x='zhang', y='yi', z='xing')
class Foo:
    pass
# ======> {'x': 'zhang', 'y': 'yi', 'z': 'xing'}
# -----> <class '__main__.Foo'>
# 类名-----> <class '__main__.Foo'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
3. 类的装饰器的应用

类装饰器本质上和函数装饰器原理、作用相同,都是为其它函数增加额外的功能。但是相比于函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器可以直接依靠类内部的__call__方法来实现,当使用 @ 形式将类装饰器附加到函数上时,就会调用类装饰器的__call__方法。而不需要向函数装饰器那样,在装饰器函数中定义嵌套函数,来实现装饰功能。
【应用】使用类装饰器为一个函数的执行增加计时功能。

import time


class Foo():
    def __init__(self, func):  # 初始化函数中传入函数对象的参数
        self._func = func

    def __call__(self):  # 定义__call__方法,直接实现装饰功能
        start_time = time.time()
        self._func()
        end_time = time.time()
        print('花费了 %.2f' % (end_time - start_time))


@Foo  # bar=Foo(bar)
def bar():
    print('bar函数的执行时间为:')
    time.sleep(2.5)

bar()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

执行bar()方法,就会自动调用Foo类内部的__call__方法。其执行结果为:
在这里插入图片描述

二、元类metaclass

元类属于python面向对象编程的深层魔法。python中一切皆为对象,类本身也是一个对象,当使用关键字class的时候就会创建一个对象(这里的对象指的是类而非类的实例)。
一个简单的例子帮助理解:

class Foo:
    pass

f = Foo()
print(type(f))  # <class '__main__.Foo'>
print(type(Foo))  # <class 'type'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上例可以看出 f 是由Foo这个类产生的对象,而Foo本身也是对象,它是由type类创建的。

1. 什么是元类?

元类是类的类,是类的模板。元类是用来控制如何创建类的,正如类是创建对象的模板一样,简单来说,元类的实例就是类。
上文我们基于python中一切皆为对象的概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type。type有一种完全不同的能力,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。

type的语法如下:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
  • 1

创建一个简单的type元类:

MyShinyClass = type('MyShinyClass', (), {})  # 返回一个类对象
print(MyShinyClass)
# 输出:<class '__main__.MyShinyClass'>
print(MyShinyClass())  # 创建一个该类的实例
# 输出:<__main__.MyShinyClass object at 0x000001E8A7992F40>
  • 1
  • 2
  • 3
  • 4
  • 5

你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。
若一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以继承type来自定义元类。

2. 一个简单的例子
# 使用type构建Foo类
Foo = type('Foo', (), {'bar': True})

# 使用type构建FooChild类继承Foo类
FooChild = type('FooChild', (Foo,), {})

print(FooChild)  # <class '__main__.FooChild'>
print(FooChild.bar)  # bar属性是由Foo继承而来------>输出:True

def echo_bar(self):
    print(self.bar)

FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})  # 为Foochild类增加方法
print(hasattr(Foo, 'echo_bar'))  # Foo对象不包含echo_bar方法------>输出:False
print(hasattr(FooChild, 'echo_bar'))  # FooChild包含echo_bar方法------>输出:True

my_foo = FooChild()
my_foo.echo_bar()  # 输出:True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

由上例可以看出,在Python中,类也是对象,我们可以动态的创建类。这就是当我们使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

3. 练习题

【例1】在元类中控制把自定义类的数据属性都变成大写。

分析题目:

class Mymetaclass(type):
    def __new__(cls, name, bases, attrs):
        print(name)  # Chinese
        print(bases)  # ()
        print(attrs)  # {'__module__': '__main__', '__qualname__': 'Chinese', 'country': 'China', 'tag': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x000001F21E66B310>}
        update_attrs = {}

class Chinese(metaclass=Mymetaclass):
    country = 'China'
    tag = 'Legend of the Dragon'

    def walk(self):
        print('%s is walking' % self.name)

print(Chinese.__dict__)  # {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None}
# 我们想要的结果就是在Chinese类的数据字典中将country和tag这两个属性变为大写。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

最终实现:

class Mymetaclass(type):
    def __new__(cls, name, bases, attrs):
        update_attrs = {}
        for k, v in attrs.items():
            if not callable(v) and not k.startswith('__'):  # 数据属性是不可以被调用且不是以__开头的
                update_attrs[k.upper()] = v
            else:
                update_attrs[k] = v  # 其余不变
        return type.__new__(cls, name, bases, update_attrs)

class Chinese(metaclass=Mymetaclass):
    country = 'China'
    tag = 'Legend of the Dragon'

    def walk(self):
        print('%s is walking' % self.name)

print(Chinese.__dict__)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行结果如下:
在这里插入图片描述
【例2】在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性,并按一定格式修改__dict__字典。

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        # 控制类Foo的创建
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    def __call__(self, *args, **kwargs):
        # 控制Foo的调用过程,即Foo对象的产生过程
        obj = self.__new__(self)  # obj是类的实例对象
        self.__init__(obj, *args, **kwargs)
        # print(obj.__dict__)----->输出为:{'name': 'egon', 'age': 18, 'sex': 'male'}
        obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
        # print(obj.__dict__)----->输出为:{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}
        return obj

class Foo(object, metaclass=Mymeta):  # Foo=Mymeta(...)
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

obj = Foo('egon', 18, 'male')
print(obj.__dict__)  # 结果为:{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/213246
推荐阅读
相关标签
  

闽ICP备14008679号