python—面向对象高级编程

  • 补齐python3内容.

  • 资料来源:

    https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

  • 更新

    1
    2019.01.17  初始化

面向对象高级编程

使用__slots__

  • 动态语言可以随时修改和添加类与对象的属性和方法.python自然也不例外.

  • 过于自由的修改,对类和对象本身不利,python中使用 __slots__ 来约束这种行为.

    1
    2
    class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
  • 限制类中只能再增加 name 和 age属性.仅对 Student 类生效,对子类无效.

使用@property

  • 装饰器,不仅在函数中其作用,还可以在类中使用,限定属性的读写和范围.

  • 类属性的读写,都是通过get/set方式进行,太过繁琐.Python内置的 @property 装饰器,将一个方法变为属性调用.

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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

最终的赋值的终点是 self._score .操作时候直接访问 xxx.score 即可.

  • 限定只读,只定义getter,不定义setter.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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 只读.

多重继承

  • 没什么多说的.

    1
    2
    class Bat(Mammal, Flyable):
    pass

    多重继承的设计,相对java的单继承,自由度更大,但能不能用好,取决于写代码的人.

定制类

  • 类中形如 __xxx__ 的变量或者函数名

str && repr

  • __str____repr__ 是类/对象的打印输出. str 是调用print 函数的输出, repr 是 直接输出,常用于调试.

  • 示例:

    1
    2
    3
    4
    5
    6
    class Student(object):
    def __init__(self, name):
    self.name = name
    def __str__(self):
    return 'Student object (name=%s)' % self.name
    __repr__ = __str__

iter && next

  • 用于 for 循环,__iter__ 用于返回一个可迭代对象(通常是自身). 同时 for 循环时,不断调用 __next__ ,获取下一个结果,直到遇到 StopIteration 错误.

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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 # 返回下一个值

getitem

  • 通过 __getitem__ 可以将一个对象,包装成一个list dict tuple.

  • 示例: list (包含不完整切片)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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

    对于切片的负数 和 [:5] 没有处理.

  • dict时,相对应的还有__setitem__() __delitem__().

  • 主要是python是非强类型的语言,不必非要继承并实现接口.

getattr

  • 当试图调用不存在的属性和方法时,报错.此时需要找 __getattr__.

  • __getattr__ 中直接返回属性和函数均可.当未匹配时返回 None.也可以自定义返回的错误类型.

  • 示例

    1
    2
    3
    4
    5
    6
    class Student(object):

    def __getattr__(self, attr):
    if attr=='age':
    return lambda: 25
    raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
  • 可以把一个类的所有属性和方法调用全部动态化处理(理解暂时不太清除,仅记录)

  • 现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:

    http://api.server/user/friends
    http://api.server/user/timeline/list

  • 如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。

  • 利用完全动态的__getattr__,我们可以写出一个链式调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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__
    1
    2
    >>> Chain().status.user.timeline.list
    '/status/user/timeline/list'
  • 无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!

call

  • python中函数与对象的区别十分模糊,所谓函数实际上可以看作是实现了 __call__ 的一类对象,反过来一个对象通过添加(动态/静态) __call__ 也可以当作函数调用.

  • 判断一个对象是否可当作函数调用,使用callable函数判断,可调用,返回True.

  • 示例:

    1
    2
    3
    4
    5
    6
    class Student(object):
    def __init__(self, name):
    self.name = name

    def __call__(self):
    print('My name is %s.' % self.name)
    1
    2
    3
    >>> s = Student('Michael')
    >>> s() # self参数不要传入
    My name is Michael.

枚举类

  • 枚举用于限定变量的值域,防止错误的赋值导致的严重后果.Python提供了Enum类.

  • 简单定义

    1
    2
    3
    from enum import Enum

    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
  • 枚举类定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

元类

  • python作为动态语言,类和函数不是在编译时定义,而是运行时动态创建,除了编写代码创建类/函数外,python亦可以通过 type/metaclass 动态创建类/函数.

  • 实际上,即使在python 中直接定义的类/函数 也是在运行时通过 type() 载入的,因此也可以直接通过 type() 直接创建.

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> 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'>
  • metaclass 元类 ,简单来说,对象来源于类,而python中的 类 可以来源于 metaclass 元类. 通过 metaclass 可以非常简单的实现 对类的生成和修改.

  • 示例:习惯上 metaclass 的类名总是以 Metaclass 结尾

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # metaclass是类的模板,所以必须从`type`类型派生:
    class ListMetaclass(type):
    # 创建类的对象,类的名称,父类 , 方法集合
    def __new__(cls, name, bases, attrs):
    #方法添加 add 方法
    attrs['add'] = lambda self, value: self.append(value)
    return type.__new__(cls, name, bases, attrs)

    class MyList(list, metaclass=ListMetaclass):
    pass

    创建 MyList 的时候,调用了 ListMetaclass.__new__() , 在创建时添加了 add 方法.

  • 示例2:ORM数据库(简)
    一个ORM数据库,需要动态的根据字段,生成类,与java中不同,python 非常简单.

    • Field 基类,保存数据库的字段名和字段类型

      1
      2
      3
      4
      5
      6
      7
      8
      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)
    • 各个类型的Field

      1
      2
      3
      4
      5
      6
      7
      8
      9
      class StringField(Field):

      def __init__(self, name):
      super(StringField, self).__init__(name, 'varchar(100)')

      class IntegerField(Field):

      def __init__(self, name):
      super(IntegerField, self).__init__(name, 'bigint')
    • ModelMetaclass

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class ModelMetaclass(type):

      def __new__(cls, name, bases, attrs):
      if name=='Model':#如果是 Model 基类,不修改属性等.
      return type.__new__(cls, name, bases, attrs)
      print('Found model: %s' % name)
      mappings = dict()
      for k, v in attrs.items():#保存 子类所有属性到mappings
      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

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      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))
    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class User(Model):
      # 定义类的属性到列的映射:
      id = IntegerField('id')
      name = StringField('username')
      email = StringField('email')
      password = StringField('password')

      # 创建一个实例:
      u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
      # 保存到数据库:
      u.save()
      • 创建 User 时,查找 metaclass 未定义,继续查找父类 Model,执行父类的 metaclass .(子类会隐性继承父类的 metaclass ).
      • 进入ListMetaclass.__new__() 遍历 User 的属性,找出Field 类型的,写入 mappings .删除这些属性(防止干扰).,将SQL的表名 User 写入table字段.
      • .save调用时,遍历 mappings ,组合table 成相应SQL语句.(这里仅print)