richardmyu / blog

个人(issues)博客
https://github.com/richardmyu/blog/issues
MIT License
0 stars 0 forks source link

Python 中的方括号运算符和点运算符有什么区别 #22

Open richardmyu opened 3 years ago

richardmyu commented 3 years ago

先接触了 JavaScript,自然而然的觉得两者没有什么区别,直到某次用方括号代替点获取属性报错,然后才意识到在 python 中,两者是有区别的。

先看一个例子:

c = 3 + 4j

print(c.real) // 3.0
print(c.imag) // 4.0
print(c["real"]) // TypeError: 'complex' object is not subscriptable

可以看到用方括号运算符访问属性,会得到一个报错:复杂对象没有下标。

d = {'a': 1, 'b': 2}

print(d["a"]) // 1
print(d.a) // AttributeError: 'dict' object has no attribute 'a'
print(d.pop) // <built-in method pop of dict object at 0x00000258698D2E58>

其实由报错信息可以看出,点运算符是用来访问属性的,而方括号运算符是根据“下标”获取定义的数据的。

下标:在字典中指 key;在列表、元组和字符串中,指索引。

小结

点运算符,用来获取语言内置的属性、方法等等;而方括号运算符则是用来获取用户定义的数据,一般是字典、列表、元组以及字符串的成员。


参考:

1.What's the difference between the square bracket and dot notations in Python?

2.Python中的方括号和点符号有什么区别?

wallstreet1314 commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

richardmyu commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

是的,是有这种感觉。话说这两个词是来自源码吗?还是你自己编的 😄 :

wallstreet1314 commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

是的,是有这种感觉。话说这两个词是来自源码吗?还是你自己编的 😄 :

没看源码,所以猜的,😂,有个还记错了,应该是getattribute,都是保留字方法,list和字典及object都是class type,应该都和魔术方法相关,可以搞个class redict(dict):试着添加一下属性方法

richardmyu commented 3 years ago

补充

1.JavaScript 属性访问表达式

一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法。不过,在 JavaScript 也可以使用方括号表示法来访问对象的属性。从功能上看,这两种访问属性的方法没有任何区别,但方括号语法的主要优点是可以通过变量来访问属性;如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。通常,除非必须使用变量来访问属性,否则我们建议使用点表示法。【1】

在 ECMAScript 3 中,点运算符后的标识符不能是保留字,比如,o.foro.class 是非法的,因为 for 是 JavaScript 的关键字,class 是保留字。如果一个对象的属性名是保留字,则必须使用方括号的形式访问它们,比如 o["for"]o["class"]。ECMASript 5 对此放宽了限制(包括 ECMAScript 3 的某些实现),可以在点运算符后直接使用保留字。【2】

总之,在 JavaScript 中获取属性时,一般使用点运算符,当属性名不是合法字符(比如包含空格),或不知道属性名而用变量代替时,需要用方括号运算符。

还有一种特殊情况,即属性名为数字的情况,必须使用方括号运算符。


参考:

1.JavaScript 高级程序设计(第三版)第五章 引用类型 p85 2.JavaScript 权威指南 第六章 对象 p123

richardmyu commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

是的,是有这种感觉。话说这两个词是来自源码吗?还是你自己编的 😄 :

没看源码,所以猜的,😂,有个还记错了,应该是getattribute,都是保留字方法,list和字典及object都是class type,应该都和魔术方法相关,可以搞个class redict(dict):试着添加一下属性方法

我看的书上没这些玩意 🤣 ,你说的 __getattribute____getitem__ 以及 “魔术方法”,我在网上找到一些资料了,准备细读研究一番 :yum:,感谢你的回复,让我又开阔了眼界 :100:

wallstreet1314 commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

是的,是有这种感觉。话说这两个词是来自源码吗?还是你自己编的 😄 :

没看源码,所以猜的,😂,有个还记错了,应该是getattribute,都是保留字方法,list和字典及object都是class type,应该都和魔术方法相关,可以搞个class redict(dict):试着添加一下属性方法

我看的书上没这些玩意 🤣 ,你说的 __getattribute____getitem__ 以及 “魔术方法”,我在网上找到一些资料了,准备细读研究一番 :yum:,感谢你的回复,让我又开阔了眼界 :100:

不客气,我试着建了一个库,里面重自定义了一种字典类型,并添加了部分属性功能,但无法复制,才开始在py charm里用GitHub,不大会操作,可以去看看,感觉很有意思,似乎整个Python就是一个类对象

richardmyu commented 3 years ago

Python里面全是类,猜测可能是get attractiongetitem的区别

是的,是有这种感觉。话说这两个词是来自源码吗?还是你自己编的 😄 :

没看源码,所以猜的,😂,有个还记错了,应该是getattribute,都是保留字方法,list和字典及object都是class type,应该都和魔术方法相关,可以搞个class redict(dict):试着添加一下属性方法

我看的书上没这些玩意 🤣 ,你说的 __getattribute____getitem__ 以及 “魔术方法”,我在网上找到一些资料了,准备细读研究一番 😋,感谢你的回复,让我又开阔了眼界 💯

不客气,我试着建了一个库,里面重自定义了一种字典类型,并添加了部分属性功能,但无法复制,才开始在py charm里用GitHub,不大会操作,可以去看看,感觉很有意思,似乎整个Python就是一个类对象

好的。关于类,大概是因为 python 是真正面向对象的语言吧,而 JavaScript 不是 😆

richardmyu commented 3 years ago

2.python 属性引用与抽取与切片

属性引用(attribute references)【1】

属性引用是后面带有一个句点加一个名称的原型:

attributeref ::=  primary "." identifier

此原型必须求值为一个支持属性引用的类型的对象,多数对象都支持属性引用。 随后该对象会被要求产生以指定标识符为名称的属性。 这个产生过程可通过重载 __getattr__() 方法来自定义。 如果这个属性不可用,则将引发 AttributeError 异常。 否则的话,所产生对象的类型和值会根据该对象来确定。 对同一属性引用的多次求值可能产生不同的对象。

抽取(subscriptions)【2】

对序列(字符串、元组或列表)或映射(字典)对象的抽取操作通常就是从相应的多项集中选择一 :

subscription ::=  primary "[" expression_list "]"

此原型必须求值为一个支持抽取操作的对象(例如列表或字典)。 用户定义的对象可通过定义 __getitem__() 方法来支持抽取操作。

对于内置对象,有两种类型的对象支持抽取操作:

正式句法规则并没有在序列中设置负标号的特殊保留条款;但是,内置序列所提供的 __getitem__() 方法都可通过在索引中添加序列长度来解析负标号 (这样 x[-1] 会选出 x 中的最后一项)。 结果值必须为一个小于序列中项数的非负整数,抽取操作会选出标号为该值的项(从零开始数)。 由于对负标号和切片的支持存在于对象的 __getitem__() 方法,重载此方法的子类需要显式地添加这种支持。

字符串的项是字符。 字符不是单独的数据类型而是仅有一个字符的字符串。

对特定 类型 的抽取操作会创建一个泛型别名。 在此情况下,用户自定义类型可通过提供 __class_getitem__() 类方法来支持抽取操作。

切片(slicings)【3】

切片就是在序列对象(字符串、元组或列表)中选择某个范围内的 。 切片可被用作表达式以及赋值或 del 语句的目标。 切片的句法如下:

slicing      ::=  primary "[" slice_list "]"
slice_list   ::=  slice_item ("," slice_item)* [","]
slice_item   ::=  expression | proper_slice
proper_slice ::=  [lower_bound] ":" [upper_bound] [ ":" [stride] ]
lower_bound  ::=  expression
upper_bound  ::=  expression
stride       ::=  expression

此处的正式句法中存在一点歧义:任何形似表达式列表的东西同样也会形似切片列表,因此任何抽取操作也可以被解析为切片。 为了不使句法更加复杂,于是通过定义将此情况解析为抽取优先于解析为切片来消除这种歧义(切片列表未包含正确的切片就属于此情况)。

切片的语义如下所述。 原型通过一个根据下面的切片列表来构造的键进行索引(与普通抽取一样使用 __getitem__() 方法):

一个正确切片的转换就是一个切片对象,该对象的 start, stopstep 属性将分别为表达式所给出的下界、上界和步长值,省略的表达式将用 None 来替换。


参考

1.6.3.1. 属性引用 2.6.3.2. 抽取 3.6.3.3. 切片

richardmyu commented 3 years ago

3.__getattr__ and __getattribute__ and __getitem__

__getattr__ 【1】

当默认属性访问因引发 AttributeError 而失败时被调用 (可能是调用 __getattribute__() 时由于 name不是一个实例属性或 self的类关系树中的属性而引发了 AttributeError;或者是对 name 特性属性调用 __get__() 时引发了 AttributeError)。此方法应当返回(找到的)属性值或是引发一个 AttributeError 异常。

请注意如果属性是通过正常机制找到的,__getattr__() 就不会被调用。(这是在 __getattr__()__setattr__() 之间故意设置的不对称性。)这既是出于效率理由也是因为不这样设置的话 __getattr__() 将无法访问实例的其他属性。要注意至少对于实例变量来说,你不必在实例属性字典中插入任何值(而是通过插入到其他对象)就可以模拟对它的完全控制。

__getattribute__ 【2】

此方法会无条件地被调用以实现对类实例属性的访问。如果类还定义了 __getattr__(),则后者不会被调用,除非 __getattribute__() 显式地调用它或是引发了 AttributeError。此方法应当返回(找到的)属性值或是引发一个 AttributeError 异常。为了避免此方法中的无限递归,其实现应该总是调用具有相同名称的基类方法来访问它所需要的任何属性,例如 object.__getattribute__(self, name)

注解:此方法在作为通过特定语法或内置函数隐式地调用的结果的情况下查找特殊方法时仍可能会被跳过。

__getitem__ 【3】

可以定义下列方法(object.__len__object.__getitem__...)来实现容器对象。 容器通常属于序列(如列表或元组)或映射(如字典),但也存在其他形式的容器。

调用此方法以实现 self[key] 的求值。对于序列类型,接受的键应为整数和切片对象。请注意负数索引(如果类想要模拟序列类型)的特殊解读是取决于 __getitem__() 方法。如果 key 的类型不正确则会引发 TypeError 异常;如果为序列索引集范围以外的值(在进行任何负数索引的特殊解读之后)则应引发 IndexError 异常。对于映射类型,如果 key 找不到(不在容器中)则应引发 KeyError 异常。


参考

1.object.__getattr__ 2.object.__getattribute__ 3.object.__getitem__

richardmyu commented 3 years ago

4.魔术方法

在 Python 中,所有以 “__” 双下划线包起来的方法,都统称为“Magic Method”,中文称魔术方法,这些方法在进行特定的操作时会自动被调用,例如类的初始化方法 __init__

魔术方法,在很多网页、blog 上可以看到,但目前没有看到有人指出其来源或命名原因;而在 python 手册上,并没有什么 'Magic Method',只有 'Special method'。

一个类可以通过定义具有特殊名称的方法来实现由特殊语法所引发的特定操作 (例如算术运算或下标与切片)。这是 Python 实现 操作符重载 的方式,允许每个类自行定义基于操作符的特定行为。例如,如果一个类定义了名为 __getitem__() 的方法,并且 x 为该类的一个实例,则 x[i] 基本就等同于 type(x).__getitem__(x, i)。除非有说明例外情况,在没有定义适当方法的情况下尝试执行一种操作将引发一个异常 (通常为 AttributeErrorTypeError)。【1】

将一个特殊方法设为 None 表示对应的操作不可用。例如,如果一个类将 __iter__() 设为 None,则该类就是不可迭代的,因此对其实例调用 iter() 将引发一个 TypeError (而不会回退至 __getitem__())。【1】


参考

1.3.3. 特殊方法名称