> 文章列表 > Python从入门到精通8天(装饰器的基本使用)

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

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):@namedef 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 ereturn b# 这里相当于给装饰器传参
@a(1,2)
def c():print("你好")c()

带参数的类装饰器

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

具体代码如下:

class A():# 此时初始化__init__特殊方法不在接收函数为参数# 接受的装饰器的参数def __init__(self,a1,b1):self.a = a1self.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 = jk = 2@propertydef a(self):print(f"被property装饰器修饰,所以我是类属性{self.j}")return self.j@staticmethoddef b(j):print(f"被staticmethod修饰,所以我是静态方法{j}")@classmethoddef 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 = nameself._age = age# 不可以使用其他的属性# self.b = 3   # AttributeError: 'Person' object has no attribute 'b'# 名字只能读取,不能被改变@propertydef name(self):return self._name@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = agedef play(self):if self._age <= 16:print('%s正在玩飞行棋.' % self._name)else:print('%s正在玩斗地主.' % self._name)def main():person = Person('王大锤', 22)# 直接更改年龄person.age = 10person.play()# _gender表示受保护的属性,尽管可以访问# 但是不建议这么做,避免出现错误person._gender = '男'main()

练习(工资结算系统)

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

代码如下:

from abc import ABCMeta, abstractmethodclass Employee(object, metaclass=ABCMeta):"""员工"""def __init__(self, name):"""初始化方法:param name: 姓名"""self._name = name# 保护name属性@propertydef name(self):return self._name# 规范子类的行为,必须要有get_salary方法,如果没有,则不能实例化类# 通过 @abstractmethod 装饰器,我们告诉 Python 这是一个抽象方法,子类必须实现它。@abstractmethoddef get_salary(self):"""获得月薪:return: 月薪"""passclass Manager(Employee):"""部门经理"""def get_salary(self):return 15000.0class Programmer(Employee):"""程序员"""def __init__(self, name, working_hour=0):super().__init__(name)self._working_hour = working_hour@propertydef working_hour(self):return self._working_hour# 将可读工作时间改为可修改@working_hour.setterdef working_hour(self, working_hour):self._working_hour = working_hour if working_hour > 0 else 0def get_salary(self):return 150.0 * self._working_hourclass Salesman(Employee):"""销售员"""def __init__(self, name, sales=0):super().__init__(name)self._sales = sales@propertydef sales(self):return self._sales@sales.setterdef sales(self, sales):self._sales = sales if sales > 0 else 0def get_salary(self):return 1200.0 + self._sales * 0.05def 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()

英语歌曲网