理解 Python 中的 __getitem__ 方法
- 2025-02-24 09:30:00
- admin 原创
- 19
问题描述:
我已经阅读了 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__()
内部调用。但它为什么要这样做呢?
有人可以详细解释一下吗?
解决方案 1:
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__
实际上取决于您计划如何抽象数据 - 在这种情况下,我们决定将建筑物视为楼层的容器(并且您还可以为建筑物实现迭代器,甚至可以进行切片 - 即一次获取多个楼层的数据 - 这取决于您的需要。
解决方案 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'
解决方案 4:
附注:该__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))
解决方案 5:
使用__getitem__
包括实现控制流措施,由于某些奇怪的原因,这些措施无法在执行堆栈的较低位置执行:
class HeavenlyList(list):
"""don't let caller get 666th element"""
def __getitem__(self, key):
"""return element"""
if isinstance(key, slice):
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)
]
另一个有趣的用法是重写str.__getitem__
以接受str
对象以及int
s 和slice
s,使得str
输入是一个正则表达式,并且返回值是返回的匹配对象迭代器re.finditer
:
from re import finditer
class REString(str):
"""handles regular expressions"""
re_flags = 0
def __getitem__(self, key):
"""return some/all of string or re.finditer"""
if isinstance(key, str):
return finditer(key, self, flags=self.re_flags)
return super().__getitem__(key)
在现实世界中,dict.__getitem__
当程序需要分布在互联网上并通过 HTTP 提供的信息时,覆盖尤其有用。由于这些信息是远程的,因此该过程可以采用某种程度的惰性 — — 仅检索它没有或已更改的项目的数据。具体示例是让字典实例惰性地检索和存储Python 增强提案。这些文档有很多,有时会进行修订,并且它们都位于域名 已知的主机上peps.python.org
。因此,我们的想法是针对传递给 的 PEP 编号发出 HTTP GET 请求__getitem__
,如果字典中尚不包含它或 PEP 的 HTTP ETAG 已更改 ,则获取它。
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 数据模型文档的模拟容器类型部分中与其相关的特殊/dunder 方法。
解决方案 6:
为了可读性和一致性。这个问题是运算符重载存在的部分原因,因为__getitem__
是实现该函数之一。
如果您得到一个由未知作者编写的未知类,并且想要将其第 3 个元素添加到其第 5 个元素中,那么您完全可以假设这obj[3] + obj[5]
会起作用。
在不支持运算符重载的语言中,这一行代码会是什么样子的?可能是obj.get(3).add(obj.get(5))
?? 或者是obj.index(3).plus(obj.index(5))
??
第二种方法的问题在于:(1)可读性较差;(2)你无法猜测,必须查阅文档。
解决方案 7:
__getitem__
:
用于当获取物品需要采取某些行动时。
常与
__setitem__
which 一起使用,用于需要设置项目执行某些操作的情况。
例如,下面的代码计算了设置和获取项目的次数。 *您还可以查看使用和的Django 会话的实际示例:__getitem__()
`__setitem__()`
class Test:
def __init__(self):
self.item = {}
self.get_count = 0
self.set_count = 0
def __getitem__(self, key):
self.get_count += 1
return self.item.get(key)
def __setitem__(self, key, value):
self.item[key] = value
self.set_count += 1
test = Test()
print(f'set_count:{test.set_count}') # set_count:0
print(f'get_count:{test.get_count}') # get_count:0
# Set items 2 times
test['name'] = 'John'
test['age'] = 36
# Get items 3 times
print(test['name']) # John
print(test['name']) # John
print(test['age']) # 36
print(f'set_count:{test.set_count}') # set_count:2
print(f'get_count:{test.get_count}') # get_count:3
解决方案 8:
使用此技术的常见库是“电子邮件”模块。它使用类__getitem__
中的方法email.message.Message
,而该方法又被 MIME 相关的类继承。
然后在 中,您需要做的就是添加标头,以便获得具有合理默认值的有效 MIME 类型消息。幕后还有很多事情要做,但使用起来很简单。
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
解决方案 9:
Django 核心有几种有趣且巧妙的魔法方法用法,包括__getitem__
。这些是我最近的发现:
Django HTTP 请求
* 当你在 Django 中提交 GET/POST 数据时,它将以/ dict 的形式存储在 Django 的`request`对象中。此 dict 的类型为QueryDict,它继承自MultiValueDict。`request.GET``request.POST`
* 当您提交数据时,`user_id=42`QueryDict 将被存储/表示为:
`<QueryDict: {'user_id': ['42']}>`
因此,传递的数据变为
`'user_id': ['42']`
而不是直观的
`'user_id': '42'`
`MultiValueDict`的文档字符串解释了为什么需要将其自动转换为列表格式:
> 该类存在是为了解决 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 重写了中的`__getitem__`和。这是简化版本:`__get__``MultiValueDict`
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 表单
* 在 Django 中,你可以声明如下形式(包括`ModelForm`):
class ArticleForm(...):
title = ...
* 这些形式继承自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']
总之,魔术方法(或它们的覆盖)增加了代码的可读性和开发人员的体验/便利性。
解决方案 10:
好的,我就不说了。楼主质疑软件工程的基础知识。
这是关于定义类接口。一致性、可读性或其他任何东西都是次要的。
首先,这是关于项目的不同部分如何与您的对象对话。
想象一下,函数在某个对象上调用[]。现在,您需要对您拥有的某个新类型对象执行此函数所执行的操作。但您的对象不是列表、字典或元组。
现在您不需要实现任何东西,只需为对象的类定义一个getitem 。
接口由一系列内部实现构成。请明智地定义它们。
解决方案 11:
更复杂案例的更多示例
下面的示例准确显示了使用各种输入调用[]
/时得到的结果__getitem__
,这有助于阐明其工作原理:
class C(object):
def __getitem__(self, k):
return k
# Single argument is passed directly.
assert C()[0] == 0
# Multiple indices generate a tuple.
assert C()[0, 1] == (0, 1)
# Slice notation generates a slice object.
assert C()[1:2:3] == slice(1, 2, 3)
# Empty slice entries become None.
assert C()[:2:] == slice(None, 2, None)
# Ellipsis notation generates the Ellipsis class object.
# Ellipsis is a singleton, so we can compare with `is`.
assert C()[...] is Ellipsis
# Everything mixed up.
assert C()[1, 2:3:4, ..., 6, :7:, ..., 8] == \n (1, slice(2,3,4), Ellipsis, 6, slice(None,7,None), Ellipsis, 8)
然后,你对 参数的操作__getitem__
就是任意的了。当然,除了数组式索引之外的任何操作都可能产生疯狂的 API。但没有什么能阻止你疯狂地操作!
我还介绍Ellipsis
了:省略号对象的作用是什么?
在 Python 3.5.2 和 2.7.12 中测试。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)