python-17-装饰器以及类和defaultdict的使用

python装饰器(详解)
python的内置装饰器
python中property详解

1 装饰器

(1)什么是装饰器
装饰器指的是定义一个函数,该函数是用来为其他函数添加额外的功能,就是拓展原来函数功能的一种函数。
(2)为什么用装饰器

开放封闭原则
开放:指的是对拓展功能是开放的。
封闭:指的是对修改源代码是封闭的。

装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能。

1.1 需求场景

在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能。

import time
def index(x, y):
    time.sleep(3)
    print('index {} {}'.format(x, y))

index(111, 222)
index(y=111,x=222)
index(111,y=222)

1.2 解决方案

1.2.1 解决方案一:失败

问题:没有修改被装饰对象的调用方式,但是修改了其源代码

import time
def index(x, y):
    start = time.time()
    time.sleep(3)
    print('index {} {}'.format(x, y))
    stop = time.time()
    print(stop - start)

index(111, 222)

1.2.2 解决方案二:失败

问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能,但是代码冗余

import time
def index(x, y):
    time.sleep(3)
    print('index {} {}'.format(x, y))

start = time.time()
index(111, 222)
stop = time.time()
print(stop - start)

1.2.3 解决方案三:失败

问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了
wrapper包装纸、封皮、封套

import time
def index(x, y):
    time.sleep(3)
    print('index {} {}'.format(x, y))

def wrapper():
    start = time.time()
    index(111, 222)
    stop = time.time()
    print(stop - start)

wrapper()

1.2.4 方案三的优化一

方案三的优化一:将index的参数写活

import time
def index(x, y, z):
    time.sleep(3)
    print('index {} {} {}'.format(x, y, z))

def wrapper(*args, **kwargs):
    start = time.time()
    index(*args, **kwargs)
    stop = time.time()
    print(stop - start)

wrapper(3333, 4444, 5555)
wrapper(3333, z=5555, y=44444)

1.2.5 方案三的优化二

在优化一的基础上把被装饰对象写活了,原来只能装饰index。

import time
def index(x,y,z):
    time.sleep(3)
    print('index {} {} {}'.format(x, y, z))

def home(name):
    time.sleep(2)
    print('welcome {} to home page'.format(name))

def outter(func):
    # func传入函数的内存地址
    def wrapper(*args,**kwargs):
        start = time.time()
        func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
    return wrapper

index=outter(index) # index=wrapper的内存地址
home=outter(home) # home=wrapper的内存地址

index(1,2,3)
home('egon')

1.2.6 方案三的优化三

在优化二的基础上,将wrapper做的跟被装饰对象一模一样,以假乱真。

import time

def index(x,y,z):
    time.sleep(3)
    print('index {} {} {}'.format(x, y, z))

def home(name):
    time.sleep(2)
    print('welcome {} to home page'.format(name))

def outter(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print(stop - start)
        return res
    return wrapper

#偷梁换柱:home1指向wrapper函数的内存地址
home1 = outter(home)
re = home1('egon')  # re=wrapper('egon')
print('返回值--》',re)

1.2.8 语法糖

思考如何方案三的基础上不改变函数的调用方式?

# 语法糖:让你开心的语法
import time

# 定义一个装饰器
def timmer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper


# 在被装饰对象正上方的单独一行写@装饰器名字
@timmer # 表示index=timmer(index)
def index(x, y, z):
    time.sleep(3)
    print('index {} {} {}'.format(x, y, z))


# 在被装饰对象正上方的单独一行写@装饰器名字
@timmer # 表示home=timmer(home)
def home(name):
    time.sleep(2)
    print('welcome {} to home page'.format(name))


index(x=1, y=2, z=3)
home('egon')

1.3 无参装饰器模板

def outter(func):
    def wrapper(*args, **kwargs):
        # 1、调用原函数
        # 2、为其增加新功能
        res = func(*args, **kwargs)
        return res

    return wrapper

2 内置装饰器

python总共包括三个内置装饰器:

(1)staticmethod   静态方法使用
(2)classmethod    类方法
(3)property       把类的方法伪装成属性

2.1 @staticmethod

python中的staticmethod主要是方便将外部函数集成到类体中,美化代码结构,重点在不需要类实例化的情况下调用方法。用staticmethod包装的方法可以内部调用,可以通过类访问或类实例化访问。

class Student:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @staticmethod
    def from_dict(data_dict):
        name = data_dict["name"]
        age = data_dict["age"]
        return Student(name,age)

    def add_age(self):
        self.age = self.age + 10

if __name__ == "__main__":
    s1 = Student("lily", 20)
    print(s1.name, s1.age)

    data_dict = {"name": "lucy", "age": 20}
    s2 = Student.from_dict(data_dict)
    print(s2.name, s2.age)

    s1.add_age()
    print(s1.name, s1.age)

静态方法,可以被类直接调用。
非静态方法,必须实例化类之后,才能调用。

2.2 @classmethod

2.3 @property

2.3.1 什么是property属性

一种用起来像是在使用实例属性一样的特殊属性,可以对应于某个方法,希望能够像调用属性一样来调用方法 此时可以将一个方法加上property。将该函数方法,当做属性,不用()也可以执行。

class Foo:
    def func(self):
        print("用func()调用了方法")

    @property  # 定义property属性
    def prop(self):
        print("用prop调用了方法")


foo_obj = Foo()  # 实例化
foo_obj.func()  # 调用实例方法
foo_obj.prop  # 调用property属性

property属性的定义和调用要注意一下几点:定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数调用时,无需括号。

一、property属性的功能:
property属性内部进行一系列的逻辑计算,最终将计算结果返回。

二、property属性的有两种方式
(1)装饰器 即:在方法上应用装饰器。
经典类,具有一种@property装饰器。
新式类,具有三种@property装饰器
(2)类属性 即:在类中定义值为property对象的类属性。

2.3.2 经典类一种装饰器

class Goods:
    @property
    def price(self):
        return "laowang"


obj = Goods()  # 实例化
# 自动执行@property修饰的price方法,并获取方法的返回值
result = obj.price  
print(result)

经典类中的属性只有一种访问方式,其对应被@property修饰的方法

2.3.3 新式类三种装饰器

python3中默认继承object类,以python2、python3执行此程序的结果不同,因为只有在python3中才有@xxx.setter @xxx.deleter。

class Goods:
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter',value)

    @price.deleter
    def price(self):
        print('@price.deleter')


obj = Goods()  # 实例化
# 自动执行@property修饰的price方法,并获取方法的返回值
obj.price     
# 自动执行@price.setter修饰的price方法,并将123赋值给方法的参数
obj.price = 123    
# 自动执行@price.deleter修饰的price方法
del obj.price   

  
输出如下:
@property
@price.setter 123
@price.deleter 

新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法。
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。

class Goods(object):
    def __init__(self):
        self.original_price = 100  # 原价
        self.discount = 0.8  # 折扣

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

obj = Goods()  # 实例化
print(obj.price)   # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价

输出
80.0
160.0

2.2.4 类属性方式

(1)公共属性  name
(2)口头私有属性 _name
前置单下划线,私有化属性或方法,
from somemodule import 禁止导入,
类对象和子类可以访问,
在模块或类外不可以使用,只是约定,不能真正做到不能访问。
(3)私有属性 __name
前置双下划线,私有化属性或方法,只有内部可以访问,外部不能访问。
(4)__init__(python的魔法方法)

当使用类属性的方式创建property属性时,经典类和新式类无区别,参数说明如下:

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

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

代码如下:

class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'

    def set_bar(self, value): 
        """必须两个参数"""
        print("setter...")
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'laowang'

    BAR = property(get_bar, set_bar, del_bar, "description...")

obj = Foo()
# (1)自动调用第一个参数中定义的方法:get_bar
obj.BAR
# (2)自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
obj.BAR = "alex"
# (3)自动获取第四个参数中设置的值:description...
desc = Foo.BAR.__doc__
print(desc)
# (4)自动调用第三个参数中定义的方法:del_bar方法
del obj.BAR

3 defaultdict

使用defaultdict任何未定义的key都会默认返回一个根据method_factory参数不同的默认值,而相同情况下dict()会返回KeyError。

from collections import defaultdict
d1 = dict()
d2 = defaultdict(list)
print(d2['a'])  # 返回[]
print(d1['a'])  # 返回key error

from typing import Any, Dict
#Any一种特殊的类型 ,与每个类型兼容。
字典是一种可变的容器,可以存储任意类型的数据。字典的键不能重复,且只能用不可变类型作为字典的键,不可变的数据类型bool, int, float, complex, str, tuple, frozenset, bytes(字符串)。


版权声明:本文为qq_20466211原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。