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

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

由于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__
  • __iter__ && __next__
  • __getitem__
  • __getattr__
  • __call__

__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作为动态语言的另一大特点,可以动态的创建类和方法

  • __slots__: 可以用于限制实例动态添加属性的属性名
  • @property: 对于属性的getter, setter 进行简便的表示
  • 多重继承和 MixIn, 对类的功能进行扩展
  • __str__() | __repr__(): 用于自定义输出字符串
  • __getitem__(self, n) : 使类拥有索引,slice, (list数据类型的一些特性)
  • __getattr__(): 当实例上不存在该属性时,实例会从这个方法中搜索该属性
  • __call__(): 使实例能自己调用,这种形式 instance()
  • 枚举类的创建, 主要引入 from enum import Enum, unique 等模块, 使用 @unique 装饰器来确保没有重复值, 枚举类中的内部属性 __members__
  • 元类编程,利用 type 的动态创建类的能力, 内部属性 __new__()

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

2017年3月9日 19:43:20

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • 1. 使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实...
    时间之友阅读 292评论 0 1
  • 使用 __slots__ 限制实例属性, 比如,只允许对Student实例添加name和age属性。 然后,我们试...
    时间之友阅读 402评论 0 0
  • 1.元类 1.1.1类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这...
    TENG书阅读 1,255评论 0 3
  • 哥,知道你还没睡。我看到你的心情,知道你依旧很难过,我在想怎样可以帮到你。最近我学过一些心理的课程,人的思考模式影...
    冬夜旅人阅读 244评论 0 0
  • 行者无疆 郭相麟 人世间 路途遥远 在翻山越岭间 想横空出世走捷径 折腾 不停的折腾 弄得人浑身冒汗 ...
    郭相麟阅读 204评论 1 2