python面向对象编程(5)

453 阅读8分钟

python类重载运算符补充

1.成员操作符:__contains__

判断member是否存在可迭代的对象

# membership.py# 定义的对象必须是可迭代的class Iters:"""
   将所有可迭代的方法列出来 
"""
    def __init__(self, value):
        self.data = value            def __getitem__(self, i):    # 迭代、索引、分片操作的时候会被调用
        print('get[%s]:' % i, end='')                return self.data[i]            def __iter__(self):          # 进行迭代的时候会被调用
        print('iter=> ', end='')
        self.ix = 0
        return self            def __next__(self):
        print('next:', end='')                if self.ix == len(self.data):raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item            def __contains__(self, x):      # 成员变量的判断,使用in
        print('contains: ', end='')                return x in self.data

    next = __next__   def test_iter():
    X = Iters([1,2,3,4])
    print(3 in X)   # contains: True
    for i in X:
        print(i, end=' | ')if __name__ == '__main__':
    test_iter()>>> python membership.py

2.属性管理

属性访问__getattr__

  • obj.attr_name的形式访问对象属性

  • 自定义类管理属性信息,对于访问不存在的属性可以返回错误信息提示

# attr.pyclass AttrObject:
    def __getattr__(self, attr_name):
        print("Attr Object get attr method ....")                if attr_name == 'age':                        return 40
        else:                        raise AttributeError("could not access the attr[%s]" % attr_name)     # for testdef test_empty():
    e = AttrObject()
    print(e.age)        # 调用__getattr__方法,访问age属性,匹配并返回40
    print(e.hobby)      # AttributeError: could not access the attr[hobby]if __name__ == '__main__':    test_empty()    >>> python attr.py      # 2.x & 3.x表示结果一样,以下都会以此标记

属性设置__setattr__

  • 定义类对象管理属性并将属性名以及值存储在一个实例对象中

  • self.attrname 以及 instance.attrname 将会调用类的内置方法__setattr__方法

# attr.pyclass AcessControl:
    def __init__(self):
        self.hobby = "basketball"               # 会调用下面的__setattr__方法

    def __setattr__(self, key, value):
        # self.name = "xxxx"                    # 不能在__setattr__上使用self.attr,会导致递归应用循环
        print("access control set attr ...")                if key == 'age':
            self.__dict__[key] = value + 10     # 通过内建字典来保存属性数据
        else:
            self.__dict__[key] = value            def __delattr__(self, item):
        print("del item[%s]" % item)            def __getattr__(self, item):
        print("get item[%s]" % item)    def test_access_control():
    ac = AcessControl()
    ac.age = 10         # 调用__setattr__
    print(ac.age)       # 直接输入值,没有调用__getattr__
    print(ac.hobby)     # 当属性有值时,也就是非None是不会调用__getattr__方法的,如果没有值,即None就会调用__getattr__方法
    del ac.age          # 调用__delattr__
    print(ac.name)      # 调用__getattr__,调用未定义的属性时候就会回调这个函数并且返回Noneif __name__ == '__main__':    test_access_control()>>> python attr.py      # 2.x & 3.x

属性设置与访问__getattribute__

  • 这个方法将会拦截所有获取的属性的操作,包括未定义的属性

  • 属性内置函数允许我们将方法与特定类属性的获取和集合操作相关联

  • 属性描述符为特定的类的属性提供了一组__get____set__方法访问协议

  • 实例属性在类中声明但是在每一个类的对象实例中隐式存储

class AttrManager:
    def __getattribute__(self, item):
        """
        :param item:
        :return:
        避免产生递归循环引用,不要使用self.attr_name对属性进行访问或者设置,属性访问会经过这个方法
        这里没有保存数据,因此返回的时候是None
        """
        print("call  __getattribute__ for item[%s] " % item)def test_attr_manager():
    manager = AttrManager()
    manager.name = "attr manager name"
    print(manager.name)
    print(manager.age)if __name__ == '__main__':
    test_attr_manager()>>> python attr.py  # 2.x与3.x的不同输出

小结

  • __setattr____getattr__能够在python不同的版本下对未定义的属性进行访问和赋值

  • __getattribute__只能在3.x下对未定义的属性名进行访问

  • 为了更好的兼容2.x和3.x,应当重载两个方法,即__getattr__以及 __getattribute__

  • python3.x中的执行的优先级,__getattribute__ > __getattr__

  • python2.x中的执行的优先级,__getattr__ > __getattribute__

  • 如果类中没有定义__getattribute__或者 __getattr__,则在访问未定义的属性时候会报没有属性的错误

3.字符串打印:__str__, __repr__

  • __str__主要应用于print函数以及字符串函数str的转换操作,

  • __repr__应用于所有输出操作,如果有print以及str操作并定义 __str__,则会以__str__为准

  • __repr____str__均未定义的时候,默认打印的是输出对象地址信息

# str.pyclass DisplayClass:
    """
    __repr__ is used everywhere, except by print and str when a __str__ is defined.
    __str__ to support print and str exclusively
    """
    def __repr__(self):
        return "display __repr__ class"

    def __str__(self):
        return "display __str__ class"# 使用命令行的形式打印输出  2.x & 3.x 输出效果一致,以2.x作为截图>>> d = DisplayClass()>>> d                   # 调用repr>>> print(d)            # 调用str>>> print(repr(d))      # 调用repr>>> print(str(d))       # 调用str

4.运算操作符:__add__, __radd__,__iadd__

变量+常量,如X + 1024,调用__add__方法

# addition.pyclass Counter:
    def __init__(self, value = 0):
        self.data = value            def __add__(self, other):
        print("counter data[%s] add [%d]..." % (self.data,other))                return self.data + otherdef test_add():
    r = Counter()
    q = r + 1024       # call add, instance +
    print("q is %d" % q)

test_add()>>> python addition.py

常量+变量,如1024 + X,调用__radd__方法

# addition.pyclass Counter:
    ...     # 省略上述代码
    ...        def __radd__(self, other):
        print("counter data[%s] radd [%d]..." % (self.data,other))                return self.data + other    def test_radd():
     p = 1024 + r       # call radd, + instance
    print("the p is %d" % p)

test_radd()>>> python addition.py

变量+变量,如X + Y

# addition.py# 执行X+Y的时候,会先执行__add__方法,再执行__radd__方法,此时代码需要稍微调整下class Counter:
    def __radd__(self, other):
        if isinstance(other,Counter):
            print("counter data[%s] radd [%d]..." % (self.data,other.data))                elif isinstance(other,int):
            print("counter data[%s] radd [%d]..." % (self.data, other))                return self.data + other            def __add__(self, other):            if isinstance(other,Counter):
            print("counter data[%s] add [%d]..." % (self.data,other.data))                elif isinstance(other,int):
            print("counter data[%s] add [%d]..." % (self.data, other))                return self.data + other    def test_addition():
    X = Counter(2)
    Y = Counter(4)
    sum = X + Y     # X + Y ==> 2 + Y ==> 4 + 2
    print(sum)

test_addition()>>> python addition.py

5.回调:__call__

函数回调设计

# others.py# 使用函数进行回调def callback_fn(color):
    def oncall():
        print("select the color[%s]" % color)        return oncalldef test_callback_fn():
    fn = callback_fn("green")
    fn()

test_callback_fn()>>> python others.py

匿名函数回调

# others.pydef test_lamaba_callback():
    cs4 = (lambda color='red': print('turn ' + color))
    cs4()

test_lamaba_callback()>>> python others.py

类回调设计

# others.pyclass CallBack:
    def __init__(self,color):
        self.color = color            def __call__(self):
        print("call callback color[%s]" % self.color) class EventCall:
    def __init__(self,callback = None):
        self.c = callback            def press(self):
        self.c()def test_call_back():
    c1 = CallBack("red")
    c2 = CallBack("green")
    e1 = EventCall(callback = c1)
    e2 = EventCall(callback = c2)

    e1.press()      # when press then trigger callback
    e2.press()

test_call_back()>>> python others.py

6.比较运算符

  • py3.x不存在__cmp__以及cmp方法,也就是py3.x不能再使用 __cmp__以及cmp方法,py2.x仍然保留

  • 为兼容性,统一在py2.x与py3.x使用__lt____gt__方法

# others.pyclass Comp:
    """
    方式一
    """
    data = 'spam'

    def __gt__(self, other): return self.data > other            def __lt__(self, other): return self.data < other def test_comp():
    c = C()
    print(c > 'ham')
    print(c < 'ham')

test_comp()>>> python others.py

py2.x可用而py3.x不可用情况

# others.pyclass S:
    data = "spam"
    """
    python3.x将不可用
    """
    def __cmp__(self, other):
        return cmp(self.data,other)class D:
    """
    方式三,改变cmp的方法,3.x仍然报错
    """
    data = "d"

    def __cmp__(self, other):
        return (self.data > other) - (self.data < other)def test_S():
    s = S()
    print(s > 'ham')def test_d():
    d = D()
    print(d > 'ham')
    print(d < 'ham')

test_S()
test_d()>>> python others.py

7.布尔测试:__bool__, __len__,__nozero__

  • py3.x:如果没有__bool__就会执行 __len__,也就是两个都存在的时候,__bool__的优先级比 __len__

  • py2.x如果没有__bool__就会执行 __len__,也就是两个都存在的时候,__len__的优先级比 __bool__

# others.pyclass Truth:
    def __bool__(self):
        print("call bool method ...")                return True

    def __len__(self):
        print("call len method ....")                return 0def test_truth():
    X = Truth()        if X:   
        print("i'm truth ....")

test_truth()>>> python others.py

__nonzero__:仅在python2.x使用,用于返回一个布尔值,并且优先级大于上面的 __len__方法

# others.pyclass Truth:
    # 定义了__bool__方法以及__len__方法
    ....        def __nonzero__(self):
        print "call zero for python2.x"
        return False    ... # 同样的测试代码>>> python others.py

8.析构函数:__del__

__del__使用笔记

  • Need:即是否必需,python会自动回收内存空间,析构器并非是空间管理的一项必需工具

  • Predictability:即是否可预测,python退出解释器的时候,并不能保证会调用仍然存在的对象的析构函数来进行回收操作

  • Exceptions:由于存在异常情况并且不知道异常发生的时刻,析构器很难确定什么时候调用来进行回收操作

  • Cycles:对象的循环引用可以防止垃圾回收器发生一些如我们所期望的错误

代码测试

# others.pyclass Life:
    def __init__(self, name='unknown'):
        print('Hello ' + name)
        self.name = name            def live(self):
        print(self.name)            def __del__(self):
        print('Goodbye ' + self.name) def test_del():
    l = Life("keithl")
    l.live()if __name__ == '__main__':
    test_del()>>> python others.py        # 2.x & 3.x