理解 __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()
,你会得到一个TypeError
:TypeError: 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
子类。目的是避免立即实例化一个字典,该字典要么已经在现有容器中具有大量的键值对,要么在现有的键值对容器之间具有昂贵的散列过程,或者字典表示单个组分布在互联网上的资源。
举个简单的例子,假设您有两个列表,keys
和 values
,其中 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__ 方法的主要内容,如果未能解决你的问题,请参考以下文章
python - __setitem__/__getitem__/__delitem__类的内置方法
python之使用魔术方法__getitem__和__len__