[python learning notes] object oriented programming

Posted by brotherhewd on Fri, 14 Jan 2022 17:56:14 +0100

object-oriented programming

object: represents a specific thing in the problem space of the objective world, and also represents the basic element in the solution space of the software system
Object oriented programming (OP): it is not only a programming paradigm, but also a method of program development.

1. Class

Class (clas) is the construction of an object-oriented computer programming language. It is the blueprint for creating objects and describes the common properties and methods of the created objects

1. Understand the basic methods of creating classes

2. Preliminary understanding of object and object-oriented

class SuperMan:
    '''
    A class of superman
    '''

    def __init__(self,name):
        self.name = name
        self.gender = 1
        self.single = False
        self.illness = False

    def nine_negative_kungfu(self):
        return "Ya!You have to die."

guojing = SuperMan('guojing')
print(guojing.name)
print(guojing.gender)
kongfu = guojing.nine_negative_kungfu()
print(kongfu)
'''
Write a program to judge whether students have finished their homework. If completed, teach
 Teachers will give praise, otherwise they will criticize.
'''

class Student:
    def __init__(self, name, grade, subject):
        self.name = name
        self.grade = grade
        self.subject = subject
    def do_work(self, time):
        if self.grade > 3 and time > 2:
            return True
        elif self.grade < 3 and time > 0.5:
            return True
        else:
            return False

class Teacher:
    def __init__(self, name, subject):
        self.name = name
        self.subject = subject
    def evaluate(self, result=True):
        if result:
            return "You are great."
        else:
            return "You should work hard"

stu_zhang = Student('zhang', 5, 'math')   
tea_wang = Teacher('wang', 'math')    
teacher_said = tea_wang.evaluate(stu_zhang.do_work(1))    
print("Teacher {0} said: {1}, {2}".format(tea_wang.name, stu_zhang.name, teacher_said))

stu_newton = Student('Newton', 6, 'physics')
teacher_newton = tea_wang.evaluate(stu_newton.do_work(4))
print("Teacher {0} said: {1}, {2}".format(tea_wang.name, stu_newton.name, teacher_newton))

1.1. Properties

Attribute: describes what the object is

• class properties

Also known as static properties

It can only be modified through class

Instances also have class properties, but class properties cannot be modified

>>> class Foo:
	lang = 'python'

>>> dir(Foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lang']
>>> Foo.lang
'python'
>>> f = Foo()
>>> type(f)
<class '__main__.Foo'>
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lang']
>>> f.lang
'python'
>>> Foo.group = 'winner classroom'
>>> dir(Foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']
>>> f.name = 'winner'
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang', 'name']
>>> dir(Foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']

• instance properties

Instance properties, also known as dynamic properties

Create by instance

Different instances have different instance properties

Instance__ dict__ Displays all properties of the current instance

>>> class Bar:
	def __init__(self,name):
		self.name = name

>>> b = Bar('winner')
>>> c = Bar('xiaochun')
>>> b.name
'winner'
>>> c.name
'xiaochun'python
>>> b.__dict__
{'name': 'winner'}
>>> class Foo:
	lang = 'python'
	def __init__(self,name):
		self.name = name

>>> f = Foo('winner')
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lang', 'name']
>>> f.__dict__
{'name': 'winner'}
>>> f.lang  #lang is a class attribute, not an instance attribute, and cannot be__ dict__ But it can be accessed by instances
'python'

• the role of self

Unless otherwise specified, all methods in the class take self as the first parameter

self refers to the current instance

>>> class Bar:
	def __init__(self):
		print(self)
		
>>> b = Bar()
<__main__.Bar object at 0x00000181FD3E1580>
>>> b
<__main__.Bar object at 0x00000181FD3E1580>
>>> 
'''
Create a class that can calculate the number of days and weeks between any two dates.
'''
import datetime
from dateutil import rrule

class BetDate:
    def __init__(self, start_date, stop_date):
        self.start = datetime.datetime.strptime(start_date, "%Y, %m, %d")    
        self.stop = datetime.datetime.strptime(stop_date, "%Y, %m, %d")

    def days(self):
        d = self.stop - self.start
        return d.days if d.days > 0 else False 
    
    def weeks(self):
        weeks = rrule.rrule(rrule.WEEKLY, dtstart=self.start, until=self.stop)
        return weeks.count()    

fir_twe = BetDate("2019, 5, 1", "2019, 11, 25") 
d = fir_twe.days()
w = fir_twe.weeks()
print("Between 2019-5-1, 2019-11-25:")
print("Days is:", d)
print("Weeks is:", w)

1.2 method

Method: describe what the object can do

• comparison methods and functions

The naming of names and the writing of code blocks are the same

(instance) methods cannot be called alone, but only through instances / classes, while functions can be used alone

The first argument to the method must be self

>>> class Foo:
	def method(self,x):
		return x * 2

	
>>> f = Foo()
>>> f.method(2)
4
>>> Foo.method(2)
Traceback (most recent call last):
  File "<pyshell#320>", line 1, in <module>
    Foo.method(2)
TypeError: method() missing 1 required positional argument: 'x'
>>> Foo.method(f,2)
4
>>> f.method  #Classes are objects and methods are objects
<bound method Foo.method of <__main__.Foo object at 0x00000181FD3E1820>>

• class method

Use decorator: @ classmethod decorator

The first parameter of a class method: cls, which represents the class itself, has nothing to do with what is written. I'm used to using cls

>>> class Bar:
	@classmethod
	def method(cls,x):
		print(cls)
		
>>> b = Bar()
>>> b.method(2)
<class '__main__.Bar'>
>>> Bar
<class '__main__.Bar'>

• static method

Use decorator: @ staticmethod decorator

Static methods are not bound to instances

>>> class Bar2:
	@staticmethod
	def add():
		return 'add'

>>> b2 = Bar2()
>>> b2.add()
'add'
>>> Bar2.add()
'add'
>>> add()
Traceback (most recent call last):
  File "<pyshell#343>", line 1, in <module>
    add()
TypeError: add() missing 2 required positional arguments: 'x' and 'y'

practice

'''
Create a class that can pass the "year"-month-Create an instance with the string "day", and verify whether the year, month and day are legal
'''

class Date(object):
    def __init__(self,year=0,month=0,day=0):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls,date_as_string):
        year,month,day = map(int,date_as_string.split('-'))
        datel = cls(year,month,day)
        return datel

    @staticmethod
    def is_date_valid(date_as_string):
        year, month, day = map(int, date_as_string.split('-'))
        return day <= 31 and month<=12 and year <= 2038

d = Date.from_string('2019-11-11')
is_date = Date.is_date_valid('2019-11-11')
print(is_date)

2. Inherit

Inheritance is one of the properties of an object

Inheritance is an important concept in object-oriented programming

• parent and child classes

Override of parent class method: if a method with the same name as the parent class is written in the child class, the method in the parent class will be overwritten

>>> class P:
	def __init__(self,name):
		self.name = name
	def eat(self):
		return "fish"

>>> class C(P):
	pass

>>> c = C()
Traceback (most recent call last):
  File "<pyshell#356>", line 1, in <module>
    c = C()
TypeError: __init__() missing 1 required positional argument: 'name'
>>> c = C('google')
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat', 'name']
>>> class D(P):pass

>>> dir(D)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat']
>>> class E(P):
	def __init__(self,age):
		self.age = age

		
>>> e = E('winner',18)
Traceback (most recent call last):
  File "<pyshell#367>", line 1, in <module>
    e = E('winner',18)
TypeError: __init__() takes 2 positional arguments but 3 were given
>>> e = E('winner')
>>> dir(e)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'eat']
>>> e.age
'winner'
>>> e.name
Traceback (most recent call last):
  File "<pyshell#371>", line 1, in <module>
    e.name
AttributeError: 'E' object has no attribute 'name'

In single inheritance, if a child class overrides the method of the same name in the parent class and wants to use the parent class method in the subclass, the method in the parent class is called in the subclass.

>>> class E(P):
	def __init__(self,name,age):
		self.age = age
		P.__init__(self,name)	
>>> e = E('winner',18)
>>> e.age
18
>>> e.name
'winner'

>>> class E(P):
	def __init__(self,name,age):
		self.age = age
		super().__init__(name)
		
>>> e = E('winner',18)
>>> e.age
18
>>> e.name
'winner'

• single inheritance

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def get_name(self):
        return self.name

    def get_age(self):
        return self.age

class Student(Person):
    def __init__(self,school,name,age):
        self.school = school
        super().__init__(name,age)
    def grade(self,n):
        print("{0}'s grade is {1}".format(self.name,str(n)))

stu1 = Student('Soochow','winner','18')
stu1.grade(99)
print(stu1.get_name())
print(stu1.get_age())

• multi inheritance

class K1:
    def foo(self):
        print("K1-foo")

class K2:
    def foo(self):
        print("K2-foo")
    def bar(self):
        print("K2-bar")

class J1(K1,K2):
    pass

class J2(K1,K2):
    def bar(self):
        print("J2-bar")

class C(J1,J2):
    pass

print(C.__mro__)
m = C()
m.foo()
m.bar()

Operation results
(<class '__main__.C'>, <class '__main__.J1'>, <class '__main__.J2'>, <class '__main__.K1'>, <class '__main__.K2'>, <class 'object'>)
K1-foo
J2-bar
'''
Please write the class of "physicist" and regard it as "theoretical physicist"
And "experimental physicist"
'''

class Physicist:
    def __init__(self,name,iq=120,looks='handsom',subject='physics'):
        self.name = name
        self.iq = iq
        self.looks = looks
        self.subject = subject

    def research(self,field):
        print("{0} research {1}".format(self.name,field))

    def speak(self):
        print("My name is",self.name)
        print("I am",self.looks)
        print("Intelligence is",self.iq)
        print("I like",self.subject)

class ExperimentalPhysicist(Physicist):
    def __init__(self,main_study,name,iq=120,looks='handsom',subject='physics'):
        self.main_study = main_study
        super().__init__(name,iq,looks,subject)

    def experiment(self):
        print("{0} is in Physics Lab".format(self.name))

class TheoreticalPhysicist(Physicist):
    def __init__(self,theory,name,iq=120,looks='handsom',subject='physics'):
        self.theory = theory
        super().__init__(name,iq,looks,subject)

    def research(self,field,base):
        super().research(field)
        print("My theory is {0}, it is based on {1}".format(self.theory,base))

3. Polymorphism

  • Polymorphism is one of the characteristics of objects

  • Python language is naturally polymorphic

>>> def add(x,y):
	return x + y

>>> add(3,4)
7
>>> add('winner','python')
'winnerpython'

4. Encapsulation

  • Encapsulation is one of the characteristics of objects
  • Python implements encapsulation through privatization, with two underscores in front of the object__
>>> class Foo:
	__name = 'winner'
	book = 'python'
	
>>> Foo.book
'python'
>>> Foo.__name
Traceback (most recent call last):
  File "<pyshell#407>", line 1, in <module>
    Foo.__name
AttributeError: type object 'Foo' has no attribute '__name'
>>> Foo.name
Traceback (most recent call last):
  File "<pyshell#408>", line 1, in <module>
    Foo.name
AttributeError: type object 'Foo' has no attribute 'name'
>>> f = Foo()
>>> f.book
'python'
>>> f.name
Traceback (most recent call last):
  File "<pyshell#411>", line 1, in <module>
    f.name
AttributeError: 'Foo' object has no attribute 'name'
>>> f.__name
Traceback (most recent call last):
  File "<pyshell#412>", line 1, in <module>
    f.__name
AttributeError: 'Foo' object has no attribute '__name'

Encapsulated objects can only be called in this class

>>> class Foo:
	def __init__(self):
		self.name = 'winner'
		self.__group = 'winner classroom'
	def code(self):
		return self.__group
	def __author(self):
		return self.name
	
>>> f = Foo()
>>> f.code()
'winner classroom'
>>> f.__group
Traceback (most recent call last):
  File "<pyshell#426>", line 1, in <module>
    f.__group
AttributeError: 'Foo' object has no attribute '__group'
>>> f.__author()
Traceback (most recent call last):
  File "<pyshell#427>", line 1, in <module>
    f.__author()
AttributeError: 'Foo' object has no attribute '__author'

5. Custom class

  1. Understand classes and types
  2. Master the methods of customizing object types
class RoundFloat:
    def __init__(self, val):
        self.value = round(val, 2)
    
    def __str__(self):    # str: user friendly; Repl: interpreter friendly
        return "{0:.2f}".format(self.value)
    
    __repr__ = __str__

r = RoundFloat(3.1415926)
print(r)
print(type(r))
>>> class Foo:
	def __repr__(self):
		return "I am in repr"
	def __str__(self):
		return "I am in str"

	
>>> f = Foo()
>>> s
['one', 'two', 'three', 'four']
>>> f
I am in repr
>>> print(f)
I am in str
>>> 
class Fraction:
    def __init__(self, number, denom=1):
        self.number = number
        self.denom = denom

    def __str__(self):
        return str(self.number) + "/" + str(self.denom)

    __repr__ = __str__

f = Fraction(2, 3)
print(f)
>>> from fractions import Fraction  #fraction
>>> m,n = Fraction(1,6),Fraction(3,6)
>>> m
Fraction(1, 6)
>>> n
Fraction(1, 2)
>>> m+n
Fraction(2, 3)

6. Control attribute access

  1. Optimize memory: slots

    >>> a = Foo()
    >>> a.__dict__
    {}
    >>> a.age = 25
    >>> a.__dict__
    {'age': 25}
    >>> a.year = 28
    >>> a.__dict__
    {'age': 25, 'year': 28}
    

    It controls the attributes of a class. It is not allowed to increase or decrease arbitrarily. Only the specified attributes are allowed, and modification is not allowed (read-only)

    >>> class Bar:
    	__slots__ = ('name','age')
    
    >>> Bar.name='winner'
    >>> Bar.age=28
    >>> b=Bar()
    >>> b.name
    'winner'
    >>> b.age
    28
    >>> b.city = "soochow"
    Traceback (most recent call last):
      File "<pyshell#476>", line 1, in <module>
        b.city = "soochow"
    AttributeError: 'Bar' object has no attribute 'city'
    >>> b.age = 38
    Traceback (most recent call last):
      File "<pyshell#477>", line 1, in <module>
        b.age = 38
    AttributeError: 'Bar' object attribute 'age' is read-only
    
  2. Master special methods related to attributes

    getattr

    setattr

    An error occurred while accessing a property that does not exist in the class

    >>> class A:
    	def __getattr__(self,name):
    		print("you use getattr")
    	def __setattr__(self,name,value):
    		print("you use setattr")
    		self.__dict__[name] = value
    		
    >>> a = A()
    >>> a.x
    you use getattr
    >>> a.x = "haha"
    you use setattr
    >>> a.x
    'haha'
    

7. Iterators and generators

Determines whether an object can be iterated

>>> hasattr(list,"__iter__")
True

7.1 iterators

List is iteratable (non iterator object), but__ next__ Is unique to iterator objects

>>> hasattr(iter_lst,"__next__")
True
>>> hasattr(list,"__next__")
False

Reads the elements in the iterator into memory

>>> lst = [1,2,3,4]
>>> iter_lst = iter(lst)
>>> iter_lst
<list_iterator object at 0x00000181FD3F7790>
>>> lst
[1, 2, 3, 4]
>>> hasattr(iter_list,"__iter__")
Traceback (most recent call last):
  File "<pyshell#501>", line 1, in <module>
    hasattr(iter_list,"__iter__")
NameError: name 'iter_list' is not defined
>>> hasattr(iter_lst,"__iter__")
True
>>> hasattr(iter_lst,"__next__")
True

>>> iter_lst.__next__()
1
>>> iter_lst.__next__()
2
>>> iter_lst.__next__()
3
>>> iter_lst.__next__()
4
>>> iter_lst.__next__()
Traceback (most recent call last):
  File "<pyshell#509>", line 1, in <module>
    iter_lst.__next__()
StopIteration

>>> for i in iter_lst:
	print(i)
	
>>> iter_lst =iter(lst)
>>> for i in iter_lst:print(i)
1
2
3
4
class MyRange:
    def __init__(self, n):
        self.i = 1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.i <= self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

print("range(7):", list(range(7)))
print("MyRange(7):", [i for i in MyRange(7)])
class Fibs:
    def __init__(self, max):
        self.max = max
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib

fibs = Fibs(1000000)
lst = [fibs.__next__() for i in range(10)]
print(lst)
>>> import itertools
>>> counter = itertools.count(start=7)
>>> next(counter)
7
>>> next(counter)
8
>>> next(counter)
9

7.2 generator

>>> def g():
	yield 0
	yield 1
	yield 2
	
>>> ge = g()
>>> ge
<generator object g at 0x00000181FD3EDCF0>
>>> type(ge)
<class 'generator'>
>>> dir(ge)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> 

yield and return

When the execution reaches yield, the function is suspended and waits for the next execution

When return is executed, the function terminates

>>> def r_return(n):
	print("start")
	while n > 0:
		print("before return")
		return n
		n -= 1
		print('after return')	
>>> rre = r_return(3)
start
before return
>>> print(rre)
3

>>> def y_yield(n):
	print("start")
	while n > 0:
		print("before yield")
		yield n
		n -= 1
		print('after yield')		
>>> yy = y_yield(3)
>>> yy.__next__()
start
before yield
3
>>> yy.__next__()
after yield
before yield
2
>>> yy.__next__()
after yield
before yield
1
>>> yy.__next__()
after yield
Traceback (most recent call last):
  File "<pyshell#561>", line 1, in <module>
    yy.__next__()
StopIteration
def fibs():
    prev, curr = 0, 1
    while True:
        yield prev
        prev, curr = curr, prev + curr

import itertools
print(list(itertools.islice(fibs(), 10)))

Generator resolution

>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> gt = (x**2 for x in range(10))
>>> gt
<generator object <genexpr> at 0x00000181FD3EDE40>
>>> gt.__next__()
0
>>> gt.__next__()
1
>>> gt.__next__()
4
>>> gt.__next__()
9

Topics: Python Back-end