porperty属性与描述符

一、property属性

1.目的:设置property是为了在给对象的属性进行操作的时候加以管理,调用方式如下:

class Foo:
    def func(self):
        print("func is working")

    @property  # 将方法变成了一个属性
    def prop(self):
        print("prop is working")
        return "ok"  # 返回值作为调用 foo.prop 的结果


foo = Foo()
foo.func()
x = foo.prop  # 使用【对象名.property属性名】进行调用
print(x)

运行结果:
func is working
prop is working
ok

进程已结束,退出代码0

(1).demo案例

"""模拟京东网商品翻页"""
class Pager:
    def __init__(self, current_page):
        self.current_page = current_page
        self.per_items = 10  # 每个页面能容纳10个商品

    @property
    def start(self):  # 一个页面开始的单号
        value = (self.current_page - 1) * self.per_items
        return value + 1

    @property
    def end(self):  # 一个页面结束的单号
        value = self.current_page * self.per_items
        return value
    

p = Pager(2)
print(f"{p.start}-{p.end}")

运行结果:
11-20

进程已结束,退出代码0

(2) 总结
我们在对属性进行操作时,如果不使用property方法,不仅需要单独设置函数来完成这个操作,而且还需要在调用的时候在进行调用,而使用property后就可以直接进行设置,不需要单独进行调用,而且普通属性调用时,首先要进行函数的运算,在返回给属性,我们在获取时还要返回给我们,而property不需要这么繁琐,调用时就会直接进行运算,运算完成后直接将返回值交给我们就好了。

2.property的2种应用方式
(1)装饰器方式(新式类)

class Goods:
    def __init__(self,):
        self.original_price = 100
        self.discount = 0.8

    @property 
    def price(self):  # 相当于getter
        new_price = self.discount * self.original_price
        return new_price

    @price.setter  # 注意保持一致
    def price(self, value):  # 注意保持一致
        self.original_price = value

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


goods1 = Goods()
print(goods1.price)
goods1.original_price = 1000
print(goods1.price)

运行结果:
80.0
800.0

进程已结束,退出代码0

(2) 类属性方式

class Goods:
    def __init__(self,):
        self.original_price = 100
        self.discount = 0.8

    def get_price(self):
        new_price = self.discount * self.original_price
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    BAR = property(get_price, set_price, del_price)  # 将【方法名】作为参数传入,不要括号


goods1 = Goods()
print(goods1.BAR)
goods1.original_price = 1000
print(goods1.BAR)

运行结果:
80.0
800.0

进程已结束,退出代码0

类属性和装饰器属性时完全一致的,只是相比较而言,类属性方式更加简洁,而装饰器方式可读性高。

二、元类

1.元类就是一个专门用来创建其他类的特殊类,最顶层的元类就是type。类也是一个对象,也就是说可以将类作为一个实参传递。

def choose_class(name):
    if name == "foo":
        class Foo():
            print("ok")

        return Foo
    else:
        class Bar():
            pass

        return Bar


A = choose_class("foo")  # <__main__.choose_class.<locals>.Foo object at 0x000001A9F8505810> A是一个类
a = A()  # <class '__main__.choose_class.<locals>.Foo'> a是类的实例对象
print(a, A)

总结:
(1).类的定义可以放在函数中
(2).给函数返回的参数不同,得到类的对象不同,从而创建的类的对象不同
(3).可以动态的创建类

2.由type创建类
type语法:type(类名, 由父类名称组成的元组(可以为空), 包含属性的字典(名称和值) )

b = type("B", (), {})  # 这样就使用type创建了一个类,b仅为一个变量名,B为类名,一般将b写成B,提高可读性
print(help(b))

运行结果:
Help on class B in module __main__:

class B(builtins.object)  # 这就是上面创建的类
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

None

进程已结束,退出代码0

3.使用type创建带有属性的类

B = type("B", (), {"bar": True})
b = B()
print(b.bar)

运行结果:
True

进程已结束,退出代码0

4.使用type创建带有方法的类

class A(object):
    num = 100

def print_b(self):  # 实例方法
    print(self.num)

@staticmethod  # 静态方法
def print_static():
    print("print_static is working")

@classmethod  # 类方法
def print_class(cls):
    print(cls.num)


B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class})
b = B()
b.print_b()  # 调用print_b方法  
b.print_static()  # 调用print_static方法  
b.print_class()  # 调用print_class方法  

运行结果:
100
print_static is working
100

进程已结束,退出代码0

5.自定义元类
(1)使用函数自定义

def upper_attr(class_name, class_parents, class_attr):
    # class_name就是下面的类名Foo,class_parents是object,class_attr是类里面的属性以字典的形式 {"bar": bip}

    new_attr = {}
    for name, value in class_attr.items():  # name就是”bar“, value就是bip
        if not name.startswith("__"):  # name是否是以下划线开始的
            new_attr[name.upper()] = value  # 创建一个新字典

    return type(class_name, class_parents, new_attr)  # 返回一个class


# 这里太容易混淆了,class是以【return type(class_name, class_parents, new_attr)】这个语句创建的类,不是以下面的形式创建的类
class Foo(object, metaclass=upper_attr):
    bar = "bip"


print(hasattr(Foo, "bar"))  # 这里返回为False是因为Foo不是以上述形式所创建的,故没有bar
print(hasattr(Foo, "BAR"))
# print(Foo.__dict__)
f = Foo()
print(f.BAR)

运行结果:
False
True
bip

进程已结束,退出代码0

(2)使用其他类自定义

class UpperAttrMetaClass(type):
    # __new__创建一个对象,__init__只是初始化一个对象
    def __new__(cls, class_name, class_parents, class_attr):
        new_attr = {}
        for name, value in class_attr.items():  # name就是”bar“, value就是bip
            if not name.startswith("__"):  # name是否是以下划线开始的
                new_attr[name.upper()] = value  # 创建一个新字典

        return type(class_name, class_parents, new_attr)  # 返回一个class


class Foo(object, metaclass=UpperAttrMetaClass):
    bar = "bip"


print(hasattr(Foo, "bar"))
print(hasattr(Foo, "BAR"))
f = Foo()
print(f.BAR)

运行结果:
False
True
bip

进程已结束,退出代码0

总结:
元类只有在极少数情况下才会使用,所以不需要理解的太透彻。

三、描述符(Descriptor)

  1. 定义:如果一个类有__get__, __set __, __ delete __三个中的一个或多个,那么用这个类所创建的对象,可以称为描述符。
  2. 目的:描述符是具有"绑定行为"的对象属性。可以用来自定义类属性
    没啥可说的,直接上代码。
class Desc:
    def __set_name__(self, owner, name):
        self.__name = name  # Desc.__name 这是定义了一个临时属性(这个临时属性是instance这个实例对象的)用来存储描述符名即 [Desc.__name = Username]

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError(f"{value} is not a string")
        if len(value) == 0:
            raise ValueError(f"{value} is empty")
        instance.__dict__[self.__name] = value  # instance实例对象的属性中的[临时属性:self.__name] = value

    def __get__(self, instance, owner):
        return instance.__dict__[self.__name]

class Email:
    username = Desc()
    password = Desc()

    def __init__(self, username, password, iphone):
        self.username = username
        self.password = password
        self.iphone = iphone


mail = Email("路西恩", "1314521", 1234567890)

代码流程图如下:
代码流程图
3.描述符的应用
(1).使用描述符实现staticmethod (真提莫的难,我测)

class ClassMethodNew:
    def __init__(self, func):
        self.func = func  # self.func = b

    def __get__(self, instance, owner):  # self是b函数,instance是A的实例对象,owner是A类
        print(self.func, instance, owner)

        def call(*args):
            self.func(owner, *args)  # b(owner, *args),owner是A,所以这里等价于b(A)

        return call


class A:
    M = 100

    def a(self):
        print("a是实例方法")

    @ClassMethodNew  # 等价于 b = ClassMethodNew(b)
    def b(cls):
        print("b是类方法1")
        print(cls.M)
        print("b是类方法2")

    @ClassMethodNew
    def c(cls, num1, num2):
        print("c是类方法1")
        print(cls.M + num1 + num2)
        print("c是类方法2")


obj = A()
obj.b()
A.b()
obj.c(11, 22)

运行结果:
<function A.b at 0x0000015BD0A7A200> <__main__.A object at 0x0000015BD0A86850> <class '__main__.A'>
b是类方法1
100
b是类方法2
<function A.c at 0x0000015BD0A7A2A0> <__main__.A object at 0x0000015BD0A86850> <class '__main__.A'>
c是类方法1
133
c是类方法2
进程已结束,退出代码0

代码流程图如下
在这里插入图片描述
4. 描述符的调用机制
(1)_dict _访问顺序
Python中所有的对象都有一个字典,这个字典存储着它所拥有的所有属性,属性名为key,属性值为value。
instance.m的访问顺序:

  • 程序会先查找instance.__dict__["m"]是否存在
  • 不存在再找type(instance).__dict__["m"]中查找,其中type(instance)指的是instance的父类。
  • 然后找type(instance)的父类
  • 期间找到的是普通纸就输出,如果找到的是一个描述符,则调用__get__方法。
  1. 数据描述符 / 数据资料描述符( 非数据描述符 / 非数据资料描述符)
    5.1 深入理解描述符
class M:
    def __init__(self):
        self.x = 1

    def __get__(self, instance, owner):
        return self.x

    def __set__(self, instance, value):
        instance.__dict__[self.x] = value


class AA:
    m = M()  # 描述符会调用M中的方法
    n = 2

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


aa = AA(3)
print(aa.__dict__)  # aa中的属性只有score,m和n是它的父类的属性,通俗来讲就是,aa能用m和n,但不是aa的
print(aa.score)
print(aa.__dict__["score"])

print(type(aa).__dict__)  # type(aa)是aa的父类
print(aa.n)
print(type(aa).__dict__["n"])

print(aa.m)  # 在aa中找不到m,所以去父类里面找,在父类里m指向描述符,所以调用描述符方法,返回self.x = 1
print(type(aa).__dict__["m"].__get__(aa, AA))  # type(aa).__dict__["m"]这是M的实例对象,既然是实例对象,就可以调用M中的函数。所以
                                # type(aa).__dict__["m"].__get__(aa, AA)就等价于m.__get__[aa,AA],其中aa就是instance, AA就是owner
print("_" * 20)
print(AA.m)
print(AA.__dict__["m"].__get__(None, AA))

在这里插入图片描述
5.2数据描述符,非数据描述符

  • 同时定义了__set____get__的方法的描述符称为数据描述符
  • 只定义了__get__的描述符称为非数据描述符
  • 二者的区别:当属性名与描述符名相同时,在访问这个同名属性时,如果是数据描述符就会优先访问描述符,如果是非数据描述符就会先访问属性,因为非数据描述符没有__set__方法,所以会使用init赋值。
class M:
    def __init__(self):
        self.x = 1

    def __get__(self, instance, owner):
        # print("get m here")
        return self.x

    def __set__(self, instance, value):
        # print("set m here")
        self.x = value + 1


class N:
    def __init__(self):
        self.x = 1

    def __get__(self, instance, owner):
        # print("get n here")
        return self.x


class AA:
    m = M()
    n = N()

    def __init__(self, m, n):
        self.m = m  # 当属性名m与描述符名m相同时,会发生冲突,但此时描述符m是数据描述符,所以执行描述符,不执行__init__函数,此时运行结果就是aa.m = 6,即数据描述符优先级比属性高
        self.n = n  # 与上面同理,但此时描述符是数据描述符,所以现在执行__init__函数,而不执行描述符n,所以结果是aa.n = 5,非数据描述符优先级要比属性低。


aa = AA(5, 5)
# print(aa.__dict__)
# print(AA.__dict__)
print(aa.m)
print(aa.n)
# print(AA.n)
# aa.m = 6
# print(aa.m)
# print("_" * 20)
# print(AA.__dict__["n"].__get__(None, AA))
# print(AA.__dict__["n"].__get__(aa, AA))

运行结果:
6
5
进程已结束,退出代码0

5.3 只读描述符
要想制作一个只读描述符,需要同时定义__set____get__,并在__set__中引发一个AttributeError异常。定义一个引发异常__set__方法就足够让一个描述符成为数据描述符,切是只读的

class OnlyRead:
    def __set_name__(self, owner, name):
        self._property_name = 3.14

    def __set__(self, instance, value):
        raise AttributeError("This val is only read")

    def __get__(self, instance, owner):
        return self._property_name


class Math:
    pi = OnlyRead()


math = Math()
print(math.pi)
math.pi = "3"

运行结果:
Traceback (most recent call last):
  File "H:\PycharmProjects\pythonProject1\text1.py", line 18, in <module>
    math.pi = "3"
    ^^^^^^^
  File "H:\PycharmProjects\pythonProject1\text1.py", line 6, in __set__
    raise AttributeError("This is val is only read")
AttributeError: This val is only read
3.14

进程已结束,退出代码1
  1. 描述符的注意点
  • 不可以用 实例属性 定义描述符
class NameDes:
    def __init__(self):
        self.__name = None

    def __set__(self, instance, value):
        print("set is working")
        if isinstance(value, str):
            self.__name = value
        else:
            raise TypeError("必须是字符串")

    def __get__(self, instance, owner):
        print("get is working")
        return self.__name


class Person():
    # name = NameDes()  # 类属性时,描述符才有作用
    def __init__(self):
        self.name = NameDes()  # 实例对象不会起到描述符的作用


person = Person()
print(person.name)
# 如果将描述符对象赋值给实例属性,此时相当于name指向了一个普通的对象,只不过这个对象中有__get__,__set__方法而已,不会自动调用其__get__方法

  • 注意事项
class MaxValues:
    def __init__(self, inti_val, max_val):
        self.value = inti_val
        self.max_val = max_val

    def __get__(self, instance, type_):
        return self.value

    def __set__(self, instance, value):
        self.value = min(self.max_val, value)


class Widget:
    volume = MaxValues(0, 10)


a = Widget()
print("a默认的volume值:", a.volume)
a.volume = 12
print("a设置后的volume值:", a.volume)

b = Widget()  # 两个实例对象共用一个描述符对象和类对象,两者不独立
print("b默认的volume值:", b.volume)

运行结果:
a默认的volume值: 0
a设置后的volume值: 10
b默认的volume值: 10

进程已结束,退出代码0

上述代码是有问题的,在使用a设置value的之后,另一个对象访问数时就会访问被设置好的值,所以就会出错。其流程图如下:
在这里插入图片描述
改进方法如下:

class MaxValues:
    def __init__(self, inti_val, max_val):
        self.value = inti_val
        self.max_val = max_val
        self.data = {}

    def __get__(self, instance, owner):  # instance就是a,b
        if not instance:
            return self.value
        return self.data.get(instance, self.value)  # get可以取出字典中的value,但是a.get(instance, self.value)就相当于instance or self.value

    def __set__(self, instance, value):
        self.data[instance] = min(self.max_val, value)  # self.data[instance]就是字典将{“instance”: value}


class Widget:
    volume = MaxValues(0, 10)


a = Widget()
print("a默认的volume值:", a.volume)
a.volume = 12
print("a设置后的volume值:", a.volume)

b = Widget()  # 两个实例对象共用一个描述符对象和类对象,两者不独立
print("b默认的volume值:", b.volume)

运行结果:
a默认的volume值: 0
a设置后的volume值: 10
b默认的volume值: 0

进程已结束,退出代码0

其流程图如下:
在这里插入图片描述
定义一个空字典就可以避免这种情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值