@@ -44,14 +44,14 @@ So, here we go...
44
44
+ [ ▶ The sticky output function] ( #-the-sticky-output-function )
45
45
+ [ ▶ The chicken-egg problem * ] ( #-the-chicken-egg-problem- )
46
46
+ [ ▶ Subclass relationships] ( #-subclass-relationships )
47
+ + [ ▶ Methods equality and identity] ( #-methods-equality-and-identity )
47
48
+ [ ▶ All-true-ation * ] ( #-all-true-ation- )
48
49
+ [ ▶ The surprising comma] ( #-the-surprising-comma )
49
50
+ [ ▶ Strings and the backslashes] ( #-strings-and-the-backslashes )
50
51
+ [ ▶ not knot!] ( #-not-knot )
51
52
+ [ ▶ Half triple-quoted strings] ( #-half-triple-quoted-strings )
52
53
+ [ ▶ What's wrong with booleans?] ( #-whats-wrong-with-booleans )
53
54
+ [ ▶ Class attributes and instance attributes] ( #-class-attributes-and-instance-attributes )
54
- + [ ▶ Non-reflexive class method * ] ( #-non-reflexive-class-method- )
55
55
+ [ ▶ yielding None] ( #-yielding-none )
56
56
+ [ ▶ Yielding from... return! * ] ( #-yielding-from-return- )
57
57
+ [ ▶ Nan-reflexivity * ] ( #-nan-reflexivity- )
@@ -1122,6 +1122,107 @@ The Subclass relationships were expected to be transitive, right? (i.e., if `A`
1122
1122
1123
1123
-- -
1124
1124
1125
+ # ## ▶ Methods equality and identity
1126
+ < !-- Example ID : 94802911 - 48fe - 4242 - defa- 728ae893fa32 -- ->
1127
+
1128
+ 1 .
1129
+ ```py
1130
+ class SomeClass:
1131
+ def method(self ):
1132
+ pass
1133
+
1134
+ @ classmethod
1135
+ def classm(cls ):
1136
+ pass
1137
+
1138
+ @ staticmethod
1139
+ def staticm():
1140
+ pass
1141
+ ```
1142
+
1143
+ ** Output:**
1144
+ ```py
1145
+ >> > print (SomeClass.method is SomeClass.method)
1146
+ True
1147
+ >> > print (SomeClass.classm is SomeClass.classm)
1148
+ False
1149
+ >> > print (SomeClass.classm == SomeClass.classm)
1150
+ True
1151
+ >> > print (SomeClass.staticm is SomeClass.staticm)
1152
+ True
1153
+ ```
1154
+
1155
+ Accessing `classm` twice, we get an equal object , but not the * same* one? Let' s see what happens
1156
+ with instances of `SomeClass` :
1157
+
1158
+ 2 .
1159
+ ```py
1160
+ o1 = SomeClass()
1161
+ o2 = SomeClass()
1162
+ ```
1163
+
1164
+ ** Output:**
1165
+ ```py
1166
+ >> > print (o1.method == o2.method)
1167
+ False
1168
+ >> > print (o1.method == o1.method)
1169
+ True
1170
+ >> > print (o1.method is o1.method)
1171
+ False
1172
+ >> > print (o1.classm is o1.classm)
1173
+ False
1174
+ >> > print (o1.classm == o1.classm == o2.classm == SomeClass.classm)
1175
+ True
1176
+ >> > print (o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
1177
+ True
1178
+ ```
1179
+
1180
+ Accessing` classm` or `method` twice, creates equal but not * same* objects for the same instance of `SomeClass` .
1181
+
1182
+ # ### 💡 Explanation
1183
+ * Functions are [descriptors](https:// docs.python.org/ 3 / howto/ descriptor.html). Whenever a function is accessed as an
1184
+ attribute, the descriptor is invoked, creating a method object which " binds" the function with the object owning the
1185
+ attribute. If called, the method calls the function, implicitly passing the bound object as the first argument
1186
+ (this is how we get `self ` as the first argument, despite not passing it explicitly).
1187
+ ```py
1188
+ >> > o1.method
1189
+ < bound method SomeClass.method of < __main__.SomeClass object at ... >>
1190
+ ```
1191
+ * Accessing the attribute multiple times creates a method object every time! Therefore `o1.method is o1.method` is
1192
+ never truthy. Accessing functions as class attributes (as opposed to instance) does not create methods, however; so
1193
+ `SomeClass.method is SomeClass.method` is truthy.
1194
+ ```py
1195
+ >> > SomeClass.method
1196
+ < function SomeClass.method at ... >
1197
+ ```
1198
+ * `classmethod ` transforms functions into class methods. Class methods are descriptors that, when accessed, create
1199
+ a method object which binds the * class * (type ) of the object , instead of the object itself.
1200
+ ```py
1201
+ >> > o1.classm
1202
+ < bound method SomeClass.classm of < class ' __main__.SomeClass' >>
1203
+ ```
1204
+ * Unlike functions, `classmethod ` s will create a method also when accessed as class attributes (in which case they
1205
+ bind the class , not to the type of it). So `SomeClass.classm is SomeClass.classm` is falsy.
1206
+ ```py
1207
+ >> > SomeClass.classm
1208
+ < bound method SomeClass.classm of < class ' __main__.SomeClass' >>
1209
+ ```
1210
+ * A method object compares equal when both the functions are equal, and the bound objects are the same. So
1211
+ `o1.method == o1.method` is truthy, although not the same object in memory.
1212
+ * `staticmethod ` transforms functions into a " no-op" descriptor, which returns the function as - is . No method
1213
+ objects are ever created, so comparison with `is ` is truthy.
1214
+ ```py
1215
+ >> > o1.staticm
1216
+ < function SomeClass.staticm at ... >
1217
+ >> > SomeClass.staticm
1218
+ < function SomeClass.staticm at ... >
1219
+ ```
1220
+ * Having to create new " method" objects every time Python calls instance methods and having to modify the arguments
1221
+ every time in order to insert `self ` affected performance badly.
1222
+ CPython 3.7 [solved it](https:// bugs.python.org/ issue26110) by introducing new opcodes that deal with calling methods
1223
+ without creating the temporary method objects. This is used only when the accessed function is actually called, so the
1224
+ snippets here are not affected, and still generate methods :)
1225
+
1125
1226
# ## ▶ All-true-ation *
1126
1227
1127
1228
< !-- Example ID : dfe6d845- e452- 48fe - a2da- 0ed3869a8042 -- >
@@ -1451,49 +1552,6 @@ True
1451
1552
1452
1553
---
1453
1554
1454
- ### ▶ Non-reflexive class method *
1455
-
1456
- <!-- Example ID: 3649771a-f733-413c-8060-3f9f167b83fd -->
1457
-
1458
- ```py
1459
- class SomeClass:
1460
- def instance_method(self):
1461
- pass
1462
-
1463
- @classmethod
1464
- def class_method(cls):
1465
- pass
1466
- ```
1467
-
1468
- **Output:**
1469
-
1470
- ```py
1471
- >>> SomeClass.instance_method is SomeClass.instance_method
1472
- True
1473
- >>> SomeClass.class_method is SomeClass.class_method
1474
- False
1475
- >>> id(SomeClass.class_method) == id(SomeClass.class_method)
1476
- True
1477
- ```
1478
-
1479
- #### 💡 Explanation:
1480
-
1481
- - The reason `SomeClass.class_method is SomeClass.class_method` is `False` is due to the `@classmethod` decorator.
1482
-
1483
- ```py
1484
- >>> SomeClass.instance_method
1485
- <function __main__.SomeClass.instance_method(self)>
1486
- >>> SomeClass.class_method
1487
- <bound method SomeClass.class_method of <class '__main__.SomeClass'>
1488
- ```
1489
-
1490
- A new bound method every time `SomeClass.class_method` is accessed.
1491
-
1492
- - `id(SomeClass.class_method) == id(SomeClass.class_method)` returned `True` because the second allocation of memory for `class_method` happened at the same location of first deallocation (See Deep Down, we're all the same example for more detailed explanation).
1493
-
1494
- ---
1495
-
1496
-
1497
1555
### ▶ yielding None
1498
1556
<!-- Example ID: 5a40c241-2c30-40d0-8ba9-cf7e097b3b53 --->
1499
1557
```py
0 commit comments