A summary of special methods for implementing custom classes in python

  • 2020-04-02 14:10:33
  • OfStack

Note that this has a special use in Python if you see variable or function names with shapes like s/s s/s.

__slots__ we already know how to use, __len__ () method we also know that in order to make the class ACTS on the len () function.

In addition, there are many special-purpose functions in Python's classes that can help you customize your classes.

The __str__

Let's first define a Student class and print an instance:


>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print Student('Michael')
<__main__.Student object at 0x109afb190>

Print out a bunch < X109afb190 __main__. Student object at 0 > Not good.

How do you print well? All you need to do is to define the method with a nice string return:


>>> 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)

The printed instance is not only nice, but also easy to see the important data inside the instance.

But careful friends will find that directly typed variables do not use print, print out the instance is not good:


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

This is because the direct display variable called not the __str__ (), but __repr__ (), the difference between the two is the __str__ () returns the user to see the string, and __repr__ () returns the developers see string, that is to say, __repr__ () is in the service of the debugging.

The solution is to redefine one more s/p (). But usually the code is the same for both arbitration () and arbitration (), so there's a lazy way to write this:


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

__iter__

If a class wants to be used for... The in loop, like a list or tuple, must implement a method with iteration () that returns an iteration object, and the Python for loop calls the next() method of the iteration object to get the next value of the loop until it encounters a StopIteration error and exits the loop.

Let's take the Fibonacci number as an example and write a Fib class that can act 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 the iteration object, so it returns itself     def next(self):
        self.a, self.b = self.b, self.a + self.b # Calculate the next value
        if self.a > 100000: # Conditions to exit the loop
            raise StopIteration();
        return self.a # Returns the next value

Now, try applying an instance of Fib to the for loop:


>>> for n in Fib():
...     print n
...
1
1
2
3
5
...
46368
75025

The __getitem__

An instance of Fib works on a for loop and looks a bit like a list, but using it as a list still doesn't work. For example, take the fifth element:


>>> Fib()[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing

To represent fetching the elements by subscript like list, you need to implement the method of s/p () :

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

Now, you can access any item in the sequence by subscript:

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

But list has a magic way of slicing:

>>> range(100)[5:10]
[5, 6, 7, 8, 9]

Fib reported an error. The reason is that the incoming parameter of s/s can either be an int or slice, so make a decision:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

Now try a slice of Fib:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

However, the step parameter is not processed:

>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

There is no handling of negative Numbers either, so there is still a lot of work to be done to implement a correctly with s/s ().

Furthermore, if the object is treated as dict, the argument of s/s () may also be an object that can be used as a key, such as STR.

The corresponding method is the s/s s/s () method, which assigns a value to a collection by treating the object as a list or dict. Finally, there is a method with s/s () to delete an element.

All in all, our self-defined classes behave just like Python's native list, tuple, and dict, thanks to the "duck type" of dynamic languages, which doesn't force inheritance of an interface.

__getattr__

Normally, when we call a method or property of a class, we report an error if it doesn't exist. For example, define the Student class:


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

Calling the name attribute is fine, but calling the nonexistent score attribute is problematic:

>>> s = Student()
>>> print s.name
Michael
>>> print s.score
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'score'

The error message clearly tells us that the score attribute was not found.

To avoid this error, Python has another mechanism besides adding a score attribute, which is to write a method with successive getattr__() and return an attribute dynamically. The modifications are as follows:


class Student(object):     def __init__(self):
        self.name = 'Michael'     def __getattr__(self, attr):
        if attr=='score':
            return 99

When calling a nonexistent attribute, such as score, the Python interpreter tries to call s/s getattr__(self, 'score') to try to get the attribute, thus giving us a chance to return the score value:

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

Return function is also ok:

class Student(object):     def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

Only the calling mode should be changed to:

>>> s.age()
25

Note that the existing attribute, such as name, will not be found in arbitration.

In addition, notice that any call such as s.attc will return None, this is because the default return we defined is None. To make the class respond to only a few specific attributes, we need to throw the error of AttributeError as per the convention:


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

This allows virtually all the properties and method calls of a class to be dynamically processed without any special means.

What does this feature of full dynamic invocation actually do? What it does is, it can be called for completely dynamic situations.

For example:

Now many websites do REST API, such as sina weibo, douban, etc., the URL of calling API is similar to:

(link: http://api.server/user/friends)
(link: http://api.server/user/timeline/list)

If you're going to write the SDK, you're going to have to write a method for each of the urls of the API, and it's going to take a lot of work, and once the API changes, the SDK has to change it.

With the fully dynamic s/p getattr__, we can write a chain call:


class Chain(object):     def __init__(self, path=''):
        self._path = path     def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))     def __str__(self):
        return self._path

Try:

>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

In this way, no matter how the API changes, the SDK can implement completely dynamic calls based on the URL, and it will not change with the increase of the API!

There are other REST apis that put parameters in the URL, such as the GitHub API:


GET /users/:user/repos

When called, you need to replace :user with the actual user name. If we can write a chain call like this:

Chain().users('michael').repos

You can call the API very easily. The kid shoe that has interest can try to write out.

__call__

An object instance can have its own properties and methods, and when we call an instance method, we call it with instance.method(). Can it be called directly on the instance itself? Similar to the instance ()? In Python, the answer is yes.

Any class can be called directly on the instance by defining a method with the function of s/s. See an example:


class Student(object):
    def __init__(self, name):
        self.name = name     def __call__(self):
        print('My name is %s.' % self.name)

The method of invocation is as follows:

>>> s = Student('Michael')
>>> s()
My name is Michael.

S/s can also be defined. Calling an instance directly is like calling a function, so you can think of 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 be created dynamically at run time, because instances of the class are created at run time.

So, how do you tell if 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 the function and the class instance with arbitration call() defined above:


>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('string')
False

Using the callable() function, we can determine whether an object is "callable" or not.

summary

Python's class allows you to define many custom methods that make it easy to generate specific classes.


Related articles: