|
| 1 | +# Magic methods |
| 2 | + |
| 3 | +In [the class tutorial](../basics/classes.md) we learned to define a |
| 4 | +class like this: |
| 5 | + |
| 6 | +```py |
| 7 | +class Website: |
| 8 | + |
| 9 | + def __init__(self, url, founding_year, free_to_use): |
| 10 | + self.url = url |
| 11 | + self.founding_year = founding_year |
| 12 | + self.free_to_use = free_to_use |
| 13 | + |
| 14 | + def info(self): |
| 15 | + print("URL:", self.url) |
| 16 | + print("Founding year:", self.founding_year) |
| 17 | + print("Free to use:", self.free_to_use) |
| 18 | +``` |
| 19 | + |
| 20 | +After doing that we can create a new Website object like |
| 21 | +`Website('https://github.com/', 2008, True)`. Python first creates the |
| 22 | +Website object, and then calls `__init__` with the arguments we passed |
| 23 | +to Website to set it up. Methods that have a name `__like_this__` and a |
| 24 | +special meaning are called **magic methods** or **special methods**. |
| 25 | + |
| 26 | +Most magic methods define what the object has or what it can do, like |
| 27 | +"does it have a length" or "can we for loop over it". There are other |
| 28 | +magic methods that do other things also, like `__init__`. |
| 29 | + |
| 30 | +Some magic methods have a default implementation that is used if the |
| 31 | +class doesn't define anything else. For example, if we don't define an |
| 32 | +`__init__` then our class will take no arguments and it won't have any |
| 33 | +attributes by default. We'll learn more about this when we'll talk about |
| 34 | +[inheritance](classes2.md). |
| 35 | + |
| 36 | +**TODO:** write a `classes2.md`. |
| 37 | + |
| 38 | +## Custom len() |
| 39 | + |
| 40 | +Let's get started by defining an object that has a length: |
| 41 | + |
| 42 | +```py |
| 43 | +>>> class Thing: |
| 44 | +... def __len__(self): |
| 45 | +... return 5 |
| 46 | +... |
| 47 | +>>> t = Thing() |
| 48 | +>>> t |
| 49 | +<__main__.Thing object at 0x7f05e4597198> |
| 50 | +>>> t.__len__() |
| 51 | +5 |
| 52 | +>>> len(t) |
| 53 | +5 |
| 54 | +>>> |
| 55 | +``` |
| 56 | + |
| 57 | +This is what most magic methods are like. So far we have learned to use |
| 58 | +`len()` with lists, strings and other built-in types, but now we can |
| 59 | +call `len()` on our own Thing object. Many things can be fully |
| 60 | +customized with magic methods. |
| 61 | + |
| 62 | +Note that magic methods like `__len__` need to be defined in the class, |
| 63 | +just attaching an attribute called `__len__` doesn't work: |
| 64 | + |
| 65 | +```py |
| 66 | +>>> class EmptyThing: |
| 67 | +... pass |
| 68 | +... |
| 69 | +>>> def length(): |
| 70 | +... return 5 |
| 71 | +... |
| 72 | +>>> e = EmptyThing() |
| 73 | +>>> e.__len__ = length |
| 74 | +>>> e.__len__() |
| 75 | +5 |
| 76 | +>>> len(e) |
| 77 | +Traceback (most recent call last): |
| 78 | + File "<stdin>", line 1, in <module> |
| 79 | +TypeError: object of type 'EmptyThing' has no len() |
| 80 | +>>> |
| 81 | +``` |
| 82 | + |
| 83 | +You don't really need to worry about why Python works like this, but |
| 84 | +it's explained |
| 85 | +[here](https://docs.python.org/3/reference/datamodel.html#special-method-lookup) |
| 86 | +if you want to know more about it. |
| 87 | + |
| 88 | +## Custom str() and repr() |
| 89 | + |
| 90 | +You have probably noticed that typing something to the interactive `>>>` |
| 91 | +prompt is not quite the same thing as printing it. For example, |
| 92 | +strings behave like this: |
| 93 | + |
| 94 | +```py |
| 95 | +>>> 'hello' |
| 96 | +'hello' |
| 97 | +>>> print('hello') |
| 98 | +hello |
| 99 | +>>> |
| 100 | +``` |
| 101 | + |
| 102 | +Typing `'hello'` without a print displayed it with quotes, but printing |
| 103 | +it displayed it without the quotes. Both of these can be customized with |
| 104 | +magic methods. `print('hello')` does the same thing as |
| 105 | +`print(str('hello'))`, and typing `'hello'` without a print does roughly |
| 106 | +the same thing as `print(repr('hello'))`. |
| 107 | + |
| 108 | +```py |
| 109 | +>>> repr('hello') # a string that literally contains 'hello' |
| 110 | +"'hello'" |
| 111 | +>>> str('hello') # same as just 'hello' |
| 112 | +'hello' |
| 113 | +>>> print(repr('hello')) |
| 114 | +'hello' |
| 115 | +>>> print(str('hello')) |
| 116 | +hello |
| 117 | +>>> |
| 118 | +``` |
| 119 | + |
| 120 | +As usual, `repr(thing)` does the same thing as `thing.__repr__()` and |
| 121 | +`str(thing)` does the same thing as `thing.__str__()`. Usually you don't |
| 122 | +need to and you shouldn't define `__str__` yourself because `__str__` |
| 123 | +defaults to `__repr__`. |
| 124 | + |
| 125 | +Here's an example that hopefully clarifies things: |
| 126 | + |
| 127 | +```py |
| 128 | +>>> class Website: |
| 129 | +... def __repr__(self): |
| 130 | +... return '<a Website object>' |
| 131 | +... |
| 132 | +>>> w = Website() |
| 133 | +>>> w.__repr__() |
| 134 | +'<a Website object>' |
| 135 | +>>> repr(w) |
| 136 | +'<a Website object>' |
| 137 | +>>> w.__str__() |
| 138 | +'<a Website object>' |
| 139 | +>>> str(w) |
| 140 | +'<a Website object>' |
| 141 | +>>> w |
| 142 | +<a Website object> |
| 143 | +>>> print(w) |
| 144 | +<a Website object> |
| 145 | +>>> |
| 146 | +``` |
| 147 | + |
| 148 | +The `repr()` values can also be easily used with string formatting. |
| 149 | +Instead of this... |
| 150 | + |
| 151 | +```py |
| 152 | +>>> message = 'hello' |
| 153 | +>>> print("message is " + repr(message)) |
| 154 | +message is 'hello' |
| 155 | +>>> |
| 156 | +``` |
| 157 | + |
| 158 | +...you can do this: |
| 159 | + |
| 160 | +```py |
| 161 | +>>> print("message is %r" % message) |
| 162 | +message is 'hello' |
| 163 | +>>> print("message is {!r}".format(message)) |
| 164 | +message is 'hello' |
| 165 | +>>> |
| 166 | +``` |
| 167 | + |
| 168 | +The `__repr__` method can return any string, but usually you should |
| 169 | +follow one of these styles: |
| 170 | + |
| 171 | +1. A piece of code that describes how another, similar object can be |
| 172 | + created. |
| 173 | + |
| 174 | + ```py |
| 175 | + >>> class Website: |
| 176 | + ... def __init__(self, name, founding_year): |
| 177 | + ... self.name = name |
| 178 | + ... self.founding_year = founding_year |
| 179 | + ... def __repr__(self): |
| 180 | + ... return 'Website(name=%r, founding_year=%r)' % ( |
| 181 | + ... self.name, self.founding_year) |
| 182 | + ... |
| 183 | + >>> github = Website('GitHub', 2008) |
| 184 | + >>> github |
| 185 | + Website(name='GitHub', founding_year=2008) |
| 186 | + >>> |
| 187 | + ``` |
| 188 | + |
| 189 | + This is useful for simple data containers like this Website class. |
| 190 | + |
| 191 | +2. A description of the object wrapped between `<` and `>`. |
| 192 | + |
| 193 | + ```py |
| 194 | + >>> class Website: |
| 195 | + ... def __init__(self, name, founding_year): |
| 196 | + ... self.name = name |
| 197 | + ... self.founding_year = founding_year |
| 198 | + ... def __repr__(self): |
| 199 | + ... return '<Website %r, founded in %r>' % ( |
| 200 | + ... self.name, self.founding_year) |
| 201 | + ... |
| 202 | + >>> github = Website('GitHub', 2008) |
| 203 | + >>> github |
| 204 | + <Website 'GitHub', founded in 2008> |
| 205 | + >>> |
| 206 | + ``` |
| 207 | + |
| 208 | + This style is good when you want to tell more about the object than |
| 209 | + you can by showing the `__init__` arguments. Python's built-in |
| 210 | + things also use this style more: |
| 211 | + |
| 212 | + ```py |
| 213 | + >>> import random |
| 214 | + >>> random |
| 215 | + <module 'random' from '/some/path/random.py'> |
| 216 | + >>> |
| 217 | + ``` |
| 218 | + |
| 219 | +## Other magic methods |
| 220 | + |
| 221 | +There are many more magic methods, and I don't see any reason to list |
| 222 | +them all here. [The official |
| 223 | +documentation](https://docs.python.org/3/reference/datamodel.html) has |
| 224 | +more information about magic methods if you need it. You can also just |
| 225 | +keep reading this tutorial, and we'll learn more about some of the most |
| 226 | +useful and commonly used magic methods. |
| 227 | + |
| 228 | +## When should we use magic methods? |
| 229 | + |
| 230 | +Usually magic methods are overkill. `website.has_user(user)` or |
| 231 | +`user in website.userlist` is way better than something weird like |
| 232 | +`user @ website`. People expect `website.has_user(user)` to check if |
| 233 | +the user has registered on the website, but nobody knows what |
| 234 | +`user @ website` does. Explicit is better than implicit, and simple is |
| 235 | +better than complex. |
| 236 | + |
| 237 | +On the other hand, adding informative `__repr__` methods can make a |
| 238 | +module much nicer to use. I recommend using `__repr__` methods in things |
| 239 | +that other people will import and use in their projects, but `__repr__` |
| 240 | +methods aren't worth it for simple scripts that aren't meant to be |
| 241 | +imported. |
| 242 | + |
| 243 | +## Summary |
| 244 | + |
| 245 | +- Magic methods define what instances of a class can do and how, like |
| 246 | + "does it have a length" or "what does it look like when I print it". |
| 247 | +- Python uses magic methods to implement many things internally. |
| 248 | +- Defining custom `__repr__` methods is often a good idea when making |
| 249 | + things that other people will import and use in their own projects. |
| 250 | + Other than that, magic methods are usually not worth it. |
| 251 | + |
| 252 | +*** |
| 253 | + |
| 254 | +You may use this tutorial freely at your own risk. See |
| 255 | +[LICENSE](../LICENSE). |
| 256 | + |
| 257 | +[Previous](../basics/classes.md) | [Next](iterators.md) | |
| 258 | +[List of contents](../README.md#advanced) |
0 commit comments