Python Learning Directory

  1. Python3 is used under Mac
  2. Data types for Python learning
  3. Python learning functions
  4. Advanced features of Python learning
  5. Learning functional programming in Python
  6. Modules for learning Python
  7. Object-oriented programming for Learning Python
  8. Advanced object-oriented programming for Learning Python
  9. Error debugging and testing for Python learning
  10. Learning IO programming in Python
  11. Python learning processes and threads
  12. Regular for Learning Python
  13. Common modules for learning Python
  14. Network programming for Python learning

Data encapsulation, inheritance and polymorphism are just three basic concepts in object-oriented programming. In Python, object orientation also has many advanced features, such as multiple inheritance, custom classes, metaclass concepts, and so on.

_slots_

Effect: Limits the attributes of the instance.

Python allows classes to be defined with a special __slots__ variable that limits the number of attributes that can be added to the class instance:

class Student(object):
    __slots__ = ('name'.'age') Use a tuple to define the name of the property that is allowed to bind
    
>>> s = Student() Create a new instance
>>> s.name = 'Michael' # bind attribute 'name'
>>> s.age = 25 # bind attribute 'age'
>>> s.score = 99 # bind attribute 'score'
Traceback (most recent call last):
  File "<stdin>", line 1.in <module>
AttributeError: 'Student' object has no attribute 'score'
Copy the code

Since ‘score’ is not put in __slots__, the score attribute cannot be bound, and attempting to bind score will result in an AttributeError error.

Note that attributes defined by __slots__ only apply to the current instance of the class, not to inherited subclasses:

>>> class GraduateStudent(Student):
.    pass.>>> g = GraduateStudent()
>>> g.score = 9999
Copy the code

Unless __slots__ is also defined in a subclass, in which case a subclass instance is allowed to define its own __slots__ plus its parent’s __slots__.

@property

The @property decorator in Python is responsible for calling a method as a property:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer! ')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100! ')
        self._score = value
Copy the code

To turn a getter into a property, we simply add @property, which creates another decorator, @score.setter, to turn a setter into a property assignment, so we have a controlled property operation:

>>> s = Student()
>>> s.score = 60 # OK, actually convert to s.set_score(60)
>>> s.score # OK, actually convert to s.goet_score ()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!
Copy the code

Notice the magic @property, when we operate on an instance property, we know that the property is probably not exposed directly, but is implemented through getter and setter methods.

You can also define a read-only property by defining only getter methods and not setter methods:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth
Copy the code

The birth property above is a read-write property, while age is a read-only property because age can be calculated from birth and the current time.

Multiple inheritance

When designing class inheritance relationships, the main line is usually a single inheritance, for example, Ostrich inherits from Bird. However, if you need to “mix in” additional functionality, you can do so through multiple inheritance, such as having Ostrich inherit Runnable in addition to Bird. This design is often called a MixIn.

To better visualize the inheritance relationship, we changed Runnable and Flyable to RunnableMixIn and FlyableMixIn. Similarly, you can define CarnivorousMixIn and HerbivoresMixIn to allow an animal to have several mixins at the same time:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
Copy the code

The purpose of mixins is to add functionality to a class, so that when designing a class, we prioritize combining the functionality of multiple mixins through multiple inheritance, rather than designing multi-level and complex inheritance relationships.

Custom classes

Note that variable or function names like __slots__ have a form like __xxx__. These have special uses in Python. We already know how to use __slots__, and the __len__() method is used to allow class to operate on len(). In addition, there are many such special-purpose functions in Python classes that help you customize your classes.

_str_

>>> class Student(object):
.    def __init__(self, name):
.        self.name = name
.    def __str__(self):
.        return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
Copy the code

In this way, the printed instance is not only good-looking, but also easy to see the important data inside the instance. But if you type the variable directly instead of print, the printed instance is still not pretty:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
Copy the code

This is because the direct display variable does not call __str__(), but __repr__(). The difference is that __str__() returns the string seen by the user, while __repr__() returns the string seen by the program developer, that is, __repr__() is for debugging.

The solution is to define another __repr__(). But __str__() and __repr__() codes are usually the same, so here’s a lazy way to write:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__
Copy the code

_iter_

If a class wants to be used for… An in loop, like a list or tuple, must implement an __iter__() method that returns an iterator. Python’s for loop then calls the iterator’s __next__() method to get the next value of the loop. Exit the loop until a StopIteration error is encountered.

Let’s use the Fibonacci column as an example to write a Fib class that can operate on the for loop:

class Fib(object):
   def __init__(self):
       self.a, self.b = 0.1 Initialize two counters A, b

   def __iter__(self):
       return self The instance itself is an iterator, so return itself

   def __next__(self):
       self.a, self.b = self.b, self.a + self.b # Calculate the next value
       if self.a > 100000: Conditions for exiting the loop
           raise StopIteration()
       return self.a # return the next value
Copy the code

Now, try applying Fib instances to the for loop:

>>> for n in Fib():
.    print(n)
...
1
1
2
3
5.46368
75025
Copy the code

_getitem_

Fetching elements by index like a list requires the __getitem__() method:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1.1
        for x in range(n):
            a, b = b, a + b
        return a
Copy the code

Now you can access any item in the sequence by pressing the index:

>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
Copy the code

But list has a magic slicing method:

>>> list(range(100))5:10]
[5.6.7.8.9]
Copy the code

An error is reported for Fib. The reason is that __getitem__() may be passed an int or a slice object, so check:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n is the index
            a, b = 1.1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n is slice
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1.1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L 
Copy the code

_getattr_

Python has a mechanism for writing a __getattr__() method that dynamically returns a property:

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99
Copy the code

When calling a non-existent attribute, such as score, the Python interpreter tries to call __getattr__(self, ‘score’) to try to get the attribute, which gives us a chance to return the value of score:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
Copy the code

It is also perfectly fine to return a function:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
Copy the code

The call method is changed to:

>>> s.age()
25
Copy the code

Note that __getattr__ is called only if the attribute is not found. Existing attributes, such as name, are not looked up in __getattr__.

Also, notice that any call to s.abc returns None, because __getattr__ we define returns None by default. To make the class respond to only a few specific attributes, we throw AttributeError by convention:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
Copy the code

_call_

An object instance can have its own properties and methods. When we call instance methods, we call them with instance.method().

Similarly, any class simply needs to define a __call__() method to call an instance directly. Take an example:

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

    def __call__(self):
        print('My name is %s.' % self.name)
        
>>> s = Student('Michael')
>>> s() The # self argument is not passed in
My name is Michael.
Copy the code

__call__() can also define arguments. Calling an instance directly is just like calling a function, so you can treat an object as a function, and a function as an object, because there’s no fundamental difference between the two.

If you think of an object as a function, the function itself can actually be created dynamically at runtime, since instances of classes are created at runtime, thus blurring the line between objects and functions.

So, how do you determine whether a variable is an object or a function? In fact, more often than not, we need to determine whether an object can be called. The object that can be called is a Callable object, such as a function and the class instance we defined above with __call__() :

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1.2.3])
False
>>> callable(None)
False
>>> callable('str')
False
Copy the code

Enumeration class

Enum defines a class type for an enumerated type, and then each constant is a unique instance of class.

from enum import Enum

Month = Enum('Month', ('Jan'.'Feb'.'Mar'.'Apr'.'May'.'Jun'.'Jul'.'Aug'.'Sep'.'Oct'.'Nov'.'Dec'))
Copy the code

This gives us an enumeration class of type Month, which can be used directly to reference a constant, or to enumerate all its members:

for name, member in Month.__members__.items():
    print(name, '= >', member, ', ', member.value)
Copy the code

The value property is an int constant automatically assigned to the member, counting from 1 by default.

If you need more precise control over enumeration types, you can derive custom classes from Enum:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun value is set to 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
Copy the code

The @unique decorator helps us check to make sure there are no duplicate values.

There are several ways to access these enumerated types:

>>> day1 = Weekday.Mon >>> print(day1) Weekday.Mon >>> print(Weekday.Tue) Weekday.Tue >>> print(Weekday['Tue']) Weekday.Tue >>> print(Weekday.Tue.value) 2 >>> print(day1 == Weekday.Mon) True >>> print(day1 == Weekday.Tue) False >>> print(Weekday(1)) Weekday.Mon >>> print(day1 == Weekday(1)) True >>> Weekday(7) Traceback (most recent call last): ... ValueError: 7 is not a valid Weekday >>> for name, member in Weekday.__members__.items(): ... print(name, '=>', member) ... Sun => Weekday.Sun Mon => Weekday.Mon Tue => Weekday.Tue Wed => Weekday.Wed Thu => Weekday.Thu Fri => Weekday.Fri Sat =>  Weekday.SatCopy the code

As you can see, you can either refer to an enumerated constant with a member name or obtain it directly from the value of a value.

The metaclass

type()

The biggest difference between a dynamic language and a static language is that functions and classes are defined not at compile time but dynamically at run time.

For example, if we want to define a Hello class, write a hello.py module:

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)
Copy the code

When the Python interpreter loads the Hello module, it executes all of the module’s statements in turn. As a result, it dynamically creates a Hello class object.

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>
Copy the code

The type() function looks at the type of a type or variable. Hello is a class whose type is type, and h is an instance whose type is class Hello.

We say that the definition of a class is created dynamically at runtime, and the way to create a class is to use the type() function.

The type() function can both return the type of an object and create a new type. For example, we can create a Hello class by using type() instead of class Hello(object)… Definition:

>>> def fn(self, name='world'): print('Hello, %s.' % name) ... >>> Hello = type('Hello', (object,), dict(Hello =fn)) # Create Hello class >>> h = Hello() >>> h.hello(), world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>Copy the code

To create a class object, the type() function takes three arguments in turn:

  1. Class name;
  2. A collection of inherited parent classes. Note that Python supports multiple inheritance. If there is only one parent class, remember the single-element form of a tuple;
  3. The class method name is bound to the function, where we call the functionfnBind to the method namehelloOn.

Creating a class through type() is exactly the same as writing a class directly, because when the Python interpreter encounters a class definition, it simply scans its syntax and calls type() to create the class.

Normally, we use class Xxx… To define a class, however, the type () function also allows us to create the class dynamic, that is to say, a dynamic language runtime support itself dynamically create the class, and the static and the language has very big different, to create the class, in a static language runtime must be constructed to invoke the compiler source code strings, or with the aid of some tools to generate bytecode implementation, It’s all dynamic compilation in nature, which can be very complicated.

metaclass

In addition to using type() to dynamically create classes, you can also use metaclass to control class creation behavior.

Metaclass is a metaclass.

Once we have defined the class, we can create an instance of the class, so: define the class first, then create the instance.

But what if we want to create a class? Then you have to create aclass based on metaclass, so define the metaclass first and then create the class.

First you define metaclass, then you create the class, and then you create the instance.

So, metaclass allows you to create classes or modify classes. In other words, you can think of aclass as an “instance” created by metaclass.

Next: Error debugging and testing for Learning Python