理解 __getitem__ 方法

Posted

技术标签:

【中文标题】理解 __getitem__ 方法【英文标题】:Understanding __getitem__ method 【发布时间】:2017-09-23 10:56:13 【问题描述】:

我已经浏览了 Python 文档中__getitem__ 的大部分文档,但我仍然无法理解它的含义。

所以我只能理解__getitem__ 用于实现像self[key] 这样的调用。但是它有什么用呢?

假设我有一个以这种方式定义的 python 类:

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

    def __getitem__(self,key):
        print ("Inside `__getitem__` method!")
        return getattr(self,key)

p = Person("Subhayan",32)
print (p["age"])

这会按预期返回结果。但是为什么首先使用__getitem__?我还听说 Python 在内部调用 __getitem__。但是为什么会这样呢?

有人可以详细解释一下吗?

【问题讨论】:

这可能对一个示例使用感兴趣:How to properly subclass dict and override getitem & setitem 在您的示例中使用 __getitem__ 没有多大意义,但假设您需要编写一个自定义列表或字典类,它必须与现有代码一起使用使用[]。这就是__getitem__ 有用的情况。 在我看来,主要用例是当您编写一个表示事物集合的自定义类时。这允许您使用熟悉的列表/数组索引(如 planets[i])来访问给定项目,即使 planets 实际上不是列表(并且它可以在幕后使用它选择的任何数据结构,例如链接的列表或图形,或实现它选择的任何非列表函数,列表不能)。 【参考方案1】:

马聪很好地解释了__getitem__ 的用途——但我想给你一个可能有用的例子。 想象一个模拟建筑物的类。在建筑物的数据中,它包含许多属性,包括对占据每层楼的公司的描述:

如果不使用__getitem__,我们会有这样的类:

class Building(object):
     def __init__(self, floors):
         self._floors = [None]*floors
     def occupy(self, floor_number, data):
          self._floors[floor_number] = data
     def get_floor_data(self, floor_number):
          return self._floors[floor_number]

building1 = Building(4) # Construct a building with 4 floors
building1.occupy(0, 'Reception')
building1.occupy(1, 'ABC Corp')
building1.occupy(2, 'DEF Inc')
print( building1.get_floor_data(2) )

然而,我们可以使用 __getitem__(及其对应的 __setitem__)来使 Building 类的使用“更好”。

class Building(object):
     def __init__(self, floors):
         self._floors = [None]*floors
     def __setitem__(self, floor_number, data):
          self._floors[floor_number] = data
     def __getitem__(self, floor_number):
          return self._floors[floor_number]

building1 = Building(4) # Construct a building with 4 floors
building1[0] = 'Reception'
building1[1] = 'ABC Corp'
building1[2] = 'DEF Inc'
print( building1[2] )

您是否像这样使用__setitem__ 实际上取决于您计划如何抽象数据 - 在这种情况下,我们决定将建筑物视为楼层的容器(您还可以为建筑物实现迭代器,并且甚至可能切片的能力——即一次获取多个楼层的数据——这取决于你需要什么。

【讨论】:

只是为了分享我在多次阅读答案后才学到的东西:一旦你有一个 getitem 你就不必显式调用那个函数。当他调用building1[2] 时,它在内部调用自己的getitem。所以@tony-suffolk-66 的观点是,类的任何属性/变量都可以在运行时通过简单地调用 objectname[variablename] 来检索。只是澄清这一点,因为最初对我来说并不清楚,并在这里写下来希望它对某人有所帮助。如有多余请删除 @mithunpaul object[index] 表示法不用于获取类的属性/变量/属性 - 它是对容器对象的索引 - 例如从父对象检索子对象parent 维护其孩子的列表。在我的示例中 - Building 类是一个容器(在本例中是 Floor 名称),但它可以是 Floor 类的容器类。 除非它不支持len(),你会得到一个TypeErrorTypeError: object of type 'Building' has no len() 支持 len(以及其他功能,如迭代等)并不是我的示例的目的。不过,实现一个 dunder_len 方法是微不足道的。 如果你在那个类上实现 len 它将是可迭代的(因为 getitem 使用整数索引)。 len 不会“确定可迭代对象” - 它确定它是已知长度的序列。【参考方案2】:

通过键或索引获取项目的[] 语法只是语法糖。

当您评估a[i] 时,Python 调用a.__getitem__(i)(或type(a).__getitem__(a, i),但这种区别是关于继承模型的,在这里并不重要)。即使a的类可能没有显式定义这个方法,它通常也是继承自一个祖先类。

此处列出了所有 (Python 2.7) 特殊方法名称及其语义:https://docs.python.org/2.7/reference/datamodel.html#special-method-names

【讨论】:

【参考方案3】:

魔术方法__getitem__ 基本上用于访问列表项、字典条目、数组元素等。它对于快速查找实例属性非常有用。

在这里,我通过一个示例类 Person 来展示这一点,该类可以通过“姓名”、“年龄”和“出生日期”(出生日期)进行实例化。 __getitem__ 方法以一种可以访问索引实例属性的方式编写,例如名字或姓氏、出生日期、日期、月份或年份等。

import copy

# Constants that can be used to index date of birth's Date-Month-Year
D = 0; M = 1; Y = -1

class Person(object):
    def __init__(self, name, age, dob):
        self.name = name
        self.age = age
        self.dob = dob

    def __getitem__(self, indx):
        print ("Calling __getitem__")
        p = copy.copy(self)

        p.name = p.name.split(" ")[indx]
        p.dob = p.dob[indx] # or, p.dob = p.dob.__getitem__(indx)
        return p

假设一个用户输入如下:

p = Person(name = 'Jonab Gutu', age = 20, dob=(1, 1, 1999))

__getitem__ 方法的帮助下,用户可以访问索引的属性。例如,

print p[0].name # print first (or last) name
print p[Y].dob  # print (Date or Month or ) Year of the 'date of birth'

【讨论】:

很好的例子!当 init 中有多个参数时,我一直在寻找如何实现 getitem 并且我一直在努力寻找合适的实现,终于看到了这个!点赞,谢谢! 使用 getitem 来访问这样的属性是可怕的(在我看来) - 编写属性并创建只读虚拟属性要好得多。考虑可读性。你的 p[y].dob 它读起来好像 p 是一个容器 - 而不是 p 是一个具有属性的实例。使用您的模块,虚拟属性会更好地读取代码。你也可以 - 如果你坚持 - 使用 _getattr 来实现虚拟属性,但属性是更清洁的解决方案。 @TonySuffolk66 你能举例说明你的意思吗?也许使用您的建议重写此答案中的解决方案。谢谢。 @JoseQuijada 任何解决方案对于评论来说都太长了 - 但我很清楚,当您使用 p[0] 语法时,代码被解读为暗示 p 是一个容器,因此很可能是p[1]p[2]len(p) 将返回一个整数。你的代码虽然没有这样做 - 对你来说 p[0]p 的修饰符。尽管它有效,但它会使其他人难以阅读代码。一个更具可读性的系统是为 'firstname'、'lastname'、'dob_year' 等编写属性——它们的作用显而易见——而不是 p[0].name 神奇地表示名字。【参考方案4】:

__getitem__ 可用于实现“惰性”dict 子类。目的是避免立即实例化一个字典,该字典要么已经在现有容器中具有大量的键值对,要么在现有的键值对容器之间具有昂贵的散列过程,或者字典表示单个组分布在互联网上的资源。

举个简单的例子,假设您有两个列表,keysvalues,其中 k:v for k,v in zip(keys, values) 是您需要的字典,为了速度或效率的目的必须将其设为惰性:

class LazyDict(dict):
    
    def __init__(self, keys, values):
        self.keys = keys
        self.values = values
        super().__init__()
        
    def __getitem__(self, key):
        if key not in self:
            try:
                i = self.keys.index(key)
                self.__setitem__(self.keys.pop(i), self.values.pop(i))
            except ValueError, IndexError:
                raise KeyError("No such key-value pair!!")
        return super().__getitem__(key)

用法:

>>> a = [1,2,3,4]
>>> b = [1,2,2,3]
>>> c = LazyDict(a,b)
>>> c[1]
1
>>> c[4]
3
>>> c[2]
2
>>> c[3]
2
>>> d = LazyDict(a,b)
>>> d.items()
dict_items([])

【讨论】:

【参考方案5】:

为了可读性一致性。这个问题是为什么存在运算符重载的一部分,因为__getitem__ 是实现它的函数之一。

如果你得到一个未知作者编写的未知类,并且你想将它的第 3 个元素添加到它的第 5 个元素,你可以很好地假设 obj[3] + obj[5] 可以工作。

在不支持运算符重载的语言中,该行会是什么样子?可能类似于obj.get(3).add(obj.get(5))??或者obj.index(3).plus(obj.index(5))??

第二种方法的问题在于 (1) 它的可读性要差得多,并且 (2) 您无法猜测,您必须查找文档。

【讨论】:

【参考方案6】:

使用这种技术的通用库是“电子邮件”模块。它使用email.message.Message 类中的__getitem__ 方法,而该方法又被MIME 相关类继承。

然后,您只需添加您的标头即可获得具有合理默认值的有效 MIME 类型消息。引擎盖下还有很多事情要做,但用法很简单。

message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject

【讨论】:

【参考方案7】:

附带说明,__getitem__ 方法还允许您将对象变成可迭代

示例:如果与iter() 一起使用,它可以生成任意数量的int 平方值:

class MyIterable:
    def __getitem__(self, index):
        return index ** 2


obj = MyIterable()
obj_iter = iter(obj)

for i in range(1000):
    print(next(obj_iter))

【讨论】:

以上是关于理解 __getitem__ 方法的主要内容,如果未能解决你的问题,请参考以下文章

带有“__getitem__”的类没有“get”方法

Python 类特殊方法__getitem__如何使用?

python - __setitem__/__getitem__/__delitem__类的内置方法

python之使用魔术方法__getitem__和__len__

python笔记62 - __getitem__ 方法学习与使用

__getitem____iter____next__iter和next的使用方法介绍