理解 Python 中的 __getitem__ 方法

2025-02-24 09:30:00
admin
原创
19
摘要:问题描述:我已经阅读了 Python 文档中的大部分文档__getitem__(),但仍然无法理解它的含义。因此,我所能理解的就是__getitem__()用于实现类似 的调用self[key]。但它有什么用呢?假设我有一个以这种方式定义的python类:class Person: def __ini...

问题描述:

我已经阅读了 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对象以及ints 和slices,使得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__。这些是我最近的发现:

  1. 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')`
  1. 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 中测试。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1307  
  华为IPD产品开发流程是一套先进且成熟的产品开发管理体系,对众多企业提升产品竞争力有着重要的借鉴意义。它涵盖多个关键要素,这些要素相互关联、相互作用,共同构建起高效、科学的产品开发流程。深入剖析其中的五个核心要素,能让我们更好地理解华为成功背后的产品开发逻辑,为企业的产品创新与发展提供有力的指导。市场管理市场管理是IP...
IPD框架   40  
  华为集成产品开发(IPD)体系作为一套先进的产品开发管理理念和方法,在华为的发展历程中发挥了至关重要的作用。在供应链管理领域,IPD同样展现出巨大的价值,深刻影响着企业的运营效率、产品质量以及市场竞争力。通过将IPD理念融入供应链管理,华为实现了从产品规划到交付的全流程优化,为企业的持续发展奠定了坚实基础。IPD对供应...
IPD集成产品开发流程   37  
  IPD(Integrated Product Development)项目管理作为一种先进的产品开发管理模式,旨在通过整合跨部门资源,实现产品的高效开发与上市。然而,在实际推行过程中,IPD项目管理面临着诸多风险,若处理不当,可能导致项目进度延迟、成本超支甚至项目失败。深入了解这些风险并制定有效的应对策略,对于保障IP...
华为IPD流程   31  
  华为作为全球知名的科技企业,其成功背后的管理模式备受关注。其中,IPD(集成产品开发)产品开发流程对华为的创新发展起到了至关重要的推动作用。IPD不仅仅是一种流程,更是一种先进的管理理念,它将产品开发视为一个系统工程,涵盖了从市场需求分析、产品规划、研发、生产到上市等多个环节,通过整合企业内外部资源,实现高效、协同的产...
IPD流程中PDCP是什么意思   34  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用