美文网首页
面向对象高级编程

面向对象高级编程

作者: Sun_atom | 来源:发表于2017-11-23 16:41 被阅读0次

    使用slots

    正常情况下,当我们定义了一个Class,并创建了一个关于Class的instance后,我们可以该instance绑定任何属性和方法,这表现了动态语言的灵活性。

    class Student(object):
        pass
    

    给instance绑定属性:

    >>>s = Student()
    >>>s.name = 'Michael'
    >>>print('s.name')
    Michael
    

    给instance绑定一个方法:

    >>> def set_age(self, age): # 定义一个函数作为实例方法
    ...     self.age = age
    ...
    >>> from types import MethodType
    >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
    >>> s.set_age(25) # 调用实例方法
    >>> s.age # 测试结果
    25
    

    注意,给一个instance绑定的方法对另一个instance不成立

    >>>s2 = Student
    >>>s2.set_age(25)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'set_age'
    

    为了解决这个,给所有的instance都绑定方法,我们选择给Class绑定方法

    >>> def set_score(self, score):
    ...     self.score = score
    ...
    >>> Student.set_score = set_score
    

    给Class绑定方法后,所有的instance都可以调用。这体现了动态语言的优点。

    使用slots(限制instance的属性)

    为了达到限制的目的,可以再定义Class的时候,定义一个特殊的slots变量,来限制该Class实例可以添加的属性。

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

    验证:

    >>> s = Student() # 创建新的实例
    >>> s.name = 'Michael' # 绑定属性'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'
    

    注意,slots定义的属性仅仅对当前类起作用,对集成的子类不起作用。除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots

    使用@property

    为了解决在绑定属性时,虽然将属性直接暴露出去但是无法检查参数的问题(导致例子中的成绩可以随意修改)

    s = Student()
    s.score = 9999
    

    为了解决这样的问题可以添加方法来get和set

    class Student(object):
    
        def get_score(self):
             return self._score
    
        def set_score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    这样就可以:

    >>> s = Student()
    >>> s.set_score(60) # ok!
    >>> s.get_score()
    60
    >>> s.set_score(9999)
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

    为了简化,使类的属性即可以被检查又可以通过访问属性的简单方式进行访问,可以使用Python内置的装饰器:

    class Student(object):
        """docstring for Student"""
        @property
        def  score(self):
            return self._score
    
        @score.setter
        def score(self,value):
            if not isinstance(value,int):
                raise ValueError('score mmust be an integer')
            if value < 0 or value > 0:
                raise ValueError('score must between 0-100')
            self._score = value
    

    实质就是将一个方法变成属性,方便访问。
    把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

    >>> s = Student()
    >>> s.score = 60 # OK,实际转化为s.set_score(60)
    >>> s.score # OK,实际转化为s.get_score()
    60
    >>> s.score = 9999
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

    定义只读属性的方法:只定义getter方法,不定义setter方法

    多重继承

    继承是面向对象编程的三个重要特点之一,因为通过继承,子类就可以扩展父类的功能。

    #    Dog - 狗狗;
    #    Bat - 蝙蝠;
    #    Parrot - 鹦鹉;
    #    Ostrich - 鸵鸟。
    #    哺乳类:能跑的哺乳类,能飞的哺乳类;
    #    鸟类:能跑的鸟类,能飞的鸟类。
    # 使用多重继承
    class Animal(object):
       pass
    
    # 大类:
    class Mammal(Animal):
       pass
    
    class Bird(Animal):
       pass
    
    # 各种动物:
    class Dog(Mammal):
       pass
    
    class Bat(Mammal):
       pass
    
    class Parrot(Bird):
       pass
    
    class Ostrich(Bird):
       pass
    
    

    为了给动物加上Runnable和Flyable的功能,可以定义相应的类:

    class Runnable(object):
        def run(self):
            print('Running...')
    
    class Flyable(object):
        def fly(self):
            print('Flying...')
    

    对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:

    class Dog(Mammal,Runnable):
        pass
    

    对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:

    class Bat(Mammal, Flyable):
        pass
    

    Mixln

    在设计类的继承关系时,通常主线都是单一继承下来的,但是如果需要“混入”特殊的功能,通过多重继承就可以实现(Mixln),这是因为通过多重继承子类就可以同时获得多个父类的功能。
    为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn,类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:

    class Dog(Mannal,RunnableMixln,CarnivorousMixln):
       pass
    

    MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

    定制类

    除了之前介绍过的slotslen方法之外还有很多有特殊用途的函数可以帮我们定制类。

    str

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    >>> print(Student('Michael'))
    <__main__.Student object at 0x109afb190>
    
    #为了改变上面不美观的输出结果,需要定义好__str__()方法,返回一个好看的字符串
    
    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    ...
    >>> print(Student('Michael'))
    Student object (name: Michael)
    

    但是直接敲变量不用print打印出来的实例还是不好看:

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

    这是因为直接显示变量调用的不是str(),而是repr(),两者的区别是str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。
    解决方案就是直接将事先定义好的str赋值给repr

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

    iter

    如果一个类想被用于for... in循环,就必须实现一个iter()方法,该方法返回一个迭代的对象,然后python的for循环就会不断的调用该迭代的next()方法得到循环的下一个值,知道StopIteration.
    例如:

    class Fib(object):
        def __init__(self):
            self.a, selff.b = 0,1  #初始化两个计数器a,b 
        def __iter__(self):      
            return self #实例本身就是迭代对象,所以返回自己
        def __next__(self):
            self.a,self.b = self.b,self.a+self.b    #计算下一个值
            if self.a > 100000: # 退出循环的条件
                raise StopIteration()
            return self.a # 返回下一个值 
    

    getitem

    上面的例子中的instance虽然可以yongfor循环,但是不可以完全把它当做list处理:

    >>> Fib()[5]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'Fib' object does not support indexing
    

    为了取出下标元素,需要实现getitem方法:

    class Fib(object):
        def __getitem__(self, n):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
    

    为了进一步的实现list中的slice操作:

    #我们必须让__getitem__()判断传入的参数是int还是slice
    class Fib(object):
        """docstring for Fib"""
    
        def __getitem__(self, n):
            if isinstance(n, int):
                a, b = 1, 1
                for x in range(n):
                    a, b = b, a + b
                return a
            if isinstance(n, slice):
                start = n.start
                stop = n.stop
                if start is None:
                    start = 0
                a, b = 1, 1
                L = []
                for x in range(stop)
                    if x >= start:
                        L.append(a)
                    a, b = b, a + b
                return L
    

    如果要让getitem实现对step参数和负数的处理还需要进一步的完善。

    getattr

    正常情况下,当调用类的方法或属性时,如果不存在就会报错。
    例子:

    class Student(object):
    
        def __init__(self):
            self.name = 'Michael'
    
    #调用name属性,没有问题,但是调用不存在的score属性就有问题了
    >>> s = Student()
    >>> print(s.name)
    Michael
    >>> print(s.score)
    Traceback (most recent call last):
      ...
    AttributeError: 'Student' object has no attribute 'score'
    

    要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个getattr()方法,动态返回一个属性。修改如下:

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

    当调用不存在的属性时,比如score,Python解释器会试图调用getattr(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:

    >>> s = Student()
    >>> s.name
    'Michael'
    >>> s.score
    99
    

    注意,只有在没有找到属性和的情况下才调用_getattr,已有的属性不会在__getattr中查找。
    注意到任意调用如s.abc都会返回None,这是因为我们定义的getattr默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

    class Student(object):
    
        def __getattr__(self, attr):
            if attr=='age':
                return lambda: 25
            raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
    

    call

    一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用,也可以通过call直接在instance上调用(不用显式的写出method)。

    class Student(object):
        def __init__(self, name):
            self.name = name
    
        def __call__(self):
            print('My name is %s.' % self.name)
    
    #调用方式
    >>>s  = Student('Michael')
    >>>s()
    My name is Michael
    

    除此之外,call()还可以定义参数,这让对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者本身就没什么根本的区别。
    更多的时候我们判断一个对象是否可以被调用,能被调用的对象是一个Callable对象:

    >>> callable(Student())
    True
    >>> callable(max)
    True
    >>> callable([1, 2, 3])
    False
    >>> callable(None)
    False
    >>> callable('str')
    False
    

    使用枚举类

    当我们定义常量的时候,可以使用大写变量通过整数来定义:

    JAN = 1
    FEB = 2
    MAR = 3
    ...
    NOV = 11
    DEC = 12
    

    但是变量的类型仍然是int。
    更好的方法是为这样的枚举类型定义一个Class类型,每个变量都是Class的一个唯一的实例

    #获得Month类型的枚举类
    from enum import Enum
    Month = Enum ('Month',('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) 
    
    #可以直接使用Month.Jan来引用一个常量,或者枚举它的全部成员:
    for name,member in Month.__members__.items():
        print(name,'=>',member,',',member.value)
    

    value属性则是自动赋给成员的int常量,默认从1开始计数。
    如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

    from enum import Enum, unique
    
    @unique   #@unique装饰器可以帮助我们检查保证没有重复值。
    class Weekday(Enum):
        Sun = 0 # Sun的value被设定为0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6
    

    使用元类

    type()

    动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
    前情提要:已经写好了hello.py的Module:

    class Hello(object):
        def  hello(self,name='world'):
            print('Hello,%s.' %name)
    

    当Python解释器载入hello Module使,就会依次执行该Module的所有语句,执行结果就是动态的创建一个Hello的class对象(类也是一种对象)。

    >>> from hello import Hello
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class 'hello.Hello'>
    

    type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。
    type()函数可以返回一个对象的类型,又可以创建出新的类型。

    #可以通过type()函数创建出Hello类,而无需通过之前的方法
    >>> def fn(self, name='world'): # 先定义函数
    ...     print('Hello, %s.' % name)
    ...
    >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class '__main__.Hello'>
    

    要创建一个Class对象,type()函数依次传入3个参数:
    1.class的名称
    2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
    3.class的方法名称与函数绑定
    通过type()函数创建的类和直接写Class是一样的,因为Python解释器遇到Class定义时,仅仅是扫面一下class定义的语法,然后调用type()函数创建出class
    正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

    metaclass

    除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass:先定义metaclass,就可以创建类,最后创建instance(可以将类看成是metaclass创建的实例)
    例子:使用metaclass给自定义的Mylist增加一个add方法

    #metaclass是类的模板,必须从type类派生
    class ListMetaclass(type):
        def __new__(cls,name,bases,attrs):
            attrs['add'] = lambda self,value : self.append(value)
            return type.__new__(cls,name,bases,attrs)
    

    有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字metaclass:

    class Mylist(list,metaclass=ListMetaclass):
        pass
    

    当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

    new()方法接收到的参数依次是:
    当前准备创建的类的对象;
    类的名字;
    类继承的父类集合;
    类的方法集合。

    #测试:
    
    >>> L = MyList()
    >>> L.add(1)
    >> L
    [1]
    
    #普通的list没有add()方法
    >>> L2 = list()
    >>> L2.add(1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'list' object has no attribute 'add'
    

    元类的方法可以用在写ApI或者数据库中的ORM(object relationship mapping)等方面。

    为了加深理解,附上一个知乎上的回答:
    [type和object的关系]https://www.zhihu.com/question/38791962/answer/78172929

    相关文章

      网友评论

          本文标题:面向对象高级编程

          本文链接:https://www.haomeiwen.com/subject/vuiavxtx.html