From f13b98e6d56d8eac1225c198bac8c9e713ebeea7 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Tue, 3 Nov 2020 01:55:49 +0200 Subject: [PATCH] Add section on methods equality and identity Closes: #233 --- CONTRIBUTORS.md | 2 +- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9dd1d2b..659a382 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,7 +21,7 @@ Following are the wonderful people (in no specific order) who have contributed t | Ghost account | N/A | [#96](https://github.com/satwikkansal/wtfpython/issues/96) | koddo | [koddo](https://github.com/koddo) | [#80](https://github.com/satwikkansal/wtfpython/issues/80), [#73](https://github.com/satwikkansal/wtfpython/issues/73) | | jab | [jab](https://github.com/jab) | [#77](https://github.com/satwikkansal/wtfpython/issues/77) | -| Jongy | [Jongy](https://github.com/Jongy) | [#208](https://github.com/satwikkansal/wtfpython/issues/208), [#210](https://github.com/satwikkansal/wtfpython/issues/210) | +| 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) | | Diptangsu Goswami | [diptangsu](https://github.com/diptangsu) | [#193](https://github.com/satwikkansal/wtfpython/issues/193) | --- diff --git a/README.md b/README.md index ea95470..f2a8691 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ So, here we go... + [▶ The sticky output function](#-the-sticky-output-function) + [▶ The chicken-egg problem *](#-the-chicken-egg-problem-) + [▶ Subclass relationships](#-subclass-relationships) + + [▶ Methods equality and identity](#-methods-equality-and-identity) + [▶ All-true-ation *](#-all-true-ation-) + [▶ The surprising comma](#-the-surprising-comma) + [▶ Strings and the backslashes](#-strings-and-the-backslashes) @@ -1122,6 +1123,84 @@ The Subclass relationships were expected to be transitive, right? (i.e., if `A` --- +### ▶ Methods equality and identity + + +1. +```py +class SomeClass: + def method(self): + pass + + @classmethod + def classm(cls): + pass + + @staticmethod + def staticm(): + pass +``` + +**Output:** +```py +>>> print(SomeClass.method is SomeClass.method) +True +>>> print(SomeClass.classm is SomeClass.classm) +False +>>> print(SomeClass.classm == SomeClass.classm) +True +>>> print(SomeClass.staticm is SomeClass.staticm) +True +``` + +Accessing `classm` twice, we get an equal object, but not the *same* one? Let's see what happens +with instances of `SomeClass`: + +2. +```py +o1 = SomeClass() +o2 = SomeClass() +``` + +**Output:** +```py +>>> print(o1.method == o2.method) +False +>>> print(o1.method == o1.method) +True +>>> print(o1.method is o1.method) +False +>>> print(o1.classm is o1.classm) +False +>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm) +True +>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm) +True +``` + +Accessing` classm` or `method` twice, creates equal but not *same* objects for the same instance of `SomeClass`. + +#### 💡 Explanation +* Functions are [descriptors](https://docs.python.org/3/howto/descriptor.html). Whenever a function is accessed as an +attribute, the descriptor is invoked, creating a method object which "binds" the function with the object owning the +attribute. If called, the method calls the function, implicitly passing the bound object as the first argument +(this is how we get `self` as the first argument, despite not passing it explicitly). +* Accessing the attribute multiple times creates multiple method objects! Therefore `o1.method is o2.method` is +never truthy. Accessing functions as class attributes (as opposed to instance) does not create methods, however; so +`SomeClass.method is SomeClass.method` is truthy. +* `classmethod` transforms functions into class methods. Class methods are descriptors that, when accessed, create +a method object which binds the *class* (type) of the object, instead of the object itself. +* Unlike functions, `classmethod`s will create a method also when accessed as class attributes (in which case they +bind the class, not to the type of it). So `SomeClass.classm is SomeClass.classm` is falsy. +* A method object compares equal when both the functions are equal, and the bound objects are the same. So +`o1.method == o1.method` is truthy, although not the same object in memory. +* `staticmethod` transforms functions into a "no-op" descriptor, which returns the function as-is. No method +objects are ever created, so comparison with `is` is truthy. +* Having to create new "method" objects every time Python calls instance methods affected performance badly. +CPython 3.7 [solved it](https://bugs.python.org/issue26110) by introducing new opcodes that deal with calling methods +without creating the temporary method objects. This is used only when the accessed function is actually called, so the +snippets here are not affected, and still generate methods :) + ### ▶ All-true-ation *