def main(): # function
print("Hello world")
if __name__ == '__main__': # then entry point
main()
"""this is a docstring at the top of the module - gives help on the module"""
import this # do imports at the top
def main(): # main at top or bottom
"""functions get docstrings too"""
if __name__... always at the bottom, assumed from now on
Keywords are special. You can't reuse keywords as names for other things.
Now #Python has 33 reserved keywords - in an interpreter:
>>> import keyword
>>> len(keyword.kwlist)
33
>>> import builtins
>>> len([name for name in dir(builtins) if name[0].islower()])
72
len is a function that tells you how long a list or other sized container is.
Because the function names aren't keywords, you could overwrite them. Don't.
"dir" is a function that gives you a list of names of the attributes of an object.
Above, we use a list comprehension to create a list of function names.
First part (map), required: `f(X)` - just X is X (like below)
Middle, required: `for X in iterable`
Last (filter), optional: `if filter(X)`
Builtin functions start with lowercase letters:
[name for name in dir(builtins) if name[0].islower()]
"import", "def", "for", "in", and "if" are keywords - Python will stop you from overwriting them.
"len" and "dir" are functions, Python *won't* stop you from overwriting them.
Use descriptive names.
For example:
import builtins
def list_builtin_functions():
"""returns a list of the builtin functions"""
return [name for name in dir(builtins) if name[0].islower()]
Please be careful about your naming!
>>> subscriptable = "a string"
>>> subscriptable[0]
'a'
With negative indexes, start from the end:
>>> subscriptable[-1]
'g'
Now recall that everything is an object. Some objects can be changed, in-place. Others can't.
This property is called mutability.
Immutable objects can't be changed in-place.
Immutable objects include all numbers, strings of characters, tuples (which are like lists), and frozensets (like sets).
You can do math with them - they follow the algebraic order of operations.
Parentheses, Exponents (use **), Mult, Div, Add, and Sub, or PEMDAS.
Floor division uses a double slash, //
>>> 5 // 3
1
Modulo uses the % sign:
>>> 5 % 3
2
"5 divided by 3 is 1 with a remainder of 2."
This is the public API. str has 44 such public methods:
>>> len([attr for attr in dir(str) if not attr.startswith('_')])
44
def public_api(obj):
"""The public API of an object is the collection of
attributes that don't start with an underscore.
"""
return [attr for attr in dir(obj) if not attr.startswith('_')]
>>> public_api(int)
['bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
>>> public_api(str)
['capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace'...
Some we use more than others. Common is .split and .join.
(Note that .join is a method of the joining string):
>>> jenny = "555.867.5309"
>>> jenny.split('.')
['555', '867', '5309']
>>> '-'.join(jenny.split('.'))
'555-867-5309'
>>> jenny.replace('.', '-')
'555-867-5309'
We've also seen .startswith:
>>> jenny.startswith('555')
True
There's also .endswith:
>>> jenny.endswith('309')
True
When we bind multiple names to a value that is immutable, and then seemingly change one name, the object isn't changed, instead the name now points to a new object created with information from the original and your modification.
>>> foo = bar = 'blah'
>>> foo
'blah'
>>> bar
'blah'
When foo is "modified" it just points to a new string:
>>> foo += " what?"
>>> foo
'blah what?'
>>> bar
'blah'
.casefold (for comparing case insensitive)
.maketrans to make a table for .translate
.(r)partition
.rsplit (limit splits in both directions)
.(l/r)strip to remove whitespace
.splitlines to split on newlines
>>> empty_tuple = ()
>>> empty_tuple
()
Commas req'd for a non-empty tuples:
>>> one_tuple = 'one element',
>>> one_tuple
('one element',)
>>> two_tuple = 'foo', 'bar'
>>> two_tuple
('foo', 'bar')
>>> a_tuple = 1, 2, 3, 4, 5, 4, 3, 2, 1,
This allows you to add lines without version control showing more lines edited than needed.
>>> public_api(tuple)
['count', 'index']
tuple.count returns how many elements are in it:
>>> a_tuple.count(1)
2
.index only returns the index of the first element:
>>> a_tuple.index(4)
3
These methods mostly do what you'd think.:
>>> public_api(list)
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Want to mutate it in place? But keep original? copy it.
>>> a = b = []
>>> a.append('abc')
>>> b
['abc']
and
>>> lists = [[]]*2
>>> lists[0].extend('abc')
>>> lists
[['a', 'b', 'c'], ['a', 'b', 'c']]
In both cases, "both" lists are the *same* list.
(Note extend takes an iterable, while append just adds it.)
They have 1) no semantic order, and 2) only keep unique elements:
>>> a_set = {3,2,1,2,3}
>>> a_set
{1, 2, 3}
You can subtract sets:
>>> a_set - {1,2}
{3}
find their symmetric difference:
>>> a_set ^ {2,3,4}
{1, 4}
>>> a_set | {4,5}
{1, 2, 3, 4, 5}
get their intersection
>>> a_set & {1,2,5}
{1, 2}
Not yet mutated, but they *are* mutable:
>>> a_set
{1, 2, 3}
These operations can be done in-place.
>>> a_set |= {4,5}
>>> a_set
{1, 2, 3, 4, 5}
['add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
#python
Want to be sure?
Do, for example,
>>> help(set.update)
A hash is an arbitrary calculation based on the value of the element.
The hash will always be the same for the life of the process.
>>> hash(1234)
1234
>>> hash('a')
-5912694457115165145
>>> hash('b')
478268980950274941
>>> [hash(i) for i in range(-5, 6)]
[-5, -4, -3, -2, -2, 0, 1, 2, 3, 4, 5]
(hash returns -2 for -1 because -1 is an error in the C code...)
But since hashes are based on the value, and mutable objects values can change, we can't hash mutable objects:
>>> hash(set())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> fs = frozenset('abc')
>>> fs
frozenset({'c', 'b', 'a'})
>>> hash(fs)
-2704306362333158484
Now we can put a set in a set:
>>> a_set = {frozenset('abc'), 2,3}
>>> a_set
{2, 3, frozenset({'c', 'b', 'a'})}
Now the dict object.
The dict is ordered by insertion, mutable mapping of keys->values.
>>> a_dict = dict(a=1, b=2)
>>> a_dict
{'a': 1, 'b': 2}
We can lookup the values by the keys:
>>> a_dict['a']
1
>>> a_dict['c']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'c'
We use the dict.get method to work around this:
>>> a_dict.get('c', 'default')
'default'
The default default is None.
>>> a_dict
{'a': 1, 'b': 2}
>>> a_dict.setdefault('c', 3)
'default'
>>> a_dict
{'a': 1, 'b': 2, 'c': 3}
This is an underappreciated dict method. Know it. Use it.
>>> public_api(dict)
['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
I don't use .fromkeys because of this:
>>> d = dict.fromkeys('ab', [])
>>> d['a'].append('?')
>>> d
{'a': ['?'], 'b': ['?']}
>>> [key * value for key, value in a_dict.items()]
['a', 'bb', 'ccc']
.values just iterates over the values.
a dict iterates over the keys, so to do that just use it:
>>> '-'.join(a_dict)
'a-b-c'
>>> a_dict.keys() - {'a'}
{'c', 'b'}
Again, to find out more, just do help(dict.method)
#python #programming #tutorial #thread
if condition():
do_something()
elif other_condition():
do_something_else()
elif third_condition():
do_third_possible_thing()
else: # otherwise the above were all false so do this:
do_only_other_possible_thing()
if this():
do_that()
else: # No! Use elif instead!
if that():
do_this()
else:
if other():
do_other_thing()
the elif's allow us to avoid repetitive nesting.
if this():
do_this()
if that():
do_that()
This is just to point out that the else and elif's are completely optional...
for each_element in an_iterable:
do_something_with(each_element)
The keywords are "for" and "in".
#python's for loops are for-each loops, they iterate over each element in an iterable.
"continue" - stops the current iteration, but continues the loop.
(Perhaps there could be a better word for it.)
"break" -stops the entire (inner) iteration/loop.
"else" - runs if loop didn't break.
for i in it:
if skip_this_loop_for(i):
continue # go to next i!
if stop_loop(i):
break
# stop looping, else skipped too
do_something(i)
else:
# didn't break
finished_without_breaking()
They can be just:
while condition():
do_something_again()
Trouble-spot: I sometimes see the below when the above is intended:
while True:
do_something_again()
if condition():
break
while looping():
res = prework()
if res:
continue # back up to next loop
if stop_loop():
break # stop looping, else skipped too
do_something()
else:
# didn't break - looping() returned False
no_break()
def do_something(arg, kwarg='default'):
"help on the function"
# more code here
return ...
def func():
yield 'foo'
yield 'bar'
>>> gen = func()
>>> list(gen)
['foo', 'bar']
Generators get used up:
>>> list(gen)
[]
We may call func again though:
>>> list(func())
['foo', 'bar']
Class definitions can define custom objects, data, and methods.
If you have:
- functionality but no data, maybe use modules instead.
- data but no functionality, maybe use builtin datatypes instead.
We can say that the child class "is-a" type of the parent.
Be careful to make child classes substitutable for the parent without breaking their code.
class MyObject(object):
def __init__(self, a, b):
self.a, self,b = a, b
#python's objects have special methods that start and end with "__". They allow custom objects to use Python's syntax:
>>> my_ob = MyObject(2, 3)
>>> my_ob.b
3
Or my answer on #stackoverflow here: stackoverflow.com/q/40272161/541…
Or my #pygotham talk here:
bool, the type of True and False, is a subclass of int
>>> issubclass(bool, int)
True
to recreate this, we might start with:
class Bool(int): ...
How does our program continue? It continues with exception handling, with try-blocks.
try-except: if there's a type of error (or its subclass), handle it. Be specific here to avoid hiding bugs. Do not use a bare except or catch BaseException.
try-finally: error or not, guarantee you do something
try: ...
except SpecificException as exc: ... # be specific!
else: ... # optional, runs if no exception at all, avoids hiding bugs in the try block.
finally: ... # optional
and
try: ...
finally: ...
finally is *guaranteed* to run before the block leaves.
But to get to enough knowledge to be able to write a Python program, you need a pretty good understanding of the builtin functions.
1. introspection,
2. input-output,
3. iteration,
4. math,
5. object oriented,
6. functional, and
7. meta programming
*my* categories, not perfect, but useful to me. Note: does not include datatypes.
- help* -> __doc__
- dir* -> list names
- vars -> __dict__
- type -> __class__
- callable
- repr
- format
- hash*
- id
- len*
- reversed
- sorted
Check out the documentation on these here: docs.python.org/3/library/func…
*we've seen and used these
- input
- open
Iteration includes:
- all
- any
- enumerate
- zip
- range
- iter
- next
- slice
Math includes:
- abs
- sum
- max
- min
- divmod
- pow
- bin
- oct
- hex
- chr
- ord
- round
Again, read the docs on these.
OOP:
(writing)
- super
- property
- staticmethod
- classmethod
(using)
- getattr
- setattr
- delattr
- isinstance
- issubclass
Functional (redundant to list comps & gen exprs):
- map
- filter
Meta:
- eval
- exec
- type
- compile
- exit
- globals
- locals