python面向对象编程(6)

429 阅读6分钟

python新式类模型

  • python3.x的所有类都会自动转换为一个”新式类”,不论是否有继承object对象

  • python2.x必须显式地指定类继承object父类才表示”新式类”

新式类与经典类模板
# py3.xclass Dtrees:
    """
    Dtrees默认是新式类
    """
    pass# py2.xclass Dtrees(object):
     """
    py2.x必须显式地继承object作为父类,此时才表示是新式类
    """
    passclass Dtrees:
    """
    Dtrees默认是经典类
    """
    pass
新式类的特征

定义经典类与新式类

# newstyle.py,python环境为2.xclass Classic:
    """
    python2.x默认类为经典类
    由于__getatt__ 与 __getattribute__功能效果一样,这里只用__getattr__演示
    """
    def __getattr__(self, method_name):
        print("call Classic __getattr__,it would call built-in[%s] method " % method_name)                return getattr(self.__name,method_name)class NewStyleClass(object):    def __init__(self):
        self.__name = "newstyle name"
    """
    python2.x需要指明为新式类,python3.x默认为新式类
    """
    def __getattr__(self, item):
        print("call NewStyle __getattr__,it would call built-in[%s] method " %item)                return getattr(self.__name,item)def test_dir():
    C = Classic()
    N = NewStyleClass()
    print(dir(C)        # 经典类内置有__getattr__方法
    print(dir(N)        # 新式类的内置方法继承object对象>>> python newstyle.py

新式类中的内置属性方法的获取将以类作为搜索起点,直接跳过类的实例搜索

  • 在新式类中,路由指向__getattr__以及 __getattribute__的类内置函数(__X__)直接跳过不再被拦截

# newstyle.pydef test_classis():
    C = Classic()
    print("python2.x classic instance built-in attributes:")
    print(str(C))       # 调用str()会执行内置函数__str__,但是会先被__getattr__方法拦截,然后再调用__str__
    print(C.__str__())  # 执行的输出与上述结果一样def test_new():
    N = NewStyleClass()
    print("python3.x classic instance built-in attributes:")
    print(str(N))               # 调用str()会执行内置函数__str__,但是没有被__getattr__方法拦截
    print(type(N).__str__(N))   # 必须通过类的方式调用>>> python newstyle.py

  • 经典类实例添加内置操作能够正常访问使用,新式类实例则不行

# newstyle.pydef test_builtin():
    C = Classic()
    C.__add__ = lambda x:x+2    # 为类实例添加内置属性方法
    print(C+2)

    N = NewStyleClass()     
    N.__add__ = lambda x: x + 2 
    print(N + 2)                # unsupported operand type(s) for +: 'NewStyleClass' and 'int',类中没有定义__add__>>> python newstyle.py

类模型的变更

  • classes are types,类都是type的一个实例

# newstyle.pydef test_model_change():
    C = Classic()
    print(type(C))
    print(C.__class__)

    N = NewStyleClass()
    print(type(N))
    print(N.__class__)>>> python newstyle.py

  • types are classes,type都是类的一个实例

# newstyle.py ...class D:passclass NClass(object):passdef test_model_change2():
    C1 = Classic()
    C2 = D()
    print(type(C1) == type(C2))     # 经典类中的所有实例都拥有相同的type

    N1 = NewStyleClass()
    N2 = NClass()
    print(type(N1) == type(N2))     # 新式类:type is class,class is type>>> python newstyle.py

所有的类都继承object,object是处于顶级的基类

  • 类实例对象的type是class,class的type是type,type的type是type

# newstyle.py def test_class():
    print(type("str class"))
    print(type(str))
    print(type(type))>>> python newstyle.py

  • 所有的新式类拥有来自基类object的属性

# newstyle.py def test_inherit():
    print(Classic.__bases__)
    print(dir(Classic))
    print(NewStyleClass.__bases__)
    print(dir(object))
    print(dir(NewStyleClass))>>> python newstyle.py

继承树搜索在以同一个基类的基础下将以广度优先的方式进行遍历

  • 拥有一个相同的顶层类

# newstyle.py class A1(object):attr = 1class B(A1):passclass C(A1):attr = 4class D2(B,C):passdef test_search():
    d = D2()
    print(d.attr)       # 经典类搜索:深度优先,即 D2 -- B --- A1 --- C,新式类:即D2 --- B --- C  --- A1
    print(D2.__mro__)    # 列出D的遍历搜索方式,只有在新式类中生效并且只有属于同一个类下的才会走广度优先if __name__ == '__main__':
    test_search()>>> python newstyle.py       # 经典类和新式类的输出结果

可以参考先前的python面向对象编程(1)中提到的属性继承搜索

  • 顶层基类不是相同一个

# newstyle.pyclass A1(object):attr = 1class A2(object):attr = 2class B(A1):passclass C(A2):attr = 4class T(A2):passclass D2(B,T,C):passdef test_search():
    d = D2()    
    print(d.attr)       
    print(D2.__mro__)    >>> python newstyle.py    # 新式类的输出结果

新式类的扩展

使用__slots__槽来存储实例对象的属性名称

  • 1.定义:assigning a sequence of string attribute names,用一个序列存储字符串属性名称

  • 2.作用:

    • 1)避免随意添加类的实例属性,只能通过槽指定的属性来做设置和访问

    • 2)可以优化内存的使用和加快程序执行的速度

  • 3.声明:

    • 1)通过内置属性__slots__变量来定义

    • 2)必须定义在类顶部的语句中

  • 4.注意点:

    • 必须为槽定义的属性名称进行分配值,如果没有分配而进行访问将会报错,即AttributeError

  • 5.代码测试

# newstyle_extend.pyclass MyObject(object):
    """
    定义属性槽,类实例只能使用下面的属性来进设置和访问,试图设置或者访问不在槽定义的会报错
    """
    __slots__ = ['age','name','job']def test_slots():
    obj = MyObject()            # print(obj.age)      # 未分配但尝试访问报错
    obj.age = 10
    print(obj.age)        # obj.hobby = u"不存在槽中的属性将报错">>> python newstyle_extend.py

  • 6.槽与命名空间字典

    • __slots__会排除实例字典属性,除非字典属性会显式地定义在槽中

    • 一旦我们需要定义槽以外的属性存储在命名空间字典的时候,需要在槽里面添加一个属性,即__dict__

# newstyle_extend.pyclass MyObject(object):
    """
    定义属性槽,类实例只能使用下面的属性来进设置和访问,试图设置或者访问不在槽定义的会报错
    """
    __slots__ = ['age','name','job','__dict__']def test_slots():
    obj = MyObject()        # 这里用unicode,打印输出的时候是以unicode的形式输出
    obj.hobby = u"不存在槽中的hobby属性将存储在命名空间字典中"  
    print(obj.__dict__)>>> python newstyle_extend.py

  • 7.在继承树中使用槽,子类将继承父类的槽属性取并集操作

# newstyle_extend.pyclass D(object):
    __slots__ = ['a','b']class E(object):
    __slots__ = ['c', 'd']class M(E):
    __slots__ = ['xx','a']      # 子类使用槽的时候,只能继承一个父类,否则报multiple bases have instance lay-out conflictdef test_inherhit_class():
    m = M()
    print(dir(m))>>> python newstyle_extend.py

属性访问器

  • 使用property函数来获取属性,其格式为:property(get,set,del,doc),即包含了获取、设置、删除以及文档属性的操作

# property.pyclass PropertyObject(object):
    def get_name(self):
        print("call get_name method...")                return "keithl"

    def set_name(self, value):
        print("call set_name method for value[%s]..." % value)
        self.__name = value

    name = property(fget = get_name,fset = set_name)def test_property():
    po = PropertyObject()
    print(po.name)
    po.name = "xiaokun"if __name__ == '__main__':
    test_property()>>> python property.py

  • 使用装饰器代替上述的属性设置

# property.pyclass PropertyObject(object):
    @property
    def name(self):
        print("call get_name method...")                return "keithl"

    @name.setter
    def name(self,value):
        print("call set_name method  value[%s]..." % value)
        self.__name = value    ... 测试代码省略 >>> python property.py

属性工具:__getattribute__和描述符

在前面详细说明了属性获取与重载运算符的关系,不熟悉的同学可以参考下python面向对象编程(5)

  • 新式类中的__getattribute__方法会拦截所有属性获取的操作,包括上述属性定义的操作

  • 使用类的描述符__get__& __set__

# property.pyclass NameDescriptor(object):
    def __get__(self, instance, owner):return "get_keithl"
    def __set__(self, instance, value):instance._name="set_"+valueclass descriptors(object):
    name = NameDescriptor()def test_name_desc():
    nd = descriptors()
    print(nd.name)
    nd.name = "keithl"
    print(nd.name)
    print(nd._name)>>> python property.py

如有收获,欢迎转发~