曲曲的秘密学术基地

纯化欲望、坚持严肃性

欢迎!我是曲泽慧(@zququ),目前在深圳(ICBI,BCBDI,SIAT)任职助理研究员。


病毒学、免疫学及结构生物学背景,可以在 RG 上找到我已发表的论文

本站自2019年7月已访问web counter

python 魔法方法 (6) 描述符,property()函数的原理

整理自小甲鱼鱼C论坛

描述符的概念

描述符就是将某种特殊类型的类的实例指派给另一个类的属性。而这个特殊类型的类,就是至少要再这个类里面定义__get__()、__set__()或__delete__()三个特殊方法中的任意一个。

魔法方法 含义
get(self, instance, owner) 用于访问属性,它返回属性的值
set(self, instance, value) 将在属性分配操作中调用,不返回任何内容
delete(self, instance) 控制删除操作,不返回任何内容

描述符的解释

class MyDespritor:
    def __get__(self, instance, owner):
        print("getting...", self, instance, owner)

    def __set__(self, instance, value):
        print("setting...", self, instance, value)

    def __delete__(self, instance):
        print("deleting...", self, instance)

class Test:
    x = MyDespritor()

MyDespritor实现了__get__()、__set__()和__delete__()方法,并且将它的类实例指派给Test类的属性,所以MyDescriptor就是描述符类。

当实例化Test类没然后尝试对x属性进行各种操作,看看描述符类的响应:

>>> test = Test()
>>> test.x
getting... <__main__.MyDescriptor object at 0x02D7FE90>  
            # self,是描述符类自身的实例

           <__main__.Test object at 0x02FE0930> 
            # instance,是这个描述符的拥有者所在的类的实例

           <class '__main__.Test'>
            # owner, 是这个描述符的拥有者所在的类本身
>>> test.x = 'X-man'
setting... <__main__.MyDescriptor object at 0x02D7FE90>
           <__main__.Test object at 0x02FE0930>
           X-man    # 最有一个参数value的值
>>> del test.x
deleting... <__main__.MyDescriptor object at 0x02D7FE90>
            <__main__.Test object at 0x02FE0930>

property()函数的原理

复习property()函数

什么是property()函数

property()返回一个可以设置属性的属性,当然如何设置属性还是需要人为来写代码。第一个参数是获得属性的方法名,第二个参数是设置属性的方法名,第三个参数是删除属性的方法名。例如,

class C:
    def __init__(self, size=10):
        self.size = size

    def getSize(self, value):
        return self.size

    def setSize(self, value):
        self.size = value

    def delSize(self):
        del self.size

    x = property(getSize, setSzie, delSize)
property()函数的作用

当我们想要对程序进行大概,就可能需要把setSize和getSize修改为setXSize和getXSize,那就不得不修改用户调用的接口,这样的体验非常不好。

有了property(),为用户访问size属性,只提供了x属性。无论内部如何改动,只需要相应地修改property()的参数,用户仍然只需要去操作x属性即可,没有任何影响。如下,

>>> c = C()
>>> c.x
10
>>> c.x = 12
>>> c.x
12
>>> c.size
12
>>> del c.x
>>> c.size
AttributeError: 'C' object has no attribute 'size'

property()函数的原理

通过描述符写一个类似于property()的Myproperty(),

class Myproperty:
    def __init__(self, fget = None, fset = None, fdel = None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

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

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

    def __delete__(self, instance):
        self.fdel(instance)

class C:
    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 = Myproperty(getX, setX, delX)


>>> c = C()
>>> c.x = 'X-man'  # 用x去干预_x
>>> c.x
'X-man'
>>> c._x
'X-man'
>>> del c.x
>>> c._x
AttributeError: 'C' object has no attributer '_x'

从而实现了property()函数的功能。

最后从一个实例来总结一下描述符:

先定义一个温度类,然后定义两个描述符类用于描述摄氏度和华氏度两个属性。两个属性会自动进行转换,也就是说,可以给摄氏度这个属性赋值,然后打印的华氏度属性是自动转换后的结果。

class Celsius:        # 摄氏度的描述符类
    def __init__(self, value = 26.0):
        self.value = float(value)

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

    def __set__(self, instance, value):
        self.value = float(value)

class Fahrenheit:
    def __init__(self, value = 78.8):
        self.value = float(value) *1.8

    def __get__(self, instance, owner):
        return instance.cel * 1.8 + 32

    def __set__(self, instance, value):
# 因为要用华氏度去设置,所以要用逆运算先计算出摄氏度,在执行__set__()方法
        instance.cel = (float(value) - 32) / 1.8

class Temperature:
    cel = Celsius()
    fah = Fahrenheit()


>>> temp = Temperature()
>>> temp.cel
26.0
>>> temp.fah
78.80000000000001

问题

(1) 按要求编写描述符 MyDes:当类的属性被访问、修改或设置的时候,分别作出提醒。

要求:

>>> class Test:
        x = MyDes(10, 'x')

>>> test = Test()
>>> y = test.x
正在获取变量x
>>> y
10
>>> test.x = 8
正在修改变量x
>>> del test.x
正在删除变量x
~这个变量没法删除~
>>> test.x
正在获取变量x
8

代码:

class MyDespritor:
    def __init__(self, value = None, name = None):
        self.value = value
        self.name  = name
    def __get__(self, instance, owner):
        print("正在获取变量:" + self.name)
        return self.value
    def __set__(self, instance, value):
        print("正在修改变量:" + self.name)
        self.value = value
    def __delete__(self, instance):
        print("正在删除变量:" + self.name + '\n'
              "噢~这个变量没法删除~")

class Test:
    x = MyDespritor(10, 'x')

test = Test()
y = test.x
print(y)
test.x = 8
del test.x
test.x
print(test.x)

(2) 按要求编写描述符 MyDes:记录指定变量的读取和写入操作,并将记录以及触发时间保存到文件(record.txt)

要求:

>>> class Test:
        x = Record(10, 'x')
        y = Record(8.8, 'y')

>>> test = Test()
>>> test.x
10
>>> test.y
8.8
>>> test.x = 123
>>> test.x = 1.23
>>> test.y = 'I love FishC.com!'

并产生record.txt。

x变量于北京时间Thu Dec 26 20:20:29 2019被读取,x = 10
y变量于北京时间Thu Dec 26 20:20:29 2019被读取,y = 8.8
x变量与北京时间Thu Dec 26 20:20:29 2019被修改,x = 123
x变量与北京时间Thu Dec 26 20:20:29 2019被修改,x = 1.23
y变量与北京时间Thu Dec 26 20:20:29 2019被修改,y = I love FIshC.com

代码如下,

import time

class Record:
    def __init__(self, value = None, name = None):
        self.value = value
        self.name = name
        self.filename = 'record.txt'

    def __get__(self, instance, owner):
        with open(self.filename, 'a', encoding = 'utf-8') as f:
            f.write('%s变量于北京时间%s被读取,%s = %s\n' % \
                    (self.name, time.ctime(), self.name, self.value))
        return self.value

    def __set__(self, instance, value):
        self.value = value
        with open(self.filename, 'a', encoding = 'utf-8') as f:
            f.write('%s变量与北京时间%s被修改,%s = %s\n' % \
                    (self.name, time.ctime(), self.name, self.value))

class Test:
    x = Record(10, 'x')
    y = Record(8.8, 'y')

test = Test()
print(test.x)
print(test.y)
test.x = 123
test.x = 1.23
test.y = 'I love FIshC.com'

(3) 编写描述符 MyDes,使用文件来存储属性,属性的值会直接存储到对应的pickle文件。如果属性被删除了,文件也会同时被删除,属性的名字也会被注销。

要求:

>>> class Test:
        x = MyDes('x')
        y = MyDes('y')

>>> test = Test()
>>> test.x = 123
>>> test.y = "I love FishC.com!"
>>> test.x
123
>>> test.y
'I love FishC.com!'

产生对应的文件存储变量的值,如果我们删除x属性,对应的文件也不见了。

代码:

import os
import pickle

class MyDes:
    saved = []
    def __init__(self, name, value = None):
        self.name = name
        self.value = value
        self.filename = self.name + '.pkl'

    def __get__(self, instance, owner):
        if self.name not in MyDes.saved:
            raise AttributeError('%s 属性还没有赋值' % self.name)
        with open(self.filename, 'rb') as f:
            value = pickle.load(f)
        return value

    def __set__(self, instance, value):
        with open(self.filename, 'wb') as f:
            pickle.dump(value, f)
            MyDes.saved.append(self.name)

    def __delete__(self, instance):
        os.remove(self.filename)
        MyDes.saved.remove(self.name)


class Test:
    x = MyDes('x')
    y = MyDes('y')
Last One

汇编语言 实验10 (2) 解决除法溢出的问题

div 复习div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数, 数据类型 寄存器 存放内容 8位 al 商   ah 余数 16位 ax 商   dx 余数 ...…

汇编语言More
Next One

A Pythonic Card Deck

Referenced from Luciano Ramalho, ‘Fluent Python’Example:import collectionsCard = collections.namedtuple('Card', ['rank', 'suit'])class FrenchDeck: ranks = [str(n) for n in range(2, 11) + list('JQKA')] suits = 'spades diamonds clubs heart'.sp...…

pythonMore