Python 中的成员资格测试入门
有时您需要查明某个值是否存在于值集合中。换句话说,您需要检查给定值是否是成员值的集合。这种检查通常称为会员资格测试.
可以说,执行此类检查的自然方法是迭代这些值并将它们与目标值进行比较。您可以在以下人员的帮助下完成此操作for循环和一个条件语句.
考虑以下is_member()
功能:
>>>>>> def is_member(value, iterable):
... for item in iterable:
... if value is item or value == item:
... return True
... return False
...
该函数有两个参数,目标value
和值的集合,通常称为iterable
。循环遍历iterable
而条件语句检查目标是否value
等于当前值。请注意,条件检查对象身份和is
或为了价值平等与等于运算符 (==
)。这些测试略有不同但互补。
如果条件为真,则函数回报 True
,跳出循环。这次提前回归短路循环操作。如果循环结束时没有任何匹配项,则函数返回False
:
>>>>>> is_member(5, [2, 3, 5, 9, 7])
True
>>> is_member(8, [2, 3, 5, 9, 7])
False
第一次致电is_member()
回报True
因为目标值,5
,是当前列表中的成员,[2, 3, 5, 9, 7]
。第二次调用该函数返回False
因为8
不存在于输入值列表中。
像上面这样的成员资格测试在编程中非常常见和有用,以至于 Python 有专门的运算符来执行这些类型的检查。您可以了解会员经营者如下表所示:
Operator |
Description |
Syntax |
in |
Returns True if the target value is present in a collection of values. Otherwise, it returns False . |
value in collection |
not in |
Returns True if the target value is not present in a given collection of values. Otherwise, it returns False . |
value not in collection |
与布尔运算符,Python 通过使用常见的英语单词而不是可能令人困惑的符号作为运算符来提高可读性。
笔记:不要混淆in
关键词当它作为会员运营商与in
中的关键字for
循环语法。它们具有完全不同的含义。这in
运算符检查某个值是否在值集合中,而in
中的关键字for
循环表示您要从中绘制的可迭代对象。
与许多其他运营商一样,in
和not in
是二元运算符。这意味着您可以通过连接两个操作数来创建表达式。在这种情况下,这些是:
-
左操作数:您要在值集合中查找的目标值
-
右操作数:可以找到目标值的值的集合
成员资格测试的语法如下所示:
value in collection
value not in collection
在这些表达中,value
可以是任何Python对象。同时,collection
可以是任何可以保存值集合的数据类型,包括列表、元组, 字符串, 套, 和字典。它也可以是一个实现了.__contains__()
显式支持成员资格测试或迭代的方法或用户定义的类。
如果您使用in
和not in
运算符正确,那么您使用它们构建的表达式将始终计算为布尔值价值。换句话说,这些表达式将始终返回True
或者False
。另一方面,如果您尝试在不支持成员资格测试的事物中找到价值,那么您将得到类型错误. 之后,您将了解有关支持成员资格测试的 Python 数据类型的更多信息。
既然您知道什么是会员运营商,那么是时候了解他们如何运作的基础知识了。
蟒蛇的in
操作员
为了更好地理解in
运算符,您将首先编写一些小的说明性示例来确定给定值是否是在一个列表:
>>>>>> 5 in [2, 3, 5, 9, 7]
True
>>> 8 in [2, 3, 5, 9, 7]
False
第一个表达式返回True
因为5
出现在您的号码列表中。第二个表达式返回False
因为8
不存在于列表中。
根据in
操作员文档,像这样的表达式value in collection
相当于下面的代码:
any(value is item or value == item for item in collection)
这生成器表达式包含在调用中任何()构建一个布尔值列表,该列表是通过检查目标是否value
具有相同的身份或等于当前item
在collection
。致电给any()
检查结果布尔值中是否有任何一个是True
,在这种情况下函数返回True
。如果所有的值都是False
, 然后any()
回报False
.
蟒蛇的not in
操作员
这not in
会员运营商的做法恰恰相反。使用此运算符,您可以检查给定值是否不在值的集合:
>>>>>> 5 not in [2, 3, 5, 9, 7]
False
>>> 8 not in [2, 3, 5, 9, 7]
True
在第一个例子中,你得到False
因为5
是在[2, 3, 5, 9, 7]
。在第二个例子中,你得到True
因为8
不在值列表中。这种否定逻辑可能看起来像是绕口令。为了避免混淆,请记住您正在尝试确定该值是否为not给定值集合的一部分。
笔记:这not value in collection
构造的工作原理与value not in collection
一。然而,前一种结构更难以阅读。因此,您应该使用not in
作为单个操作符而不是使用not
否定结果in
.
通过对会员运营商如何工作的快速概述,您已准备好进入下一个级别并了解如何in
和not in
使用不同的内置数据类型。
使用in
和not in
使用不同的 Python 类型
全部内置序列——比如列表、元组、范围对象和字符串——支持成员资格测试in
和not in
运营商。集合和字典等集合也支持这些测试。默认情况下,字典上的成员资格操作会检查字典是否具有给定的键。但是,字典还具有显式方法,允许您将成员运算符与键、值和键值对一起使用。
在以下部分中,您将了解使用的一些特殊性in
和not in
具有不同的内置数据类型。您将从列表、元组和range
开始事情的对象。
列表、元组和范围
到目前为止,您已经编写了一些使用in
和not in
运算符来确定给定值是否存在于现有值列表中。对于这些示例,您已明确使用list
对象。因此,您已经熟悉成员资格测试如何与列表一起使用。
对于元组,成员运算符的工作方式与对于列表的工作方式相同:
>>>>>> 5 in (2, 3, 5, 9, 7)
True
>>> 5 not in (2, 3, 5, 9, 7)
False
这里没有什么惊喜。这两个示例的工作方式与以列表为中心的示例相同。在第一个示例中,in
运算符返回True
因为目标值,5
,在元组中。在第二个例子中,not in
返回相反的结果。
对于列表和元组,成员资格运算符使用搜索算法迭代底层集合中的项目。因此,当你的迭代变得更长时,搜索时间成正比增加。使用大O表示法,你会说对这些数据类型的成员资格操作有一个时间复杂度的在).
如果您使用in
和not in
运营商与range
对象,那么你会得到类似的结果:
>>>>>> 5 in range(10)
True
>>> 5 not in range(10)
False
>>> 5 in range(0, 10, 2)
False
>>> 5 not in range(0, 10, 2)
True
到那个时刻range
对象,乍一看似乎没有必要使用成员资格测试。大多数时候,您会事先知道结果范围内的值。但如果你使用的是range()
带有在运行时确定的参数?
笔记:创建时range
对象,您最多可以传递三个参数给range()
。这些论点是start
, stop
, 和step
。他们定义了这个数字开始范围,范围必须达到的数字停止创造价值,以及步生成的值之间。
考虑以下示例,其中使用随机的运行时确定参数的数字:
>>>>>> from random import randint
>>> 50 in range(0, 100, randint(1, 10))
False
>>> 50 in range(0, 100, randint(1, 10))
False
>>> 50 in range(0, 100, randint(1, 10))
True
>>> 50 in range(0, 100, randint(1, 10))
True
在您的计算机上,您可能会得到不同的结果,因为您正在使用随机范围。在这些具体的例子中,step
是唯一变化的值。在实际代码中,您可以有不同的值start
和stop
以及。
为了range
对象,成员资格测试背后的算法使用表达式计算给定值的存在(value - start) % step) == 0
,这取决于用于创建手头范围的参数。这使得会员测试非常高效当他们操作时range
对象。在这种情况下,你会说它们的时间复杂度是复杂度(1).
笔记:列表、元组和range
对象有一个.index()
方法,返回给定值在基础序列中第一次出现的索引。此方法对于在序列中定位值非常有用。
有些人可能认为他们可以使用该方法来确定某个值是否在序列中。但是,如果该值不在序列中,则.index()
提出一个值错误:
>>>>>> (2, 3, 5, 9, 7).index(8)
Traceback (most recent call last):
...
ValueError: tuple.index(x): x not in tuple
您可能不想通过引发异常来确定某个值是否在序列中,因此您应该使用成员运算符而不是.index()
以此目的。
请记住,成员资格测试中的目标值可以是任何类型。该测试将检查该值是否在目标集合中。例如,假设您有一个假设的应用程序,用户使用用户名和密码进行身份验证。你可以有这样的东西:
# users.py
username = input("Username: ")
password = input("Password: ")
users = [("john", "secret"), ("jane", "secret"), ("linda", "secret")]
if (username, password) in users:
print(f"Hi {username}, you're logged in!")
else:
print("Wrong username or password")
这是一个幼稚的例子。不太可能有人会这样处理他们的用户和密码。但示例表明目标值可以是任何数据类型。在本例中,您使用表示给定用户的用户名和密码的字符串元组。
以下是该代码在实践中的工作原理:
$ python users.py
Username: john
Password: secret
Hi john, you're logged in!
$ python users.py
Username: tina
Password: secret
Wrong username or password
在第一个示例中,用户名和密码是正确的,因为它们位于users
列表。在第二个示例中,用户名不属于任何注册用户,因此身份验证失败。
在这些示例中,需要注意的是,数据在登录元组中的存储顺序至关重要,因为类似("john", "secret")
不等于("secret", "john")
在元组比较中,即使它们具有相同的项目。
在本节中,您探索了一些示例,这些示例展示了具有常见 Python 内置序列的成员资格运算符的核心行为。然而,还剩下一个内置序列。是的,弦!在下一节中,您将了解成员运算符如何在 Python 中处理此数据类型。
弦乐
Python 字符串是每个 Python 开发人员工具包中的基本工具。与元组、列表和范围一样,字符串也是序列,因为它们的项或字符按顺序存储在内存中。
您可以使用in
和not in
当您需要确定目标字符串中是否存在给定字符时,请使用字符串运算符。例如,假设您使用字符串来设置和管理给定资源的用户权限:
>>>>>> class User:
... def __init__(self, username, permissions):
... self.username = username
... self.permissions = permissions
...
>>> admin = User("admin", "wrx")
>>> john = User("john", "rx")
>>> def has_permission(user, permission):
... return permission in user.permissions
...
>>> has_permission(admin, "w")
True
>>> has_permission(john, "w")
False
这User
类有两个参数,一个用户名和一组权限。要提供权限,您可以使用一个字符串,其中w
意味着用户拥有写允许,r
意味着用户拥有读许可,以及x
暗示执行权限。请注意,这些字母与您在 Unix 风格中找到的字母相同文件系统权限.
里面的会员测试has_permission()
检查当前是否user
有一个给定的permission
或不返回True
或者False
因此。为此,需要将in
运算符搜索权限字符串以查找单个字符。在此示例中,您想知道用户是否有写允许。
但是,您的权限系统有一个隐藏的问题。如果使用空字符串调用该函数会发生什么?这是你的答案:
>>>>>> has_permission(john, "")
True
因为空字符串始终被视为任何其他字符串的子字符串,所以像这样的表达式"" in user.permissions
将返回True
。根据谁有权访问您的用户权限,这种成员资格测试行为可能意味着您的系统存在安全漏洞。
您还可以使用隶属度运算符来确定是否字符串包含子字符串:
>>>>>> greeting = "Hi, welcome to Real Python!"
>>> "Hi" in greeting
True
>>> "Hi" not in greeting
False
>>> "Hello" in greeting
False
>>> "Hello" not in greeting
True
对于字符串数据类型,表达式如下substring in string
是True
如果substring
是其一部分string
。否则,表达式为False
.
笔记:与列表、元组等其他序列不同range
对象、字符串提供了.find()
在现有字符串中搜索给定子字符串时可以使用的方法。
例如,您可以执行以下操作:
>>>>>> greeting.find("Python")
20
>>> greeting.find("Hello")
-1
如果子字符串存在于底层字符串中,则.find()
返回子字符串在字符串中开始的索引。如果目标字符串不包含子字符串,那么你会得到-1
因此。所以,像这样的表达式string.find(substring) >= 0
将相当于substring in string
测试。
然而,成员资格测试更具可读性和明确性,这使得它在这种情况下更可取。
对字符串使用成员资格测试时要记住的重要一点是字符串比较区分大小写:
>>>>>> "PYTHON" in greeting
False
本次会员测试返回False
因为字符串比较区分大小写,并且"PYTHON"
大写的不存在于greeting
。要解决这种区分大小写的问题,您可以使用以下任一方法标准化所有字符串。上()或者。降低()方法:
>>>>>> "PYTHON".lower() in greeting.lower()
True
在此示例中,您使用.lower()
将目标子字符串和原始字符串转换为小写字母。此转换欺骗了隐式字符串比较中的大小写敏感性。
发电机
发电机功能和生成器表达式创建高效内存迭代器作为。。而被知道生成器迭代器。为了提高内存效率,这些迭代器按需生成项目,而不在内存中保留完整的值系列。
实际上,生成器函数是功能使用的是屈服其正文中的声明。例如,假设您需要一个生成器函数,它接受数字列表并返回一个迭代器,该迭代器从原始数据生成平方值。在这种情况下,您可以执行以下操作:
>>>>>> def squares_of(values):
... for value in values:
... yield value ** 2
...
>>> squares = squares_of([1, 2, 3, 4])
>>> next(squares)
1
>>> next(squares)
4
>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
...
StopIteration
该函数返回一个生成器迭代器,根据需要生成平方数。您可以使用内置的下一个()函数从迭代器中检索连续值。当生成器迭代器完全消耗完时,它会引发一个StopIteration
例外以传达没有剩下更多值的情况。
您可以在生成器函数上使用成员运算符,例如squares_of()
:
>>>>>> 4 in squares_of([1, 2, 3, 4])
True
>>> 9 in squares_of([1, 2, 3, 4])
True
>>> 5 in squares_of([1, 2, 3, 4])
False
这in
当您将它与生成器迭代器一起使用时,运算符按预期工作,返回True
如果该值存在于迭代器中并且False
否则。
然而,在检查生成器的成员资格时,您需要注意一些事情。生成器迭代器只会生成每个项目一次。如果您消耗了所有项目,那么迭代器将耗尽,您将无法再次迭代它。如果您仅使用生成器迭代器中的一些项目,那么您只能迭代剩余的项目。
当你使用in
或者not in
在生成器迭代器上,运算符将在搜索目标值时消耗它。如果该值存在,则运算符将消耗直到目标值的所有值。其余值在生成器迭代器中仍然可用:
>>>>>> squares = squares_of([1, 2, 3, 4])
>>> 4 in squares
True
>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
...
StopIteration
在这个例子中,4
位于生成器迭代器中,因为它是2
。所以,in
回报True
。当你使用next()
从中检索值square
, 你得到9
,这是3
。此结果确认您无法再访问前两个值。您可以继续拨打电话next()
直到你得到一个StopIteration
当生成器迭代器耗尽时出现异常。
同样,如果生成器迭代器中不存在该值,则运算符将完全消耗迭代器,并且您将无法访问其任何值:
>>>>>> squares = squares_of([1, 2, 3, 4])
>>> 5 in squares
False
>>> next(squares)
Traceback (most recent call last):
...
StopIteration
在此示例中,in
操作员消耗squares
彻底、回归False
因为目标值不在输入数据中。因为生成器迭代器现在已耗尽,所以调用next()
和squares
作为一个论点提出StopIteration
.
您还可以使用生成器表达式创建生成器迭代器。这些表达式使用相同的语法列表推导式但替换方括号 ([]
) 带圆括号 (()
)。您可以使用in
和not in
带有生成器表达式结果的运算符:
>>>>>> squares = (value ** 2 for value in [1, 2, 3, 4])
>>> squares
<generator object <genexpr> at 0x1056f20a0>
>>> 4 in squares
True
>>> next(squares)
9
>>> next(squares)
16
>>> next(squares)
Traceback (most recent call last):
...
StopIteration
这squares
多变的现在保存由生成器表达式生成的迭代器。该迭代器从输入数字列表中生成平方值。生成器表达式中的生成器迭代器与生成器函数中的生成器迭代器的工作方式相同。因此,当您在成员资格测试中使用它们时,同样的规则也适用。
当您使用in
和not in
带有生成器迭代器的运算符。当您使用无限迭代器时,可能会出现此问题。下面的函数返回一个产生无限整数的迭代器:
>>>>>> def infinite_integers():
... number = 0
... while True:
... yield number
... number += 1
...
>>> integers = infinite_integers()
>>> integers
<generator object infinite_integers at 0x1057e8c80>
>>> next(integers)
0
>>> next(integers)
1
>>> next(integers)
2
>>> next(integers)
3
>>> next(integers)
这infinite_integers()
函数返回一个生成器迭代器,它存储在integers
。该迭代器根据需要生成值,但请记住,将会有无限的值。因此,在此迭代器中使用成员运算符并不是一个好主意。为什么?好吧,如果目标值不在生成器迭代器中,那么您将陷入无限循环,这将使您的执行悬挂.
字典和集合
Python 的成员运算符也可用于字典和集合。如果您使用in
或者not in
直接在字典上使用运算符,然后它会检查字典是否有给定的键。您还可以使用以下命令进行此检查.keys()方法,这更明确地表达了您的意图。
您还可以检查给定值或键值对是否在字典中。要执行这些检查,您可以使用.values()和。项目()方法分别为:
>>>>>> likes = {"color": "blue", "fruit": "apple", "pet": "dog"}
>>> "fruit" in likes
True
>>> "hobby" in likes
False
>>> "blue" in likes
False
>>> "fruit" in likes.keys()
True
>>> "hobby" in likes.keys()
False
>>> "blue" in likes.keys()
False
>>> "dog" in likes.values()
True
>>> "drawing" in likes.values()
False
>>> ("color", "blue") in likes.items()
True
>>> ("hobby", "drawing") in likes.items()
False
在这些示例中,您使用in
操作员直接在您的likes
字典来检查是否"fruit"
, "hobby"
, 和"blue"
键是否在字典中。请注意,即使"blue"
是一个值likes
,测试返回False
因为它只考虑键。
接下来,您将使用.keys()
方法得到相同的结果。在这种情况下,显式的方法名称可以让其他程序员阅读您的代码时更加清楚您的意图。
检查一个值是否像"dog"
或者"drawing"
存在于likes
,你使用.values()
方法,它返回一个查看对象与底层字典中的值。类似地,检查一个键值对是否包含在likes
, 你用.items()
。请注意,目标键值对必须是键和值按顺序排列的两项元组。
如果您使用集合,则成员资格运算符的工作方式与列表或元组的工作方式相同:
>>>>>> fruits = {"apple", "banana", "cherry", "orange"}
>>> "banana" in fruits
True
>>> "banana" not in fruits
False
>>> "grape" in fruits
False
>>> "grape" not in fruits
True
这些示例表明您还可以使用隶属度运算符检查给定值是否包含在集合中in
和not in
.
现在您知道如何in
和not in
运算符使用不同的内置数据类型,是时候通过几个示例将这些运算符付诸实践了。
把Python的in
和not in
运营商行动起来
会员资格测试in
和not in
是编程中非常常见的操作。您会在许多现有的 Python 代码库中找到此类测试,并且也会在您的代码中使用它们。
在以下部分中,您将学习如何根据以下内容替换布尔表达式or具有成员资格测试的操作员。由于成员资格测试在您的代码中非常常见,因此您还将学习如何使这些测试更加高效。
更换链式or
运营商
使用隶属度测试将复合布尔表达式替换为多个or
运算符是一种有用的技术,可以让您简化代码并使其更具可读性。
要查看此技术的实际应用,假设您需要编写一个函数,将颜色名称作为字符串并确定它是否是原色。为了解决这个问题,您将使用RGB(红、绿、蓝)颜色型号:
>>>>>> def is_primary_color(color):
... color = color.lower()
... return color == "red" or color == "green" or color == "blue"
...
>>> is_primary_color("yellow")
False
>>> is_primary_color("green")
True
In is_primary_color()
,您使用复合布尔表达式,该表达式使用or
运算符检查输入颜色是否为红色、绿色或蓝色。尽管此函数按预期工作,但条件可能会令人困惑并且难以阅读和理解。
好消息是,您可以用紧凑且可读的成员资格测试替换上述条件:
>>>>>> def is_primary_color(color):
... primary_colors = {"red", "green", "blue"}
... return color.lower() in primary_colors
...
>>> is_primary_color("yellow")
False
>>> is_primary_color("green")
True
现在你的函数使用in
运算符检查输入颜色是否为红色、绿色或蓝色。将原色集分配给正确命名的变量,例如primary_colors
还有助于使您的代码更具可读性。最后的检查现在已经很清楚了。任何阅读您代码的人都会立即明白您正在尝试根据 RGB 颜色模型确定输入颜色是否为原色。
如果您再次查看该示例,您会发现原色已存储在一个集合中。为什么?您将在下一节中找到答案。
编写高效的成员资格测试
Python 使用一个数据结构称为哈希表实现字典和集合。哈希表有一个显着的特性:在数据结构中查找任何给定值大约需要相同的时间,无论表有多少个值。使用 Big O 表示法,您会说哈希表中的值查找的时间复杂度为复杂度(1),这使得它们速度超级快。
现在,哈希表的这个特性与字典和集合的成员资格测试有什么关系?嗯,事实证明in
和not in
操作员在操作这些类型时工作速度非常快。此细节允许您通过在成员资格测试中优先使用字典和集合而不是列表和其他序列来优化代码的性能。
要了解集合比列表效率高多少,请继续创建以下脚本:
# performance.py
from timeit import timeit
a_list = list(range(100_000))
a_set = set(range(100_000))
list_time = timeit("-1 in a_list", number=1, globals=globals())
set_time = timeit("-1 in a_set", number=1, globals=globals())
print(f"Sets are {(list_time / set_time):.2f} times faster than Lists")
该脚本创建一个包含十万个值的整数列表和一个包含相同数量元素的集合。然后该脚本计算确定该数字是否所需的时间-1
位于列表和集合中。你事先就知道-1
未出现在列表或集中。因此,成员资格操作员必须在获得最终结果之前检查所有值。
正如您已经知道的,当in
运算符在列表中搜索值,它使用时间复杂度为的算法在)。另一方面,当in
运算符在集合中搜索值,它使用哈希表查找算法,其时间复杂度为复杂度(1)。这一事实可以在性能方面产生很大的差异。
继续吧运行你的脚本从命令行使用以下命令:
$ python performance.py
Sets are 1563.33 times faster than Lists
尽管您的命令的输出可能略有不同,但当您在此特定成员资格测试中使用集合而不是列表时,它仍然会显示出显着的性能差异。对于列表,处理时间将与值的数量成正比。对于一组,任何数量的值的时间都几乎相同。
此性能测试表明,当您的代码对大型值集合进行成员资格检查时,您应该尽可能使用集合而不是列表。当您的代码在执行期间执行多个成员资格测试时,您还将受益于集合。
但是,请注意,仅仅为了执行一些成员资格测试而将现有列表转换为集合并不是一个好主意。请记住,将列表转换为集合是一个操作在)时间复杂度。
使用operator.contains()
用于会员测试
这in
运算符具有等效功能操作员模块,它出现在标准库。该函数称为包含()。它需要两个参数——值的集合和目标值。它返回True
如果输入集合包含目标值:
>>>>>> from operator import contains
>>> contains([2, 3, 5, 9, 7], 5)
True
>>> contains([2, 3, 5, 9, 7], 8)
False
第一个参数contains()
是值的集合,第二个参数是目标值。请注意,参数的顺序与常规成员资格操作不同,其中目标值排在第一位。
当您使用类似工具时,此功能会派上用场地图(), 或者筛选()处理代码中的可迭代对象。例如,假设您有一堆笛卡尔点作为元组存储在列表中。您想要创建一个仅包含不在坐标轴上的点的新列表。使用filter()
函数,你可以想出以下解决方案:
>>>>>> points = [
... (1, 3),
... (5, 0),
... (3, 7),
... (0, 6),
... (8, 3),
... (2, 0),
... ]
>>> list(filter(lambda point: not contains(point, 0), points))
[(1, 3), (3, 7), (8, 3)]
在此示例中,您使用filter()
检索不包含的点0
协调。为此,您使用contains()
在一个拉姆达功能。因为filter()
返回一个迭代器,您将所有内容包装在调用中list()
将迭代器转换为点列表。
尽管上面示例中的构造有效,但它非常复杂,因为它意味着导入contains()
,创建一个lambda
函数在其之上,并调用几个函数。您可以使用列表理解获得相同的结果contains()
或者not in
直接操作员:
>>>>>> [point for point in points if not contains(point, 0)]
[(1, 3), (3, 7), (8, 3)]
>>> [point for point in points if 0 not in point]
[(1, 3), (3, 7), (8, 3)]
上面的列表推导式比等效的列表推导式更短并且可以说更具可读性filter()
从前面的例子中调用。它们也不太复杂,因为您不需要创建lambda
函数或调用list()
,所以你减少了知识要求。
支持用户定义的类中的成员资格测试
提供一个.__包含__()方法是在您自己的类中支持成员资格测试的最明确和首选的方法。 Python会自动调用这个特殊方法当您在成员资格测试中使用类的实例作为正确的操作数时。
您可能会添加一个.__contains__()
方法仅适用于用作值集合的类。这样,类的用户将能够确定给定值是否存储在类的特定实例中。
举个例子,假设您需要创建一个最小的堆数据结构来存储以下值LIFO(后进先出)原则。自定义数据结构的要求之一是支持成员资格测试。因此,您最终编写了以下课程:
# stack.py
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __contains__(self, item):
return item in self.items
你的Stack
类支持堆栈数据结构的两个核心功能。你可以推一个值到栈顶并且pop来自堆栈顶部的值。请注意,您的数据结构使用list
对象在引擎盖下存储和操作实际数据。
您的班级还支持会员测试in
和not in
运营商。为此,该类实现了.__contains__()
方法依赖于in
运营商本身。
要试用您的课程,请继续运行以下代码:
>>>>>> from stack import Stack
>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)
>>> 2 in stack
True
>>> 42 in stack
False
>>> 42 not in stack
True
你们班完全支持in
和not in
运营商。做得好!您现在知道如何支持您自己的班级中的会员测试。
请注意,如果给定的类有.__contains__()
方法,那么该类不必是可迭代的,成员运算符也可以工作。在上面的例子中,Stack
不可迭代,并且运算符仍然可以工作,因为它们从.__contains__()
方法。
除了提供一个之外,至少还有两种方法来支持用户定义的类中的成员资格测试.__contains__()
方法。如果您的班级有.__iter__()或一个.__getitem__()方法,那么in
和not in
运营商也工作。
考虑以下替代版本Stack
:
# stack.py
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __iter__(self):
yield from self.items
这.__iter__()
特殊的方法让你的班级可迭代的,这足以让成员资格测试发挥作用。来吧,尝试一下!
支持成员资格测试的另一种方法是实施.__getitem__()
在类中使用从零开始的整数索引来处理索引操作的方法:
# stack.py
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __getitem__(self, index):
return self.items[index]
Python 自动调用.__getitem__()
执行时的方法索引操作在底层对象上。在此示例中,当您执行以下操作时stack[0]
,您将获得第一项Stack
实例。 Python 利用.__getitem__()
使会员运营商正常运作。