Python学习之面向对象高级编程

Python学习目录python

  1. 在Mac下使用Python3
  2. Python学习之数据类型
  3. Python学习之函数
  4. Python学习之高级特性
  5. Python学习之函数式编程
  6. Python学习之模块
  7. Python学习之面向对象编程
  8. Python学习之面向对象高级编程
  9. Python学习之错误调试和测试
  10. Python学习之IO编程
  11. Python学习之进程和线程
  12. Python学习之正则
  13. Python学习之经常使用模块
  14. Python学习之网络编程

数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有不少高级特性,如:多重继承、定制类、元类等概念。编程

_slots_

做用:限制实例的属性。网络

Python容许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:app

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'
复制代码

因为'score'没有被放到__slots__中,因此不能绑定score属性,试图绑定score将获得AttributeError的错误。函数式编程

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

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
复制代码

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

@property

装饰器(decorator)能够给函数动态加上功能,对于类的方法,装饰器同样起做用,Python内置的@property装饰器就是负责把一个方法变成属性调用的:post

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 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!
复制代码

注意到这个神奇的@property,咱们在对实例属性操做的时候,就知道该属性极可能不是直接暴露的,而是经过getter和setter方法来实现的。测试

还能够定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

class Student(object):

 @property
    def birth(self):
        return self._birth

 @birth.setter
    def birth(self, value):
        self._birth = value

 @property
    def age(self):
        return 2015 - self._birth
复制代码

上面的birth是可读写属性,而age就是一个只读属性,由于age能够根据birth和当前时间计算出来。

多重继承

在设计类的继承关系时,一般,主线都是单一继承下来的,例如,Ostrich继承自Bird。可是,若是须要“混入”额外的功能,经过多重继承就能够实现,好比,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计一般称之为MixIn。

为了更好地看出继承关系,咱们把RunnableFlyable改成RunnableMixInFlyableMixIn。相似的,你还能够定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
复制代码

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

定制类

到相似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。__slots__咱们已经知道怎么用了,__len__()方法咱们也知道是为了能让class做用于len()函数。除此以外,Python的class中还有许多这样有特殊用途的函数,能够帮助咱们定制类。

_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,打印出来的实例仍是很差看:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
复制代码

这是由于直接显示变量调用的不是__str__(),而是__repr__(),二者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__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循环,相似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,而后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

咱们以斐波那契数列为例,写一个Fib类,能够做用于for循环:

class Fib(object):
   def __init__(self):
       self.a, self.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 # 返回下一个值
复制代码

如今,试试把Fib实例做用于for循环:

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025
复制代码

_getitem_

像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): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            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 
复制代码

_getattr_

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
复制代码

返回函数也是彻底能够的:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
复制代码

只是调用方式要变为:

>>> s.age()
25
复制代码

注意,只有在没有找到属性的状况下,才调用__getattr__,已有的属性,好比name,不会在__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__()方法,就能够直接对实例进行调用。请看示例:

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
        
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
复制代码

__call__()还能够定义参数。对实例进行直接调用就比如对一个函数进行调用同样,因此你彻底能够把对象当作函数,把函数当作对象,由于这二者之间原本就没啥根本的区别。

若是你把对象当作函数,那么函数自己其实也能够在运行期动态建立出来,由于类的实例都是运行期建立出来的,这么一来,咱们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象仍是函数呢?其实,更多的时候,咱们须要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,好比函数和咱们上面定义的带有__call__()的类实例:

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

枚举类

「枚举类」Enum是为枚举类型定义一个class类型,而后,每一个常量都是class的一个惟一实例。

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
复制代码

这样咱们就得到了Month类型的枚举类,能够直接使用Month.Jan来引用一个常量,或者枚举它的全部成员:

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

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

若是须要更精确地控制枚举类型,能够从Enum派生出自定义类:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
复制代码

@unique装饰器能够帮助咱们检查保证没有重复值。

访问这些枚举类型能够有若干种方法:

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
  ...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
复制代码

可见,既能够用成员名称引用枚举常量,又能够直接根据value的值得到枚举常量。

元类

type()

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

比方说咱们要定义一个Hello的class,就写一个hello.py模块:

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

当Python解释器载入hello模块时,就会依次执行该模块的全部语句,执行结果就是动态建立出一个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

咱们说class的定义是运行时动态建立的,而建立class的方法就是使用type()函数。

type()函数既能够返回一个对象的类型,又能够建立出新的类型,好比,咱们能够经过type()函数建立出Hello类,而无需经过class Hello(object)...的定义:

>>> 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的方法名称与函数绑定,这里咱们把函数fn绑定到方法名hello上。

经过type()函数建立的类和直接写class是彻底同样的,由于Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,而后调用type()函数建立出class。

正常状况下,咱们都用class Xxx...来定义类,可是,type()函数也容许咱们动态建立出类来,也就是说,动态语言自己支持运行期动态建立类,这和静态语言有很是大的不一样,要在静态语言运行期建立类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会很是复杂。

metaclass

除了使用type()动态建立类之外,要控制类的建立行为,还可使用metaclass。

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

当咱们定义了类之后,就能够根据这个类建立出实例,因此:先定义类,而后建立实例。

可是若是咱们想建立出类呢?那就必须根据metaclass建立出类,因此:先定义metaclass,而后建立类。

链接起来就是:先定义metaclass,就能够建立类,最后建立实例。

因此,metaclass容许你建立类或者修改类。换句话说,你能够把类当作是metaclass建立出来的“实例”。

下一篇:Python学习之错误调试和测试

相关文章
相关标签/搜索