• Python学习--11 面向对象高级编程


    多重继承

    Python里允许多重继承,即一个类可以同时继承多个类:

    class Mammal(Animal):
        pass
        
    class Runnable(object):
        def run(self):
            print('Running...')
            
    class Dog(Mammal, Runnable):
        pass
    

    这样,Dog同时拥有MammalRunnable的属性和方法。

    __slots__限制实例的属性

    由于类的实例可以动态绑定新的属性,有时候我们不希望这样,可以通过__slots__进行限制:

    class Student(object):
        __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    

    然后,我们试试:

    >>> s = Student() # 创建新的实例
    >>> s.name = 'yjc' # 绑定属性'name'
    >>> s.age = 25 # 绑定属性'age'
    >>> s.score = 99 # 绑定属性'score'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'score'
    

    由于score没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

    使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

    >>> class SubStudent(Student):
    ...     pass
    ...
    >>> g = SubStudent()
    >>> g.score = 99
    

    除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

    @property装饰器

    当我们通过实例使用类的属性时,通常不希望直接访问,而是处理之后再暴露出来。例如:

    class Student(object):
        def setScore(self, value):
            if(value > 100):
                value = 100
            if(value < 0):
                value = 0
            self.__score = value
    
        def getScore(self):
            return self.__score
    
    s = Student()
    s.setScore(199)
    print(s.getScore())
    

    输出:

    100
    

    这里,__score属性我们通过setScore先设置,然后使用getScore获得,并对不合理值进行了处理。

    上面我们通过类里的方法实现了类属性的设置和访问。那么,有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?Python里的@property装饰器就是做这个的:

    class Student(object):
    
        @property
        def score(self):
            return self.__score
    
        @score.setter
        def score(self, value):
            if(value > 100):
                value = 100
            if(value < 0):
                value = 0
            self.__score = value
    
    s = Student()
    s.score = 199
    print(s.score)
    

    输出:

    100
    

    Python内置的@property装饰器就是负责把一个方法变成属性调用。此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值。

    @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

    定制类

    我们可以使用类似__slots__这种变量或者函数名来定制类,这些在Python里是有特殊作用的。

    通过自定义下面这些属性或方法,我们可以对类做自定义处理:

    __slots__:限制实例的属性
    __len__():自定义返回长度

    __str__():当尝试使用print打印类的时候,自定义返回类的内容。因为默认打印出一堆<__main__.Student object at 0x109afb190>,不好看。

    class Student(object):
        def __init__(self, name):
            self.name = name
            
        def __str__(self):
            return 'Student object (name: %s)' % self.name
            
    print(Student('yjc'))
    

    输出:

    Student object (name: yjc)
    

    这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

    __repr__():与__str__()类似,当直接敲变量Student('yjc')不用print的时候,会自动调用该方法。

    __getattr__():默认调用类里不存在的属性时,会报错。通过该方法,可以动态返回一个属性。

    class Student(object):
    
        def __init__(self):
            self.name = 'yjc'
    
        def __getattr__(self, attr):
            if attr=='score':
                return 80
    

    这时候调用score属性,不会报错了:

    >>> s = Student()
    >>> s.name
    'yjc'
    >>> s.score
    80
    

    __call__():通过覆写该方法,可以将实例像方法那样直接调用:

    class Student(object):
        def __init__(self, name):
            self.name = name
    
        def __call__(self):
            print('My name is %s.' % self.name)
    

    调用方式如下:

    >>> s = Student('yjc')
    >>> s() # self参数不要传入
    My name is yjc.
    

    __call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

    通过callable()函数,我们就可以判断一个对象是否是可调用对象:

    >>> callable(Student())
    True
    >>> callable(max)
    True
    

    枚举类

    Python提供Enum类来实现枚举功能:

    # coding: utf-8
    from enum import Enum
    
    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    
    # 可以直接使用Month.Jan来引用一个常量:
    print(Month.Jan.value)
    
    # 枚举所有成员:
    for name,member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)
    

    输出:

    1
    Jan => Month.Jan , 1
    Feb => Month.Feb , 2
    Mar => Month.Mar , 3
    Apr => Month.Apr , 4
    May => Month.May , 5
    Jun => Month.Jun , 6
    Jul => Month.Jul , 7
    Aug => Month.Aug , 8
    Sep => Month.Sep , 9
    Oct => Month.Oct , 10
    Nov => Month.Nov , 11
    Dec => Month.Dec , 12
    

    value属性则是自动赋给成员的int常量,默认从1开始计数。

    如果想自定义value值:

    # coding: utf-8
    from enum import Enum,unique
    
    @unique
    class Month(Enum):
        Jan = 0
        Feb = 1
        Mar = 2
    
    print(Month.Jan.value)
    
    for name,member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)
    

    输出:

    0
    Jan => Month.Jan , 0
    Feb => Month.Feb , 1
    Mar => Month.Mar , 2
    

    @unique装饰器可以帮助我们检查保证没有重复值。如果重复了,会报ValueError错误:

    ValueError: duplicate values found in <enum 'Month'>
    

    元类

    动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

    type()

    type()可以查看一个类型或变量的类型:

    # coding:utf-8
    
    class Hello(object):
        pass
    
    h = Hello()
    
    print(type(h))
    print(type(Hello))
    print(type(object))
    

    输出:

    <class '__main__.Hello'>
    <class 'type'>
    <class 'type'>
    

    通过打印我们发现,类Hello的类型是type,但它的实例类型是class Hello类型。

    Python里class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。

    type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类:

    # 定义成员方法:减
    def sub(self, x, y):
        return x-y
    
    # 生成类
    Hello = type('Hello', (object,), {"add":add, "mysub":sub})
    
    h = Hello()
    print(h.add(1, 2))
    print(h.mysub(1, 2))
    
    print(type(h))
    print(type(Hello))
    

    输出:

    3
    -1
    <class '__main__.Hello'>
    <class 'type'>
    

    要创建一个class对象,type()函数依次传入3个参数:

    1. class的名称;
    2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
    3. class的方法名称与函数绑定。

    通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

    metaclass

    metaclass,直译为元类,可以理解为类的模板。通过metaclass也可以动态创建类。

    metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,我们不会碰到需要使用metaclass的情况。

    通过metaclass创建出类,需要:先定义metaclass,然后创建类。下面的示例是给自定义的MyList增加一个add方法:

    示例:

    # coding: utf-8
    
    # metaclass是类的模板,所以必须从`type`类型派生:
    class ListMetaclass(type):
        def __new__(cls, name, base, attrs):
            attrs['add'] = lambda self,value: self.append(value)
    
            #打印参数信息
            print(cls, '
    ', name, '
    ', base, '
    ', attrs, '
    ')
    
            return type.__new__(cls, name, base, attrs)
    
    # 根据metaclass产生类
    class MyList(list, metaclass=ListMetaclass):
        pass
    
    # 类继承
    class OtherList(MyList):
        pass
    
    L = MyList()
    L.add('3')
    
    print(L)
    

    输出:

    <class '__main__.ListMetaclass'> 
     MyList 
     (<class 'list'>,) 
     {'add': <function ListMetaclass.__new__.<locals>.<lambda> at 0x02424540>, '__module__': '__main__', '__qualname__': 'MyList'} 
    
    <class '__main__.ListMetaclass'> 
     OtherList 
     (<class '__main__.MyList'>,) 
     {'add': <function ListMetaclass.__new__.<locals>.<lambda> at 0x02424588>, '__module__': '__main__', '__qualname__': 'OtherList'} 
    
    ['3']
    

    以上通过metaclass动态生成了MyList类,并增加了成员方法add()

    通过分析输出,我们可以发现:__new__()方法接收到的参数依次是:

    1. 当前准备创建的类的对象,例如ListMetaclass
    2. 类的名字,例如MyList
    3. 类继承的父类集合,例如list
    4. 类的方法集合,例如add__module____qualname__

    什么时候需要用到metaclass呢?ORM就是一个典型的例子。

  • 相关阅读:
    同时使用gitee和github
    vim的四种模式及模式切换
    Vim使用入门
    Vim, Vim Diff, Vim Easy, Vim Read-only 区别
    公钥与私钥
    Linux使用Aria2命令下载BT种子/磁力/直链文件
    Content-Type /AJAX /@ResponseBody
    IDEA 添加serialVersionUID 检查
    Servlet中的Context Path | Servlet Path | Path Info
    第K大的数
  • 原文地址:https://www.cnblogs.com/52fhy/p/6297828.html
Copyright © 2020-2023  润新知