第七章-面向对象高级编程
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中可以引入enum中Enum来定义枚举型
通过定义一个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]