Python从入门到精通8天(装饰器的基本使用)

装饰器的基本使用

  • 装饰器概述
  • 用函数实现装饰器
  • 用类实现装饰器
  • 带参数的函数装饰器
  • 带参数的类装饰器
  • 内置装饰器的使用
  • __slots__魔法
  • 练习(工资结算系统)

装饰器概述

装饰器是一个著名的设计模式,经常被用于有切面(aspect)需求的场景,如插入日志、性能测试、事务处理等。装饰器可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

用函数实现装饰器

装饰器本质上是一个函数,它接收一个函数作为参数,然后返回新的函数。在装饰器的使用中,我们将原始函数作为参数传递给装饰器,然后将返回的新函数替换原始函数的引用。当我们调用原始函数时,实际上会调用装饰器返回新函数,从而实现对原始函数的修改或扩展。

实现原理如下:

# 装饰器的具体实现

# 定义一个装饰器
# 这里的参数f为一个函数
def name(f):

    # 这里因为name2中有参数,所以这里也得有参数
    def name1(a):
        print("我已经从name1变成了name2")

        # 这里是实现对name2函数的调用
        f(a)
        print("我已经调用了name2函数,所以装饰器使用成功,我可以退出了")

    # 如果我们使用了装饰器,也就意味着我们将name1函数赋值给了我们装饰器要修饰的函数
    return name1

# 直接继承父类Object,这里就不用进行初始化了
class Student(object):
    @name
    def name2(self):
        print("我是在装饰器的内部被调用")

# 实例化类
student = Student()

student.name2()

用类实现装饰器

上面我们将装饰器用函数进行的表示,那么能不能用类进行实现呢?

在这之前我们需要对__call__这个特殊方法进行了解。如果一个类定义了__call__()方法,那么它的实例可以像函数一样被调用。当你调用一个对象时,实际上是调用了该对象的__call__()方法。这个法可以接收任意数量的参数,并且可以返回任意类型的值。

具体代码如下:

class Decorator:
    # 这里的func参数用于接受函数
    def __init__(self,func):
        self.func = func

    # 使用特殊方法__call__

    def __call__(self, *args, **kwargs):
        print("被装饰器装饰的函数在这里被调用了")
        self.func(*args,**kwargs)
        return print("我已经被调用完毕")

# 这里相当于对类进行实例化
# 自动调用__init__方法
# 将装饰器修饰的函数传给__init__方法中的参数
@Decorator
def add(a,b):
    return print(a+b)

add(3,4)

带参数的函数装饰器

函数可以带参数,类也可以带参数,那么我们的装饰器可以带参数吗?当然可以。

具体代码如下:

# 这里是接受装饰器的参数
def a(j,k):
	# 这里是将装饰器修饰的函数进行传参
    def b(f):
        def e():
            print(f"我是{j}{k}")
            f()

        return e
    return b

# 这里相当于给装饰器传参
@a(1,2)
def c():
    print("你好")

c()

带参数的类装饰器

当使用类实现装饰器时,我们的__call__方法将接收函数作为参数,__init__将接收装饰器的参数。

具体代码如下:

class A():
    # 此时初始化__init__特殊方法不在接收函数为参数
    # 接受的装饰器的参数
    def __init__(self,a1,b1):
        self.a = a1
        self.b = b1

    # 用__call__方法接受函数为参数,并实现调用
    def __call__(self, f):
        def b():
            print(f"我接受完了函数{self.a}{self.b}")
            f()
            print("我已经调用完毕")
        return b

@A(1,2)
def c():
    print("我是被装饰器修饰的函数")

c()

内置装饰器的使用

通过上面的几个例子,大家对装饰器的运行应该没有多大问题了。我们现在就来学习Python的内置装饰器,分别是staticmethod、classmethod和property,分别把类中的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法用处不是特别的多。

  • @property装饰器
    它的作用是将一个方法转化为一个只读属性,当访问该属性时,会自动调用该方法。

  • @classmethod装饰器
    它的作用是将被修饰的函数转换成类方法,指的是传入类作为第一个参数的方法。类方法可以在不实例化对象的情况下调用,但他可以访问和修改类的变量和方法。

  • @staticmethod装饰器
    它的作用是将函数变为静态方法,在类中不需要实例化对象就可以调用的方法。静态方法与类有关联,但不依赖于类的实例或类变量。

具体代码如下:

class A(object):

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

    k = 2

    @property
    def a(self):
        print(f"被property装饰器修饰,所以我是类属性{self.j}")
        return self.j

    @staticmethod
    def b(j):
        print(f"被staticmethod修饰,所以我是静态方法{j}")


    @classmethod
    def c(cls):
        print("被classmethod修饰所以我是类方法{0},我可以修改变量".format(cls.k+1))


# staticmethod装饰器,不需要实例化对象便能使用
A.b(1)

# classmethod装饰器,不需要实例化对象便能使用

A.c()

# property装饰器,将一个方法转换成一个只读属性
a1 = A(1)
# a1.a = 2   #can't set attribute 'a'
print(a1.a)

__slots__魔法

Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。

具体代码如下:

class Person(object):

    # 限定Person对象只能绑定_name, _age和_gender属性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age
        
        # 不可以使用其他的属性
        # self.b = 3   # AttributeError: 'Person' object has no attribute 'b'
        
	# 名字只能读取,不能被改变
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 22)
    # 直接更改年龄
    person.age = 10
    person.play()
    # _gender表示受保护的属性,尽管可以访问
    # 但是不建议这么做,避免出现错误
    person._gender = '男'


main()

练习(工资结算系统)

要求:某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成

代码如下:

from abc import ABCMeta, abstractmethod


class Employee(object, metaclass=ABCMeta):
    """员工"""

    def __init__(self, name):
        """
        初始化方法

        :param name: 姓名
        """
        self._name = name
	
	# 保护name属性
    @property
    def name(self):
        return self._name
	
	# 规范子类的行为,必须要有get_salary方法,如果没有,则不能实例化类
	# 通过 @abstractmethod 装饰器,我们告诉 Python 这是一个抽象方法,子类必须实现它。
    @abstractmethod
    def get_salary(self):
        """
        获得月薪

        :return: 月薪
        """
        pass


class Manager(Employee):
    """部门经理"""

    def get_salary(self):
        return 15000.0


class Programmer(Employee):
    """程序员"""

    def __init__(self, name, working_hour=0):
        super().__init__(name)
        self._working_hour = working_hour

    @property
    def working_hour(self):
        return self._working_hour
        
	# 将可读工作时间改为可修改
    @working_hour.setter
    def working_hour(self, working_hour):
        self._working_hour = working_hour if working_hour > 0 else 0

    def get_salary(self):
        return 150.0 * self._working_hour


class Salesman(Employee):
    """销售员"""

    def __init__(self, name, sales=0):
        super().__init__(name)
        self._sales = sales

    @property
    def sales(self):
        return self._sales

    @sales.setter
    def sales(self, sales):
        self._sales = sales if sales > 0 else 0

    def get_salary(self):
        return 1200.0 + self._sales * 0.05


def main():
    emps = [
        Manager('刘备'), Programmer('诸葛亮'),
        Manager('曹操'), Salesman('荀彧'),
        Salesman('吕布'), Programmer('张辽'),
        Programmer('赵云')
    ]
    for emp in emps:
		
		# isinstance()可以判断一个变量的类型
        if isinstance(emp, Programmer):
            emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
        elif isinstance(emp, Salesman):
            emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
            
        # 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
        print('%s本月工资为: ¥%s元' %(emp.name, emp.get_salary()))


if __name__ == '__main__':
    main()

你可能感兴趣的:(Python从入门到精通,python,开发语言,装饰器)