PythonPython

#8 面向对象高级编程,python内部属性方法

2017-03-09  本文已影响40人  JamesSawyer

一.实例绑定方法和给类绑定方法

由于python动态语言的特性,在创建类之后,可以给实例或类再绑定方法。给实例绑定方法,只对该实例有用,其它实例则没有该方法;给对象绑定方法,则所有实例都会存在该方法

使用 MethodType 给实例绑定方法

class Student(object):
  pass

s1 = Student()

# 定义一个实例方法
def set_age(self, age):
  self.age = age

# 从 'types' 模块中 引入MethodType方法
from types import MethodType
s1.set_age = MethodType(set_age, s1) # 给实例绑定方法
s.set_age(25)
s.age
25

# 其它实例需要再绑定一次才会有set_age方法
>>> s2 = Student()
>>> s2.set_age(25)
AttributeError: 'Student' object has no attribute 'set_age'

给所有实例都绑定方法,则直接给类绑定方法:

>>> Student.set_age = set_age
>>> s1.set_age(20)
20
>>> s2.set_age(20)
20

__slots__ 限制实例属性

为了限制实例属性,我们可以使用 __slots__ 变量:

比如只允许实例添加 'name', 'age' 属性

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

>>> s = Student()
>>> s.name = 'james'   # OK
>>> s.age = 27 # OK
>>> s.gender = 'M'  # AttributeError: 'Student' object has no attribute 'gender'

值得注意的是,__slots__ 只对当前类实例起限制作用,对其子类不起作用

二.@property

这个就是装饰器(decorator),主要用于对类中属性 getter, setter 访问器属性进行设置

如果我们要对一个属性进行一定控制,可以设置其get,set方法,比如对 'age' 属性进行一定的限制:

class Student(object):
  def get_age(self):
    return self._age
  def set_age(self, value):
    if not isinstance(value, int):
      raise ValueError('age must be an integer')
    if value < 0 or value > 120:
      raise ValueError('age must between 0 - 120')
    self._age = value

# 使用
>>> s = Student()
>>> s.set_age(60) # OK
>>> s.get_age()
60

>>> s.set_age(9999)
age must between 0 - 120

但是通过 set_age(), get_age() 方法实在是不够简洁,于是python中 @property 属性就可以很好的解决这个问题,我们可以直接的通过 's.age = 100', 's.age' 这样的方式来进行设置和读取

上面的例子可以写为:

class Student(object):
  @property
  def age(self):  # 定义getter
    return self._age
  @age.setter     # 定义setter, 如果不定义,则属性为只读
  def age(self, age):
    if not isinstance(value, int):
      raise ValueError('age must be an integer')
    if value < 0 or value > 120:
      raise ValueError('age must between 0 - 120')
    self._age = value

>>> s = Student()
>>> s.age = 190 # ERROR
>>> s.age = 62 #OK
>>> s.age
62

上面的例子可以看出,@property 只是python的一种简便写法,另外设置只读,只需要将setter省略即可:

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

  # age为只读属性
  @property
  def age(self):
    return 2017 - self._birth

三.多重继承 和 MixIn

这个特性还是很方便的,在C#中,只支持单一继承,想要继承某些功能,需要通过接口的方式,而python直接可以通过多重继承来完成。

当然python在设计继承关系时,主线都是单一继承下来的,额外的功能继承使用类似接口的方式,只不过python中称之为 MixIn

比如狗属于动物(Aniaml),狗还能跑(Runable),则可以让狗继承这2个大类,主线为Animal,MixIn是Runable(命名规范,一般命名为RunableMixIn)

class Animal(object):
  pass

class RunnableMixIn(object):
  def run(self):
    print('I can run')

class Dog(Animal, RunnableMixIn):
  pass

MixIn的目的就是给一个类添加多个功能, 这样,在设计类的时候,我们应优先考虑通过多重继承来组合多个MixIn功能,而不是层次结构复杂的继承关系

python 内置的MixIn

Python自带的 TCPServerUDPServer 两类服务网络,而要同时服务多个用户就必须使用多线程或多进程模型,这2种模型由 ThreadingMixInForkingMixIn 提供,通过组合可以创建适合的服务

例如,编写 多进程模式的TCP服务

class MyTCPServer(TCPServer, ForkingMixIn):
  pass

编写 多线程模式的UDP服务

class MyUDPServer(UDPServer, ThreadingMixIn):
  pass

编写 协程模型

class MyTCPServer(TCPServer, CoroutineMixIn):
  pass

四.python内部属性方法

python以 __xxx__ 的形式表示特殊变量,很多内部属性方法名称都是以这种形式命名的,下面介绍几种python内部的函数,对类的行为进行拦截修改:

__str__() && __repr__()

如果打印一个实例的类型,会得到下面结果:

class Student(object):
  def __init__(self, name):
    self.name = name

>>> print(Student('James'))
<__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('James'))
Student object (name: Michael)

但是如果不调用print函数,打印的依旧不好看:

>>> s = Student('James')
>>> s
<__main__.Student object at 0x109afb31>

这是因为直接显示变量调用的是repr(),这个函数是程序开发者看到的字符串, 所以还需要将这个函数改写,可以直接将上面改写的 __str__() 函数赋值给这个函数即可

class Student(object):
  def __init__(self, name):
    self.name = name
  def __str__(self):
    return 'Student object (name, %s)' % self.name
  __repr__ = __str__  

>>> s = Student('James')
>>> s  
Student object (name: Michael)

__iter__() && __next__()

如果想使一个类能够使用 for...in 循环,可以在类中添加 __iter__() 方法,这个方法返回一个可迭代的对象,此处为实例本身,然后python的for循环会不停的调用该迭代对象的 __next__() 方法, 直到遇到 StopIteration 异常退出循环

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 > 1000: # 退出循环的条件
      raise StopIteration()
    return self.a # 返回下一个值

# 调用
>>> for n in Fib():
...   print(n)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987

__getitem__()

这个方法是类拥有 索引 的能力

class Fib(object):
  def __getitem__(self, n):
    a, b = 1, 1
    for x in range(n):
      a, b = b, a + b
    return a

>>> f = Fib()
>>> f[0]    
1
>>> f[2]    
1
>>> f[2]
2
>>> f[3]
3

为了list中实现slice功能,可以对 __getitem__(self, n) 中的 n 进行判断,有可能是 int 类型,也可能是 slice 类型

class Fib(object):
  def __getitem__(self, n):
    if isinstance(n, int):
      a, b = 1, 1
      for x in range(n):
        a, b = b, a + b
      return a
    if isinstance(n, slice): # n是切片
      start = n.start # slice的属性 start, 不能换成别的名字
      stop = n.stop # slice的属性 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

>>> f = Fib()
>>> f[:5]
[1, 1, 2, 3, 5]

这只是模拟了部分slice的功能,比如step,负数还没有进行处理

其它的一些内部函数有 __setitem__() __delitem__() 等,这些函数可以对对象属性进行拦截,模拟list, tuple, dict等数据类型的功能

__getattr__()

当python编译器在调用不存在的属性时,会尝试调用 __getattr__(self, attr) 这个函数来获取该属性,该方法默认返回None

class Student(object):
  def __init__(self):
    self.name = 'James'
  def __getattr__(self, attr):
    if attr == 'age':
      return lambda: 25  # 返回一个函数
    raise AttributeError('对象没有该属性')

>>> s = Student()    
>>> s.name
'James'
>>> f = s.age # 返回一个函数
>>> f()
25
# 如果访问未定义的属性, 会直接报错
>>> print(s.gender)
对象没有该属性

可以根据这个特性来写出一个链式调用的类:

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

  # 默认path为attr,范围Chain调用自身构造器
  def __getattr__(self, path):
    return Chain('%s/%s' % (self._path, path))

  def __str__(self):
    return self._path

  __repr__ = __str__

>>> s = Chain().status.user
'/status/user'

这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变

__call__()

实例自己调用,一般的实例方法为 instance.method() 这种调用形式,使用 __call__(), 我们可以实现 instance() 这种形式的调用

class Student(object):
  def __init__(self, name):
    self.name = name
  def __call__(self):
    print('my name is %s' % self.name)

>>> s = Student('James')
>>> s()
my name is James

五.枚举类

在js中我们想使用枚举,一般通过对象的形式进行模拟,比如:

var Weekday = {
  'Sun': 0,
  'Mon': 1
  // ...
}

在python中可以通过 Enum() 构造器, 或者 类继承Enum 的方式来实现枚举类

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

>>> Month.Jan
Month.Jan
# 通过 value 属性获取对应int值, 从1开始
>>> Month.Jan.value
1

# 通过 '__members__' 来访问枚举类中的成员
for name, member in Month.__members__.items():
  print(name, '=>', member, ',', member.value)

使用类继承的方式:

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

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

其中 @unique 装饰器可以帮助我们检查保证没有重复值。

六.元类编程

python中的 type() 函数有2个功能

  1. 判断对象类型
  2. 创建类

判断对象类型

>>> type(123) is int
True

创建类:使用class创建类,实质上是通过type()来创建的, 语法:

type(ClassName:string, ClassesToExtend:tuple, attrs:dict)

比如创建一个 'Hello' 类:

def fn(self, name='world'):
  print('Hello, %s' % name)

Hello = type('Hello', (object,), dict(hell0=fn))  # 注意tuple只有1个元素时,别忘记了逗号
# 将函数fn绑定到方法名hello上

>>> h = Hello()
>>> h.hello()
Hello World

metaclass

type() 除了动态的创建类之外,还可以作为 类的类 用作继承

创建类的实质过程是: 先定义metaclass(元类), 然后创建类, 再创建实例, 也就是说metaclass 可以创建类

按照命名习惯metaclass通常以 'Metaclass' 结尾,一般在调用 inti() 之前,先调用 __new__() 方法。

__new__() 的签名为:

比如下面一个元类:

# metaclass为类的模版, 所以必须从 'type' 类型派生
class ListMetaclass(type):
  def __new__(cls, name, bases, attrs):
    attrs['add'] = lambda self, value: self.append(value)
    return type.__new__(cls, name, bases, attrs)

派生类使用metaclass 关键词参数

class MyList(list, metaclass=ListMetaclass):
  pass

>>> ml = MyList()
>>> ml.add(1)
>>> ml
[1]

元类编程主要用于需要动态生成类的情况下,元类编程较为复杂,待进一步学习

总结

本章主要介绍面向对象高级编程,主要是python的一些内部属性和方法的使用,这和js中的proxy,reflection很想,元类编程则是python作为动态语言的另一大特点,可以动态的创建类和方法

总的来说,本章内容比较难,需要日后更多的实际操作了解,待了解

2017年3月9日 19:43:20

上一篇下一篇

猜你喜欢

热点阅读