Skip to content

Commit 70061a3

Browse files
authored
Create classes.md
1 parent dbf7c15 commit 70061a3

File tree

1 file changed

+382
-0
lines changed

1 file changed

+382
-0
lines changed

classes.md

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
# Defining and using custom classes in Python
2+
3+
When I was getting started in Python I learned to make classes for
4+
tkinter GUI's before I understood how they work. Everything I did with
5+
classes worked, but I didn't understand how. Hopefully you'll first
6+
learn to understand classes, and then learn to use them.
7+
8+
### Why should I use custom classes in my projects?
9+
10+
Python comes with a lot of classes that you are already familiar with.
11+
12+
```py
13+
>>> str
14+
<class 'str'>
15+
>>> int
16+
<class 'int'>
17+
>>> list
18+
<class 'list'>
19+
>>> dict
20+
<class 'dict'>
21+
>>>
22+
```
23+
24+
Calling these classes as if they were functions makes a new **instance**
25+
of them. For example, `str()` makes a `str` instance, also known as a
26+
string.
27+
28+
```py
29+
>>> str()
30+
''
31+
>>> int()
32+
0
33+
>>> list()
34+
[]
35+
>>> dict()
36+
{}
37+
>>>
38+
```
39+
40+
Let's say you make a program that processes data about website. With a
41+
custom class, you're not limited to `str`, `int` and other classes
42+
Python comes with. Instead you can define a Website class, and make
43+
Websites and process information about websites directly. Defining your
44+
own object types like this is called **object-orientated programming**.
45+
46+
### First class
47+
48+
In Python, `pass` does nothing.
49+
50+
```py
51+
>>> pass
52+
>>>
53+
```
54+
55+
Let's use it to define an empty class.
56+
57+
```py
58+
>>> class Website:
59+
... pass
60+
...
61+
>>> Website
62+
<class '__main__.Website'>
63+
>>>
64+
```
65+
66+
Note that I named the class `Website`, not `website`. This way we know
67+
that it's a class. Built-in classes use lowercase names (like `str`
68+
instead of `Str`) because they are faster to type, but use CapsWord
69+
names for your classes.
70+
71+
Now we can make a Website instance by calling the class.
72+
73+
```py
74+
>>> stackoverflow = Website()
75+
>>> stackoverflow
76+
<__main__.Website object at 0x7f36e4c456d8>
77+
>>> type(stackoverflow)
78+
<class '__main__.Website'>
79+
>>>
80+
```
81+
82+
We can attach more information about stackoverflow to the new Website
83+
instance.
84+
85+
```py
86+
>>> stackoverflow.url = 'http://stackoverflow.com/'
87+
>>> stackoverflow.founding_year = 2008
88+
>>> stackoverflow.free_to_use = True
89+
>>>
90+
```
91+
92+
We can also access the information easily.
93+
94+
```py
95+
>>> stackoverflow.url
96+
'http://stackoverflow.com/'
97+
>>> stackoverflow.founding_year
98+
2008
99+
>>> stackoverflow.free_to_use
100+
True
101+
>>>
102+
```
103+
104+
`url`, `founding_year` and `free_to_use` are not variables, they are
105+
**attributes**. More specifically, they are **instance attributes**.
106+
107+
If we make another Website, does it have the same `url`, `founding_year`
108+
and `free_to_use`?
109+
110+
```py
111+
>>> effbot = Website()
112+
>>> effbot.url
113+
Traceback (most recent call last):
114+
File "<stdin>", line 1, in <module>
115+
AttributeError: 'Website' object has no attribute 'url'
116+
>>>
117+
```
118+
119+
It doesn't. We'd need to define the attributes for effbot also.
120+
121+
The attributes are stored in a dictionary called `__dict__`. It's not
122+
recommended to use it for code that needs to be reliable, but it's a
123+
handy way to see which attributes the instance contains.
124+
125+
```py
126+
>>> stackoverflow.__dict__
127+
{'free_to_use': True,
128+
'founding_year': 2008,
129+
'url': 'http://stackoverflow.com/'}
130+
>>> effbot.__dict__
131+
{}
132+
>>>
133+
```
134+
135+
### Class attributes
136+
137+
What happens if we set an attribute of the `Website` class to some value
138+
instead of doing that to an instance?
139+
140+
```py
141+
>>> Website.is_online = True
142+
>>> Website.is_online
143+
True
144+
>>>
145+
```
146+
147+
Seems to be working, but what happened to the instances?
148+
149+
```py
150+
>>> stackoverflow.is_online
151+
True
152+
>>> effbot.is_online
153+
True
154+
>>>
155+
```
156+
157+
What was that? Setting `Website.is_online` to a value also set
158+
`stackoverflow.is_online` and `effbot.is_online` to that value!
159+
160+
Actually, `is_online` is still not in stackoverflow's or effbot's
161+
`__dict__`. stackoverflow and effbot get that attribute directly from
162+
the `Website` class.
163+
164+
```py
165+
>>> stackoverflow.__dict__
166+
{'free_to_use': True,
167+
'founding_year': 2008,
168+
'url': 'http://stackoverflow.com/'}
169+
>>> effbot.__dict__
170+
{}
171+
>>>
172+
```
173+
174+
In most cases it's not recommended to use class attributes. Using
175+
instance attributes instead is simpler.
176+
177+
### Functions and methods
178+
179+
Let's define a function that prints information about a website.
180+
181+
```py
182+
>>> def website_info(website):
183+
... print("URL:", website.url)
184+
... print("Founding year:", website.founding_year)
185+
... print("Free to use:", website.free_to_use)
186+
...
187+
>>> website_info(stackoverflow)
188+
URL: http://stackoverflow.com/
189+
Founding year: 2008
190+
Free to use: True
191+
>>>
192+
```
193+
194+
Seems to be working. We should be able to get information about all
195+
websites, so maybe we should attach the `website_info` function to the
196+
Website class?
197+
198+
```py
199+
>>> Website.info = website_info
200+
>>> Website.info(stackoverflow)
201+
URL: http://stackoverflow.com/
202+
Founding year: 2008
203+
Free to use: True
204+
>>>
205+
```
206+
207+
It's working, but `Website.info(stackoverflow)` is a lot of typing, so
208+
wouldn't `stackoverflow.info()` be much better?
209+
210+
```py
211+
>>> stackoverflow.info()
212+
URL: http://stackoverflow.com/
213+
Founding year: 2008
214+
Free to use: True
215+
>>>
216+
```
217+
218+
What the heck happened? We didn't define a `stackoverflow.info`, it just
219+
magically worked!
220+
221+
`Website.info` is our `website_info` function, so `stackoverflow.info`
222+
should also be the same function. But `Website.info` takes a `website`
223+
argument, which we didn't give it when we called `stackoverflow.info()`!
224+
225+
But is `stackoverflow.info` the same thing as `Website.info`?
226+
227+
```py
228+
>>> Website.info
229+
<function website_info at 0x7f36e4c39598>
230+
>>> stackoverflow.info
231+
<bound method website_info of <__main__.Website object at 0x7f36e4c456d8>>
232+
>>>
233+
```
234+
235+
It's not.
236+
237+
Instead, `stackoverflow.info` is a **method**. Effbot also has a `.info`
238+
method. So `Website.info(stackoverflow)` does the same thing as
239+
`stackoverflow.info()`, and when `stackoverflow.info()` is called it
240+
automatically gets `stackoverflow` as an argument.
241+
242+
In other words, `Class.method(instance)` does the same thing as
243+
`instance.method()`. This also works with built-in classes, for
244+
example `'hello'.lower()` is same as `str.lower('hello')`.
245+
246+
### Defining methods when defining the class
247+
248+
Maybe we could define a method when we make the class instead of adding
249+
it later?
250+
251+
```py
252+
>>> class Website:
253+
...
254+
... def info(self): # self is a Website instance
255+
... print("URL:", self.url)
256+
... print("Founding year:", self.founding_year)
257+
... print("Free to use:", self.free_to_use)
258+
...
259+
>>> stackoverflow = Website()
260+
>>> stackoverflow.url = 'http://stackoverflow.com/'
261+
>>> stackoverflow.founding_year = 2008
262+
>>> stackoverflow.free_to_use = True
263+
>>> stackoverflow.info()
264+
URL: http://stackoverflow.com/
265+
Founding year: 2008
266+
Free to use: True
267+
>>>
268+
```
269+
270+
It's working. The `self` argument in `Website.info` was `stackoverflow`.
271+
You could call it something else too such as `me`, `this` or `instance`,
272+
but use `self` instead. Other Python users have gotten used to it, and
273+
the official style guide recommens it also.
274+
275+
We still need to set `url`, `founding_year` and `free_to_use` manually.
276+
Maybe we could add a method to do that?
277+
278+
```py
279+
>>> class Website:
280+
...
281+
... def initialize(self, url, founding_year, free_to_use):
282+
... self.url = url
283+
... self.founding_year = founding_year
284+
... self.free_to_use = free_to_use
285+
...
286+
... def info(self):
287+
... print("URL:", self.url)
288+
... print("Founding year:", self.founding_year)
289+
... print("Free to use:", self.free_to_use)
290+
...
291+
>>> stackoverflow = Website()
292+
>>> stackoverflow.initialize('http://stackoverflow.com/', 2008, True)
293+
>>> stackoverflow.info()
294+
URL: http://stackoverflow.com/
295+
Founding year: 2008
296+
Free to use: True
297+
>>>
298+
```
299+
300+
That works. The attributes we defined in the initialize method are also
301+
available in the info method. We could also access them directly from
302+
`stackoverflow`, for example with `stackoverflow.url`.
303+
304+
But we still need to call `stackoverflow.initialize`. In Python, there's
305+
a "magic" method that runs when we create a new Website by calling the
306+
Website class. It's called `__init__` and it does nothing by default.
307+
308+
```py
309+
>>> class Website:
310+
...
311+
... def __init__(self, url, founding_year, free_to_use):
312+
... self.url = url
313+
... self.founding_year = founding_year
314+
... self.free_to_use = free_to_use
315+
...
316+
... def info(self):
317+
... print("URL:", self.url)
318+
... print("Founding year:", self.founding_year)
319+
... print("Free to use:", self.free_to_use)
320+
...
321+
>>> stackoverflow = Website('http://stackoverflow.com/', 2008, True)
322+
>>> stackoverflow.info()
323+
URL: http://stackoverflow.com/
324+
Founding year: 2008
325+
Free to use: True
326+
>>>
327+
```
328+
329+
Classes have many other magic methods too, but I'm not going to cover
330+
them in this tutorial.
331+
332+
### When should I use classes?
333+
334+
Don't do this:
335+
336+
```py
337+
class MyProgram:
338+
339+
def __init__(self):
340+
print("Hello!")
341+
word = input("Enter something: ")
342+
print("You entered " + word + ".")
343+
344+
345+
program = MyProgram()
346+
```
347+
348+
Usually you shouldn't use a class if you're only going to make one
349+
instance of it, and you don't need a class either if you're only going
350+
to have one method. In this example `MyProgram` has only one method and
351+
only one instance.
352+
353+
Make functions instead, or just write your code without any functions if
354+
it's short enough for that. This program does the same thing and it's
355+
much more readable:
356+
357+
```py
358+
print("Hello!")
359+
word = input("Enter something: ")
360+
print("You entered " + word + ".")
361+
```
362+
363+
### Important things
364+
365+
Here are some of the most important things covered in this tutorial.
366+
Make sure you understand them.
367+
368+
- Object-orientated programming is programming with custom data types.
369+
In Python that means using classes and instances.
370+
- Use CapsWords for class names and lowercase_words_with_underscores for
371+
other names. This makes it easy to see which objects are classes and
372+
which objects are instances.
373+
- Calling a class as if it was a function makes a new instance of it.
374+
- `foo.bar = baz` sets `foo`'s attribute `bar` to `baz`.
375+
- Use class attributes for functions and instance attributes for other
376+
things.
377+
- Functions as class attributes can be accessed as instance methods.
378+
They get their instance as the first argument. Call that `self` when
379+
you define the method.
380+
- `__init__` is a special method, and it's ran when a new instance of a
381+
class is created. It does nothing by default.
382+
- Don't use classes if your code is easier to read without them.

0 commit comments

Comments
 (0)