Introduction to python_7 (Learning Notes)

function

Definition of function

Remember "Everything is an object" in Python? Python treats functions as objects and can return from another function to build higher-order functions, such as: parameters are functions and return values are functions.

Let's start with the definition of a function.

  • The function begins with the def keyword followed by the function name and parentheses ().
  • The code executed by the function starts with a colon and is indented.
  • return [expression] ends the function, optionally returning a value to the caller. return without expression is equivalent to returning None.

def functionname (parameters):
"Function Document String"
 functionsuite
 return [expression]

Calls to functions

def printme(str):
    print(str)


printme("I want to call a user-defined function!")  # I want to call user-defined functions!
printme("Call the same function again")  # Call the same function again
temp = printme('hello') # hello
print(temp)  # None

Function Documentation

def MyFirstFunction(name):
    "Function Definition Procedure name Is a formal parameter"
    # Indicates occupying a parameter position
    print('Passed in{0}It's called an argument, it's a specific parameter value!'.format(name))


MyFirstFunction('My Programming Life')  
# My programmer's life passed in is called an argument, which is a specific parameter value!

print(MyFirstFunction.__doc__)  
# name is a formal parameter in the function definition process

help(MyFirstFunction)
# Help on function MyFirstFunction in module __main__:
# MyFirstFunction(name)
#    name is a formal parameter in the function definition process

Function parameters

Python functions have a very flexible and diverse form of parameters, which can be used to make simple calls and pass in very complex parameters.

  • Positional argument
  • Default argument
  • Variable argument
  • Keyword argument
  • Named keyword argument
  • Parameter combination

1.Location parameters

def functionname(arg1):
"Function Document String"
 functionsuite
 return [expression]

  • arg1 - Positional parameters, which are fixed when calling a function.

2.Default parameters

def functionname(arg1, arg2=v):
"Function Document String"
 functionsuite
 return [expression]

  • arg2 = v - Default parameter = Default value, when a function is called, the value of the default parameter is considered the default value if it is not passed in.
  • Default parameters must be placed after positional parameters, or the program will error.
  • Python allows the order of parameters when a function is called to be inconsistent with that when declared, because the Python interpreter can match parameter values with parameter names.
def printinfo(name, age=8):
    print('Name:{0},Age:{1}'.format(name, age))


printinfo('Xiao Ming')  # Name: Xiaoming, Age:8
printinfo('Xiao Ming', 10)  # Name: Xiaoming, Age:10
printinfo(age=12, name='Xiao Ming')  # Name: Xiaoming, Age:12

3. Variable parameters

As the name implies, a variable parameter means that the number of parameters passed in is variable, ranging from 0, 1, 2 to any number, and is a variable length parameter.

def functionname(arg1, arg2=v, *args):
"Function Document String"
 functionsuite
 return [expression]

  • args - Variable parameters, which can range from zero to any number, are automatically assembled into tuples.
  • Variable names with an asterisk (*) hold all unnamed variable parameters.
def printinfo(arg1, *args):
    print(arg1)
    for var in args:
        print(var)


printinfo(10)  # 10
printinfo(70, 60, 50)
# 70
# 60
# 50

4.Keyword parameters

def functionname(arg1, arg2=v, args, *kw):
"Function Document String"
 functionsuite
 return [expression]

  • **kw - Key parameters, which can range from zero to any number, are automatically assembled into a dictionary.
def printinfo(arg1, *args, **kwargs):
    print(arg1)
    print(args)
    print(kwargs)


printinfo(70, 60, 50)
# 70
# (60, 50)
# {}
printinfo(70, 60, 50, a=1, b=2)
# 70
# (60, 50)
# {'a': 1, 'b': 2}

The similarities and differences between Variable Parameters and Keyword Parameters are summarized as follows:

  • Variable parameters allow zero to any number of parameters to be passed in, which are automatically assembled into a tuple when the function is called.
  • Keyword parameters allow you to pass in zero to any number of parameters that are automatically assembled into a dictionary (dict) within a function.

5.Named keyword parameters

def functionname(arg1, arg2=v, args, *, nkw, *kw):
"Function Document String"
 functionsuite
 return [expression]

  • *, nkw - Named keyword parameter, keyword parameter that the user wants to enter, defined by adding a separator before nkw*.
  • If you want to restrict the name of a keyword parameter, you can use Named Keyword Parameter.
  • When using named keyword parameters, it is important to note that parameter names cannot be missing.
def printinfo(arg1, *,nkw, **kwargs):
    print(arg1)
    print(nkw)
    print(kwargs)


printinfo(70, nkw=10, a=1, b=2)
# 70
# 10
# {'a': 1, 'b': 2}

printinfo(70, 10, a=1, b=2)
# TypeError: printinfo() takes 1 positional argument but 2 were given
  • There is no parameter name nwk written, so 10 is treated as a "location parameter", whereas the original function has only one location function and now calls two, so the program will make an error.

6.Parameter combination

Functions can be defined in Python using positional, default, variable, named keyword, and keyword parameters, four of which can be used together, but note that the order of parameter definitions must be:

  • Location parameter, default parameter, variable parameter, and keyword parameter.
  • Location parameter, default parameter, named keyword parameter, and keyword parameter.

Note the syntax for defining variable and keyword parameters:

  • *args is a variable parameter, args receives a tuple
  • **kw is a keyword parameter and kW receives a dict

Named keyword parameters are designed to restrict parameter names that callers can pass in while providing default values. Define named keyword parameters by not forgetting to write the delimiter*, otherwise position parameters are defined.

Warning: Although you can combine up to five parameters, don't use too many combinations at the same time, otherwise the function is difficult to understand.

Return value of function

def add(a, b):
    return a + b


print(add(1, 2))  # 3
print(add([1, 2, 3], [4, 5, 6]))  # [1, 2, 3, 4, 5, 6]
def printme(str):
    print(str)

temp = printme('hello') # hello
# No return, return None
print(temp) # None 
print(type(temp))  # <class 'NoneType'>

Variable Scope

  • In Python, a program variable is not accessible anywhere; access depends on where the variable is assigned.
  • Variables defined within a function have a local scope, which is called a local variable.
  • A variable defined outside a function has a global scope, which is called a global variable.
  • Local variables can only be accessed within their declared functions, while global variables can be accessed throughout the program.
def discounts(price, rate):
    final_price = price * rate
    return final_price


old_price = float(input('Please enter the original price:'))  # 98
rate = float(input('Please enter a discount rate:'))  # 0.9
new_price = discounts(old_price, rate)
print('Price after discount is:%.2f' % new_price)  # 88.20
num = 1

def fun1():
    global num  # Globalkeyword declaration is required
    print(num)  # 1
    num = 123
    print(num)  # 123

fun1()
print(num)  # 123

Nested Function

def outer():
    print('outer The function is called here')

    def inner():
        print('inner The function is called here')

    inner()  # This function can only be called inside the outer function


outer()
# The outer function is called here
# The inner function is called here

closure

It is an important grammatical structure in functional programming and a special embedded function.
If a variable with an outer non-global scope is referenced within an internal function, the internal function is considered a closure.
Closure provides access to variables in the outer non-global scope, which is called the closure scope.

def funX(x):
    def funY(y):
        return x * y

    return funY


i = funX(8)
print(type(i))  # <class 'function'>
print(i(5))  # 40
# The return value of a closure is usually a function.

def make_counter(init):
    counter = [init]

    def inc(): counter[0] += 1

    def dec(): counter[0] -= 1

    def get(): return counter[0]

    def reset(): counter[0] = init

    return inc, dec, get, reset


inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
print(get())  # 3
dec()
print(get())  # 2
reset()
print(get())  # 0
# A nonlocal keyword is required if you want to modify variables in the closure scope

def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal keyword declaration
        num = 100
        print(num)

    inner()
    print(num)


outer()

# 100
# 100 (when nonlocal is not used, the output is 10)

recursion

  • If a function calls itself internally, it is a recursive function.
# Utilize loops
n = 5
for k in range(1, 5):
    n = n * k
print(n)  # 120

# Utilize Recursion
def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n - 1)


print(factorial(5)) # 120
# Fibonacci sequence f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1

# Utilize loops
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
    k = i + j
    lst.append(k)
    i = j
    j = k
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# Utilize Recursion
def recur_fibo(n):
    if n <= 1:
        return n
    return recur_fibo(n - 1) + recur_fibo(n - 2)


lst = list()
for k in range(11):
    lst.append(recur_fibo(k))
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
# Set the number of recursive layers, Python default is 100

import sys

sys.setrecursionlimit(1000)

The sys.setrecursionlimit () method is used to set the maximum depth of the Python interpreter stack to the required limit. This limit prevents any program from entering infinite recursion, which would otherwise overflow the C stack and crash Python.

Lambda expression

Definition of anonymous functions

There are two types of functions in Python:

  • Class 1: Normal functions defined by def keywords
  • Class 2: Anonymous functions defined with lambda keywords

Python uses the lambda keyword to create an anonymous function instead of the def keyword, which does not have a function name and has the following syntax:

lambda argument_list: expression

  • lambda - A keyword that defines an anonymous function.
  • argument_list - Function parameters, which can be position parameters, default parameters, keyword parameters, as well as parameter types in normal functions.
  • :-Colon, add a colon between the function parameters and the expression.
  • Expression - is just an expression, entering function parameters and outputting some values.

Be careful:

  • There is no return statement in the expression because lambda does not need it to return, and the result of the expression itself is the return value.
  • Anonymous functions have their own namespace and cannot access parameters outside their own parameter list or in the global namespace.
def sqr(x):
    return x ** 2


print(sqr)
# <function sqr at 0x0000027519A80CA0>

y = [sqr(x) for x in range(10)]
print(y)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

lbd_sqr = lambda x: x ** 2
print(lbd_sqr)
# <function <lambda> at 0x0000027519B693A0>

y = [lbd_sqr(x) for x in range(10)]
print(y)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]


sumary = lambda arg1, arg2: arg1 + arg2
print(sumary(10, 20))  # 30

func = lambda *args: sum(args)
print(func(1, 2, 3, 4, 5))  # 15

Application of Anonymous Functions

Functional programming means that every block of code is immutable and consists of pure functions, in which pure functions refer to functions that are independent of each other and do not affect each other. For the same input, there will always be the same output without any side effects.

# Non-functional programming

def f(x):
    for i in range(0, len(x)):
        x[i] += 10
    return x


x = [1, 2, 3]
f(x)
print(x)
# [11, 12, 13]
# Functional programming

def f(x):
    y = []
    for item in x:
        y.append(item + 10)
    return y


x = [1, 2, 3]
f(x)
print(x)
# [1, 2, 3]

Anonymous functions are often used in the high-order function of functional programming, which has two main forms:

  • The parameter is a function (filter, map)
  • The return value is a function (closure)

For example, in filter and map functions:

  • filter(function, iterable) filters the sequence, filters out elements that do not meet the criteria, returns an iterator object, and if converted to a list, uses list() to convert.
odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist))  # [1, 3, 5, 7, 9]
  • map(function, *iterables) maps the specified sequence based on the function provided.
m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))  
# [1, 4, 9, 16, 25]

m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
print(list(m2))  
# [3, 7, 11, 15, 19]

In addition to Python's built-in functions, we can also define higher-order functions ourselves.

def apply_to_list(fun, some_list):
    return fun(some_list)

lst = [1, 2, 3, 4, 5]
print(apply_to_list(sum, lst))
# 15

print(apply_to_list(len, lst))
# 5

print(apply_to_list(lambda x: sum(x) / len(x), lst))

Classes and Objects

Object=Property+Method

Objects are instances of classes. In other words, classes mainly define the structure of objects, and then we create objects using classes as templates. Classes contain not only method definitions, but also data shared by all instances.

  • Encapsulation: Information Hiding Technology

We can use the keyword class to define a Python class, followed by the class name, semicolon, and class implementation.

class Turtle:  # Class name conventions in Python begin with uppercase letters
    """A simple example of a class"""
    # attribute
    color = 'green'
    weight = 10
    legs = 4
    shell = True
    mouth = 'Big mouth'

    # Method
    def climb(self):
        print('I'm crawling very hard forward...')

    def run(self):
        print('I'm running fast forward...')

    def bite(self):
        print('Bite you to death!!')

    def eat(self):
        print('It's satisfying to have something to eat...')

    def sleep(self):
        print('Sleep, sleep, good night, zzz')


tt = Turtle()
print(tt)
<__main__.Turtle object at 0x0000027519B90670>

print(type(tt))
# <class '__main__.Turtle'>

print(tt.__class__)
# <class '__main__.Turtle'>

print(tt.__class__.__name__)
# Turtle

tt.climb()
# I'm crawling very hard forward...

tt.run()
# I'm running fast forward...

tt.bite()
# Bite you to death!!

# Python classes are also objects. They are instances of type
print(type(Turtle))
# <class 'type'>
  • Inheritance: The mechanism by which subclasses automatically share data and methods between parent classes
class MyList(list):
    pass

lst = MyList([1, 5, 2, 7, 8])
lst.append(9)
lst.sort()
print(lst)

# [1, 2, 5, 7, 8, 9]
  • Polymorphism: Different objects respond to different actions for the same method
class Animal:
    def run(self):
        raise AttributeError('Subclasses must implement this method')


class People(Animal):
    def run(self):
        print('People are walking')


class Pig(Animal):
    def run(self):
        print('pig is walking')


class Dog(Animal):
    def run(self):
        print('dog is running')


def func(animal):
    animal.run()


func(Pig())
# pig is walking

What is self?

The self of Python is equivalent to the this pointer of C++.

class Test:
    def prt(self):
        print(self)
        print(self.__class__)

t = Test()
t.prt()
# <__main__.Test object at 0x0000027519B900A0>
# <class '__main__.Test'>

Class methods differ only from normal functions in that they must have an additional first parameter name (corresponding to the instance, that is, the object itself), which by convention is self. When calling a method, we do not need to explicitly provide the parameter corresponding to the parameter self.

class Ball:
    def setName(self, name):
        self.name = name

    def kick(self):
        print("My name is%s,Damn, who kicked me..." % self.name)


a = Ball()
a.setName("The Ball A")
b = Ball()
b.setName("The Ball B")
c = Ball()
c.setName("The Ball C")
a.kick()
# My name is Ball A. Damn, who kicked me...
b.kick()
# My name is Ball B, damn, who kicked me...

Python's Magic

It is said that Python objects are naturally given some magical methods, which are all about object-oriented Python...

They are a special way to add magic to your class...

If your object implements one of these methods, the method will be called by Python in special cases, and everything happens automatically...

Class has a magic method named u init_ (self[, param1, param2...]), which is called automatically when the class is instantiated.

class Ball:
    def __init__(self, name):
        self.name = name

    def kick(self):
        print("My name is%s,Damn, who kicked me..." % self.name)


a = Ball("The Ball A")
b = Ball("The Ball B")
c = Ball("The Ball C")
a.kick()
# My name is Ball A. Damn, who kicked me...
b.kick()
# My name is Ball B, damn, who kicked me...

Public and Private

Defining a private variable in Python requires only two underscores u before the variable or function name, and the function or variable will be private.

class JustCounter:
    __secretCount = 0  # private variable
    publicCount = 0  # Open Variables

    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print(self.__secretCount)


counter = JustCounter()
counter.count()  # 1
counter.count()  # 2
print(counter.publicCount)  # 2

# Python's Private is Pseudo Private
print(counter._JustCounter__secretCount)  # 2 
print(counter.__secretCount)  
# AttributeError: 'JustCounter' object has no attribute '__secretCount'
class Site:
    def __init__(self, name, url):
        self.name = name  # public
        self.__url = url  # private

    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)

    def __foo(self):  # Private Method
        print('This is a private method')

    def foo(self):  # Public method
        print('This is a public method')
        self.__foo()


x = Site('My Programming Life', 'https://blog.csdn.net/xxx')
x.who()
# name: My Programming Life
# url :  https://blog.csdn.net/xxx

x.foo()
# This is a public method
# This is a private method

x.__foo()
# AttributeError: 'Site' object has no attribute '__foo'

inherit

Python also supports inheritance of classes, and derived classes are defined as follows:

class DerivedClassName(BaseClassName):
 statement-1
  .
  .
  .
 statement-N

BaseClassName (base class name) must be defined in one scope with a derived class. In addition to classes, expressions can also be used, which is useful when a base class is defined in another module:

class DerivedClassName(modname.BaseClassName):
 statement-1
  .
  .
  .
 statement-N

# If a method or property with the same name as the parent class is defined in a subclass, the method or property corresponding to the parent class is automatically overwritten.

# Class Definition
class people:
    # Define basic properties
    name = ''
    age = 0
    # Define private properties that are not directly accessible outside the class
    __weight = 0

    # Define construction methods
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w

    def speak(self):
        print("%s say: I %d Age." % (self.name, self.age))


# Single Inheritance Example
class student(people):
    grade = ''

    def __init__(self, n, a, w, g):
        # Call the parent class's constructor
        people.__init__(self, n, a, w)
        self.grade = g

    # Override method of parent class
    def speak(self):
        print("%s say: I %d Aged, I'm reading %d grade" % (self.name, self.age, self.grade))


s = student('The programmed life of the pony', 10, 60, 5)
s.speak()
# The programmer of the pony said: I am 10 years old, I am in fifth grade
  • Note: If the above program is removed: people._init_(self, n, a, w), the output is: I AM 0 years old, I am in fifth grade, because the construction method of the subclass overrides the construction method of the parent class.
import random

class Fish:
    def __init__(self):
        self.x = random.randint(0, 10)
        self.y = random.randint(0, 10)

    def move(self):
        self.x -= 1
        print("My Location", self.x, self.y)


class GoldFish(Fish):  # Goldfish
    pass


class Carp(Fish):  # carp
    pass


class Salmon(Fish):  # Salmon
    pass


class Shark(Fish):  # Shark
    def __init__(self):
        self.hungry = True

    def eat(self):
        if self.hungry:
            print("The dream to eat is to eat every day!")
            self.hungry = False
        else:
            print("Too much to eat!")
            self.hungry = True


g = GoldFish()
g.move()  # My position 8 3
s = Shark()
s.eat() # The dream to eat is to eat every day!
s.move()  
# AttributeError: 'Shark' object has no attribute 'x'

There are two ways to solve this problem:

  • Call unbound parent method Fish._init_(self)
  • Use the super function super()._init_()
class Shark(Fish):  # Shark
    def __init__(self):
        Fish.__init__(self)
        self.hungry = True

    def eat(self):
        if self.hungry:
            print("The dream to eat is to eat every day!")
            self.hungry = False
        else:
            print("Too much to eat!")
            self.hungry = True
class Shark(Fish):  # Shark
    def __init__(self):
        super().__init__()
        self.hungry = True

    def eat(self):
        if self.hungry:
            print("The dream to eat is to eat every day!")
            self.hungry = False
        else:
            print("Too much to eat!")
            self.hungry = True

Although Python supports multiple forms of inheritance, we generally do not use multiple inheritance because it can be confusing.

class DerivedClassName(Base1, Base2, Base3):
 statement-1
  .
  .
  .
 statement-N

It is important to note the order of the parent classes in parentheses. If the parent class has the same method name and is not specified when using a subclass, Python searches from left to right, that is, if a method is not found in a subclass, it searches from left to right for methods in the parent class.

# Class Definition
class People:
    # Define basic properties
    name = ''
    age = 0
    # Define private properties that are not directly accessible outside the class
    __weight = 0

    # Define construction methods
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w

    def speak(self):
        print("%s say: I %d Age." % (self.name, self.age))


# Single Inheritance Example
class Student(People):
    grade = ''

    def __init__(self, n, a, w, g):
        # Call the parent class's constructor
        People.__init__(self, n, a, w)
        self.grade = g

    # Override method of parent class
    def speak(self):
        print("%s say: I %d Aged, I'm reading %d grade" % (self.name, self.age, self.grade))


# Another class, preparation before multiple inheritance
class Speaker:
    topic = ''
    name = ''

    def __init__(self, n, t):
        self.name = n
        self.topic = t

    def speak(self):
        print("My name is %s,I am a speaker and the subject of my speech is %s" % (self.name, self.topic))


# multiple inheritance
class Sample01(Speaker, Student):
    a = ''

    def __init__(self, n, a, w, g, t):
        Student.__init__(self, n, a, w, g)
        Speaker.__init__(self, n, t)

# The method name is the same, and the default call is to precede the parent's method in parentheses
test = Sample01("Tim", 15, 80, 9, "Python")
test.speak()  
# My name is Tim, I'm a speaker, and the subject of my speech is Python

class Sample02(Student, Speaker):
    a = ''

    def __init__(self, n, a, w, g, t):
        Student.__init__(self, n, a, w, g)
        Speaker.__init__(self, n, t)

# The method name is the same, and the default call is to precede the parent's method in parentheses
test = Sample02("Tim", 15, 80, 9, "Python")
test.speak()  
# Tim said: I'm 15 and I'm in fourth grade

combination

class Turtle:
    def __init__(self, x):
        self.num = x


class Fish:
    def __init__(self, x):
        self.num = x


class Pool:
    def __init__(self, x, y):
        self.turtle = Turtle(x)
        self.fish = Fish(y)

    def print_num(self):
        print("Tortoise in sink%s Only, small fish%s strip" % (self.turtle.num, self.fish.num))


p = Pool(2, 3)
p.print_num()
# There are 2 turtles and 3 small fish in the pool

Classes, Class Objects, and Instance Objects

Class object and instance object

Class object: Create a class, in fact, an object opens up a space in memory, called a class object, class object has only one.

class A(object):
 pass

Instance object: An object created by an instantiation class, called an instance object, can have more than one instance object.

class A(object):
    pass

# The instantiated objects a, b, c belong to the instance object.
a = A()
b = A()
c = A()

Class attributes: Variables defined outside the methods inside a class are called class attributes. Class attributes belong to class objects and share the same class attributes among multiple instance objects. In other words, class attributes can be shared by all objects instantiated through the class.

class A():
    a = 0  #Class Properties
    def __init__(self, xx):
        A.a = xx  #Use of class properties can be invoked through (class name. class properties).

Instance properties: Instance properties are related to a specific instance object, and one instance object and another instance object do not share properties. It is said that instance properties can only be used in their own objects, other objects cannot be used directly, because who called self, its value belongs to the object.

# create a class object
class Test(object):
    class_attr = 100  # Class Properties

    def __init__(self):
        self.sl_attr = 100  # Instance Properties

    def func(self):
        print('Class object.Value of class attribute:', Test.class_attr)  # Call Class Properties
        print('self.Value of class attribute', self.class_attr)  # Equivalent to turning class attributes into instance attributes
        print('self.Value of Instance Property', self.sl_attr)  # Call Instance Properties


a = Test()
a.func()

# Class object. Value of class attribute: 100
# Value of self.class attribute 100
# self.Instance property value 100

b = Test()
b.func()

# Class object. Value of class attribute: 100
# Value of self.class attribute 100
# self.Instance property value 100

a.class_attr = 200
a.sl_attr = 200
a.func()

# Class object. Value of class attribute: 100
# Value 200 for self.class attribute
# self.Instance property value 200

b.func()

# Class object. Value of class attribute: 100
# Value of self.class attribute 100
# self.Instance property value 100

Test.class_attr = 300
a.func()

# Class object. Value of class attribute: 300
# Value 200 for self.class attribute
# self.Instance property value 200

b.func()
# Class object. Value of class attribute: 300
# Value 300 for self.class attribute
# self.Instance property value 100

Note: Attributes and method names are the same, and attributes override methods.

class A:
    def x(self):
        print('x_man')


aa = A()
aa.x()  # x_man
aa.x = 1
print(aa.x)  # 1
aa.x()
# TypeError: 'int' object is not callable

What is a binding?

Python strictly requires that methods require instances to be invoked, and this limitation is Python's so-called binding concept.

The data properties of Python objects are usually stored in a dictionary named. u dict_ and can be accessed directly or obtained using Python's built-in function vars().

class CC:
    def setXY(self, x, y):
        self.x = x
        self.y = y

    def printXY(self):
        print(self.x, self.y)


dd = CC()
print(dd.__dict__)
# {}

print(vars(dd))
# {}

print(CC.__dict__)
# {'__module__': '__main__', 'setXY': <function CC.setXY at 0x000000C3473DA048>, 'printXY': <function CC.printXY at 0x000000C3473C4F28>,
# '__dict__': <attribute '__dict__' of 'CC' objects>, '__weakref__': <attribute '__weakref__' of 'CC' objects>, '__doc__': None}

dd.setXY(4, 5)
print(dd.__dict__)
# {'x': 4, 'y': 5}

print(vars(CC))
# {'__module__': '__main__', 'setXY': <function CC.setXY at 0x000000632CA9B048>, 'printXY': <function CC.printXY at 0x000000632CA83048>,
# '__dict__': <attribute '__dict__' of 'CC' objects>, '__weakref__': <attribute '__weakref__' of 'CC' objects>, '__doc__': None}

print(CC.__dict__)
# {'__module__': '__main__', 'setXY': <function CC.setXY at 0x000000632CA9B048>, 'printXY': <function CC.printXY at 0x000000632CA83048>,
# '__dict__': <attribute '__dict__' of 'CC' objects>, '__weakref__': <attribute '__weakref__' of 'CC' objects>, '__doc__': None}

Some related built-in functions (BIF s)

  • The issubclass(class, classinfo) method is used to determine whether the parameter class is a subclass of the type parameter classinfo.
  • A class is considered to be a subclass of itself.
  • classinfo can be a tuple of class objects and returns True as long as class is a subclass of any of the candidate classes.
class A:
    pass


class B(A):
    pass


print(issubclass(B, A))  # True
print(issubclass(B, B))  # True
print(issubclass(A, B))  # False
print(issubclass(B, object))  # True
  • The isinstance(object, classinfo) method is used to determine whether an object is a known type, similar to type().
  • type() does not consider a child to be a parent type, regardless of inheritance.
  • isinstance() considers a subclass to be a parent type and considers inheritance.
  • False is always returned if the first parameter is not an object.
  • If the second parameter is not a class or tuple of class objects, a TypeError exception is thrown.
a = 2
print(isinstance(a, int))  # True
print(isinstance(a, str))  # False
print(isinstance(a, (str, int, list)))  # True


class A:
    pass


class B(A):
    pass


print(isinstance(A(), A))  # True
print(type(A()) == A)  # True
print(isinstance(B(), A))  # True
print(type(B()) == A)  # False
  • hasattr(object, name) is used to determine whether an object contains corresponding attributes.
class Coordinate:
    x = 10
    y = -5
    z = 0


point1 = Coordinate()
print(hasattr(point1, 'x'))  # True
print(hasattr(point1, 'y'))  # True
print(hasattr(point1, 'z'))  # True
print(hasattr(point1, 'no'))  # False
  • getattr(object, name[, default]) returns an object property value.
class A(object):
    bar = 1


a = A()
print(getattr(a, 'bar'))  # 1
print(getattr(a, 'bar2', 3))  # 3
print(getattr(a, 'bar2'))
# AttributeError: 'A' object has no attribute 'bar2'
class A(object):
    def set(self, a, b):
        x = a
        a = b
        b = x
        print(a, b)


a = A()
c = getattr(a, 'set')
c(a='1', b='2')  # 2 1
  • setattr(object, name, value) corresponds to the function getattr(), which sets the value of an attribute that does not necessarily exist.
class A(object):
    bar = 1


a = A()
print(getattr(a, 'bar'))  # 1
setattr(a, 'bar', 5)
print(a.bar)  # 5
setattr(a, "age", 28)
print(a.age)  # 28
  • delattr(object, name) is used to delete attributes.
class Coordinate:
    x = 10
    y = -5
    z = 0


point1 = Coordinate()

print('x = ', point1.x)  # x =  10
print('y = ', point1.y)  # y =  -5
print('z = ', point1.z)  # z =  0

delattr(Coordinate, 'z')

print('--delete z After Attribute--')  # --After deleting the z attribute--
print('x = ', point1.x)  # x =  10
print('y = ', point1.y)  # y =  -5

# Trigger error
print('z = ', point1.z)
# AttributeError: 'Coordinate' object has no attribute 'z'
  • class property([fget[, fset[, fdel[, doc]]]]) is used to return property values in new classes.
    • fget - Function to get attribute values
    • fset - Function to set property values
    • fdel - Delete Attribute Value Function
    • doc - Attribute Description Information
class C(object):
    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.")


cc = C()
cc.x = 2
print(cc.x)  # 2

del cc.x
print(cc.x)
# AttributeError: 'C' object has no attribute '_C__x'

Magic Method

Magic methods are always surrounded by double underlines, such as _init_.

Magic methods are all about object-oriented Python, and if you don't know magic, you're not aware of the power of object-oriented Python.

The magic of magic methods is that they are always called automatically at the right time.

The first argument to the magic method should be cls (class method) or self (instance method).

  • cls: the name representing a class
  • self: the name representing an instance object

Basic magic

  • The u init_ (self[,...]) constructor, the initialization method invoked when an instance is created
class Rectangle:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def getPeri(self):
        return (self.x + self.y) * 2

    def getArea(self):
        return self.x * self.y


rect = Rectangle(4, 5)
print(rect.getPeri())  # 18
print(rect.getArea())  # 20
  • The first method called by u new_u (cls[,...]) when an object is instantiated calls u new_u before calling u init_u initialization.
    • _new_u requires at least one parameter, cls, representing the class to be instantiated, which is automatically provided by the Python interpreter at the time of instantiation and is passed directly to u init_.
    • _new_u instantiates the current class and returns the instance to self of u init_. However, when u new_ is executed, it does not necessarily enter u init_u. Only when u new_ returns an instance of the current class cls, will u init_u of the current class enter.
class A(object):
    def __init__(self, value):
        print("into A __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into A __new__")
        print(cls)
        return object.__new__(cls)


class B(A):
    def __init__(self, value):
        print("into B __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into B __new__")
        print(cls)
        return super().__new__(cls, *args, **kwargs)


b = B(10)

# <class>indicates that it is a class object. u main_u.B indicates that this class object is a class object named B in the current program.

# Result:
# into B __new__
# <class '__main__.B'>
# into A __new__
# <class '__main__.B'>
# into B __init__

class A(object):
    def __init__(self, value):
        print("into A __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into A __new__")
        print(cls)
        return object.__new__(cls)


class B(A):
    def __init__(self, value):
        print("into B __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into B __new__")
        print(cls)
        return super().__new__(A, *args, **kwargs)  # Changed cls to A


b = B(10)

# Result:
# into B __new__
# <class '__main__.B'>
# into A __new__
# <class '__main__.A'>
  • If u new_u does not return an instance of the current class cls correctly, u init_u will not be invoked, not even the instance of the parent class, and no u init_ will be invoked.
class Earth:
    pass


a = Earth()
print(id(a))  # 3246693674912
b = Earth()
print(id(b))  # 3246692922656

class Earth:
    __instance = None  # Define a class attribute for judgment

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
            return cls.__instance
        else:
            return cls.__instance


a = Earth()
print(id(a))  # 3246693672464
b = Earth()
print(id(b))  # 3246693672464
  • The u new_u method primarily provides you with a way to customize the instantiation process of these classes when you inherit immutable classes (such as int, str, tuple)
class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls, string)


a = CapStr("i love python")
print(a)  # I LOVE PYTHON
  • The u del_ (self) destructor, a method that is called when an object is about to be recycled by the system.

Python uses automatic reference counting (ARC)This method reclaims the space occupied by an object. When a variable in the program references the Python object, Python automatically guarantees that the object reference count is 1; when two variables in the program reference the Python object, Python automatically guarantees that the object reference count is 2, and so on, if the object reference count becomes 20 means that the object is no longer referenced by a variable in the program, indicating that the program no longer needs the object, so Python recycles it.

Most of the time, Python's ARC can accurately and efficiently recycle every object in the system. However, if a circular reference occurs in the system, such as object a holding an instance variable referencing object b and object b holding an instance variable referencing object a, then the reference counts for both objects are1. In fact, programs no longer have variable references to them, and the system should recycle them, so Python's garbage collector may not be as fast as it can, waiting for a dedicated Cyclic Garbage Collector to detect and recycle this reference cycle.

class C(object):
    def __init__(self):
        print('into C __init__')

    def __del__(self):
        print('into C __del__')


c1 = C()
# into C __init__
c2 = c1
c3 = c2
del c3
del c2
del c1
# into C __del__
  • __str__(self):

  • Trigger u str_u when you print an object

  • Trigger u str_u when you format with%s

  • Trigger u str_u when STR strongly converts a data type

  • __repr__(self):

  • repr is str's replacement

  • Execute u str_u when u str_ is present, execute u repr_u when u str_ is not implemented

  • The result of the repr(obj) built-in function is the return value of u repr_u

  • Trigger u repr_u when you format with%r

class Cat:
    """Define a cat class"""

    def __init__(self, new_name, new_age):
        """Called automatically after the object is created, It completes the initialization of the object"""
        self.name = new_name
        self.age = new_age

    def __str__(self):
        """Returns a description of an object"""
        return "Name is:%s , Age is:%d" % (self.name, self.age)
        
    def __repr__(self):
        """Returns a description of an object"""
        return "Cat:(%s,%d)" % (self.name, self.age)

    def eat(self):
        print("%s Eating fish...." % self.name)

    def drink(self):
        print("%s Drinking Coke..." % self.name)

    def introduce(self):
        print("Name is:%s, Age is:%d" % (self.name, self.age))


# An object was created
tom = Cat("Tom", 30)
print(tom)  # The name is Tom and the age is 30
print(str(tom)) # The name is Tom and the age is 30
print(repr(tom))  # Cat: (Tom, 30)
tom.eat()  # Tom is eating fish...
tom.introduce()  # The name is Tom and the age is 30

The results returned by u str_ (self) are readable. That is, the meaning of u str_ is to get information that is easy for people to read, just like 2019-10-11 below.

The result returned by u repr_u(self) should be more accurate. In other words, u repr_ exists for debugging purposes and ease of use by developers.

import datetime

today = datetime.date.today()
print(str(today))  # 2021-10-03
print(repr(today))  # datetime.date(2021, 10, 3)
print('%s' %today)  # 2021-10-03
print('%r' %today)  # datetime.date(2021, 10, 3)

Arithmetic Operators

Type factory functions refer to the creation of objects through functions rather than classes.

class C:
    pass


print(type(len))  # <class 'builtin_function_or_method'>
print(type(dir))  # <class 'builtin_function_or_method'>
print(type(int))  # <class 'type'>
print(type(list))  # <class 'type'>
print(type(tuple))  # <class 'type'>
print(type(C))  # <class 'type'>
print(int('123'))  # 123

# In this example, the list factory function processes a meta-ancestor object into a list object.
print(list((1, 2, 3)))  # [1, 2, 3]
  • _u add_u (self, other) defines the behavior of addition: +
  • _u sub_u(self, other) defines the behavior of subtraction: -
class MyClass:

    def __init__(self, height, weight):
        self.height = height
        self.weight = weight

    # Two objects add up in length and remain the same in width. Returns a new class
    def __add__(self, others):
        return MyClass(self.height + others.height, self.weight + others.weight)

    # Subtracts the width of two objects and keeps the length constant. Returns a new class
    def __sub__(self, others):
        return MyClass(self.height - others.height, self.weight - others.weight)

    # Speak about your parameters
    def intro(self):
        print("High to", self.height, " Re-to", self.weight)


def main():
    a = MyClass(height=10, weight=5)
    a.intro()

    b = MyClass(height=20, weight=10)
    b.intro()

    c = b - a
    c.intro()

    d = a + b
    d.intro()


if __name__ == '__main__':
    main()

# 10 for 5
# High 20 to 10
# 10 for 5
# Height 30 and weight 15
  • _u mul_ (self, other) defines the behavior of multiplication: *
  • _u truediv_ (self, other) Defines the behavior of true division: /
  • _u floordiv_ (self, other) Defines the behavior of integer division: //
  • _u mod_ (self, other) defines the behavior of the modularization algorithm:%
  • _u divmod_ (self, other) defines the behavior when called by divmod()

divmod(a, b) combines the result of division with the result of the remainder operation and returns a tuple (a // b, a% b) containing the quotient and the remainder.

print(divmod(7, 2))  # (3, 1)
print(divmod(8, 2))  # (4, 0)
  • _u pow_ (self, other[, module]) defines the behavior when called by power() or ** operations
  • _u lshift_u (self, other) defines the behavior of a bitwise left shift: < < <.
  • _u rshift_u (self, other) defines the behavior of a bitwise right shift: >>
  • _u and_u(self, other) Defines the behavior of bitwise and operations:&
  • _u xor_ (self, other) defines the behavior of bitwise XOR operations: ^
  • _u or_u(self, other) Defines the behavior of bitwise or operations: |

Inverse arithmetic operator

The inverse magic cube method maintains a one-to-one correspondence with the arithmetic operator, except that the magic method of the inverse operation has an additional "r". It is called when the file left operation does not support the corresponding operation.

  • _u radd_ (self, other) defines the behavior of addition: +
  • _u rsub_ (self, other) defines the behavior of subtraction: -
  • _u rmul_u(self, other) Defines the behavior of multiplication: *
  • _u rtruediv_ (self, other) Defines the behavior of true division: /
  • _u rfloordiv_ (self, other) Defines the behavior of integer division: //
  • _u rmod_u (self, other) defines the behavior of the modularization algorithm:%
  • _u rdivmod_u (self, other) defines the behavior when called by divmod()
  • _u rpow_ (self, other[, module]) defines the behavior when called by power() or ** operations
  • _u rlshift_u (self, other) defines the behavior of a bitwise left shift: < <
  • _u rrshift_u (self, other) defines the behavior of a bitwise right shift: >>
  • _u rand_ (self, other) defines the behavior of bitwise and operations: &
  • _u rxor_ (self, other) defines the behavior of bitwise XOR operations: ^
  • _u ror_u (self, other) Defines the behavior of bitwise or operations: |

a + b

Here the additive is a and the added number is b, so a is active. The inverse operation is that if the u add_u() method of the a object is not implemented or does not support the corresponding operation, Python will call the u radd_() method of B.

class Nint(int):
    def __radd__(self, other):
        return int.__sub__(other, self) # Notice that self is behind


a = Nint(5)
b = Nint(3)
print(a + b)  # 8
print(1 + b)  # -2

Incremental assignment operator

  • _u iadd_ (self, other) defines the behavior of assignment addition: +=
  • _u isub_ (self, other) defines the behavior of assignment subtraction: -=
  • _u imul_u(self, other) Defines the behavior of assignment multiplication: *=
  • _u itruediv_ (self, other) defines the behavior of assignment true division: /=
  • _u ifloordiv_ (self, other) defines the behavior of assigning integer division: //=
  • _u imod_u (self, other) defines the behavior of the modulus assignment algorithm:%=
  • _u ipow_ (self, other[, modulo]) defines the behavior of assigning a power operation: **=
  • _u ilshift_ (self, other) defines the behavior of assigning bitwise left shifts: <<=
  • _u irshift_ (self, other) defines the behavior of assigning bitwise right shifts: >=
  • _u iand_ (self, other) defines the behavior of assignment bitwise and operation: &=
  • _u ixor_ (self, other) defines the behavior of assigning bitwise exclusive or operations: ^=
  • _u ior_u (self, other) defines the behavior of assigning bits or operations: |=

Unary operator

  • _u neg_u(self) Defines the behavior of a plus sign: +x
  • _u pos_u(self) Defines the behavior of a negative sign: -x
  • _u abs_u(self) defines the behavior when called by abs()
  • _u invert_u(self) defines the behavior of bitwise negation: ~x

Property Access

  • _u getattr_u (self, name): Defines the behavior of a user when attempting to acquire a property that does not exist.
  • _u getattribute_u (self, name): Defines the behavior when a property of the class is accessed (invokes the method first to see if the property exists, and then u getattr_u if it does not exist).
  • _u setattr_ (self, name, value): Defines the behavior when an attribute is set.
  • _u delattr_ (self, name): Defines the behavior when an attribute is deleted.
class C:
    def __getattribute__(self, item):
        print('__getattribute__')
        return super().__getattribute__(item)

    def __getattr__(self, item):
        print('__getattr__')

    def __setattr__(self, key, value):
        print('__setattr__')
        super().__setattr__(key, value)

    def __delattr__(self, item):
        print('__delattr__')
        super().__delattr__(item)


c = C()
c.x
# __getattribute__
# __getattr__

c.x = 1
# __setattr__

del c.x
# __delattr__

descriptor

A descriptor is an attribute that assigns an instance of a particular type of class to another class.

  • _u get_u (self, instance, owner) is used to access a property and returns the value of the property.
  • The u set_u (self, instance, value) is called in the property assignment operation and does not return anything.
  • The u del_ (self, instance) controls the deletion operation and does not return anything.
class MyDecriptor:
    def __get__(self, instance, owner):
        print('__get__', self, instance, owner)

    def __set__(self, instance, value):
        print('__set__', self, instance, value)

    def __delete__(self, instance):
        print('__delete__', self, instance)


class Test:
    x = MyDecriptor()


t = Test()
t.x
# __get__ <__main__.MyDecriptor object at 0x000000CEAAEB6B00> <__main__.Test object at 0x000000CEABDC0898> <class '__main__.Test'>

t.x = 'x-man'
# __set__ <__main__.MyDecriptor object at 0x00000023687C6B00> <__main__.Test object at 0x00000023696B0940> x-man

del t.x
# __delete__ <__main__.MyDecriptor object at 0x000000EC9B160A90> <__main__.Test object at 0x000000EC9B160B38>

Custom Sequence

Protocols are very similar to interfaces in other programming languages in that they specify which methods you must define. However, protocols in Python are less formal. In fact, protocols are more like a guide in Python.

Container Type Protocol

  • If you want custom containers to be immutable, you only need to define u len_() and u getitem_() methods.
  • If you want custom containers to be mutable, in addition to the u len_() and u getitem_() methods, you need to define u setitem_() and u delitem_() methods.
class CountList:
    def __init__(self, *args):
        self.values = [x for x in args]
        self.count = {}.fromkeys(range(len(self.values)), 0)

    def __len__(self):
        return len(self.values)

    def __getitem__(self, item):
        self.count[item] += 1
        return self.values[item]


c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1])  # 3
print(c2[2])  # 6
print(c1[1] + c2[1])  # 7

print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}

print(c2.count)
# {0: 0, 1: 1, 2: 1, 3: 0, 4: 0}
  • _u len_u (self) defines the behavior (returning the number of elements in the container) when called by len().
  • _u getitem_ (self, key) defines the behavior of getting elements in a container, equivalent to self[key].
  • _u setitem_ (self, key, value) defines the behavior of setting the specified element in the container, equivalent to self[key] = value.
  • _u delitem_ (self, key) defines the behavior of deleting a specified element from a container, equivalent to del self[key].
# Write a customized list of changes that require you to record how many times each element in the list has been visited

class CountList:
    def __init__(self, *args):
        self.values = [x for x in args]
        self.count = {}.fromkeys(range(len(self.values)), 0)

    def __len__(self):
        return len(self.values)

    def __getitem__(self, item):
        self.count[item] += 1
        return self.values[item]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]
        for i in range(0, len(self.values)):
            if i >= key:
                self.count[i] = self.count[i + 1]
        self.count.pop(len(self.values))


c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1])  # 3
print(c2[2])  # 6
c2[2] = 12
print(c1[1] + c2[2])  # 15
print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
print(c2.count)
# {0: 0, 1: 0, 2: 2, 3: 0, 4: 0}
del c1[1]
print(c1.count)
# {0: 0, 1: 0, 2: 0, 3: 0}

iterator

  • Iteration is one of Python's most powerful functions and a way to access collection elements.
  • An iterator is an object that remembers the location of traversal.
  • Iterator objects are accessed from the first element of the collection until all elements are accessed.
  • Iterators can only move forward and not backward.
  • String, list, or tuple objects can be used to create iterators:
string = 'python'
for c in string:
    print(c)

'''
p
y
t
h
o
n
'''

for c in iter(string):
    print(c)
links = {'B': 'Baidu', 'A': 'Ali', 'T': 'tencent'}
for each in links:
    print('%s -> %s' % (each, links[each]))
    
'''
B -> Baidu
A -> Ali
T -> tencent
'''

for each in iter(links):
    print('%s -> %s' % (each, links[each]))
  • Iterators have two basic methods: iter() and next().
  • The iter(object) function is used to generate an iterator.
  • next(iterator[, default]) returns the next item of the iterator.
  • iterator - Iterable object
  • Default - Optional to set the default value to be returned when there is no next element, and if not, the StopIteration exception will be triggered if there is no next element.
links = {'B': 'Baidu', 'A': 'Ali', 'T': 'tencent'}

it = iter(links)
while True:
    try:
        each = next(it)
    except StopIteration:
        break
    print(each)

# B
# A
# T

it = iter(links)
print(next(it))  # B
print(next(it))  # A
print(next(it))  # T
print(next(it))  # StopIteration

Using a class as an iterator requires two magic methods u iter_() and u next_() to be implemented in the class.

  • _u iter_u(self) defines the behavior of elements in an iteration container and returns a special iterator object that implements the u next_() method and identifies the completion of iteration by a StopIteration exception.
    _u next_() returns the next iterator object.
  • StopIteration exceptions are used to identify the completion of an iteration and prevent an infinite loop from occurring. In the u next_() method, we can set the StopIteration exception to be triggered to end the iteration after a specified number of cycles have been completed.
class Fibs:
    def __init__(self, n=10):
        self.a = 0
        self.b = 1
        self.n = n

    def __iter__(self):
        return self

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


fibs = Fibs(100)
for each in fibs:
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89

generator

  • In Python, functions that use yield are called generator s.
  • Unlike normal functions, a generator is a function that returns an iterator and can only be used for iteration operations. A simpler point understanding generator is an iterator.
  • During the call generator run, the function pauses and saves all current run information each time yielding is encountered, returns the value of yielding, and continues to run from the current location the next time the next() method is executed.
  • Calling a generator function returns an iterator object.
def myGen():
    print('Generator Execution!')
    yield 1
    yield 2
    
myG = myGen()
for each in myG:
    print(each)

'''
Generator Execution!
1
2
'''

myG = myGen()
print(next(myG))  
# Generator Execution!
# 1

print(next(myG))  # 2
print(next(myG))  # StopIteration
def libs(n):
    a = 0
    b = 1
    while True:
        a, b = b, a + b
        if a > n:
            return
        yield a


for each in libs(100):
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89

Keywords: Python

Added by Anidazen on Sun, 03 Oct 2021 19:54:33 +0300