lists=[10,12,14,16,18]print(lists[0])# Index starts at 0print(lists[-1])# Last index at -1print(lists[0:3])# Slicing: exclusive of end value# i.e. get i=(0, 1, .. n-1)print(lists[3:])# "slice from i=3 to end"

Note: notice that the consistent order on printing of the dictionaries, even though the inputs are reordered. The ordering of hash tables is well defined, but not in a human-intuitive sense. We should therefore treat the data as if it was unordered.

# I don't like pears, so let's buy apples and bananasshopping_list=[('apples',50),('bananas',20)]total=0foritem,quantityinshopping_list:price=price_table[item]print('Adding {}{} at {}p each'.format(quantity,item,price))total+=price*quantityprint(total)

"Object-oriented programming (OOP) refers to a type of computer programming in which programmers define not only the data type of a data structure, but also the types of operations (functions) that can be applied to the data structure."

Note: Passing of self is done implicitly in other languages e.g. C++ and Java, and proponents of those languages may argue that this is better. "Explicit is better than implicit" is simply the python way.

More info: The Constructor creates the instance, and the Initialiser Initialises its contents. Most languages e.g. C++ refer to these interchangably and perform these steps together, however the new style classes in Python splits the process.

The difference is quite fine, and for most purposes we do not need to redefine the behaviour of __new__. This is discussed in several Stack Overflow threads, e.g.

classContainer(object):"""Simple container which stores an array as an instance attribute and an instance method"""def__init__(self,N):self.data=np.linspace(0,1,N)defplot(self):fig=plt.figure()ax=fig.add_subplot(111)ax.plot(self.data,'bx')mydata=Container(11)# 11 is passed as 'N' to __init__print(mydata.__dict__)# __dict__ is where the attr: value# pairs are stored!mydata.plot()

Note: There's a couple of things going on in this example which are worth elaborating on. By specifying ClassName.attribute, in this case Container.data = 100 we've overwritten the value of data that EVERY instance of the Container class will access. Hence printing b.data gives the expected result.

By setting a.data at the same time, we have set an instance attribute, which is given priority and called first even though we overwrote the class attribute after assigning this.

This could create a hard to track bug. To avoid it:

Stick to instance variables unless you specifically need to share data e.g. constants, total number or list of things that are shared

Don't overwrite things you know are class attributes with instance.attr unless you really know what you're doing (even then, it's probably better and more readable to make it an instance attribute)

For a really in depth explanation of class vs instance attributes, see either of the following links:

Note: This behaviour can be over used in Python. Programmers from C++ or Java backgrounds may want to make all data hidden or private and access the data with 'getter' or 'setter' functions, however it's generally accepted by Python programmers that getters and setters are unnecessary. The Pythonista phrase is "we are all consenting adults here", meaning you should trust the programmer to interact with your classes and they should trust you to document/indicate which parts of the data not to touch unless they know what they're doing (hence the underscore convention). See the top answer on this Stack Overflow thread.

If you were wondering why we should use super().method instead of BaseClass.method, other than the convenience of renaming classes, it relates to multiple inheritance which is beyond the scope of this course. If you need to write programs with multiple inheritance (and there are strong arguments against this), you may want to look at this blog for advanced use of super.

classWave(object):def__init__(self,freq):self.freq=freqself._data=np.sin(np.linspace(0,np.pi,101)*np.pi*2*freq)def__str__(self):"""RETURNS the string for printing"""return"Wave frequency: {}".format(self.freq)def__lt__(self,wave2):returnself.freq<wave2.freqwav_low=Wave(10)wav_high=Wave(50)# A high frequency waveprint(wav_high)wav_low<wav_high