@@ -44,14 +44,14 @@ So, here we go...
4444 + [ ▶ The sticky output function] ( #-the-sticky-output-function )
4545 + [ ▶ The chicken-egg problem * ] ( #-the-chicken-egg-problem- )
4646 + [ ▶ Subclass relationships] ( #-subclass-relationships )
47+ + [ ▶ Methods equality and identity] ( #-methods-equality-and-identity )
4748 + [ ▶ All-true-ation * ] ( #-all-true-ation- )
4849 + [ ▶ The surprising comma] ( #-the-surprising-comma )
4950 + [ ▶ Strings and the backslashes] ( #-strings-and-the-backslashes )
5051 + [ ▶ not knot!] ( #-not-knot )
5152 + [ ▶ Half triple-quoted strings] ( #-half-triple-quoted-strings )
5253 + [ ▶ What's wrong with booleans?] ( #-whats-wrong-with-booleans )
5354 + [ ▶ Class attributes and instance attributes] ( #-class-attributes-and-instance-attributes )
54- + [ ▶ Non-reflexive class method * ] ( #-non-reflexive-class-method- )
5555 + [ ▶ yielding None] ( #-yielding-none )
5656 + [ ▶ Yielding from... return! * ] ( #-yielding-from-return- )
5757 + [ ▶ Nan-reflexivity * ] ( #-nan-reflexivity- )
@@ -1122,6 +1122,107 @@ The Subclass relationships were expected to be transitive, right? (i.e., if `A`
11221122
11231123-- -
11241124
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+
11251226# ## ▶ All-true-ation *
11261227
11271228< !-- Example ID : dfe6d845- e452- 48fe - a2da- 0ed3869a8042 -- >
@@ -1451,49 +1552,6 @@ True
14511552
14521553---
14531554
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-
14971555### ▶ yielding None
14981556<!-- Example ID: 5a40c241-2c30-40d0-8ba9-cf7e097b3b53 --->
14991557```py
0 commit comments