python 装饰器

闭包:

  • 内部函数外部函数作用域里的变量的引用
  • 函数内的属性,都是有生命周期,都是在函数执行期间
  • 闭包内的闭包函数私有化了变量,完成了数据的封装,类似面向对象

闭是封闭(函数中的函数),包是包含(该内部函数对外部函数作用域而非全局作用域变量的引用。)

>>> def func1():
...     print ('func1 running...')
...     def func2():
...             print ('func2 running...')
...     func2()
... 
>>> func1()
func1 running...
func2 running...

内部函数func2作用域都在外部函数func1作用域之内
如果试图在外部函数的外部调用内部函数将会报错。

>>> func2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'func2' is not defined

装饰器

  • 不影响原函数的基础上增加新功能。

  • 以闭包形式实现。

  • 使用方法:在被装饰函数的前一行,加@装饰器函数名

实例应用

现在一个项目中,有很多函数 ,由于项目越来越大,功能越来越多,导致程序越来越慢。

其中一个功能函数功能,实现一百万次的累加。

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print('sum: {}'.format(s))

现在想计算一下函数的运行时间,如何解决?如何能应用到所有函数上?

解决办法 1

start = time.time()
my_count()
end = time.time()
print('共计执行:{} 秒'.format(end-start))

这种办法是最简单的实现方式,但是一个函数没问题,但是要有1000个函数,那么每个函数都要写一遍,非常麻烦并且代码量凭空多了三千行。

这明显是不符合开发的原则的,代码太冗余

解决办法 2

def count_time(func):
    start = time.time()
    func()
    end = time.time()
    print('共计执行:{} 秒'.format(end-start))

count_time(my_count)

经过修改后,定了一个函数来实现计算时间的功能,通过传参,将需要计算的函数传递进去,进行计算。

修改后的代码,比之前好很多。

但是在使用时,还是需要将函数传入到时间计算函数中。

能不能实现在使用时,不影响函数原来的使用方式,而又能实现计算功能呢?

解决办法 3

def count_time(func):
    def wrapper():      #wrapper 装饰
        start = time.time()
        func()
        end = time.time()
        print('共计执行:%s 秒'%(end - start)) # 使用%d显示,取整后是0秒,因为不到一秒
    return wrapper   #返回的是一个函数名

my_count = count_time(my_count)
my_count()          #加个()

此次在解释办法2的基础上,又将功能外添加了一层函数定义,实现了以闭包的形式来进行定义

在使用时,让 my_count 函数重新指向了 count_time 函数返回后的函数引用。这样在使用 my_count 函数时,就和原来使用方式一样了。

这种形式实际上就是塌装饰器的实现原理。

之前我们用过装饰器,如:@property 等

那么是否可以像系统装饰器一样改进呢?

解决办法 4

import time

def count_time(func):
    def wrapper():      #wrapper 装饰
        start = time.time()
        func()
        end = time.time()
        print('共计执行:%s 秒'%(end - start)) # 使用%d显示,取整后是0秒,因为不到一秒
    return wrapper

@count_time     # 这实际就相当于解决方法3中的 my_count = count_tiem(my_count)
def my_count():
    s = 0
    for i in range(10000001):
        s += i
    print('sum : ', s)

my_count()

这样实现的好处是,定义好了闭包函数后。只需要通过 @xxx 形式的装饰器语法,将 @xxx 加到要装饰的函数前即可。

使用者在使用时,根本不需要知道被装饰了。只需要知道原来的函数功能是什么即可。

这种不改变原有函数功能基础上,对函数进行扩展的形式,称为装饰器。

在执行 @xxx 时 ,实际就是将 原函数传递到闭包中,然后原函数的引用指向闭包返回的装饰过的内部函数的引用。

装饰器的几种形式

根据被装饰函数定义的参数和返回值定义形式不同,装饰器也对应几种变形。

  1. 无参无返回值
    def setFunc(func):
        def wrapper():
            print('Start')
            func()
            print('End')
        return wrapper

    @setFunc
    def show():
        print('show')

    show()
  1. 无参有返回值
    def setFunc(func):
        def wrapper():
            print('Start')
            return func()    
        return wrapper

    @setFunc   # show = setFunc(show)
    def show():
        return 100

    print(show() * 100)
  1. 有参无返回值
    def setFunc(func):
        def wrapper(s):
            print('Start')
            func(s)
            print('End')
        return wrapper

    @setFunc   
    def show(s):
        print('Hello %s' % s)

    show('Tom')
  1. 有参有返回值
    def setFunc(func):
        def wrapper(x, y):
            print('Start')
            return func(x, y)
        return  wrapper

    @setFunc
    def myAdd(x, y):
        return  x + y

    print(myAdd(1, 2))

万能装饰器

能不能实现适用于上述四种形式的万能装饰器?

通过可变参数(*args/**kwargs)

def setfun(func):
    def wrapper(*args,**kwargs):
        print("hi")
        return func(*args,**kwargs)
    return wrapper

@setfun
def fun(x,*y,**z):
    print(x,y,z)
fun(1,2,3,n=9)

hi
1 (2, 3) {‘n’: 9}

函数被多个装饰器所装饰

一个函数在使用时,通过一个装饰器来扩展,可能并不能完成达到预期。

Python 中允许一个函数被多个装饰器所装饰。

##函数被多个装饰器所装饰
def setFunc1(func):
    print("1")
    def wrapper1(*args,**kwargs):
        print("3")
        print("Wrapper context 1 Start".center(40,'-'))
        func(*args,**kwargs)
        print("5")
        print("Wrapper context 1 End".center(40, '-'))
    return wrapper1

def setFunc2(func):
    print("2")
    def wrapper2(*args,**kwargs):
        print("4")
        print("Wrapper context 2 Start".center(40,'-'))
        func(*args,**kwargs)
        print("6")
        print("Wrapper context 2 End".center(40, '-'))
    return wrapper2

@setFunc1
@setFunc2
def show(*args,**kwargs):
    print("show run.")

show()
2
1
3
--------Wrapper context 1 Start---------
4
--------Wrapper context 2 Start---------
show run.
6
---------Wrapper context 2 End----------
5
---------Wrapper context 1 End----------Wrapper Context 1 Start...
Wrapper Context 2 Start...
Show Run ...
Wrapper Context 2 End...
Wrapper Context 1 End...

看看输出结果就能理解运行顺序。

小结

  1. 函数可以像普通变量一样,做为函数的参数或返回值进行传递
  2. 函数内部可以定义另外一个函数,这样做的目的可以隐藏函数功能的实现
  3. 闭包实际也是一种函数定义形式。
  4. 闭包定义规则是在外部函数中定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用
  5. Python 中装饰器就是由闭包来实现的
  6. 装饰器的作用是在不改变现有函数基础上,为函数增加功能。
  7. 通过在已有函数前,通过**@闭包函数名**的形式来给已有函数添加装饰器
  8. 装饰器函数根据参数和返回值的不同,可细分为四种定义形式
  9. 可以通过可变参数和关键字参数来实现能用装饰器定义形式
  10. 一个装饰器可以为多个函数提供装饰功能,只需要在被装饰的函数前加 @xxx 即可
  11. 通过类也可以实现装饰器效果,需要重写 initcall 函数
  12. 类实现的装饰器在装饰函数后,原来的函数引用不在是函数,而是装饰类的对象
  13. 一个函数也可以被多个装饰器所装饰,但是实际在使用时,并不多见,了解形式即可

静态方法

  • 通过装饰器@staticmethod来装饰。
  • 可以通过实例
class Dog:
    type = "狗"
    def __init__(self):
        name = None

    #静态方法
    @staticmethod
    def introduce(): #静态方法不会自动传递实例对象和类对象
        print("犬科哺乳动物,属于肉食目。")

dog = Dog()
Dog.introduce()
dog.introduce()

犬科哺乳动物,属于肉食目。
犬科哺乳动物,属于肉食目。

静态方法是类中的函数,不需要实例。

静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但和类本身没有关系,也就是说,它不会涉及类中属性和方法的操作。

–>静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

类方法

  • 类对象所拥有的方法
  • 需要用装饰器@classmethod来标识其为类方法。
  • 对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数
### 类方法
class Dog:
    __type = "狗"
    
    # 类方法,用class来进行装饰
    @classmethod
    def get_type(cls):
        return cls.__type  
print(Dog.get_type())
class Foo(object):
    """类三种方法语法形式"""
 
    def instance_method(self):
        print("是类{}的实例方法,只能被实例对象调用".format(Foo))
 
    @staticmethod
    def static_method():
        print("是静态方法")
 
    @classmethod
    def class_method(cls):
        print("是类方法")
 
foo = Foo()
foo.instance_method()
foo.static_method()
foo.class_method()

#实例方法只能被实例对象调用,静态方法(由@staticmethod装饰的方法)、类方法(由@classmethod装饰的方法),可以被类或类的实例对象调用。
#实例方法,第一个参数必须要默认传实例对象,一般习惯用self。
#静态方法,参数没有要求。
#类方法,第一个参数必须要默认传类,一般习惯用cls。

是类<class ‘main.Foo’>的实例方法,只能被实例对象调用
是静态方法
是类方法

使用场景:

  • 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
  • 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
  • 如果在类外面写一个同样的函数来做这些事,打乱了逻辑关系,导致代码维护困难,使用静态方法。

注意:

类中定义了同名的对象方法,类方法以及静态方法时,调用方法会优先执行最后定义的方法

property

property 本身的意义就是属性、性质,在 python 中主要为属性提供便利的操作方式。

思考

如果现在需要设计一个 银行帐户类 ,这个类中包含了帐户人姓名和帐户余额,不需要考虑具体的操作接口,你会怎么设计?

实现与复盘

简单实现

class Account(object):
    def __init__(self, name, money):
        self.name = name    # 帐户人姓名
        self.balance = money    # 帐户余额

这样的设计有什么问题? 很显然,这样设计起来很简单方便,但是所有的属性外部都能访问修改,非常不安全。 如何改进呢?

改进一 隐藏实现细节

对于帐户的信息,特别是金额,这是不能够让用户直接修改的,如果要改变信息,需要窗口去办理。

程序实现也是一样,在使用对象时,尽量不要让使用者直接操作对象中的属性,这样会带来安全隐患。

改进办法,使用私有属性

class Account(object):
    def __init__(self, name, money):
        self.__name = name    # 帐户人姓名
        self.__balance = money    # 帐户余额

代码改进以后,将所有的属性都设计成了对象私有属性,确实从外部在使用时,并不知道对象内部的属性是什么,不能直接修改对象了,隐藏了实现的细节。

但是随之又产生了另外一个问题,如果确实需要对这两个属性要进行修改怎么办呢?

改进二 提供精确的访问控制

在之前的学习中,学习过 set/get方法,是专门来为类的私有属性提供访问接口

class Account(object):
    def __init__(self, name, money):
        self.__name = name    # 帐户人姓名
        self.__balance = money    # 帐户余额
    # 帐户人姓名,在创建帐户时就已经确定,不允许修改,所以对外不提供姓名的 set 方法
    def get_name(self):
        return self.__name

    def set_balance(self,money):
        self.__balance = money

    def get_balance(self):
        return self.__balance

经过修改,外部使用这个类的对象时,想使用对象中的属性,只能通过类中提供的 set/get 接口来操作,提高了程序的安全性。

这样,程序基本达到了设计需求,但是能不能更加完善呢?

如果在使用这个类的对象过程中,由于误操作,传入了不正常的数据,导致数据异常。该如何以避免这种情况发生呢?

比如:设置金额时出现了负数,或字符串,或其它类型的对象。

改进三 保证数据有效性

在 set 方法中,对传入的数据进行判断有效性,如果是无效数据,提示用户出错。

class Account(object):
    def __init__(self, name, money):
        self.__name = name    # 帐户人姓名
        self.__balance = money    # 帐户余额

    def get_name(self):
        return self.__name

    def set_balance(self,money):
        if isinstance(money, int):
            if money >= 0:
                self.__balance = money
            else:
                raise ValueError('输入的金额不正确')
        else:
            raise ValueError('输入的金额不是数字')

    def get_balance(self):
        return self.__balance

经过几个版本的迭代,程序越来越健壮。安全性也越来越高。

但是在使用的过程中,能不能更加精练一些呢?即然 set/get 方法是用来操作属性的方法,那么能不能以属性操作的方式来使用呢?

答案是肯定的。

property 类

在 Python 中,提供了一个叫做 property 的类,通过对这个创建这个类的对象的设置,在使用对象的私有属性时,可以在使用属性的函数的调用方式,而像普通的公有属性一样去使用属性,为开发提供便利。

property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

property 是一个类,init方法由四个参数组成,实例后返回一个用来操作属性的对象 参数一:属性的获取方法 参数二:属性的设置方法 参数三:属性的删除方法 参数四:属性的描述

class Account(object):
    def __init__(self, name, money):
        self.__name = name    # 帐户人姓名
        self.__balance = money    # 帐户余额

    def __get_name(self):
        return self.__name

    def set_balance(self,money):
        if isinstance(money, int):
            if money >= 0:
                self.__balance = money
            else:
                raise ValueError('输入的金额不正确')
        else:
            raise ValueError('输入的金额不是数字')

    def get_balance(self):
        return self.__balance
    # 使用 property 类来为属性设置便利的访问方式
    name = property(__get_name)
    balance = property(get_balance, set_balance)


ac = Account('tom', 10)
print(ac.name)
print(ac.balance)
ac.balance = 1000
print(ac.balance)

# out
tom
10
1000

通过 property 类实例对象以后,在使用对象中的属性时,就可以像使用普通公有属性一样来调用,但是实际调用的还是 set/get 方法。 在实例 property 对象时,不是所有的参数都需要写,比如示例中的 name 只提供了 get 方法,并且是一个私有的方法。这样就完全隐藏了内部的实现细节 。

@property 装饰器

Python 语法中,提供一种装饰器语法,在函数定义的上一行,使用 @xxx 的形式来使用装饰器。

装饰器的作用就是提供装饰的功能,在不改变原来函数功能的基础上,添加新的功能。(装饰器语法会在后面的课程中单独讲解)

这种形式被称为语法糖。

语法糖指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。 语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。

利用 @property 装饰器,可以用来简化定义新的属性或修改现有的操作。

class Account(object):
    def __init__(self, name, money):
        self.__name = name    # 帐户人姓名
        self.__balance = money    # 帐户余额

    # property 只能对获取方法装饰,并且获取方法不需要再写 get
    @property
    def name(self):
        return self.__name

    # 如果 property 装饰的属性还有 set 方法,需要写到 get方法后定义
    @property
    def balance(self):
        return self.__balance

    # 实现 set 方法, 格式: @xxx.setter ,xxx 要和property装饰的方法名一致
    @balance.setter
    def balance(self, money):
        if isinstance(money, int):
            if money >= 0:
                self.__balance = money
            else:
                raise ValueError('输入的金额不正确')
        else:
            raise ValueError('输入的金额不是数字')


ac = Account('tom', 10)
print(ac.name)
print(ac.balance)
ac.balance = 1000
print(ac.balance)

注意:

  • 在使用 @property 装饰属性时,只能装饰获取方法
  • @property 装饰属性时, set/get 方法不需要再属性名前加 set 和 get ,直接写属性名即可
  • 如果一个属性同时有 set/get 方法,那么要先实现 @property 对获取方法的定义
  • 再实现设置方法的定义,定义时使用 @xxx.setter 装饰,xxx 要和获取方法名保持一致

总结

  1. 在设计类时,尽量使用私有属性,然后使用 set/get 接口来提供读写操作
  2. 使用 set/get 接口方法,可以方便控制数据的有效性,精细化控制访问权限,隐藏实现细节
  3. 在定义 set/get 函数时,可以使用实例 property 类的对象,或 使用 @property 装饰器来对方法进行处理
  4. 处理之后的 set/get 函数在使用时,可以像直接使用属性一样进行操作,但实际调用还是函数,简化操作
  5. 一个类,一个是装饰器
  6. Python 中提供了很多语法糖,语法糖的作用是用来简化操作,使代码开发更简单,是一种对开发人员‘更甜蜜’语法

__ new __ 方法

  • 创建对象时,系统会自动调用new方法
  • 开发者可以实现new方法来自定义对象的创建过程

1.在内存中为对象分配空间

2.返回对象的引用。(即对象的内存地址)

class Cat(object):
    def __new__(cls, name):
        print("创建对象")
        # return super().__new__(cls)
        return object.__new__(cls) #这里容易弄错 传的形参为什么是类?代表要实例化的类

    def __init__(self, name):
        print("对象初始化")
        self.name = name

    def __str__(self):
        return "%s" % self.name


lanmao = Cat("蓝猫")
lanmao.age = 20
print(lanmao) #直接print实例就好了有str

总结:

  • __new__至少要有一个参数cls,代表要实例化的,此参数在实例化时由Python解释器自动提供
  • __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例
  • __init__有一个参数self,就是这个__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
  • 如果创建对象时传递了自定义参数,且重写了new方法,则new也必须 “预留” 该形参,否则init方法将无法获取到该参数

self

目标:

  • self关键字的使用
  • 方法中定义属性

提问:如果对象的方法中需要使用该对象的属性,怎么办?

  • 关键字 self 主要用于对象方法中,表示调用该方法的对象
  • 在方法中使用 self,可以获取到调用当前方法的对象,进而获取到该对象的属性和方法

提问:调用对象的方法时,为什么不需要设置self对应的实参?

  • 某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给方法,所以开发者只需要在定义方法时 “预留” 第一个参数为 self 即可
class Cat:
    # 方法
    def introduce(self):
        print("名字是:%s, 年龄是:%d" % (self.name, self.age))

# 创建了一个对象
tom = Cat()
# 给对象tom添加了一个属性,叫name,,里面的值是"汤姆"
tom.name = "汤姆"
tom.age = 5
tom.introduce() # 打印结果:名字是汤姆, 年龄是5

方法中定义属性

  • 使用self操作属性 和对象的变量名操作属性效果上相同,如果属性在赋值时还没有定义,则会自动定义属性并赋值
class Cat:
    # 方法
    def introduce(self):
        self.type = "小型动物"

# 创建了一个对象
tom = Cat()
# 调用对象的方法
tom.introduce() 
# 获取对象的属性
print(tom.type)

# 创建另一个对象
lanmao = Cat()
# 获取对象的属性
print(tom.type)  # 会发生什么? AttributeError: 'Cat' object has no attribute 'type'
# 调用对象的方法
tom.introduce()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值