zoukankan      html  css  js  c++  java
  • python风格对象

    对象表示形式

    python提供了两种获取对象字符串表示形式的标准方式

    repr()         //便于开发者理解的方式返回对象的字符串表示形式(一般来说满足obj==eval(repr(obj)))

    str()           //便于用户理解的方式返回对象的字符串表示形式

    要使对象能这两种内置函数的参数,需要实现__repr__和__str__特殊方法,为repr()和str()提供支持。为了给对象提供其他表示形式,还会用到__bytes__和__format__

    bytes()      //获取对象字节序列表示形式

    format()    //特殊格式显示对象字符串表示

    构建一个向量类:

    from array import array
    import math
    
    
    class Vector:
        typecode = 'd'
    
        def __init__(self, x, y):
            self.x = float(x)
            self.y = float(y)
    
        def __iter__(self):       #将类实例变为可迭代对象
            return (i for i in (self.x, self.y))
    
        def __repr__(self):         #构成供开发者使用的字符串
            class_name = type(self).__name__
            return '{}({!r}, {!r})'.format(class_name, *self)
    
        def __str__(self):         #构成供用户使用的字符串
            return str(tuple(self))
    
        def __bytes__(self):        #将对象实例转为字节序列
            return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
    
        def __eq__(self, other):     #实现   ==
            return tuple(self) == tuple(other)
    
        def __abs__(self):           #计算模长
            return math.hypot(self.x, self.y)
    
        def __bool__(self):         #零向量
            return bool(abs(self))

    使用:

    if __name__ == '__main__':
        V = Vector(3, 4)
        print(V.x, V.y)
        x, y = V              #是可迭代对象,故可以元组拆包
        print((x, y))
        repr_V = repr(V)   #字符串表示
        print(repr_V)
        print(eval(repr_V) == V)   #执行这个字符串,打印结果
        octets = bytes(V)         
        print(octets)
        print(abs(V))      #打印模长
        print((bool(V), bool(Vector(0, 0))))   #零向量bool返回False
    
    
    3.0 4.0
    (3.0, 4.0)
    Vector(3.0, 4.0)
    True
    b'dx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@'
    5.0
    (True, False)

    classmethod与staticmethod

    在上例中,可以使用bytes()将对象实例转化为字节序列:

        def __bytes__(self):        #将对象实例转为字节序列
            return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))

    现使用classmethod装饰器来实现字节序列转对象实例的方法:

        @classmethod
        def frombytes(cls, octets):
            typecode = (chr(octets[0]))
            memv = memoryview(octets[1:]).cast(typecode)   #使用传入的字节序列创建视图,使用typecode转换
            return cls(*memv)

    使用:

    vec = Vector.frombytes(octets)         #调用者是vector类
    print(vec)
    
    (3.0, 4.0)

    classmethod是类的方法,而不是实例的方法,它改变了调用方法的方式,因此类方法的第一个参数是类本身而不是实例。(类似c++静态方法)

    而staticmethod装饰器也改变方法调用方式,但第一个参数不是特殊的值。其实,静态方法就是普通的函数

    class Demo:
        @classmethod
        def klassmeth(*args):
            return args               #返回全位置参数
    
        @staticmethod
        def statmeth(*args):
            return args                 #返回全位置参数
    
    if __name__ == '__main__':
        print(Demo.klassmeth())         
        print(Demo.klassmeth('spam'))
        print(Demo.statmeth())
        print(Demo.statmeth('spam'))
    
    #结果
    (<class '__main__.Demo'>,) 
    (<class '__main__.Demo'>, 'spam')      #无论如何调用,第一个参数始终是Demo类
    ()                
    ('spam',)            #行为类似于普通函数

    格式化显示

    内置format()函数和str.format()方法把各个类型的格式化方式委托给相应的.__format__(format_spec)方法。format_spec是格式说明符,它是以下之一:

    1.format(my_obj,format_spec)的第二个参数

    2.str.format()方法的格式字符串,{}里代换字段中冒号的部分

    使用示例:

    brl = 1/2.43
    print(brl)
    form1 = format(brl, '0.4f')      #使用前者,格式说明符是0.4f
    form2 = '1 BRL = {rate:0.2f} USE'.format(rate=brl)  #使用后者,代替冒号部分,格式说明符是0.2f
    print(form1)
    print(form2)
    
    
    0.4115226337448559
    0.4115
    1 BRL = 0.41 USE

    格式规范语言为一些内置类型提供了专用的表示代码,比如b表示二进制int类型,x表示十六进制int类型,f表示小数形式的float类型,%表示百分数形式。

    print(format(42, 'b'))
    print(format(42, 'x'))
    print(format(2/3, '0.1%'))
    print(format(2/3, '0.3f'))
    
    101010
    2a
    66.7%
    0.667

    用户可自行定义__format__方法,如果没有,会调用__str__方法返回值。而未定义__format__方法又传入格式说明符作为参数,将抛出TypeError。

    实现可散列的对象

    要把类实例变为可散列的对象,必须实现__hash__方法和__eq__方法,而__hash__方法需要保证类对象散列值不变。例如Vector类,则需要让x,y是只可读类型。

    class Vector:
        typecode = 'd'
    
        def __init__(self, x, y):
            self.__x = float(x)         #使用双下划线把属性标记为私有
            self.__y = float(y)
    
        @property
        def x(self):
            return self.__x               #使用property装饰器将读值方法标记为特性,即可以使用obj.x获取x
        
        @property
        def y(self):
            return self.__y                #同x

    之后添加__hash__方法就可以将向量变为可散列的:

        def __hash__(self):
            return hash(self.x) ^ hash(self.y)

    实际上只要能够正确实现__hasn__和__eq__方法并且保证实例散列值不会变化即可。

    私有属性和保护属性

    python不能用private修饰符创建私有属性,但python有个简单机制避免子类覆盖私有属性

    例如,有人编写了Dog类,用到了mood实例属性却未开放,这时你创建了Dog的子类Beagle,如果你在毫不知情的情况下创建了mood实例属性,那么继承的方法就会覆盖掉Dog中的mood属性。出现了问题却难以发现。

    为避免这种情况,如果以__mood命名(前面加双下划线,尾部最多有一个下划线)命名实例属性,那么python会把属性名存入__dict__属性中,而且会在前面加一个下划线和类名。对于上例来说父类会变为_Dog__mood而子类会变为_Beagle__mood。这个语言特性叫做名称改写。

    对于向量类的实例:

    print(V.__dict__)
    {'_Vector__x': 3.0, '_Vector__y': 4.0}

    它的目的是避免意外访问,却不能防止故意访问(做坏事),任何人都能直接读取私有属性并且给它赋值

    V._Vector__x = 5.0
    print(V)
    
    #结果  
    (5.0, 4.0)

    也就是说,它是"私有"却不是真正的私有,不可变也不是真正的不可变。

    同时,有人将单划线(_xxx)称为保护属性。

    __solts__

    默认情况下,python在各个实例中名为__dict__的字典里存储实例属性。但由于字典底层使用散列表实现,速度快也耗费大量内存。若处理上百万个属性不多的实例,可通过__slots__属性可节省大量内存,它将使用元组而不是字典来存储实例属性。

    子类不能继承父类的__slots__属性,只会使用自己类中定义的。

    方式:创建一个类属性__slots__,将它的值设置为一个字符串构成的可迭代对象,其中各个元素表示各个实例属性。一般使用元组:

    class Vector:
        __slots__ = ('__x', '__y')

    这个属性定义是为了告诉解释器:这个类中所以的实例属性都保存在这。这样,python会在各个实例中使用类似元组的结构存储实例变量。

    实例只能拥有__slots__列出的属性,除非把__dict__属性加入其中(但这样做 失去了节省内存的功效)

    如果定义了类的__slots__属性,此时想把实例作为弱引用的目标,需要把__weakref__添加到__slots__属性中。

    覆盖类属性

    类属性可以为实例属性提供默认值。Vector类中使用self.typecode读取类属性的值,实例本身没有类属性,self.typecode获取的是类属性Vector.typecode的值。但是如果为不存在的实例属性赋值,就会新建实例属性。

    if __name__ == '__main__':
        v1 = Vector(1.1, 2.2)
        dumpd = bytes(v1)
        print(dumpd)
        v1.typecode = 'f'         #双精度浮点数表示分量
        dumpf = bytes(v1)
        print(dumpf)
        print(Vector.typecode)
    
    
    #结果
    b'dx9ax99x99x99x99x99xf1?x9ax99x99x99x99x99x01@'
    b'fxcdxccx8c?xcdxccx0c@'
    d                      #类属性没有变

    也可修改类属性来修改所有实例的typecode默认值

    Vector.typecode = 'f'

    一般使用方式是创建一个子类,在子类中覆盖掉类属性。

    以上来自《流畅的python》

  • 相关阅读:
    麻省理工公开课:线性代数 第4课 A的LU分解
    麻省理工公开课:线性代数 第3课 乘法和逆矩阵
    麻省理工公开课:线性代数 第2课 矩阵消元
    麻省理工公开课:线性代数 第1课 方程组的几何解释
    线性代数导论(一)向量介绍
    鸟哥的linux私房菜——第6章 Linux的文件权限与目录配置
    Python学习(七)数组读写和保存
    Python学习(六)向量化
    Python学习(五)Numpy通用函数汇总
    Python学习(四)数组和矩阵
  • 原文地址:https://www.cnblogs.com/lht-record/p/10290051.html
Copyright © 2011-2022 走看看