Python(廖)之面向对象高级编程

一 .  使用@property

(1)  Python内置的@porperty 装饰器就是负责把一个方法变成属性调用。

class Student(object):
    @property
    def score(self):
        return self._score
    @score.setter
    def 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 bettween 0~100!")
        self._score=value

        

注:以上代码通过@property把一个getter方法变成属性,同时针对这个getter方法,@property会衍生出一个@score.setter的装饰器,用于把一个setter方法变成属性

s=Student()

s.score=60

s.score

同样,我们可以通过这种方式,对一个类创建只读属性
 

class Student(object):
    @property
    def birth(self):
        return self._birth
    @birth.setter
    def birth(self,value):
        self._birth=value
    @property
    def age(self):
        return 2019-self._birth

    
>>> s=Student()
>>> s.birth=1999
>>> s.birth
1999
>>> s.age
20
 

小结:

    @property
    def width(self):
        return self._width
    @width.setter
    def width(self,value):
        self._width=value
    @property
    def height(self):
        return self._height
    @height.setter
    def height(self,value):
        self._height=value
    @property
    def resolution(self):
        return self._width*self._height

    
>>> s=Student()
>>> s.height=100
>>> s.height
100
>>> s.width=50
>>> s.width
50
>>> s.resolution
5000

小结:其实就是讲解如何通过@property快速对一个类的属性进行get和set方法的设置。

二,多重继承

(1)  通过继承,子类可以扩展父类的功能

>>> 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

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

        
>>> class Dog(Mammal,Runnable):
    pass

>>> class Bat(Mammal,Flyable):
    pass

(2) Mixln

1)在设计类的继承关系时,通常,主线都是单一继承下来

2) 通过多重继承可以实现增加额外的功能,这种继承称之为Mixin。

  MixLn的目的就是给一个类增加多个功能,这样,在设计类的时候,优先考虑通过多重继承来着组合多个Mixln的功能,而不是

设计多层次的复杂的继承关系。

这样一来,我们不需要复杂而庞杂的继承连,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

三,定制类

(1) Python的class中还有很多这样有特殊用途的函数,可以帮助我们定制类。

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

通过对Python对每个类的特殊函数根据需要进行定制化处理。

__str__  : 返回用户看到的字符串

__repr__:返回程序开发者看到的字符串。注:为调试服务

(2)  如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__()

方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值

直到遇到StopIteration错误时退出循环。

__iter__:

>>> class Fib(object):
    def __init__(self):
        self.a,self.b=0,1
    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

    
>>> for n in Fib():
    print(n)

    
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025

(3)  Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,因为无法通过下标取出元素

要表现的像list那样按照下标取出,需要实现__getitem__()方法:

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

    
>>> f=Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
>>> 

但list有个神奇的切分方法:

>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]
>>> 

对于Fib却报错,原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice:

>>> class Fib(object):
    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

        
>>> f=Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> 

然后还要对step参数作处理,也没有对负数作处理。所以,要正确实现一个

__getitem__()还是有很多工作要做的。

(4) __getattr__

1)正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错

2)Python提供一个机制应对这种报错:

   定制一个__getattr__()方法,动态返回一个属性:

   

>>> class Student(object):
    def __init__(self):
        self.name='Michael'

        
>>> s=Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
  File "<pyshell#384>", line 1, in <module>
    print(s.score)
AttributeError: 'Student' object has no attribute 'score'
>>> class Student(object):
    def __init__(self):
        self.name='Michael'
    def __getattr__(self,attr):
        if attr=='score':
            return 99

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

当调用不存在的属性是,Python解释器会试图调用__getattr__(self,'score')来尝试获得属性。

实例1:

>>> class Student(object):
    def __getattr__(self,attr):
        if attr=='age':
            return lambda:25

此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。

要让class只响应特定的几个属性,我们按照约定,抛出AttributeError的错误:

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

    
>>> 

(5) __call__

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用

>>> 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(max)
True
>>> callable([1,2,3])
False
>>> callable(None)
False
>>> callable('str')
False

通过callable()函数,可以判断一个对象是否是"可调用'对象

三,使用枚举类

建立一个枚举类型的对象

>>> from enum import Enum
>>> Month=Enum('Month',('Jan','Feb','Mar','Apr','May','Jun','Aug','Sep','Oct','Nov','Dec'))

遍历一个枚举对象

>>> for name,member in Month.__members__.items():
    print(name,'=>',member,',',member.value)

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

也可以自己定义一个枚举类:

>>> from enum import Enum,unique
>>> @unique
class Weekday(Enum):
    Sun=0
    Mon=1
    Tue=2
    Wed=3
    Thu=4
    Fri=5
    Sat=6
 

@unique 装饰器可以帮助我们检查保证没有重复值

四,使用元类

(1)动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的

而是运行时动态创建的。

(2)type()函数可以查看一个类型或变量的类型

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

 Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello

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

所以type()函数可以返回一个对象的类型,又可以创建出新的类型。

>>> 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'>

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

       1)class的名称

       2)继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法

      3)class的方法名称与函数绑定,例如,把函数fn绑定到方法名hello上

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

(4)metaclass

metaclass 直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

而创建出类,必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的‘实例’

(2) metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。

     正常情况下,你不会碰到需要使用metaclass的情况。

>>> class ListMetaclass(type):
    def __new__(cls,name,bases,attrs):
        attrs['add']=lambda self,value:self.append(value)
        return type.__new__(cls,name,bases,attrs)

    
>>> class MyList(list,metaclass=ListMetaclass):
    pass

>P>> 

当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,

要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,可以加上新的方法,

然后,返回修改后的定义。

__new__()方法接收到的参数依次是:

1. 当前准备创建的类的对象

2. 类的名字

3. 类继承的父类集合

4. 类的方法集合。

(3) 会遇到通过metaclass修改类定义的地方有:ORM

    1)ORM 全称为“Object Relational  Mapping”,即对象--关系映射

就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表。

好处在于不用直接操作SQL语句,写代码更简单。

   2)要编写一个ORM框架,所有的类都只能动态定义。因为只有使用者才能根据表的结构定义出对应的类来。

有点小复杂,不是特别懂,以后慢慢掰扯吧。

猜你喜欢

转载自blog.csdn.net/HNDX2018/article/details/87965710