Python3 study notes (19) - first understanding of object-oriented programming

1. Classes and Instances

A class is an abstract template, and an instance is an object created from a class. Each instance object has the same method, but the data may be different.
Take the Student class as an example:

    class Student(object):
        pass

The class name is followed by the class name, that is, Student. The class name is usually a capitalized word, followed by (object), indicating which class the class is inherited from. We will talk about the concept of inheritance later. Usually, if there is no The appropriate inheriting class is to use the object class, which is the class that all classes will eventually inherit from.

After defining the Student class, you can create an instance of Student based on the Student class. Creating an instance is achieved through the class name + ():

    >>> bart = Student()
    >>> bart
    <__main__.Student object at 0x10a67a590>
    >>> Student
    <class '__main__.Student'>

It can be seen that the variable bart points to an instance of Student, and the following 0x10a67a590 is the memory address. The address of each object is different, and Student itself is a class.

You can freely bind properties to an instance variable, for example, to bind a name property to the instance bart:

    >>> bart.name = 'Bart Simpson'
    >>> bart.name
    'Bart Simpson'

Since a class can play the role of a template, when creating an instance, we can force some properties that we think must be bound to be filled in. This is also just like the constructor as we know it. By defining a special init method, when an instance is created, attributes such as name, score, etc. are bound:

    class Student(object):

        def __init__(self, name, score):
            self.name = name
            self.score = score

Note that the first parameter of the init method is always self, which represents the created instance itself. Therefore, within the init method, various properties can be bound to self, because self points to the created instance itself.

With the init method, when creating an instance, you can't pass in empty parameters, you must pass in parameters that match the init method, but you don't need to pass self, the Python interpreter will pass the instance variables in by itself:

    >>> bart = Student('Bart Simpson', 59)
    >>> bart.name
    'Bart Simpson'
    >>> bart.score
    59

Compared with ordinary functions, functions defined in classes have only one difference, that is, the first parameter is always the instance variable self, and this parameter is not passed when calling. Other than that, class methods are no different from normal functions, so you can still use default arguments, variadic arguments, keyword arguments, and named keyword arguments.

2. Data encapsulation

An important feature of object-oriented programming is data encapsulation. In the above Student class, each instance has its own name and score data. We can access this data through functions, such as printing a student's grades:

    >>> def print_score(std):
    ...     print('%s: %s' % (std.name, std.score))
    ...
    >>> print_score(bart)
        Bart Simpson: 59

However, since the Student instance itself has the data, to access the data, there is no need to access it from outside functions. You can directly define the function to access the data inside the Student class, thus encapsulating the "data". . These functions that encapsulate data are associated with the Student class itself, which we call class methods:

    class Student(object):

        def __init__(self, name, score):
            self.name = name
            self.score = score

        def print_score(self):
            print('%s: %s' % (self.name, self.score))

To define a method, it is the same as a normal function except that the first parameter is self. To call a method, you only need to call it directly on the instance variable, except for self, which is not passed, and other parameters are passed in normally:

    >>> bart.print_score()
    Bart Simpson: 59

In this way, when we look at the Student class from the outside, we only need to know that the name and score need to be given to create an instance, and how to print is defined inside the Student class. These data and logic are "encapsulated", It's easy to call, but you don't need to know the details of the internal implementation.

Another benefit of encapsulation is that you can add new methods to the Student class, such as get_grade:

    class Student(object):
        ...

        def get_grade(self):
            if self.score >= 90:
                return 'A'
            elif self.score >= 60:
                return 'B'
            else:
                return 'C'

Likewise, the get_grade method can be called directly on instance variables without knowing the internal implementation details.

A class is a template for creating an instance, and an instance is a concrete object. The data owned by each instance is independent of each other and does not affect each other;

A method is a function bound to an instance. Unlike ordinary functions, a method can directly access the instance's data;

By calling a method on an instance, we are directly manipulating the data inside the object without knowing the implementation details inside the method.

Unlike static languages, Python allows binding any data to instance variables, that is, for two instance variables, although they are different instances of the same class, they may have different variable names

3. Private variables in python classes

If you want to prevent internal attributes from being accessed externally, you can add two underscores before the name of the attribute __. In Python, if the variable name of an instance __starts with a variable name, it becomes a private variable (private), which can only be accessed internally. It is not accessible from the outside, so let's change the Student class:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

   def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

After the change, there is no change to the external code, but it is no longer accessible from the 实例变量.__nameoutside 实例变量.__score:

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

This ensures that external code cannot arbitrarily modify the internal state of the object, so that the code is more robust through the protection of access restrictions.

But what if the external code wants to get or modify the name and score? You can add get_nameand get_scoresum set_scoremethods like this to the Student class:

def get_name(self):
    return self.__name
def set_score(self, score):
    self.__score = score

You may ask, the original kind of direct pass bart.score = 99can also be modified, why does it take a lot of trouble to define a method? Because in the method, the parameters can be checked to avoid passing invalid parameters:

def set_score(self, score):
    if 0 <= score <= 100:
        self.__score = score
    else:
        raise ValueError('bad score')

It should be noted that in Python, variable names are similar __xxx__, that is, those starting with a double underscore and ending with a double underscore are special variables, and special variables can be accessed directly, not private variables, so they cannot be used __name__, __score__like this variable name.

Sometimes, you will see instance variable names that start with an underscore, for example _name, such instance variables are accessible from outside, but, by convention, when you see such a variable, it means, "Although I Can be accessed, but please treat me as a private variable and don't access it at will".

Are instance variables starting with a double underscore not necessarily accessible from the outside? Not really. It cannot be accessed directly __namebecause the Python interpreter has __namechanged the variable to the outside world _Student__name, so the variable can still be accessed _Student__nameby __name:

>>> bart._Student__name
    'Bart Simpson'

Different versions of the Python interpreter may change __name to different variable names.
Note the following typo:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'

On the surface, the external code "successfully" sets the __namevariable, but in fact this __namevariable is not the same variable as the variable inside the class __name! The internal __namevariable has been automatically changed by the Python interpreter _Student__name, and the external code adds a new __namevariable to bart:

>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'

4. Inheritance

In OOP programming, when we define a class, we can inherit from an existing class, the new class is called a subclass, and the inherited class is called a base class, parent class or superclass (Base class, Super class).

For example, we've written a class called Animal that has a run() method that prints directly:

class Animal(object):
    def run(self):
        print('Animal is running...')

When we need to write Dog and Cat classes, we can inherit directly from the Animal class:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

For Dog, Animal is its parent class, and for Animal, Dog is its subclass. Cat and Dog are similar.

What are the benefits of inheritance? The biggest benefit is that the subclass gets the full functionality of the parent class. Since Animial implements the run() method, Dog and Cat, as its subclasses, automatically have the run() method without doing anything:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

结果:
Animal is running...
Animal is running...

Whether it is Dog or Cat, when they run(), they display Animal is running.... The logical approach is to display Dog is running... and Cat is running... respectively. Therefore, the improvements to the Dog and Cat classes are as follows:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

class Cat(Animal):

    def run(self):
        print('Cat is running...')

5. Polymorphism

When both the subclass and the superclass have the same run() method, we say that the subclass's run() overrides the superclass's run(), and when the code runs, the subclass's run() is always called . In this way, we get another benefit of inheritance: polymorphism.
Whether a variable is of a certain type can be determined with isinstance():

c = Dog() # c是Dog类型
>>> isinstance(c, Dog) #判断c是否为Dog类型
True
>>> isinstance(c, Animal)
True

Because Dog is inherited from Animal, when we create an instance c of Dog, we think that the data type of c is Dog, but it is also correct that c is also Animal. Dog is originally a kind of Animal!
In the inheritance relationship, if the data type of an instance is a subclass, its data type can also be regarded as a superclass. However, the reverse does not work:

>>> b = Animal()
>>> isinstance(b, Dog)
False

Dog can be regarded as Animal, but Animal cannot be regarded as Dog.

To understand the benefits of polymorphism, we need to write one more function that accepts a variable of type Animal:

def run_twice(animal):
    animal.run()
    animal.run()

When we pass in an instance of Animal, run_twice() prints out:

>>> run_twice(Animal())
Animal is running...
Animal is running...

When we pass in an instance of Dog, run_twice() prints out:

>>> run_twice(Dog())
Dog is running...
Dog is running...

Now, if we define another Tortoise type, also derived from Animal:

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')
#运行结果:
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

Add a subclass of Animal without any modification to run_twice(). In fact, any function or method that relies on Animal as a parameter can run normally without modification, because of polymorphism.

The advantage of polymorphism is that when we need to pass in Dog, Cat, Tortoise..., we only need to receive the Animal type, because Dog, Cat, Tortoise... are all Animal types, and then operate according to the Animal type. That's it. Since the Animal type has a run() method, any incoming type, as long as it is an Animal class or a subclass, will automatically call the run() method of the actual type, which is what polymorphism means:

For a variable, we only need to know that it is of Animal type, without knowing exactly its subtype, we can safely call the run() method, and the specific call of the run() method is on Animal, Dog, Cat or Tortoise On the object, it is determined by the exact type of the object at runtime. This is the real power of polymorphism: the caller just calls, regardless of the details, and when we add a subclass of Animal, just make sure the run() method is written correctly , regardless of how the original code was called. This is the famous "open-closed" principle:

Open to extensions: Allows to add Animal subclasses;

Closed to modification: functions such as run_twice() that depend on the Animal type do not need to be modified.

For static languages ​​(such as Java), if you need to pass in the Animal type, the incoming object must be of the Animal type or its subclass, otherwise, the run() method cannot be called.

For dynamic languages ​​like Python, it is not necessary to pass in the Animal type. We just need to ensure that the incoming object has a run() method:

class Timer(object):
    def run(self):
        print('Start...')

This is the "duck typing" of dynamic languages. It does not require a strict inheritance system. As long as an object "looks like a duck and walks like a duck", it can be regarded as a duck.

Python's "file-like object" is a duck typing. For real file objects, it has a read() method that returns its contents. However, many objects, as long as they have a read() method, are considered "file-like objects". The parameters that many functions receive are "file-like objects". You don't have to pass in a real file object, you can pass in any object that implements the read() method.

6. Get the properties and types of objects in a class

type() function

Basic types can be judged with type():

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

If a variable points to a function or class, you can also use type() to determine:

>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

To determine whether an object is a function, you can use the constants defined in the types module:

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

isinstance() function

The inheritance relationship is:
object -> Animal -> Dog -> Husky
Then, isinstance() can tell us whether an object is of a certain type. First create three types of objects:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

Then, judge:

>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True
>>> isinstance(h, Animal)
True
>>> isinstance(d, Husky)
False

You can also determine whether a variable is one of certain types. For example, the following code can determine whether it is a list or a tuple:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

dir() function

If you want to get all the properties and methods of an object, you can use the dir() function, which returns a list of strings, for example, to get all the properties and methods of a str object:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

Attributes and methods like xxx have special purposes in Python, such as the len method returning the length. In Python, if you call the len() function to try to get the length of an object, in fact, inside the len() function, it automatically calls the len () method of the object, so the following code is equivalent:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

getattr(), setattr() and hasattr(), we can directly manipulate the state of an object:

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

Test the object's properties:

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

Methods of test object:

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

7. Instance attributes and class attributes

The way to bind a property to an instance is through the instance variable, or through the self variable:

class Student(object):
    def __init__(self, name):
        self.name = name

    s = Student('Bob')
    s.score = 90

But what if the Student class itself needs to bind a property? Attributes can be defined directly in the class. This attribute is a class attribute and is owned by the Student class:

class Student(object):
    name = 'Student'

When we define a class attribute, although this attribute is classified as all, all instances of the class can be accessed. Let's test it out:

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324846829&siteId=291194637