面向对象高级编程——Python学习笔记08

使用__slots__

一般,定义了class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
定义一个类:

class Student(object):
    pass

然后创建一个实例,再给实例绑定一个属性:

>>> s = Student()        #创建一个实例
>>> s.name ='Mojian'    #动态给实例绑定一个属性

再给实例绑定一个方法:

>>> def set_age(self):    #定义一个函数为实例的方法
    self.age = age

>>> from types import MethodType        #1
>>> s.set_age = MethodType(set_age, s)  #2    1,2两步是给实例绑定一个方法
>>> s.set_age(24)    #调用实例方法
>>> s.age    #测试结果
24

给一个实例绑定的方法,对另一个实例是不起作用的,可以直接给calss绑定方法,就能给所有实例绑定都绑定方法:

>>> Student.set_age = set_age

给类绑定方法后,所有实例都可以调用。
一般set_age方法可以直接定义在class中,但动态绑定允许在程序运行过程中动态给class加上功能。

如果要限制实例的属性,比如,只允许对Student实例添加name和age属性。
Python允许在定义class时,定义一个特殊的变量:slots,来限制该class实例能添加的属性:

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

绑定不在slots中的属性时,将会报错AttributeError:

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

slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
除非子类中也定义了slots,这样子类实例允许定义的属性就是自身的slots加上父类的slots.

slots只能限制添加属性,不能限制通过添加方法来添加属性:

def set_city(self, city):
    self.city = city

class Student(object):
    __slots__ = ('name', 'age', 'self.city')
    pass

Student.set_city = MethodType(set_city, Student)

a = Student()
a.set_city(Beijing)
a.city

使用@property

绑定属性时,直接把属性暴露,没办法检查参数,导致可以把成绩随便改:

s = Student()
s.score = 9990

为了限制score的范围,可通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样在set_score()方法里,就可以检查参数:

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!')    #raise语法触发异常,后面的代码也不会执行
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

现在就不能随意地设置score了:

>>> 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内置的@property装饰器就是负责把一个方法变成属性调用的:

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 1 ~ 100!')
        self._score = value

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

>>> s = Student()
>>> s.score = 60
>>> s.score
60
>>> s.score = 999
Traceback (most recent call last):
    ...
ValueError: score must between 1 ~ 100!

在对实例属性操作时,该属性很可能不是直接暴露的,而是通过getter和setter方法实现的。

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

calss 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和当前时间计算出来。

多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能。

Mixln
在设计类的继承关系时,通常,主线都是单一继承下来的。但是,如需“混入”额外的功能,通过多重继承就可以实现。这种设计称之为mix in。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂继承关系。
Python自带的许多库也使用了MixIn。如,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
如,编写一个多进程模式的TCP服务,定义如下:

class MyTCPServer(TCPServer, ForkingMixIn):
    pass

编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

要搞一个更先进的协程模型,可以编写一个CoroutineMixIn:

class MyTCPServer(TCPServer, CoroutineMixIn):
    pass

这样,无需复杂庞大的继承链,只要选择组合不同的类的功能,就能快速构造出所需的子类。


定制类

Python的class有许多特殊函数,可以定制类。

__str__

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):                            #定义__str__()方法,返回好看的字符
        return ‘Student object (name: %s) ’ % self.name
    __repr__ = __str__                            #要再定义一个__repr__(),才能直接输出s的属性,此外偷懒写法
>>> s = Student(‘Mojian’)                         #__repr__()返回开发看到的字符串,是为调式服务的
>>> s
Student object (name: Mojian) 

__iter__
如果一个类需要用于for…in 循环,那么需要实现iter,该方法返回一个迭代对象,然后不断调用这个对象的next()方法获取下一个值,直到获取至返回StopIterator退出循环。

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 > 10000:                        #中止循环的条件
            raise StopIterator()
        return self.a                    #返回下个值
for n in Fib():                #把Fib实例作用于for循环
    print(n)

__getitem__

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 isintance(n, slice):    #n是切片
            start = n.start
            stop = n.stop
            if start = None:        #判断以0开头的切片
                strat = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

与之对应的是setitem()方法,把对象视作list或dict来对集合赋值。最后,还有一个delitem()方法,用于删除某个元素。

__getattr__
写一个方法,动态返回一个属性:

class Student(object):
    def __init__(self):
        self.name = 'Mojian'
    def __getattr__(self, attr):#只在__getattr__中寻找不存在的属性
        if attr == 'score':
            return lambda: 99    #可以返回一个函数或值
        raise AttributeError('\' Student \' has no attribute \' %s \'' % attr)#传入其他属性时会返回None,所以需要定义报错信息

__call__
在Python中,一个对象有自己的方法和属性,我们通过 instance.method调用一个实例的方法,任何类,只要定义了call,就可以调用实例:

class Student(object):
    def __init__(self, *name): #此为可变参数
        self.name = name
    def __call__(self):
        print('My name is %s.' % self.name)

查看一个对象是否可调用,这个对象可以是实例或函数,用callable()函数:

>>>callable([1, 2, 3])
False
>>>callable(Student())
True

链式调用,生成Get /users/Mojian/imp/repos/two:

class Chain(object):
    def __init__(self, path = 'Get '):#定义第一个实例
        self._path = path

    def __getattr__(self, path):    #调用.user('Mojian')时,转到__getattr__调用,path指向user
        return Chain('%s/%s' % (self._path, path))  #在传给Chain中把self和path连起来,返回Get/user

    #调用Chain———>__init__,因Chain()将生成一个实例,这个实例后跟着('Mojian'),将调用__call__

    def __call__(self, path):   #使user('Mojian')调用自身实例,path指向Mojian
        return Chain('%s/%s' % (self, path))#传给Chain的过程中,又把原来的 'Get/user'和Mojian连起来了

    #继续走Chain(),把Get /user/Mojian给了_path,后面跟着的imp.repos继续调用__getattr__

    def __str__(self):
        return self._path   #返回_path中生成的字符串

    __repr__ = __str__

print(Chain().users('Mojian').imp.repos('two'))#外面是一个print,里面是Chain()的实例,所以会调用str,返回_path

使用枚举类

枚举类型可以看作是一种标签或一种系列常量的组合,通常表示某种特定的有限集合。

枚举的定义

>>> from enum import Enum         #1.首先,枚举要导入eunm的模块
>>> Month = Enum('Month’, ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug','Sep', 'Oct', 'Nov', 'Dec'))                           #2.1可以直接实现,再如:Animal = Enum('Animal', 'ant bee a Dog')
>>>class Color(Enum):                #2.2或者使用class关键字,继承枚举类
    red = 0                       #每个成员的数据类型就是其所属的枚举
    orgin = 2                     
    yellow = 3                    #定义成员名时,不能重复,否则报错

    red_alias = 0                 #默认情况,不同成员值允许相同
>>> print(Color(0))               #两个相同的成员,第二个成员的名称(red_alias)会被视作第一个成员的别称(red)
Color.red                         #在通过值获取枚举成员时,只能获取第一个成员

枚举的定义方式还有通过带值到名称映射的,有set,range,tuple等很多实现方式,重要的是理解实现方式的思想。

从Enum派生出自定义类,可以更精确地控制枚举类型:

from enum import Enum, unique

@unique
class Color(Enum):
    red = 0
    red_alias = 0                #再执行时会报错,@unique装饰器可以帮助保障没有重复值

枚举取值
1.

>>>print(Color['red'])
Color.red          #通过成员的名称来获取成员

2.

>>> print(Color(1))
Color.red           #通过成员值来获取成员

3.

>>> red_member = Color.red
>>> red_member.name
‘red’
>>> red_member.value
0                  #通过成员,来获取其名称和值,此时的value是定义时设定的 0

迭代器

>>> for n in Color:
    print(n)                #枚举支持迭代器,可以遍历枚举成员

Color.red
Color.origin
Color.yellow                #如果枚举有重复值时,遍历时只获取重复值成员的第一个成员
for color in Color.__members__.items():
    print(color)            #可以用枚举的一个特殊属性__members__遍历出包括值重复的成员

('red', <Color.red: 0>)
('origin', <Color.origin: 2>)
('yellow', <Color.yellow: 3>)
('red_alias', <Color.red: 0>)

...
>>> for name, member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)       #此时的value是自动赋给成员的int常量,默认从1开始计数

枚举比较
1.

>>> Color.red is Color.red_alias
Ture                        #枚举成员可以进行同一性比较

2.

>>> Color.red == Color.yellow
False                      #枚举成员可以进行等值比较

3.

>>> Color.red < Color.origin
TypeError: unorderable types: Color() < Color()    #枚举成员不能进行大小比较

元类

type()

type()函数既可以返回一个对象的类型,又可以创建出新的类型:
>>> def fn(self, name = 'world'):        #先定义一个函数
        print('Hello, %s.' % name)

>>>Hello = type('Hello', (object ,), dict(hello = fn))
#创建Hello classtype()函数依次传入3个参数:                                                      
#1.class的名称

#2.继承的父类集合(Python支持多重继承),tuple单元素需加',' ,可以为空

#3.class的方法名称与函数绑定,这里把函数fn绑到方法名hello上。

#4.一般第三步是type接受一个字典来为类定义属性
>>>Hello = type('Hello', (), {'key': value})

#5.也可以定义一个方法,将其作为属性 赋值:
>>>Hello = type('Hello', (), {'fn' = fn})

metaclass
先定义metaclass,就可以定义class,再创建实例

就元类本身而言,它们其实很简单:

1.拦截类的创建

2.修改类

3.返回修改之后的类

元类的主要途径是创建API, 通过metaclass修改类的定义,后续补充ORM框架的编写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值