Classes |
standard features of OOP
all functions are virtual |
A Word About Names and Objects |
an object can have multiple references aka aliases argument passing is done by reference an alias is essentially a pointer |
Python Scopes and Namespaces |
these notes are taken from ChatGPT types of namespaces
x = 10 # Global namespace def my_function(): y = 20 # Local namespace print(x) # Refers to global namespace, x = 10 print(y) # Refers to local namespace, y = 20 my_function() The global Keyword
the global keyword is used to declare a variable as global within a functionthe variable will refer to the variable in the global namespace, not a local one allows modification of the value of a global variable from within a function
|
Class Definition Syntax |
class ClassName: <statement-1> . . . <statement-N> |
Class Objects |
class objects support two kinds of operations
|
Instance Objects |
data attributes are data members data attributes do not have to be declared they are created when they are first assigned to class MyClass: """A simple example class""" x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print(x.counter) del x.counter i = 12345 def f(self): return 'hello world'when MyClass is instantiated, the counter attribute is created and set to 1 the code will print 16 and the variable is deleted a method is an instance attribure that is a function all attributes of a class that are function objects define corresponding methods of its instances above
|
Method Objects |
usually a method is called after it's bound x.f is a method object and can be storedand called later xf = x.f while True: print(xf())xf = x.f while True: # print "hello world" forever print(xf())the two calls below are identical x.f() MyClass.f(x)the first argument to a function is an instance object when a non-data attribute of an instance is referenced, the instance's class is searched if the name denotes a valid class attribute (function object), references to both the instance object and the function object are packed into a method object method object{ instance object function object }when the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list then the function object is called with this new argument list |
Class and Instance Variables |
instance variables are for data unique to each instance class variables are for attributes and methods shared by all instances of the class class Dog: kind = 'canine' # class variable shared by all instances def __init__(self, name): self.name = name # instance variable unique to each instance >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # shared by all dogs 'canine' >>> e.kind # shared by all dogs 'canine' >>> d.name # unique to d 'Fido' >>> e.name # unique to e 'Buddy'shared data can have side effects with mutable objects such as lists and dictionaries example of shared list class Dog: tricks = [] # mistaken use of a class variable def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # unexpectedly shared by all dogs ['roll over', 'play dead']corrected design class Dog: def __init__(self, name): self.name = name self.tricks = [] # creates a new empty list for each dog def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks ['roll over'] >>> e.tricks ['play dead'] |
Random Remarks |
if the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance
>>> class Warehouse: ... purpose = 'storage' ... region = 'west' >>> w1 = Warehouse() >>> print(w1.purpose, w1.region) storage west >>> w2 = Warehouse() >>> w2.region = 'east' >>> print(w2.purpose, w2.region) storage eastno interfaces or abstract classes with Python no enforced data hiding, all based on convention clients can add data attributes to class instances >>> w3 = Warehouse() >>> w3.someValue = 123 >>> print(w3.someValue) 123methods may call other methods by using method attributes of the self argument class Bag: def __init__(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x) |
Inheritance |
syntax for a derived class definition
class DerivedClassName(BaseClassName): <statement-1> . . . <statement-N>BaseClassName must be defined in a namespace accessible from the derived class definition's scope class DerivedClassName(modname.BaseClassName):method references are resolved by searching the corresponding class attribute descends down the chain of base classes if necessary the method reference is valid if this yields a function object derived classes can override base class methods a base class method that calling another base class method may end up calling a method of a derived class to call the base class method directly use BaseClassName.methodname(self, arguments)two built-in functions which work with inheritance
|
Multiple Inheritance |
Python supports mutual inheritance
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>search for inherited attributesis essentially left-to-right some magic under the hoods makes certain that each base class is searched only once |
Private Variables |
convention used for private variables in Python a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API this convention suggests the variable should be considered an implementation detail and subject to change without notice limited support for name mangling a variable named with two leading underscores and one trailing underscore is textually replaced with _classname__variable useful for letting subclasses override methods without breaking intraclass method calls class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item) |
Odds and Ends |
Python uses dataclass to decorate classes with attributes classes similar to struct or record in other languages from dataclasses import dataclass @dataclass class Employee: name: str dept: str salary: int |
Iterators |
most container objects can be looped over using a
for statement under the hood the for statement calls iter() on the container object function returns an iterator object that defines the method __next__() iterator access container elements one at a time when no more elements are available, raises a StopIteration exception >>> s = 'abc' >>> it = iter(s) >>> it <str_iterator object at 0x10c90e650> >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> next(it) StopIterationto add iterator behavior to a class define an __iter__() method which returns an object with a __next__() method if the class defines __next__(), then __iter__() can just return self class Reverse: """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == 0: raise StopIteration self.index = self.index - 1 return self.data[self.index]usage >>> rev = Reverse('spam') >>> iter(rev) <__main__.Reverse object at 0x00A1DB50> >>> for char in rev: ... print(char) ... m a p s |
Generators |
generators
are a simple and powerful tool for creating iterators written like regular functions but use the yield statement whenever they want to return data def reverse(data): for index in range(len(data)-1, -1, -1): yield data[index]the __iter__() and __next__() methods are created automatically >>> for char in reverse('golf'): ... print(char) ... f l o glocal variables and execution state are automatically saved between calls when generators terminate a StopIteration is automatically raised |
Generator Expressions |
some simple generators can be coded succinctly as expressions using a syntax similar to list comprehensions
but with parentheses instead of square brackets expressions are designed for situations where the generator is used right away by an enclosing function generator expressions are more compact but less versatile than full generator definitions tend to be more memory friendly than equivalent list comprehensions >>> sum(i*i for i in range(10)) # sum of squares 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product 260 >>> unique_words = set(word for line in page for word in line.split()) >>> valedictorian = max((student.gpa, student.name) for student in graduates) >>> data = 'golf' >>> list(data[i] for i in range(len(data)-1, -1, -1)) ['f', 'l', 'o', 'g'] |