您的位置:首页 > 编程语言

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

2017-04-28 18:17 302 查看
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]


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: