第七章-面向对象高级编程

1 使用__slots__

1.1 绑定属性和方法

  1) 来给实例绑定属性

    在没有做任何限制的时候, 可以通过 实例.属性名 = 属性值  

class Student(object):
    pass

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

  2) 给某个实例绑定方法

    需要使用types下的MethodType来给实例绑定方法

    实例.绑定成的方法名 = MethonType(现有的函数, 实例名)

    注意此绑定的方法运行的是现有的那个函数, 是无法调用私有变量

    但是由于给实例绑定的方法, 别的实例无法使用

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

  3) 给类绑定方法

    通过上面的方法绑定的方法只能该实例使用, 要使得所有实例都可以使用需要给 绑定方法

    类名.方法名 = 现有的函数名

    具体绑定方法如下:

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

1.2 限定绑定属性

  由于Python可以随意的增加属性, 因而为了针对这种情况, 可以在定义函数的时候定义__slots__来限制类里面只有哪一系属性, 这样绑定别的属性就会报错.

  用元组来包裹可以定义的属性

  但是__slots__只是限定了当前类, 不会对子类产生影响

class Student(object):
    __slots__ = ('name', 'age') 

2 使用@property

  @property是一个装饰器, 是用来设置 对象属性的获取,设置方式

  针对常用的getter和setter函数, 有

class Student(object):
    def get_score(self):
         return self._score
    def set_score(self, value):
        self._score = value

  在使用的时候是:

whc = Student()
whc.set_score(100)
print(whc.get_score())

  然而更加希望设置和获取 直接能像绑定的时候的使用的话, 就需要@property了

  其中函数的名字最好改成变量名, @property表示getter, @函数名.setter就表示对应的setter了, 且该setter的函数名也要保持一致

class Student(object):
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, value):
        self._score = value

  经过这样的处理之后

>>> whc = Student()
>>> whc.score = 60 
# 等同于whc.set_score(60) >>> whc.score #等同于whc.get_score() 60

3 多重继承

  Python支持多重继承

  如现在有 哺乳动物类Mammal, 它有子类Dog类, 但是由于Dog还能跑, 所以可以定义Runnable类, 并放入Dog的定义中. Dog继承自多个类, 也就是多重继承

class Mammal(object):
    pass

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

class Dog(Mammal, Runnable):
    pass

  像Dog是一直从Animal和Mammal一直逐步继承下来的, 但是需要新增Runnable这些的新功能, 此时用多重继承来加入Runnable的方式叫做Minin("混入")

  通常为了标识, 将Runable后面加上Mixln来体现和区分, 也就是RunnableMixin

class Dog(Mammal, RunnableMixIn):
    pass

  这样的规范就可以更加清楚的分辨继承的关系

4 定制类

4.1 __str__和__repr__

  正常打印对象的显示

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

  但是__str__修改的是print()对象的时候的显示, 直接打印对象的时候显示相关的是__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__

4.2 __iter__和__next__

  如果想要使能够在for..in中循环, 形成类似迭代器的功能, 就需要实现__iter__定义返回一个迭代对象, 定义__next__方法来返回每次调用返回的值

  此处的__iter__()和__next__()就是相当于iter()和next()的功能

  iter返回本身就行, 有__iter__()才能具备迭代器的功能

  具体实现输出值的常在__next__()中定义

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
...
46368
75025

4.3 __getitem__

  既然可以让类实现遍历, 自然需要把类实现成list的成员访问, 切片操作等. 

  可以使用__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 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

  同时还有__setitem__, __delitem__两个方法用于设置值和删除值

4.4 __getattr__

  使用__getattr__可以用来防止用户调用不存在的变量的时候返回错误

  调用__getattr__()方法是在实例参数中没有找到的情况下

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

  但是此时如果获取的不是score则会返回None, 这样不好, 因而需要在别的情况下抛出一个异常

class Student(object):

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

  getattr的作用扩展RESTAPI

class Chain(object):

    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__

  这样就可以在不修改Chain的情况下根据输入来设定path

>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

4.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() # self参数不要传入
My name is Michael.

  判断一个对象是否可以被调用可以使用 callable() 函数

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

5 定义枚举类

  Python3中可以引入enumEnum来定义枚举型

  通过定义一个class类型, 每个常量就是class的唯一实例

from enum import Enum

Month = Enum('Month', ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', '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
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12

 

    Enum()需要传入两个参数, 一个是枚举类型的名字, 另一个是一个元组, 其中的内容是枚举类型中的值, 最终返回的给一个变量

    可以通过该变量.__members__来访问枚举类型的具体数据

    该变量.__members__ 是一个可迭代类型, 类似于字典形式, key是元组中的结果, value是第一个参数.元组中的元素, value.value是索引号(默认是1开始)

 

    由此可以通过 Month名字 来调用各个枚举类型的值

  另一种更加精确的定义方式是定义一个类继承自Enum, 并且使用装饰器unique来去除掉重复的值

    具体的内容是 枚举标识=值 这样的形式定义

    引用的时候直接使用 类名.枚举标识

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

  访问枚举类型的方法:

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

6 使用元类

6.1 type()

  Python这样的动态语言, 函数和类的定义, 不是在编译的时候定义的, 而是运行时动态的创建的

  使用type()可以查看类型, 其中type()里面查询的是类, 则返回的是 class type

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

  实际上, 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'>

  创建一个class对象, 需要传入3个对象:

    1) class的名称

    2) 继承的父类集合, 是tuple类型的

    3) class的方法与函数绑定

  此时使用type()创建的类与class创建的类本质是一样的, class定义时, 仅仅是扫描了一下class定义的语法, 然后调用type()创建出class

6.2 metaclass

  metaclass译作元类, 是可以创建类的类

  具体流程是:

    先定义metaclass, 再创建类, 最后创建实例

  按照默认习惯, metaclass的类名总是以Metaclass结尾

6.2.1 通过元类给list添加add功能

  元类的使用方法是:

    元类是类的模板, 需要继承自type, 且命名的时候以Metaclass结尾

    使用元类作为模板的类其实就是继承自该元类, 写法是在继承列表里加入默认参数: metaclass=元类名字

    使用元类模板的类在创建对象的时候回先调用 元类中的__new__()方法

    因而元类需要定义__new__()方法, __new__()方法接收到的参数依次是:

      1) 准备创建的类的对象

      2) 类的名字

      3) 父类的集合(元组形式)

      4) 方法的集合(字典形式)

    该元类返回的值需要用 type.__new__()来返回, 具体形式和利用type()函数生成对象类似, 只是多了第一个参数cls用于传递准备创建的对象

  完成对对list绑定一个add方法完成append()函数功能的代码如下

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

L = MyList()
print(L)
L.add(1)
print(L)

6.2.2 通过元类创建ORM框架

  具体代码如下

# Field负责保存数据库表的字段名和字段类型
class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

# 定义str类型的Field
class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

# 定义int类型的Field
class IntegerField(Field):
        def __init__(self, name):
            super(IntegerField, self).__init__(name, 'bigint')

# 编写元类
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
        attrs['__table__'] = name  # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

# 编写基类Model
class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

# 定义User类使用上述
class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

# 结果如下
# Found model: User
# Found mapping: email ==> <StringField:email>
# Found mapping: password ==> <StringField:password>
# Found mapping: id ==> <IntegerField:uid>
# Found mapping: name ==> <StringField:username>
# SQL: insert into User (password,email,username,id) values (?,?,?,?)
# ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345]

  

posted @ 2017-03-30 22:09  weihuchao  阅读(347)  评论(0编辑  收藏  举报