python基础(基本的序列和映射协议(容器类型相关魔术方法))

元素访问

在Python中,协议通常指的是规范行为的规则,类似于接口。协议指定应实现哪些方法以及这些方法应做什么。在Python中,多态仅仅基于对象的行为(而不基于祖先,如属于哪个类或其超类等),因此这个概念很重要:其他的语言可能要求对象属于特定的类或实现了特定的接口,而Python通常只要求对象遵循特定的协议。因此,要成为序列,只需遵循序列协议即可。

 

基本的序列和映射协议
序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。

  •  __len__(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
  •  __getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说,键可以是任何类型。
  •  __setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
  •  __delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。

对于这些方法,还有一些额外的要求。

  •  对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
  •  如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
  •  对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。

下面来试一试,看看能否创建一个无穷序列。
def check_index(key): 
 """ 
 指定的键是否是可接受的索引?
 键必须是非负整数,才是可接受的。如果不是整数,
 将引发TypeError异常;如果是负数,将引发Index 
 Error异常(因为这个序列的长度是无穷的)
 """ 
   if not isinstance(key, int): raise TypeError 
   if key < 0: raise IndexError 
class ArithmeticSequence: 
     def __init__(self, start=0, step=1): 
 """ 
初始化这个算术序列
 start -序列中的第一个值
 step -两个相邻值的差
 changed -一个字典,包含用户修改后的值
 """ 
         self.start = start # 存储起始值
         self.step = step # 存储步长值

         self.changed = {} # 没有任何元素被修改
     def __getitem__(self, key): 
 """ 
从算术序列中获取一个元素
 """ 
         check_index(key) 
         try: return self.changed[key] # 修改过?
         except KeyError: # 如果没有修改过,
             return self.start + key * self.step # 就计算元素的值
      def __setitem__(self, key, value): 
 """ 
修改算术序列中的元素
 """ 
          check_index(key) 
          self.changed[key] = value # 存储修改后的值
这些代码实现的是一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被修改,就使用公式self.start + key * self.step来计算它的值。
下面的示例演示了如何使用这个类:
>>> s = ArithmeticSequence(1, 2) 
>>> s[4] 

>>> s[4] = 2 
>>> s[4] 

>>> s[5] 
11 
请注意,我要禁止删除元素,因此没有实现__del__:
>>> del s[4] 
Traceback (most recent call last): 
 File "<stdin>", line 1, in ? 
AttributeError: ArithmeticSequence instance has no attribute '__delitem__' 
另外,这个类没有方法__len__,因为其长度是无穷的。
如果所使用索引的类型非法,将引发TypeError异常;如果索引的类型正确,但不在允许的范围内(即为负数),将引发IndexError异常。
>>> s["four"] 
Traceback (most recent call last): 
 File "<stdin>", line 1, in ? 
 File "arithseq.py", line 31, in __getitem__ 
 check_index(key) 
 File "arithseq.py", line 10, in checkIndex

if not isinstance(key, int): raise TypeError 
TypeError 
>>> s[-42] 
Traceback (most recent call last): 
 File "<stdin>", line 1, in ? 
 File "arithseq.py", line 31, in __getitem__ 
 check_index(key) 
 File "arithseq.py", line 11, in checkIndex 
 if key < 0: raise IndexError 
IndexError 
索引检查是由我为此编写的辅助函数check_index负责的。

 

从 list、dict 和 str 派生
要实现序列的所有魔法方法和普通方法,不仅工作量大,而且难度不小。如果只想定制某种操作的行为,就没有理由去重新实现其他所有方法。

那么该如何做呢?“咒语”就是继承。在能够继承的情况下为何去重新实现呢?在标准库中,模块collections提供了抽象和具体的基类,但你也可以继承内置类型。因此,如果要实现一种行为类似于内置列表的序列类型,可直接继承list。

重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。
来看一个简单的示例——一个带访问计数器的列表。
class CounterList(list): 
   def __init__(self, *args): 
       super().__init__(*args) 
       self.counter = 0 
   def __getitem__(self, index): 
       self.counter += 1 
       return super(CounterList, self).__getitem__(index) 
CounterList类深深地依赖于其超类(list)的行为。CounterList没有重写的方法(如append、extend、index等)都可直接使用。在两个被重写的方法中,使用super来调用超类的相应方法,并添加了必要的行为:初始化属性counter(在__init__中)和更新属性counter(在__getitem__中)。

下面的示例演示了CounterList的可能用法:
>>> cl = CounterList(range(10)) 
>>> cl 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
>>> cl.reverse() 
>>> cl 

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 
>>> del cl[3:6] 
>>> cl 
[9, 8, 7, 3, 2, 1, 0] 
>>> cl.counter 

>>> cl[4] + cl[2] 

>>> cl.counter 

如你所见,CounterList的行为在大多数方面都类似于列表,但它有一个counter属性(其初始值为0)。每当你访问列表元素时,这个属性的值都加1。执行加法运算cl[4] + cl[2]后,counter的值递增两次,变成了2。

 

容器类型相关魔术方法

__len__(self)             定义当被 len() 调用时的行为(返回容器中元素的个数)

__getitem__(self, key)              定义获取容器中指定元素的行为,相当于 self[key]

__setitem__(self, key, value)              定义设置容器中指定元素的行为,相当于 self[key] = value

__delitem__(self, key)              定义删除容器中指定元素的行为,相当于 del self[key]

__iter__(self)                定义当迭代容器中的元素的行为

__reversed__(self)                 定义当被 reversed() 调用时的行为

__contains__(self, item)                  定义当使用成员测试运算符(in 或 not in)时的行为

例:

class S():
    def __init__(self):
        self.__dict__ = {'a':'A', 'b':'B', 'c':'C'}

    def __getitem__(self, item):
        print('getitem', item)
        return self.__dict__.get(item)

    def __setitem__(self,k,v):
        print('setitem')
        self.__dict__[k] = v

    def __delitem__(self, k):
        print('delitem')
        self.__dict__.pop(k)

s = S()
print(s['a'])   #输出A

# s.name='jamfiy'    #---->setattr-------->s.__dict__['name']='jamfiy'
s['d'] = 'D'    #--->setitem--------->s.__dict__['d']='D'
print(s.__dict__)   #输出{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}

del s['a']
print(s.__dict__)   #输出{'b': 'B', 'c': 'C', 'd': 'D'}

-------------------------------------------

class T():

     n = 1

     def __iter__(self):

            return self

     def __next__(self):

            self.n += 1

            return self.n

t = T()

print(next(t)) 输出2

print(next(t)) 输出3

print(next(t)) 输出4

-------------------------------------------

class R():

     def __reversed__(self):

           return reversed([1,5,3,4,2,6])

r = R()

b = reversed(r)

print(list(b)) 输出[6,2,4,3,5,1]

-------------------------------------------

class C():

     def __init__(self):

           self.v = ['a', 'b', 'c']

     def __contains__(self, v):

           if v in self.v:

                return True

           else:

                return False

c = C()

print(c.__contains__('d'))  #输出False

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值