Python 魔术方法

Python解释器碰到特殊句法时,会使用魔术方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾

  - 举例:obj[key]背后就是__getitem__方法

  - 没有实现__getitem__方法,无法使用[]获取类中的dict

1 class A:
2     adict = dict(one=1,two=2)
3 
4 a = A()
5 print(a['one'])
6 print(a['two'])

输出:
TypeError: 'A' object is not subscriptable

  - 实现__getitem__方法后,可以使用[]

class A:
    adict = dict(one=1,two=2)
    def __getitem__(self, item):
        return A.adict[item]
a = A()
print(a['one'])
print(a['two'])


输出:

1
2

 

  - 一个纸牌类的例子,用来说明__len__和__getitem__的作用

    - 实现了这两个方法,可以用于获得长度,用[]访问数据,甚至切片和循环

import collections

'''
collections.namedtuple:用于构建一个只有属性没有方法的简单类
参数:
    1、类名 
    2、一个字符串列表,表示各个属性
'''
Card = collections.namedtuple('Card', ['rank', 'suit'])

class Deck():

    # +号可以用于列表的连接
    ranks = [str(i) for i in range(2,11)] + list('JQKA')
    suits = ['spades', 'hearts', 'clubs', 'diamonds']

    def __init__(self):
     # 双重for循环产生笛卡尔积 self._cards
= [Card(rank,suit) for suit in self.suits for rank in self.ranks ] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position] deck = Deck(); # 调用__len__ print(len(deck)) # 调用__getitem__ print(deck[0]) # 切片,继续调用__getitem__ print(deck[:3]) print(deck[12::13]) print('-' * 100) # for循环,还是调用__getitem__ for d in deck: print(d) print('-' * 100) # 排序 suit_values = {'spades':3,'hearts':2,'clubs':1,'diamonds':0} def order(card): rank_value = Deck.ranks.index(card.rank) return rank_value*len(suit_values)+suit_values[card.suit] for d in sorted(deck,key=order): print(d)


输出:

52
Card(rank='2', suit='spades')
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
[Card(rank='A', suit='spades'), Card(rank='A', suit='hearts'), Card(rank='A', suit='clubs'), Card(rank='A', suit='diamonds')]
----------------------------------------------------------------------------------------------------
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
……
Card(rank='Q', suit='diamonds')
Card(rank='K', suit='diamonds')
Card(rank='A', suit='diamonds')
----------------------------------------------------------------------------------------------------
Card(rank='2', suit='diamonds')
Card(rank='2', suit='clubs')
Card(rank='2', suit='hearts')
Card(rank='2', suit='spades')
……
Card(rank='A', suit='diamonds')
Card(rank='A', suit='clubs')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')

 

   - 一个向量类,用魔术方法__add__和__mul__重载加号和乘号

    - 加号和乘号都是中缀运算符,基本原则是不改变操作对象,而是产出一个新的值

from math import hypot

class Vector:
    def __init__(self,x,y):
        self.x=x
        self.y=y
# 在repr中被调用
# 如果没有实现__str__,print使用__repr__替代
def __repr__(self): return 'Vector({0},{1})'.format(self.x,self.y) def __abs__(self): return hypot(self.x,self.y)
# 重载加号需要实现这个方法
# 注意,重载这个方法之后,只能实现a*b,而不能实现b*a    
def __add__(self, other): if not isinstance(other,Vector): raise TypeError('only permits Vector!') return Vector(self.x+other.x,self.y+other.y)
# 默认情况下,bool(自定义类的实例)总是为true,除非自定义类实现__bool__方法
# 如果没有实现__bool__方法,bool(x)会尝试调用x.__len__(),若返回0,bool为False,否则为True
def __bool__(self): return bool(abs(self))
# 实现向量与实数的乘法
# 同样,重载这个方法只能实现Vector*n,而不能实现n*Vector
def __mul__(self, other): return Vector(self.x*other,self.y*other) v1 = Vector(3,4) print('v=',v1) print('-' * 100) print('abs(v)=',abs(v1)) print('-' * 100) v2 = Vector(4,5) print('Vector(3,4)+Vector(4,5)=',v1+v2) print('-' * 100) print('Vector(3,4)*3=',v1*3)


输出:

v= Vector(3,4)
----------------------------------------------------------------------------------------------------
abs(v)= 5.0
----------------------------------------------------------------------------------------------------
Vector(3,4)+Vector(4,5)= Vector(7,9)
----------------------------------------------------------------------------------------------------
Vector(3,4)*3= Vector(10.5,14.0)

   - 利用__format__函数指定格式

    - "{:格式说明}".format(obj) 或 format(obj, 格式说明)的调用中,python将格式说明传递给obj.__format__函数作为第二个参数

class Man:
    def __init__(self, name, age):
        self.name, self.age = name, age

    def __format__(self, format_spec):
        if format_spec == "":
            return str(self)

        result = format_spec.replace('%name', self.name).replace('%age', self.age)
        return result


class People:
    def __init__(self, *people):
        self.people = list(people)

    def __format__(self, format_spec):
        if format_spec == "":
            return str(self)
        # 如果不直接使用格式说明,而是传递给另外的类,需要用到格式化字符串的嵌套
        return ",".join('{0:{1}}'.format(c,format_spec) for c in self.people)
        # 这样调用效果相同
        # return ",".join(format(c,format_spec) for c in self.people)



alice = Man("Alice", "18")
bob = Man("Bob", "19")
candy = Man("Candy", "20")

'''
这里:后面的“%name is %age years old.”是格式说明
它匹配Man类__format__(self, format_spec)的第二个形参format_spec
'''
print('{:%name is %age years old.}'.format(alice))

print('-' * 100)

# 这样调用效果相同
print(format(alice,'%name is %age years old.'))


print('-' * 100)

# 嵌套使用格式说明
ppl = People(alice,bob ,candy)
print("{:%name is %age years old}".format(ppl))

输出:

Alice is 18 years old.
----------------------------------------------------------------------------------------------------
Alice is 18 years old.
----------------------------------------------------------------------------------------------------
Alice is 18 years old,Bob is 19 years old,Candy is 20 years old

   - 使用__eq__()和__hash__()对对象进行比较

class Complex:
    def __init__(self,real,i):
        self.real = real
        self.i=i


a = Complex(1,2)
b = Complex(1,2)
# 在没有实现__eq__的情况下,==使用对象的id进行比较
# 而hash是利用对象id/16取整
print(a==b)
print(hash(a)==hash(b))

输出:

False
False
class HashedComplex(Complex): def __init__(self,real,i): super().__init__(real,i) # 在实现__eq__的同时,也要实现__hash__方法,让他们的结果保持一致 def __hash__(self): return self.real ^ self.i # 用对象属性的异或实现__eq__ def __eq__(self, other): if not isinstance(other,Complex): raise TypeError('cannot compared with '+other.__class__.__name__) return self.real==other.real and self.i == other.i a = HashedComplex(1,2) b = HashedComplex(1,2) # 实现了__eq__的情况下,==使用对象的__eq__进行比较 print(a==b) print(hash(a)==hash(b))

输出:

True
True

   - 使用 __setitem__()、__getitem__()、__delitem__()和__contains__()实现集合模拟

class Room:
    def __init__(self,size):
        self.size=size

    def __setitem__(self, key, value):
        print('__setitem__('+str(key)+':'+str(value)+')')
        self.__dict__[key]=value

    def __getitem__(self, item):
        print('__getitem__('+str(item)+')')
        return self.__dict__[item]

    def __delitem__(self, key):
        print('__delitem__('+key+')')
        del self.__dict__[key]

    def __contains__(self, item):
        print('__contains__('+item+')')
        return self.__dict__.__contains__(item)

room = Room(100)

# 用obj.key设置属性不会调用__setitem__
room.size=150
# 设置不存在的属性也不会调用__setitem__
room.door=5
# 用obj.key取得属性不会调用__getitem__
print('room size=',room.size)
print('room door=',room.door)

print('-' * 20,'我是分割线','-' * 20)

# 用[]设置属性会调用__setitem__
room['window'] = 6
# 用[]取得属性会调用__getitem__
print('room[window]=',room['window'])


print('-' * 20,'我是分割线','-' * 20)

# 调用__contains__判断对象是否有该属性
print('window' in room)
# 调用__delitem__删除room的window属性
del room['window']
# 调用__contains__判断对象是否有该属性
print('window' in room)

输出:

room size= 150
room door= 5
-------------------- 我是分割线 --------------------
__setitem__(window:6)
__getitem__(window)
room[window]= 6
-------------------- 我是分割线 --------------------
__contains__(window)
True
__delitem__(window)
__contains__(window)
False

 

  - 迭代枚举相关魔术函数:__iter__、__reversed__、__next__

    - Iterable和Iterator

      - 首先我觉得还是要先理清楚Iterable和Iterator的关系,我们先来看一下类的层次关系

      

class Iterator(Iterable)
 |  Method resolution order:
 |      Iterator
 |      Iterable
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __iter__(self)
 |  
 |  __next__(self)
 |      Return the next item from the iterator. When exhausted, raise StopIteration

class Iterable(builtins.object)
 |  Methods defined here:
 |  
 |  __iter__(self)

      - 可以看到Iterator继承了Iterable,它们都需要实现__iter__函数,Iterator多出了一个__next__

      - 说说个人理解吧,Iterator是真正用于迭代的迭代器对象,所以它必须实现__next__,客户函数可以通过next()函数取得它的下一个值。它的__iter__函数通常返回自己,毕竟自己就是个迭代器么

      - 而Iterable是一个自定义的随便什么类,其实和迭代器没什么关系。可能它正好有一些值,你如果想让客户按一定次序访问这些值,就实现__iter__方法,返回一个上面说的迭代器给客户用。那么我们把实现了__iter__函数,自己又不是迭代器的对象,称为可迭代对象,即Iterable

    - __reversed__

      - 这个函数也返回一个迭代器,即Iterator,但产生数据的方式是反过来的,所以如果想让客户用正反两个顺序访问一个Iterable里的数据,就实现两个Iterator,一个通过__iter__返回,一个通过__reversed__返回

  谈了这么多,看一个具体例子吧

from collections.abc import Iterable,Iterator

# WeekDay的正向Iterator
class WeekDayiterator():
    def __init__(self, idx, data):
        self.index = idx
        self.data = data

    def __iter__(self):
        return self

    def __next__(self):
        result = self.data[self.index]
        self.index = (self.index + 1) % len(self.data)
        return result

# WeekDay的反向Iterator
class WeekDayiterator_reversed():
    def __init__(self, idx, data):
        self.index = idx
        self.data = data

    def __iter__(self):
        return self

    def __next__(self):
        result = self.data[self.index]
        self.index = (self.index - 1) % len(self.data)
        return result

# 自定义的Iterable类
class WeekDay():
    data = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

    def __init__(self,idx=0):
        self.index=idx

    def __iter__(self):
        return WeekDayiterator(self.index,self.data)

    def __reversed__(self):
        return WeekDayiterator_reversed(self.index,self.data)


print('WeekDay is Iterable:',issubclass(WeekDay,Iterable))
print('WeekDay is Iterator:',issubclass(WeekDay,Iterator))
print('WeekDayiterator is Iterator:',issubclass(WeekDayiterator,Iterator))
print('WeekDayiterator_reversed is Iterator:',issubclass(WeekDayiterator_reversed,Iterator))

print('-'*100)

wd1 = WeekDay()
wd1_iter = iter(wd1)
for i in range(10):
    print(next(wd1_iter))

print('-'*100)

wd1_reversed = reversed(wd1)
for i in range(10):
    print(next(wd1_reversed))

输出:

WeekDay is Iterable: True
WeekDay is Iterator: False
WeekDayiterator is Iterator: True
WeekDayiterator_reversed is Iterator: True
----------------------------------------------------------------------------------------------------
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Monday
Tuesday
----------------------------------------------------------------------------------------------------
Sunday
Saturday
Friday
Thursday
Wednesday
Tuesday
Monday
Sunday
Saturday
Friday

   - 可调用模拟:__call__

    - 实现了__call__的类,实例出的对象是可调用对象,即可以在对象后面加(),像函数那样调用它

class Add_To_Box:
    box=[]
    def __call__(self, *args, **kwargs):
        self.box.extend(args)


add_to_box = Add_To_Box()
add_to_box('Cat','Dog')
add_to_box('Pig')
print(','.join(add_to_box.box))

输出:
Cat,Dog,Pig

   - 上下文管理:__enter__和__exit__

    - 实现这两个魔术方法,可以使用with语法,让Python自动在建立对象后和with语句全部结束前分别调用对象的这两个方法

class OpenBox:
    box = ['lots of gold','lots of shit']
    password = 'show me money'
    input=''

    def __init__(self,pswd):
        print('Box is created!')
        self.input=pswd

    def __enter__(self):
        print('Box is opened!')
        if self.input == self.password:
            return self.box[0]
        else:
            return self.box[1]

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Box is closed!')


with OpenBox('show me money') as box:
    print(box)

print('-'*100)

with OpenBox('wrong password') as box:
    print(box)

输出:

Box is created!
Box is opened!
lots of gold
Box is closed!
----------------------------------------------------------------------------------------------------
Box is created!
Box is opened!
lots of shit
Box is closed!

 

  - 创建实例相关魔术方法:__new__和__init__

    - __new__在__init__之前调用,__new__是一个类方法

    - 构造方法返回的实例,是由__new__先产生,然后传递给__init__初始化的

    - 在重写__new__的时候,如果想返回这个类的实例,需要调用object.__new__

    - 一个单例模式的例子

class Singleton:
    def __new__(cls, *args, **kwargs):
        print('__new__({0}) is called'.format(cls))
        if not hasattr(cls,'instance'):
            cls.instance = object.__new__(cls)
        return cls.instance

    def __init__(self):
        print('__init__({0}) is called'.format(self))


s1 = Singleton()
s2 = Singleton()

print(s1 == s2)

输出:

__new__(<class '__main__.Singleton'>) is called
__init__(<__main__.Singleton object at 0x0000024BD91242E8>) is called
__new__(<class '__main__.Singleton'>) is called
__init__(<__main__.Singleton object at 0x0000024BD91242E8>) is called
True

   - 属性管理相关魔术方法:__getattr__、__setattr__、__getattribute__

    - 用点号运算符访问对象继承树中不存在的属性时自动调用__getattr__

    - 使用obj.key=value方式设置属性时自动调用__setattr__,注意在__setattr__中要设置属性,需要使用self.__dict__,否则将出现无限递归

    - 用点号运算符访问对象属性时,调用__getattribute__

    - 注意在__getattribute__中访问自己的属性,需要使用 super().__getattribute__(attr_name),否则将无限递归

    - 有了__getattribute__就不会访问__getattr__

class AttrException(Exception):
    pass

class Animal:
    leg = 4
    # privates里面保存私有属性
    privates=[]
    def __init__(self,name):
        # 这里也会调用__setattr__()
        self.name = name

    def __getattr__(self, item):
        print('__getattr__({0}) is called'.format(item))
        # 私有属性不让访问
        if item in self.privates:
            raise AttrException("Private attribute!")
        try:
            return self.__dict__[item]
        except KeyError:
            print('{0} is not existing'.format(item))

    def __setattr__(self, key, value):
        print('__setattr__({0},{1}) is called'.format(key,value))
        self.__dict__[key] = value

class Dog(Animal):
    pass

class Tiger(Animal):
    privates = ['bottom']
    def __getattribute__(self, item):
        print('__getattribute__({0}) is called'.format(item))
        if item in super().__getattribute__('privates'):
            raise AttrException("Private attribute!")
        return super().__getattribute__(item)


dog = Dog('Puppy')
# 对已有属性不调用 __getattr__
dog.name
# 访问不存在的属性,调用__getattr__   
dog.age

print('-' * 100)

tiger = Tiger('King')
# 对已有属性仍然调用__getattribute__
tiger.name
tiger.bottom = "Don't touch"
try:
    # 调用__getattribute__,发现是私有属性,抛错
    tiger.bottom
except AttrException as exc:
    print(exc.__repr__())

输出:

__setattr__(name,Puppy) is called
__getattr__(age) is called
age is not existing
----------------------------------------------------------------------------------------------------
__setattr__(name,King) is called
__getattribute__(__dict__) is called
__getattribute__(name) is called
__setattr__(bottom,Don't touch) is called
__getattribute__(__dict__) is called
__getattribute__(bottom) is called
AttrException('Private attribute!')

 

  - 属性描述符相关魔术方法:__get__、__set__

    - 属性描述符是一个类,有点特殊的类,它至少实现了__get__方法,也可以实现__set__甚至__delete__方法

    - 属性描述符一般只会在另外一个类中使用,被当做那个类的类属性,而且一定要是类属性哦,因为它必须存在于类的__dict__中。

      - 如果它存在于实例的__dict__中,Python不会去自动调用__get__和__set__,于是就废了

    - 只实现了__get__方法的属性描述符被称为Non-Data Descriptor,同时实现了__set__的属性描述符被称为Data Descriptor

    - 同名属性的访问顺序:

      1、Data Descriptor

      2、实例属性

      3、Non-Data Descriptor

    - 也就是说,实例属性会覆盖Non-Data Descriptor,但不会覆盖Data Descriptor

class DataDescriptor:
    def __get__(self, instance, owner):
        print('__get__({0}, {1}, {2})'.format(self,instance,owner))

    def __set__(self, instance, value):
        print('__set__({0}, {1}, {2})'.format(self,instance,value))


class NonDataDescriptor:
    def __get__(self, instance, owner):
        print('__get__({0}, {1}, {2})'.format(self,instance,owner))


class AttributeOrder:
    descriptor1 = DataDescriptor()
    descriptor2 = NonDataDescriptor()

    def __init__(self,d1,d2):
        self.descriptor1 = d1
        self.descriptor2 = d2


ao = AttributeOrder('d1','d2')
# DataDescriptor不会被实例属性覆盖
print(ao.descriptor1)
# NonDataDescriptor会被实例属性覆盖
print(ao.descriptor2)

 

posted @ 2018-10-11 22:32  StackNeverOverFlow  阅读(324)  评论(0编辑  收藏  举报