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..7137d07 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) @@ -51,7 +52,6 @@ So, here we go... + [▶ Half triple-quoted strings](#-half-triple-quoted-strings) + [▶ What's wrong with booleans?](#-whats-wrong-with-booleans) + [▶ Class attributes and instance attributes](#-class-attributes-and-instance-attributes) - + [▶ Non-reflexive class method *](#-non-reflexive-class-method-) + [▶ yielding None](#-yielding-none) + [▶ Yielding from... return! *](#-yielding-from-return-) + [▶ Nan-reflexivity *](#-nan-reflexivity-) @@ -1122,6 +1122,107 @@ 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). +```py +>>> o1.method +> +``` +* Accessing the attribute multiple times creates a method object every time! Therefore `o1.method is o1.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. +```py +>>> SomeClass.method + +``` +* `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. +```py +>>> o1.classm +> +``` +* 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. +```py +>>> SomeClass.classm +> +``` +* 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. +```py +>>> o1.staticm + +>>> SomeClass.staticm + +``` +* Having to create new "method" objects every time Python calls instance methods and having to modify the arguments +every time in order to insert `self` 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 * @@ -1451,49 +1552,6 @@ True --- -### ▶ Non-reflexive class method * - - - -```py -class SomeClass: - def instance_method(self): - pass - - @classmethod - def class_method(cls): - pass -``` - -**Output:** - -```py ->>> SomeClass.instance_method is SomeClass.instance_method -True ->>> SomeClass.class_method is SomeClass.class_method -False ->>> id(SomeClass.class_method) == id(SomeClass.class_method) -True -``` - -#### 💡 Explanation: - -- The reason `SomeClass.class_method is SomeClass.class_method` is `False` is due to the `@classmethod` decorator. - - ```py - >>> SomeClass.instance_method - - >>> SomeClass.class_method - - ``` - - A new bound method every time `SomeClass.class_method` is accessed. - -- `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). - ---- - - ### ▶ yielding None ```py