Flask系列三

阅读实际使用的开源项目如flask,对于提高编程能力有巨大好处。flask是实现网站功能,使用数据库的一个编程框架,已有中文出版有关书籍介绍。本系列讲座涉及的是非常精彩的The Flask Mega-Tutorial,开始于2017年12月6日开始,结束于2018年5月结束,每周一课。

    对于一个不太大,但足够复杂的app的代理current_app的实现方法,本系列讲座进行彻底分析,分析过程像美食家需要慢慢品味精美大餐,以体会高明厨师的精巧设计和制作。本系列首先比较各种看起来像“属性”的东西,提示一下不常用的方法,相当于在吃主餐前,品尝精美餐前小吃。

 

系列(三)

餐前小吃(3),还有三种属于属性王国的对象

Property属性

descriptor描述符

__slots__槽口

实例名.属性名,

1、 开篇

在系列(一)介绍的属性是attribute属性,有三个特征:

通过把文字、变量、或者这两者的表达式赋值给一个变量,创建类变量或实例变量;访问属性的方法是getaddr、setaddr函数,或通过getaddr、setaddr与__getaddr__、__setaddr__;

存储与字典__dict__相关。

本文涉及的三种对象,都是类中的变量或形式上的“类变量”,这些变量都是另一个类的实例,不再是由文字、变量、或者这两者的表达式赋值而创建的简单变量;python访问这种变量对象时,会访问另一个类,使用其中的处理方法,而不用getaddr、setaddr等;属性变量可以与字典__dict__无关。

2、 Property属性

Property属性:有一个形式上的“类变量”,是property类的实例,python访问属性时,会使用此property中自定义的函数。

在下面的例子

https://docs.python.org/3.6/library/functions.html?highlight=property#property

中的一个例子,其中第5行

    x = property(getx, setx, delx, "I'm the 'x' property.")

表明类变量x是property类的实例。上面说python访问这种变量对象时,会访问另一个类,使用其中的处理方法。本例将访问property的方法,其中当读属性x时调用第一个参数的方法(本例是getx函数)、写x时调用第二个参数的方法(本例是setx函数)、删除时调用第三个(setx函数)、以及第四个doc说明。下面是程序:

class C1:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")

c1 = C1()
c1.x = 1
print(c1.x)

         上述程序输出为1。

         可以分步设置getx、setx、delx。先使用getx来创建property的实例x:

        x = property(getx, doc="I'm the 'x' property.")

还可以把getx改成x,这样除了函数定义处getx改成x,上述语句也把getx改成x:

        x = property(x, doc="I'm the 'x' property.")

随后使用实例x调用x.setter、 x.deleter函数,分成三次设置,与创建x时,一次设置效果一样,property使用比较灵活。相应变化的部分代码:

        def x(self):
            return self._x
        x = property(x, doc="I'm the 'x' property.")

        def setx(self, value):
            self._x = value
        x = x.setter(setx)

        def delx(self):
            del self._x
        x = x.deleter(delx)

 

可以使用在在第二课中介绍的装饰器,用@x.setter、@x.deleter,完全等价的相关部分代码为:

        @x.setter
        def setx(self, value):
            self._x = value

        @x.deleter
        def delx(self):
            del self._x

Property功能强大,setx、delx的名称也可使用x,在上述代码中代入x。并且使用装饰类来引入property,等效代码为:

class C5:
   
def __init__(self):
       
self._x= None

   
@property
   
def x(self):
       
"""I'm the 'x' property."""
       
return self._x

   
@x.setter
   
def x(self, value):
       
self._x= value

   
@x.deleter
   
def x(self):
       
del self._x


c5 = C5()
c5.x =
5
print(c5.x)

所以从python语法上,property就是装饰器修饰property类,没有增加新的语法。其中property允许一次或分布设置。看一下形式上的“类变量”x:

>>>C5.__dict__
mappingproxy({
   
'__module__': 'my_app2.flask3_2',
    '__init__': <function C5.__init__at 0x02621F18>,
    'x': <property object at0x02620B10>,
    '__dict__': <attribute '__dict__'of 'C5' objects>,
    '__weakref__': <attribute'__weakref__' of 'C5' objects>,
    '__doc__': None})

注意:虽然x看起来像是类变量,但是实际上python是调用相应的方法,这些方法具体实现,决定了x是类变量或实例变量。本例C5中,对x的访问,实际上是对实例变量_x的访问,所以访问x是访问实例变量。

 

         对于不考虑删除的,无需有@x.deleter部分。对于也不修改的只读的属性,@x.setter部分也可没有,这样变成只读。

使用property属性的好处:

增加功能,如对输入范围进行限制,规定x的范围为0~1000,则只要修改setter部分。如要隐藏了变量,可以使用__x。根据规则,双划线开始的变量被隐藏(但结尾再加上双划线,这是特殊属性,例如__dict__)。如果使用_x,变量未隐藏,可以直接改变_x,从而跳过限制。

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

 

3、 descriptor描述符

descriptor描述符:

变量是一个称为descriptor类的实例。descriptor类是指在其定义中,至少有__get__(),__set__(),或__delete__()这三个方法之一。

根据文章中的定义,descriptor是一个有绑定现象的对象的属性,绑定现象是指属性的访问方法被重载。属性访问的方法有__get__(),__set__(),和 __delete__()。只要为对象重写了其中任一方法,就说这个对象是一个descriptor对象。

这里出现了系列(一)中的__get__,当__get__报错时,会试图使用__getter__。

下面例子来自http://www.cnblogs.com/btchenguang/archive/2012/09/18/2690802.html,x与y都是MyClass中定义的类属性,其中x是RevealAccess类的实例。由于RevealAccess重写了__get__()等方法,所以x还是descriptor。

class RevealAccess(object):
    """创建一个Descriptor类,用来打印出访问它的操作信息
    """
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving: obj=%s, objtype=%s' % (str(obj), str(objtype)) )
        print('objtype.__dict__=', objtype.__dict__)
        print('obj.y=', obj.y)
        print('Retrieving: self=%s' % (self.name) )
        return self.val

    def __set__(self, obj, val):
        print('Updating' , self.name)
        self.val = val
#使用Descriptor
class MyClass(object):
    #生成一个Descriptor实例,赋值给类MyClass的x属性
    x = RevealAccess(10, 'var "x"')
    y = 5 #普通类属性

其中本文为RevealAccess的__get__增加了三行print,以报告obj与objtype等信息,其中objtype是类变量x所在的类,本例是MyClass,obj是MyClass的实例。所以使用两者可以进行任何MyClass的类属性与实例属性所能进行的操作。

         在下面的测试中,m是MyClass的实例,在MyClass的定义中包含类变量x,x还是一个descriptor。打印出来的obj的位置信息0x027961D0,的确与后面打印的str(m)中的对象m的位置信息一致。在RevealAccess中,还能得知obj.y= 5。

>>>frommy_app2.flask3_3 import *
>>>m = MyClass()
>>>m.x
Retrieving: obj=<my_app2.flask3_3.MyClass object at 0x027961D0>,objtype=<class 'my_app2.flask3_3.MyClass'>
objtype.__dict__= {'__module__': 'my_app2.flask3_3', 'x':<my_app2.flask3_3.RevealAccess object at 0x0260B9D0>, 'y': 5, '__dict__':<attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute'__weakref__' of 'MyClass' objects>, '__doc__': None}
obj.y= 5
Retrieving: self=var "x"
10
>>>str(m) 
#比对出obj是实例
'<my_app2.flask3_3.MyClass object at 0x027961D0>'

 

注意:在本例中,x是类变量,所以对另一个MyClass的实例n,总有n.x与m.x是一个对象。

 

有了property的灵活性,为什么还要有descriptor?

答案是为了重复使用。例如我们知道最低温度为-273.16度,可以做一个descriptor,限定最低温度,然后在其它使用温度的地方使用此descriptor。

         还有下面的__slot__,也是使用descriptor。

 

4、 槽口__slots__

使用__slots__可以限定只能使用位于__slots__语句中的属性:

class S(object):
    __slots__ = ['val']

在__slot__中有属性’val’,可以写读,其它属性如’new’不在__slot__中,不可创建。

>>>x = S()
>>>x.val = 42
>>>print(x.val)
42
>>>x.new = "not possible"
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'S' object has no attribute 'new'

Python为__slots__中的名称,内部建立一个descriptor,例如本例为__slots__中的名称val,创建一个类变量,它的值是一个descriptor类的实例。内部建立的descriptor,对类变量(例如本例的val)进行访问,可以不利用字典__dict__,所以可以没有字典__dict__。

由于没有字典__dict__,不在__slots__中的变量,就无法访问了。所以上面试图创建x.new失败。

__slots__也可有包含__dict__,这样就可以动态创建属性了。

目的可能有:

为了避免字典__dict__过于庞大,占用存储,在python3.3之前尤其如此。

为了避免低效的字典__dict__访问方式。

 

因为__weakref__属性与还有类的继承等,学究可以阅读下例Python的3.6.5的说明__slots__,掌握全部细节:

         __slots__允许显式申明数据成员(如property),但不会生成__dict__和__weakref__(除非显式在__slots__中申明或在父类中可访问)。

         不用__dict__,省下的内存可很可观。

Object.__slots__:此__slots__类变量可以被后面三种方式赋值:字符串、迭代或字符串序列,这些值均是可被实例使用的变量的名称。__slots__为申明的变量保留空间,并避免自动为每一实例生成__dict__和__weakref__。

下面是注意事项。__slots__会有这么多注意事项,表示惊奇得张大嘴。但是只要记住__slots__与descriptor的联系,大部分还是容易记住的。

注意:

  • 当继承没有使用__slots__的类时,实例的__dict__和__weakref__属性都是可用的。
  • 没有__dict__变量,实例不能对没有列入__slots__定义的新变量赋值。试图对未列入的变量名称赋值将引发AttributeError。如果需要动态对新的动态变量进行赋值,则在__slots__的定义的字符串序列中,加上__dict__。
  • 没有针对每一个实例的__weakref__变量,类定义__slots__不能支持对它的实例的弱引用。如果需要支持弱引用,则在__slots__的定义的字符串序列中,加上__weakref__。
  • __slots__的实现方法是在类这一层次上,为每一个变量名称创立描述符descriptor。结果是,类属性不能用来对__slots__定义的实例变量设置默认值,否则类属性将覆盖描述符的赋值。

注释:如果申明了一个类变量,并在__slots__中包含此名称,一方面,会由于申明类变量,有了一个类变量;另一方面,会由于是descriptor,python又建立一个同名的类变量,这样产生了两个同名类变量,所以会产生问题。

  • 申明__slots__的作用并不局限于它定义所在的类。在父类中定义的__slots__,对子类也有效。但是继承的子类会有__dict__与__weakref__类变量的,除非它们也定义的__slots__(它只需包含任何其它的槽口)。
  • 如果在一个类中所定义的槽口,在其基类中也定义了,则由基类槽口所定义的实例变量不可访问(除非直接从基类复原它的descriptor)。这使得程序的意义未定。将来可能增加一个检查来预防。
  • 继承于可变长度的(如int,bytes和tuple)类,不能使用非空的__slots__。
  • 非字符的迭代可以赋值给__slots__。也可使用映射,但将来会对每个键所对应的值指定特殊含义。
  • 仅当两个类有相同的__slots__,__class__的赋值才有效。

注释:__class__的赋值是指:x.__class__ = y.__class__

  • 继承多个有槽口的父类也是可以的,但只允许一个父类有槽口定义的属性。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值