[英]Understanding __getitem__ method
我已经浏览了 Python 文档中__getitem__
的大部分文档,但我仍然无法理解它的含义。
所以我能理解的是__getitem__
用于实现像self[key]
这样的调用。 但是它有什么用呢?
假设我有一个 python class 以这种方式定义:
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__
。 但它为什么这样做呢?
有人可以更详细地解释一下吗?
Cong Ma 很好地解释了__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__
实际上取决于您计划如何抽象数据 - 在这种情况下,我们决定将建筑物视为楼层的容器(并且您还可以为建筑物实现迭代器,甚至可能能够切片 - 即一次获取多个楼层的数据 - 这取决于您的需要。
通过键或索引获取项目的[]
语法只是语法糖。
当您评估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
魔术方法__getitem__
主要用于访问列表项、字典条目、数组元素等。它对于快速查找实例属性非常有用。
在这里,我用一个示例类 Person 展示了这一点,该类可以通过“姓名”、“年龄”和“dob”(出生日期)进行实例化。 __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'
Django 内核对魔术方法有几个有趣和漂亮的用法,包括__getitem__
。 这些是我最近的发现:
Django HTTP 请求
当您在 Django 中提交 GET/POST 数据时,它将作为request.GET
/ request.POST
dict 存储在 Django 的request
object 中。 此 dict 是QueryDict类型,它继承自MultiValueDict 。
当您提交数据时,比如user_id=42
, QueryDict 将被存储/表示为:
<QueryDict: {'user_id': ['42']}>
因此,传递的数据变为
'user_id': ['42']
而不是直观的
'user_id': '42'
MultiValueDict
的文档字符串解释了为什么需要将其自动转换为列表格式:
这个 class 的存在是为了解决 cgi.parse_qs 提出的恼人问题,它为每个键返回一个列表。
鉴于QueryDict
值已转换为列表,因此需要像这样访问它们(与request.GET
相同的想法):
request.POST['user_id'][0]
request.POST['user_id'][-1]
request.POST.get('user_id')[0]
request.POST.get('user_id)[-1]
但是,这些是访问数据的可怕方式。 所以。 Django 覆盖了MultiValueDict
中的__getitem__
和__get__
。 这是简化版:
def __getitem__(self, key): """ Accesses the list value automatically using the `-1` list index. """ list_ = super().__getitem__(key) return list_[-1] def get(self, key, default=None): """ Just calls the `__getitem__` above. """ return self[key]
有了这些,您现在可以拥有更直观的访问器:
request.POST['user_id']
request.POST.get('user_id')
Django Forms
在 Django 中,您可以像这样声明 forms (包括ModelForm
):
class ArticleForm(...): title =...
这些 forms 继承自BaseForm ,并具有这些重写的魔术方法(简化版):
def __iter__(self): for name in self.fields: yield self[name] def __getitem__(self, name): return self.fields[name]
导致这些方便的模式:
# Instead of `for field in form.fields`. # This is a common pattern in Django templates. for field in form... # Instead of `title = form.fields['title']` title = form['title']
总之,魔术方法(或它们的覆盖)增加了代码的可读性和开发人员的体验/便利性。
__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([])
为了可读性和一致性。 这个问题是运算符重载存在的部分原因,因为__getitem__
是实现它的函数之一。
如果您得到一个未知类,由未知作者编写,并且您想将它的第 3 个元素添加到它的第 5 个元素,则可以很好地假设obj[3] + obj[5]
将起作用。
在不支持运算符重载的语言中,该行会是什么样子? 可能类似于obj.get(3).add(obj.get(5))
?? 或者obj.index(3).plus(obj.index(5))
??
第二种方法的问题在于(1)它的可读性要低得多,(2)你无法猜测,你必须查找文档。
使用这种技术的常见库是“电子邮件”模块。 它使用email.message.Message
类中的__getitem__
方法,该类又由 MIME 相关类继承。
然后,在获得具有合理默认值的有效 MIME 类型消息所需的全部内容中,添加您的标头。 引擎盖下还有很多事情要做,但用法很简单。
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
作为旁注, __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))
该方法是您如何从容器或序列 object 中检索一个或多个项目,如list
:
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> [1,2,3].__getitem__(-1)
3
>>> [1,2,3].__getitem__(slice(0,3,1))
[1, 2, 3]
__getitem__
的一些实现旨在处理不同类型的输入,例如当绑定到list
时,如上处理int
和slice
。 同样, []
运算符可用作__getitem__
的专用别名,
>>> [1,2,3][-1]
3
>>> [1,2,3][slice(0,3,1)]
[1, 2, 3]
对于 Python 中的许多容器/序列类型,它更熟悉地隐式处理slice
对象的int:int:int
语法糖:
>>> [1,2,3][0:3:1]
[1, 2, 3]
>>> ""[0:3:1]
''
鉴于这种架构, __getitem__
的使用包括实现控制流措施,由于某些奇怪的原因,这些措施不能在执行堆栈中执行得更低:
class HeavenlyList(list):
"""don't let caller get 666th element"""
def __getitem__(self, key):
"""return element"""
try:
super().__getitem(666)
except IndexError:
return super().__getitem__(key)
else:
if isinstance(key, slice):
if key.start > 666 or key.stop < 666:
return super().__getitem__(key)
return [
super().__getitem__(i)
for i in range(key.start, key.stop, key.step)
if i != 666
]
return super().__getitem__(key) if key != 666 else None
一个类似但更有趣的原因是允许基于slice
访问通常不允许的容器/序列类型中的元素:
class SliceDict(dict):
"""handles slices"""
def __setitem__(self, key, value):
"""map key to value"""
if not isinstance(key, int)
raise TypeError("key must be an integer")
super().__setitem__(key, value)
def __getitem__(self, key):
"""return value(s)"""
if not isinstance(key, slice):
return super().__getitem__(key)
return [
super().__getitem__(i)
for i in range(key.start, key.stop, key.step)
]
覆盖dict.__getitem__
被证明特别有用的实际问题是,当程序需要通过 Internet 分发且可通过 HTTP 获得的信息时。 因为这些信息是远程的,所以该过程可以采用某种程度的惰性——只检索它没有或已更改的项目的数据。 具体示例是让字典实例延迟检索和存储Python Enhancement Proposals 。 这些文档有很多,有时会进行修订,它们都驻留在域名peps.python.org
的主机上。 因此,想法是对传递给__getitem__
的 PEP 编号发出 HTTP GET 请求,如果字典尚未包含它或 PEP HTTP ETAG changed则获取它。
from http import HTTPStatus, client
class PEPDict(dict):
"""lazy PEP container"""
conn = client.HTTPSConnection("peps.python.org")
def __getitem__(self, pep):
"""return pep pep"""
# if lazy for too long
if self.conn.sock is None:
self.conn.connect()
# build etag check in request header
requestheaders = dict()
if pep in self:
requestheaders = {
"if-none-match": super().__getitem__(pep)[0]
}
# make request and fetch response
self.conn.request(
"GET",
"/%s/" % str(pep).zfill(4),
headers=requestheaders
)
response = self.conn.getresponse()
# (re)set the pep
if response.status = HTTPStatus.OK:
self.__setitem__(
pep, (
response.getheader("etag"),
response.read()
)
)
# raise if status is not ok or not modified
if response.status != HTTPStatus.NOT_MODIFIED:
raise Exception("something weird happened")
return super().__getitem(pep)[1]
进一步了解它的用途的一个很好的资源是在 Python 的数据 model 文档的模拟容器类型部分中查看其相关的特殊/dunder 方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.