Presentation is loading. Please wait.

Presentation is loading. Please wait.

Classes.

Similar presentations


Presentation on theme: "Classes."— Presentation transcript:

1 Classes

2 Review Object Oriented Programming centres on objects: variables that include other variables and functions. Each object has a particular area of work. Often these objects are written by other people and we use them. Code reuse is easy. We can access the inside of objects using the dot operator "."

3 Class basics

4 Object Oriented Programming
Scripting languages are very high level - you don't need to know how open works to use it. Because of this, OOP is rarer with beginners than in systems/application languages. Nevertheless, OOP: Produces cleaner code that’s easier to maintain. Helps to understand how code works, and therefore the solution to issues. Will be needed if you decide to build anything complicated or with user interactions.

5 Classes The core structure in OOP is the class. Classes are templates for making objects. You make objects ("instantiate" a class; make an instance of a class), like this: object_name = ClassName() Classes are text outlining code much like any other. In Python you can have multiple classes embedded throughout programs, but it is good practice to organise classes in modules. Modules are text files of code, often classes, that work in a particular themed area. Unlike libraries in other languages, it is not usually to have multiple classes in a single text file. Thought therefore needs to be given to reusability as modules are designed.

6 Form of a module/class # Main program import geostuff point_1 = geostuff.GeoPoint() The name of the module is determined by the filename. It is short, lowercase, and all one word. The name of the class starts with a capital and is in CamelCase. The name of the object is lowercase with underscores pass is a keyword that allows you to create empty blocks and clauses that do nothing but compile. To work, the module file should be in the same directory (or somewhere Python knows about). #geostuff.py class GeoPoint(): pass

7 Import import geostuff point_1 = geostuff.GeoPoint() This is a very explicit style. There is little ambiguity about which GeoPoint we are after (if other imported modules have GeoPoint classes). This is safest as you have to be explicit about the module. Provided there aren't two modules with the same name and class, you are fine. If you're sure there are no other GeoPoints, you can: from geostuff import GeoPoint point_1 = GeoPoint() This just imports this one class.

8 NB You will often see imports of everything in a module: from geostuff import * This is easy, because it saves you having to import multiple classes, but it is dangerous: you have no idea what other classes are in there that might replace classes you have imported elsewhere. In other languages, with, frankly better, documentation and file structures, it is easy to find out which classes are in libraries, so you see this a lot. In Python, it is strongly recommended you don't do this. If you get code from elsewhere, change these to explicit imports.

9 As If the module name is very long (it shouldn't be), you can do this: import geostuffthatisuseful as geo point_1 = geo.GeoPoint() If the classname is very long, you can: from useful import GeographicalPointForMaps as GP point_1 = GP() Some people like this, but it does make the code harder to understand.

10 Objects properties You can tell the class of objects using: print(type(point_1)) # <class 'geostuff.GeoPoint'> To check the class: print(isinstance(point_1, geostuff.GeoPoint)) # True Instead of a single class for isinstance, you can also use a tuple of classes.

11 What's happening? When you make an object, the parentheses suggest a function is being called. That function is called a constructor. It is invisible if not explicitly written. If we want to (and we usually do), we can override the invisible default version by writing our own. Overriding is where you write over invisible code with your own in a class. We'll see where invisible code comes from when we look at inheritance in a bit.

12 __init__ Here's the basic form of a class with a constructor: class GeoPoint(): def __init__ (self): pass The name __init__ is special and reserved for this purpose. The underscores are there to make the name so unusual that we're unlikely to mess with it.

13 Self You'll see that the constructor is a function that takes in one variable, "self". But equally, we don't send any arguments in: point_1 = geostuff.GeoPoint() So what gives?

14 Methods When they are used, functions in classes and objects are sent to the calling code wrapped in a whole bunch of stuff. To indicate this, Python calls them methods. (Note that in some languages methods are synonymous with functions and procedures, while in some methods and functions have different calls and actions). One thing that happens between call and action is that a variable representing the object is injected into the call. By tradition this is called self (it's not a keyword). All methods therefore take in at least one variable (usually called self) point_1 = geostuff.GeoPoint() def __init__ (self): self is the object the function lives inside; in this case point_1. From the docs: "If you access a method (a function defined in a class namespace) through an instance, you get a special object: a bound method (also called instance method) object. When called, it will add the self argument to the argument list…"

15 Bound and unbound methods
In general, there's two ways of calling functions: They may be bound to objects: a = "hello world" a.upper() Or unbound and called from the class: str.upper(a) In actual fact, in most cases, if you call the former, what happens is that the latter runs, i.e. the method in the class, not the object. This means that for methods that apparently take nothing in, there actually has to be a variable label (self) waiting to assign to the object passed in. This actually helps quite a lot when trying to understand the documentation - for example, how sorting functions (which don't seem to take in the sorted thing) work. Here's the info from the docs: "If you access a method (a function defined in a class namespace) through an instance, you get a special object: a bound method (also called instance method) object. When called, it will add the self argument to the argument list…" "When an instance method object is called, the underlying function (__func__) is called, inserting the class instance (__self__) in front of the argument list. For instance, when C is a class which contains a definition for a function f(), and x is an instance of C, calling x.f(1) is equivalent to calling C.f(x, 1). When an instance method object is derived from a class method object, the “class instance” stored in __self__ will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function." You can find a nice analysis of this by Hillel Wayne at:

16 Self self therefore represents the object. Although the self object you get passed is immutable in itself, the contents can be changed. Therefore, if we want to make variables and store data inside an object, we store it inside self.

17 Instance variables # Main program import geostuff point_1 = geostuff.GeoPoint() point_1.lat = "48N" point_1.long = "117W" print(point_1.lat) So, here's a classic class. Objects can contain other objects as variables. We can access these from the object using the dot operator. We define the variables within the self object, and within init (we'll come to why, shortly). #geostuff.py class GeoPoint(): def __init__ (self): self.lat = None self.long = None

18 NB # Main program import geostuff point_1 = geostuff.GeoPoint() point_1.x = "48N" print(point_1.x) Be warned, however, that because Python is a dynamic language, it is quite possible to alter objects by inventing variables on the fly. And you have to watch out for spelling mistakes: point_1.lng = "117W" print(point_1.long) #geostuff.py class GeoPoint(): def __init__ (self): self.lat = None self.long = None

19 __init__ There's nothing to stop you adjusting the init to take in other data: class GeoPoint(): def __init__ (self, lat, long): self.lat = lat self.long = long pt1 = geostuff.GeoPoint("48N","117W")

20 Objects We can make multiple different objects from the same class template - like taking a photocopy. point_1 = geostuff.GeoPoint() point_2 = geostuff.GeoPoint() These are not the same objects: point_1.lat = "48N" point_2.lat = "44N" print(point_1.lat) # "48N"

21 Comparing two objects a = A() b = A() # Same content as a but different object. c = a a is c # True a is b # False a == b # True

22 Functions We can also build and use other methods for objects.
# Main program import geostuff point_1 = geostuff.GeoPoint() point_1.randomize() print(point_1.lat, point_1.long) We can also build and use other methods for objects. #geostuff.py import random class GeoPoint(): def __init__(): self.lat = None self.long = None def randomize(self): self.lat = str(random.randint(0,90)) + ("N" if (random.random() < 0.5) else "S") self.long = str(random.randint(0,180)) + ("W" if (random.random() < 0.5) else "E")

23 Variables in detail

24 Variable scope review We've seen objects can contain variables (other objects) that we can access with the dot operator: object_name.variable_name However, when we set them up, the scoping issues become important. Scope, remember, means variables can only be seen in the block in which they are assigned. Previously we saw we could use the global keyword inside functions to indicate we wanted a variable outside the function.

25 Variables Classes are complicated because there are several things going on: The module. The class. The object. Methods within the class/~object. This makes things complicated: for example, what scale does "global" in a method refer to? Depending where we set them up, variables can be: Global within the module Class attributes Object instance variables Method local variables It is very easy to get the four confused.

26 The scope of variables Broadly speaking you can see most things in most places, but you need to refer to them properly. The only thing you can't access other than where it is made is method local variables. # module start global_variable = None class GeoPoint(): class_attribute = None def __init__(self): self.instance_variable = None method_local_variable = None def another_method(self): # module start print(global_variable) class GeoPoint(): print(class_attribute) def __init__(self): global global_variable print(GeoPoint.class_attribute) print(self.instance_variable) print(method_local_variable) def another_method(self): # No access to method_local_variable here. A couple of scripts to show how this runs: # main.py print("importing ") import scopetest from scopetest import global_variable # Needed to access the global variable. print("building object ") point_1 = scopetest.GeoPoint() print("calling method ") try: point_1.another_method() except Exception as e: print(e) print("getting variables ") # Note that we don't need to define global_variable as global as it isn't global here. print(scopetest.global_variable + " printed from an external script.") # Doesn't work, but doesn't fail either. print(scopetest.GeoPoint.class_attribute + " printed from an external script.") print(point_1.instance_variable + " printed from an external script.") print("getting local method variable ") # Test we can't reach method local variables: print(point_1.method_local_variable + " printed from an external script.") # scopetest.py # module start global_variable = "global_variable" print(global_variable + " printed at the module level.") class GeoPoint(): class_attribute = "class_attribute" print(class_attribute + " printed at the class level.") def __init__(self): global global_variable print(global_variable + " printed at the method level.") print(GeoPoint.class_attribute + " printed at the method level.") self.instance_variable = "self.instance_variable" print(self.instance_variable + " printed at the method level.") method_local_variable = "method_local_variable" print(method_local_variable + " printed at the method level.") def another_method(self): # No access to method_local_variable here print(method_local_variable + " printed within another method.")

27 Global variables and class attributes
In general, both global and class-level variables are dangerous. You may rely on a variable being one value, while another part of the system may be altering it. We call such issues side-effects: unintended processing changes caused by variable value changes (though some programmers think of even assignments as side effects as they change the values variable labels refer to!). We try to minimise possible side-effects but keeping scope as small as possible. To show how dangerous these can be, see if you can work out why this rocket software keeps crashing rockets into the base they launch from. It uses a class level variable. # main.py print("importing ") import scopetest4 print("building object ") rocket_target1 = scopetest4.GeoPoint() rocket_target2 = scopetest4.GeoPoint() minutes_to_detonation = 4 # Set start to launch site for both. rocket_target1.set_start_location([20,40]) rocket_target2.set_start_location([20,40]) # Only launch rocket 1. rocket_target1.launched = True rocket_target2.launched = False # Update positions so we know where both rockets are. for i in range(minutes_to_detonation, 0, -1): rocket_target1.update_location() rocket_target2.update_location() # Explode rocket 1 print("rocket 1 detonated at " + str(rocket_target1.current_coordinates)) print("rocket 2 at " + str(rocket_target2.current_coordinates)) # scopetest4.py # module start class GeoPoint(): current_coordinates = [0,0] launched = False start = [0,0] def set_start_location(self,coordinates): self.start = coordinates self.current_coordinates[0] = coordinates[0] self.current_coordinates[0] = coordinates[1] # now try uncommenting the following: # self.current_coordinates = [coordinates[0],coordinates[1]] # Why do you think there's a difference? def update_location(self): if self.launched: self.current_coordinates[0] = self.current_coordinates[0] + 1 self.current_coordinates[1] = self.current_coordinates[1] + 1 else: self.current_coordinates[0] = self.start[0] self.current_coordinates[1] = self.start[1]

28 Global variables In general, global variables are dangerous and to be avoided. They have marginal uses inside modules, and almost no uses outside them. (to access externally, you need to import them from the module - you can actually import just variables or functions from modules, as well as classes) # module start global_variable = None class GeoPoint(): def __init__(self): def another_method(self): # module start print(global_variable) class GeoPoint(): print(class_attribute ) def __init__(self): global global_variable def another_method(self):

29 Class attributes Again, class attributes are largely more dangerous than they are worth. The one advantage with them is that they are shared by all instances of the class: there is only one of them. This sometimes has some advantages. # module start class GeoPoint(): class_attribute = None def __init__(self): def another_method(self): # module start class GeoPoint(): print(class_attribute ) def __init__(self): print(GeoPoint.class_attribute) def another_method(self):

30 Method local variables
Method local variables are useful within a method, but not accessible outside them. # module start class GeoPoint(): def __init__(self): method_local_variable = None def another_method(self): # module start class GeoPoint(): def __init__(self): print(method_local_variable) def another_method(self): # No access to method_local_variable here.

31 Instance variables Instance variables are available both externally
object.instance_variable And anywhere internally: self. instance_ variable # module start class GeoPoint(): def __init__(self): self.instance_variable = None def another_method(self): # module start class GeoPoint(): def __init__(self): print(self.instance_variable) def another_method(self):

32 Instance variables vs class attributes
Say we do this: point_1 = geostuff.GeoPoint() print(point_1.variable) If an instance variable exists with this name, we get this. If not, and a class attribute exists, we get this. We can also get the latter with print(geostuff.GeoPoint.variable)

33 Instance variables vs class attributes
If we adjust the instance variables of one object, it doesn't change the instance variables of anything of the same class. If we adjust the class attributes of a class using: ClassName.variable = "boo" It changes everywhere. If we use: object_name.variable = "boo" or self.variable = "boo" and there isn't an instance variable of that name, but there is a class attribute, an instance variable is created for us (obviously, in the latter case). But, if the variable is mutable, and we do this: object_name.variable[0] = "boo" self.variable[0] = "boo" and there isn't an instance variable of that name, but there is a class attribute, the class variable is changed, meaning it is change for everything. Here's an example of the dangers of dynamic languages. Can you work out why the bombs are destroying their launchpad? # main.py print("importing ") import scopetest3 print("building object ") target = scopetest3.GeoPoint() # scopetest # module start class GeoPoint(): current_x = 20 current_y = 140 minutes_to_detonation = 4 def __init__(self): for i in range(GeoPoint.minutes_to_detonation, 0, -1): self.currant_x = self.current_x + 1 self.currant_y = self.current_y + 1 self.engines_steer() print("exploded at " + str(self.current_x) + " " + str(self.current_y)) print(self.current_x) def engines_steer(self): print("adjusting to " + str(self.current_x) + " " + str(self.current_y))

34 Issues One problem with this is that if a class we're using has its own class attribute "snake". And we do: object.snake = 10 Thinking there's no variable "snake" in the class (why would there be in a language called "Python"), suddenly we've created a new variable that takes precedence over the old one. Worse still if snake is a method; this will also be replaced. You need to be very careful before assigning new variables to classes. In general, it is also better not to reuse variable names between scope levels. XXXX write example

35 Object Philosophy Object Orientation aspires to three philosophical approaches. Encapsulation: the notion that data and the procedures to work on them are within a class, and that external code should only access both in controlled ways. Polymorphism: the notion that objects should work with multiple data types invisibly coping with whatever is thrown at them. Inheritance: the idea that classes should be built in a hierarchy of increasing complication, each layer automatically picking up the more generic behaviour of the parental code in the hierarchy.

36 Encapsulation Python is mixed bag with regards encapsulation. As a high level language much is hidden, but if you want to encapsulate your own classes it's less helpful at keeping people out of your code. We've seen that instance variables can't be accessed directly, which is good encapsulation practice. But it is hard to prevent other code accessing instance variables directly, which is bad practice (how do we know what a variable is doing in other code when we change it?).

37 Polymorphism We've seen that functions will often take in a wide variety of data types, that is, Python has parametric polymorphism. This renders the standard approach to polymorphism (overloading, or "ad hoc polymorphism": to have multiple methods with the same name and let the runtime decide which to call based on the argument types) less necessary, and Python won't generally allow methods with the same name in the same class. We can't forget, however, that different data types will result in very different operations: a function that adds two variables will have very different results for two numbers compared to two strings. If you really want to do it, you can write a single dispatch generic function:

38 Polymorphism on the sly
It's quite usual in other languages to have polymorphic constructors by overloading, so you'd have several different __init__ methods, each taking in different datatypes. Python doesn't generally allow this, but the docs suggest the following solution: class C(): def __init__(self, i=None): if i is None: ("No arguments") else: ("Argument is", i) Also using * and ** argument capture. There are some exceptions: you can create methods with the same name using decorators. See, for example:

39 Inheritance There are two areas to get our heads around with inheritance. The first is the development side: how and why do we build inheritance hierarchies? The second is on the use side: how do we use inheritance?

40 Why inherit? Animals move eat sleep reproduce Inheritance allows us to structure code appropriately. We can build code that matches natural hierarchies, with different behaviour at each level. It also allows us to pick up code without effort. By inheriting, we gain all the code from the parent class invisibly. Each level inherits the code above it. Human work play In the picture, the upward arrow means the class below inherits from the class above. Burglar hunt_target burgle

41 Terminology In standard Object Orientation, we talk about a subclass inheriting from a superclass. We may also, informally, talk about a child class inheriting from a parent class. In Python, the terminology is generally a derived class inheriting from a base class.

42 Inheritance class MySuperClass(type): pass class MyClass(metaclass=MySuperClass): class MySubclass(MyClass):

43 Overiding While it is usual for derived classes to pick up and use super class methods and variables, it can override them. Note that if you do so, any superclass method inherited into the class will also call the subclass version, which can cause issues. The derived class is searched before the super class. From the docs: “Derived classes may override methods of their base classes. Because methods have no special privileges when calling other methods of the same object, a method of a base class that calls another method defined in the same base class may end up calling a method of a derived class that overrides it. (For C++ programmers: all methods in Python are effectively virtual.)”

44 Calling superclass methods
If the derived class wants to directly access a superclass method or variable, overridden or not, it can use the super keyword: super.variable_name If you need to call a superclass init (because it needs the variables), do this: super().__init__([args...]) To call more general methods: class Derived(Base): def meth(self): super(Derived, self).meth()

45 Multiple inheritance class DerivedClassName(Base1, Base2, Base3): There is nothing to stop you inheriting from multiple classes (though this may not always work in practice: what does it mean to be both a menu and a button, for example?). Variables and methods are searched for broadly depth first, left to right; so Base1 is searched, and all its base classes, then Base2, etc.

46 issubclass Just as isinstance tests whether an object is of one or more classes, issubclass(class, classinfo) will identify whether classes subclass another.

47 Class quirks Overriding is where one element in a class or class hierarchy over write another with the same name. It is a key element of inheritance, allowing code to replace parental code, but it can cause issues. Variables in a class will quite happily override methods with the same name, so make sure your naming system doesn't encourage this. The docs suggest using nouns for variables and verbs for methods.

48 Inheritance and arguments
Inheritance allows for "Duck Typing" (formally Subtyping). Essentially for functions expecting a particular class of object (the supertype), you can pass in sub-classes (subtypes) knowing that they will look right; i.e. "If it looks like a duck, and quacks like a duck, it can be treated like a duck." def is_further_from_equator_than(self,other): if (self.lat > other.lat): return True else: return False print(sea_point_1.is_further_from_equator_than(sea_point_2))

49 Subtyping One key use for subtyping is in Design by Contract contract formation. A contract (or sometimes "interface", though notes the more general API) is a statement that certain methods and variables will be present within objects. If we can make these promises, methods that work with the objects can guarantee that they will work with the objects. For example, the Windows Operating System is held together by the Component Object Model, based on such contracts. These allow programs to send each other method requests and data knowing they can be dealt with (e.g. you can embed Excel tables into Word).

50 Subtyping Traditionally if you inherited a class you essentially guaranteed you had the methods it had, which means subtypes could replace for the supertypes in method calls. In manifestly typed systems, a method can check a argument inherits a supertype, and allow it in. Moreover, a class can inherit from an abstract class that has no implementation, with the manifest typing compiler not compiling unless the promised methods and variables are implemented in the subtype.

51 Contracts In implicitly typed systems this is not generally done, so there tends to be less formal Design by Contract. In Python, formal subtype checking is rare. However, it does have the notion of Abstract Base Classes, which you can inherit and check against. There are some pre-existing ABCs in the collections library: collections.abc You can also make and check your own ABCs with:

52 Overriding standard methods
One way in which Python stays so high-level is by implementing a lot of complicated behaviour in dunder (double underscore - double underscore) methods. In general the most common one to come across is __init__. But there are many others that you can override to change default behaviours. For example, operators like "+" call dunders within objects to work, meaning you can change how standard operators work for your classes/objects.

53 Standard methods to override/create
__new__() The constructor sequence for a class is actually __new__ followed by __init__. It is possible to change the response to building a new object by adapting __new__. __call__() Classes can be made callable so that a = B() doesn't create a new instance, but instead runs a method. To do this, write a __call__(). Operators and item getting: Classes can act like new types of numbers, sequences, or mappings if they contain certain methods. See:

54 Standard methods to override
object.__repr__(self) Should ideally return a string that could be used to create the object. Called by obj.repr() object.__str__(self) Should return a nice string describing the object or its contents. Called by obj.str()

55 To remove a standard method in a new class
Setting a standard method with pass causes it to throw an exception when called: def __getitem__() : pass

56 Methods

57 Method chaining In some languages (like JavaScript) altered objects are commonly returned from method calls. a = b.method() This means you can then call methods inside those objects, and start to create chains: c = a.method2() Becomes: c = b.method().method2() This saves making unnessary labels, so is more efficient, but sometimes needs pulling appart to debug. Python tends not to return such objects, so this is less common, but it is still worth knowing just incase you see it.

58 Class vs Object methods and variables / static

59 Static variables and methods
Many languages allow methods and variables to be associated with classes rather than objects. These are sometimes denoted as "static" variables and methods, as they aren't generated with each object. Variables can be accessed through the class, and therefore apply to all objects of that class type. We've seen that this is the case for all class attributes in Python.

60 Static variables and methods
Methods can be declared as either class methods (in which case the equivalent of self is the class) or static (in which case self is absent and nothing is passed in beyond standard variables). This is done with decorators, a tag that marks something out as special and adapts the code it is attached to. It's not common, because most methods run out of the class anyhow (see bound/unbound methods), but you can find more details here: def a (arg1, arg2, def f(cls, arg1, arg2, ...): There are other built in decorators and you can build them yourself. More here:

61 Protection

62 Access control Many languages have a notion of 'private' variables that can't be accessed from outside an object. For example, if we were sending someone sensitive information, we might not want to give people direct access to it. Instead, we usually have 'public' accessor "get" and mutator "set" methods, which negotiate getting or changing private variables, for example checking the variable isn't in use or asking for a password. a = object.getVariableA() object.setVariableA(20) Indeed, accessor and mutator methods are generally regarded as good practice whatever the data: it's generally bad practice to access variables directly as you have no idea what else is using them.

63 Application Programming Interfaces
Infact, it is usual to hide any variables and methods that outside code doesn't need access, limiting interactions to a clean and clear set of public methods. These public methods are the Application Programming Interface (API) of the code; the bits for connecting to it. These are often the bits described in the API documentation, often called the API or docs for short. This is part of Design by Contract: the idea that you design the public connections between code as the main program structure achieving a goal, allowing implementations within the code to be background detail.

64 Private In Python the security aspects of access control are less key, as the code is distributed as open text. Nevertheless, you can informally hide variables if there's no reason for outside code to interact with it. A function/method or variable name begun with an _underscore will informally be hidden when public descriptions of the code are made (for example, by dir(object) )

65 Private In addition, there is a chance that you want to make variables such that they can't be overridden by subclasses. If I make a variable in the sub and super classes, the subclass one will replace the superclass one, potentially causing issues if superclass methods are run. This is usually dealt with by making variables private, but in the absence of that option python engages in name mangling. Any label starting with at least two leading __underscores and at most one trailing underscore is replaced with _ClassName__name. This prevents subclasses accidentally overriding it. For more, see:

66 Get Attribute Set and get methods aren't common in Python, though there's nothing to stop you writing them. The problem is that people will usually try to access a variable directly. There is, however, a getaround. If a variable can't be directly found to get, the __getattr__() method is called in the object. Moreover, for all attempts to set a variable values, a method __setattr__(self, name, value) is called. Both methods are invisibly present in objects. If you remove a variable, but document its existence, the methods will be invoked to look for it. You can then override the methods to control access to another object as if it were the missing one.

67 Read only variables, get set
However, cleaner is to use the property() builtin function This sets up a variable so it can only be accessed through accessor / mutator methods. When the variable is accessed as: object_name.variable_name What will run is: object_name.getvariable_name() This can be used to make variables read only, for example.

68 Example from docs class C(): def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.")


Download ppt "Classes."

Similar presentations


Ads by Google