Skip to content

Commit c954b71

Browse files
authored
Merge pull request satwikkansal#241 from Jongy/methods
Add section on methods equality and identity
2 parents 902ca17 + 314de9a commit c954b71

File tree

2 files changed

+103
-45
lines changed

2 files changed

+103
-45
lines changed

CONTRIBUTORS.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Following are the wonderful people (in no specific order) who have contributed t
2121
| Ghost account | N/A | [#96](https://github.com/satwikkansal/wtfpython/issues/96)
2222
| koddo | [koddo](https://github.com/koddo) | [#80](https://github.com/satwikkansal/wtfpython/issues/80), [#73](https://github.com/satwikkansal/wtfpython/issues/73) |
2323
| jab | [jab](https://github.com/jab) | [#77](https://github.com/satwikkansal/wtfpython/issues/77) |
24-
| Jongy | [Jongy](https://github.com/Jongy) | [#208](https://github.com/satwikkansal/wtfpython/issues/208), [#210](https://github.com/satwikkansal/wtfpython/issues/210) |
24+
| Jongy | [Jongy](https://github.com/Jongy) | [#208](https://github.com/satwikkansal/wtfpython/issues/208), [#210](https://github.com/satwikkansal/wtfpython/issues/210), [#233](https://github.com/satwikkansal/wtfpython/issues/233) |
2525
| Diptangsu Goswami | [diptangsu](https://github.com/diptangsu) | [#193](https://github.com/satwikkansal/wtfpython/issues/193) |
2626

2727
---

README.md

+102-44
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)