打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Python 高级编程之面向切面编程 AOP(二)
userphoto

2023.03.28 泰国

关注

一、概述

前面讲了python面向对象编程(OOP:Object Oriented Programming),接下来讲一下OOP剩余的一些知识点和面向切面编程 AOP,非常重要的编程思想。


关python环境、基础介绍、面向对象编程介绍可以参考我以下几篇文章:

二、函数装饰器

1)无参函数装饰器

  • python中的装饰器(decorator)一般采用语法糖的形式,是一种语法格式。比如:@classmethod@staticmethod@property@xxx.setter@wraps()@func_name等都是python中的装饰器。
  • 装饰器,装饰的对象是函数或者方法。各种装饰器的作用都是一样的:改变被装饰函数或者方法的功能,性质。

假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示:

#funA 作为装饰器函数def funA(fn): #... fn() # 执行传入的fn参数 #... return '...'@funAdef funB(): #...

实际上,上面程序完全等价于下面的程序:

def funA(fn):    #...    fn() # 执行传入的fn参数    #...    return '...'def funB():    #...funB = funA(funB)

通过比对以上 2 段程序不难发现,使用函数装饰器 A() 去装饰另一个函数 B(),其底层执行了如下 2 步操作:

  • 将 B 作为参数传给 A() 函数;
  • 将 A() 函数执行完成的返回值反馈回 B。

示例如下:

#funA 作为装饰器函数def funA(fn): print('python 学习') fn() # 执行传入的fn参数 print('http://python.study.net') return '装饰器函数的返回值'@funAdef funB(): print('学习 Python')if __name__ == '__main__': print(funB)

输出结果:

python 学习学习 Pythonhttp://python.study.net装饰器函数的返回值

2)带数函数装饰器

在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?

比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。例如:

def funA(fn): # 定义一个嵌套函数 def say(arc): print('Python 学习:', arc) funB(arc) return say@funAdef funB(arc): print('funB():', arc)if __name__ == '__main__': funB('http://python.study.net')

输出结果:

Python 学习: http://python.study.net

这里有必要给读者分析一下这个程序,其实,它和如下程序是等价的:

def funA(fn): # 定义一个嵌套函数 def say(arc): print('Python 学习:',arc) return saydef funB(arc): print('funB():', arc)if __name__ == '__main__': funB = funA(funB) funB('http://python.study.net')

3)嵌套函数装饰器

上面示例中,都是使用一个装饰器的情况,但实际上,Python 也支持多个装饰器,比如:

@funA@funB@funCdef fun():    #...

上面程序的执行顺序是里到外,所以它等效于下面这行代码:

fun = funA( funB ( funC (fun) ) )

三、类方法修饰

1)实例方法

通常情况下,在类中定义的方法默认都是实例方法。

class Test():    #类构造方法,也属于实例方法    def __init__(self):        self.name = 'Test'        self.address = 'http://www.study.net'    # 下面定义了一个say实例方法    def say(self):        print('正在调用 say() 实例方法')if __name__ == '__main__':    t = Test()    # 通过实例对象方法实例方法    t.say()    # 通过类名访问实例对象,但是必须传实例对象名    Test.say(c)

2)类方法(@classmethod修饰)

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。

【温馨提示】和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。

和实例方法最大的不同在于,类方法需要使用@classmethod修饰符进行修饰,例如:

class Test: #类构造方法,也属于实例方法 def __init__(self): self.name = 'Test' self.add = 'http://www.Test.net' #下面定义了一个类方法 @classmethod def info(cls): print('正在调用类方法',cls)if __name__ == '__main__': t = Test() # 通过对象调用类方法 t.info() # 通过类名调用类方法,类方法推荐直接通过类名调用 Test.info()

注意,如果没有 @classmethod,则 Python 解释器会将 info() 方法认定为实例方法,而不是类方法。

3)静态方法(@staticmethod修饰)

静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

静态方法需要使用@staticmethod修饰,例如:

class Test:    @staticmethod    def info(name, add):        print(name, add)if __name__ == '__main__':    c = Test    # 通过对象调用类方法    c.info('测试', 123)    # 通过类名调用类方法(推荐)    Test.info('hello', 'world')

四、 property() 函数

传统操作类属性的方式比较麻烦,更习惯使用“类对象.属性”这种方式 , Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。

property() 函数的基本使用格式如下:

属性名=property(fget=None, fset=None, fdel=None, doc=None)
  • fget 参数用于指定获取该属性值的类方法,
  • fset 参数用于指定设置该属性值的方法,
  • fdel 参数用于指定删除该属性值的方法,
  • doc 是一个文档字符串,用于说明此函数的作用。

【注意】在使用 property() 函数时,以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个,当前也可以全部指定。也就是说,property() 函数中参数的指定并不是完全随意的。

例如,修改上面的程序,为 name 属性配置 property() 函数:

class CLanguage:    #构造函数    def __init__(self,n):        self.__name = n    #设置 name 属性值的函数    def setname(self,n):        self.__name = n    #访问nema属性值的函数    def getname(self):        return self.__name    #删除name属性值的函数    def delname(self):        self.__name='xxx'    #为name 属性配置 property() 函数    name = property(getname, setname, delname, '指明出处')#调取说明文档的 2 种方式#print(CLanguage.name.__doc__)help(CLanguage.name)clang = CLanguage('C语言中文网')#调用 getname() 方法print(clang.name)#调用 setname() 方法clang.name='Python教程'print(clang.name)#调用 delname() 方法del clang.nameprint(clang.name)

五、hasattr()、getattr()、setattr()函数

1)hasattr()

hasattr() 函数用来判断某个类实例对象是否包含指定名称的属性或方法。该函数的语法格式如下:

hasattr(obj, name)
  • obj 指的是某个类的实例对象,
  • name 表示指定的属性名或方法名。
  • 同时,该函数会将判断的结果(True 或者 False)作为返回值反馈回来。

示例如下:

class Test001:    def __init__ (self):        self.name = 'Test001'        self.add = 'http://www.test001.com'    def say(self):        print('我正在学Python')if __name__ == '__main__':    obj = Test001()    print(hasattr(obj, 'name'))    print(hasattr(obj, 'add'))    print(hasattr(obj, 'say'))    print(hasattr(obj, 'fly'))

输出结构:

TrueTrueTrueFalse

2)getattr()

getattr() 函数获取某个类实例对象中指定属性的值。没错,和 hasattr() 函数不同,该函数只会从类对象包含的所有属性中进行查找

getattr() 函数的语法格式如下:

getattr(obj, name[, default])
  • obj 表示指定的类实例对象,
  • name 表示指定的属性名,
  • default 是可选参数,用于设定该函数的默认返回值,即当函数查找失败时,如果不指定 default 参数,则程序将直接报 AttributeError 错误,反之该函数将返回 default 指定的值。

示例如下:

class Test002: def __init__ (self): self.name = 'Test002' self.add = 'http://www.test002.com' def say(self): print('我正在学Python')if __name__ == '__main__': obj = Test002() print(getattr(obj, 'name')) print(getattr(obj, 'add')) print(getattr(obj, 'say')) print(getattr(obj, 'display', 'nodisplay'))

输出结果:

Test002http://www.test002.com<bound method Test002.say of <__main__.Test002 object at 0x00000193F9B98A20>>nodisplay

3)setattr()

setattr() 函数的功能相对比较复杂,它最基础的功能是修改类实例对象中的属性值。其次,它还可以实现为实例对象动态添加属性或者方法。

setattr() 函数的语法格式如下:

setattr(obj, name, value)

示例如下:

class Test003:    def __init__ (self):        self.name = 'Test003'        self.add = 'http://www.test003.com'    def say(self):        print('我正在学Python')if __name__ == '__main__':    obj = Test003()    print(obj.name)    print(obj.add)    setattr(obj, 'name', 'Python教程')    setattr(obj, 'add', 'http://www.test003.net')    print(obj.name)    print(obj.add)

输出结果:

Test003http://www.test003.comPython教程http://www.test003.net

六、issubclass()和isinstance()函数

Python 提供了如下两个函数来检查类型:

  • issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
  • isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的对象。

示例如下:

# 定义一个字符串hello = 'Hello';# 'Hello'是str类的实例,输出Trueprint(''Hello'是否是str类的实例: ', isinstance(hello, str))# 'Hello'object类的子类的实例,输出Trueprint(''Hello'是否是object类的实例: ', isinstance(hello, object))# str是object类的子类,输出Trueprint('str是否是object类的子类: ', issubclass(str, object))# 'Hello'不是tuple类及其子类的实例,输出Falseprint(''Hello'是否是tuple类的实例: ', isinstance(hello, tuple))# str不是tuple类的子类,输出Falseprint('str是否是tuple类的子类: ', issubclass(str, tuple))# 定义一个列表my_list = [2, 4]# [2, 4]是list类的实例,输出Trueprint('[2, 4]是否是list类的实例: ', isinstance(my_list, list))# [2, 4]是object类的子类的实例,输出Trueprint('[2, 4]是否是object类及其子类的实例: ', isinstance(my_list, object))# list是object类的子类,输出Trueprint('list是否是object类的子类: ', issubclass(list, object))# [2, 4]不是tuple类及其子类的实例,输出Falseprint('[2, 4]是否是tuple类及其子类的实例: ', isinstance([2, 4], tuple))# list不是tuple类的子类,输出Falseprint('list是否是tuple类的子类: ', issubclass(list, tuple))

七、面向切面编程( AOP )

AOP(Aspect Orentied Programming) 简言之、这种在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。这样对原有代码毫无入侵性。

先来了解一下什么是装饰器吧?

其实上面也通过示例稍微讲解了python自带的几个常用的装饰器函数(@classmethod@staticmethod),其实装饰器是一个很著名的设计模式之一(后面会重点讲解23种设计模式),装饰器经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。简而言之,装饰器的作用就是为已经存在的对象添加额外的功能。

其实上面已经有示例了,那我们再来看几个示例加深一下理解:

import time def timeit(func): def wrapper(): start = time.clock() func() end =time.clock() print 'used:', end - start return wrapper @timeitdef foo(): print 'in foo()' foo()

在定义上加上@timeit这一行与另外写foo = timeit(foo)完全等价。

import time def timeit(func):    def wrapper():        start = time.clock()        func()        end =time.clock()        print 'used:', end - start    return wrapperdef foo():    print 'in foo()' foo = timeit(foo)

内置的装饰器有三个,分别是staticmethodclassmethodproperty,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。其实上面有详细的示例讲解,这里就不再仔细讲解了。再看个示例加深一下理解。

class Rabbit(object): def __init__(self, name): # 只读属性'_',私有属性'__' self._name = name @staticmethod def newRabbit(name): return Rabbit(name) @classmethod def newRabbit2(cls): return Rabbit('') @property def name(self): return self._name # 这里定义的属性是一个只读属性,如果需要再类外可写,则需要再定义一个setter: @name.setter def name(self, name): self._name = nameif __name__ == '__main__': r = Rabbit('test') print(r.name) r.name = 'hello' print(r.name)
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
可能是最全的Python面向对象入门教程了
大话 Python:python 进阶提升 -- 多线程、高并发,离我们真的那么远吗?
解密 Python 的弱引用
说说Python中的__new__和__init__的区别?
什么是迭代器,Python迭代器及其用法
Python中的super函数,你熟吗
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服