Common Customization Methods for Customization Classes of Python Object-Oriented Programming

Keywords: Attribute Programming Lambda

Catalog

object-oriented programming
__str__( )
__repr__( )
_ iter_() and _next_()
__getitem__( )
__setitem__( )
__delitem__( )
__getattr__( )
__call__( )

object-oriented programming

Previously, it has been introduced that _xxx_ is a special variable or function, such as _init_ and _slots_. This section will introduce more special methods to help us customize our definition to class.

__str__( )

_ The str_() method can return the defined string when printing an instance based on the defined string.

When the _str_() method is not used:

>>>class Stu(object):                                              #Define a class
...    def __init__(self, name):
...        self.name = name
...
>>>print(Stu('Ming'))                                              #Print an example
<__main__.Stu object at 0x00000000020E8550> 

After using the _str_() method:

>>>class Stu(object):
...    def __init__(self, name):
...        self.name = name
...    def __str__(self):
...        return 'Stu object (name: %s)' % self.name
...
>>>print(Stu('MIng'))
Stu object (name: MIng) 

This allows you to clearly see the important data inside the instance. It should be noted that the return in the _str_() method must be followed by a string.

__repr__( )

_ The purpose of repr_() is similar to that of _str_(). The difference between them is that _str_() returns a string that the user sees, while _repr_() returns a string that the programmer sees.

That is to say, if the instance is called directly in the interactive mode as defined above, it will not return the desired string.

>>>Stu('Ming')
<__main__.Stu object at 0x00000000020E8550> 

Let's change the _str_() above to _repr_(), and try again.

>>>class Stu(object):
...    def __init__(self, name):
...        self.name = name
...    def __repr__(self):
...        return 'Stu object (name: %s)' % self.name
...
>>>Stu('MIng')
Stu object (name: MIng) 

Successful return. At this point, if you use print, you can still return the desired string.

>>>print(Stu('Ming'))
Stu object (name: MIng)

What if _str_() and _repr_() are defined simultaneously?

class Stu(object):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'Stu object (name: %s)' % self.name
    def __str__(self):
        return 'str'
>>>Stu('MIng')
Stu object (name: MIng) 
>>>print(Stu('Ming'))
str

It can be seen that in this case, print gives priority to _str_().

In short, _str_() is user-oriented and _repr_() is programmer-oriented. So if you want to display it uniformly in all environments, use _repr_ directly. To distinguish, define two. Generally, the content displayed is the same, and the definition can be simplified by _repr_() = _str_().

_ iter_() and _next_()

The for...in loop acts on an iteratable object, so if you want to make your defined class available for...in loop, you have to make yourself an iteratable object.

_ The purpose of the iter_() method is to return an Iterable object Iterable.

_ The purpose of the next_() method is to define the logic of a loop and return the value of the next loop (followed by an iterator object, Iterator).

Combining the two methods, the for..in loop will call the iterative object to act on the _next_() method to get the next loop value. Exit the loop until you encounter a StopIteration error.

For example, generate Fibonacci sequence and write a Fib class.

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 iteration object, so it returns to itself

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b           # Calculate the next value
        if self.a > 1000:                                  # Conditions for exit from the loop
            raise StopIteration()
        return self.a                                      # Returns the next value

An example of acting on Fib with for..in

>>>for n in Fib():
...    print(n)
1 
1 
2 
3 
5 
8 
13 
21 
34 
55 
89 
144 
233 
377 
610 
987

In fact, the Fibonacci sequence can be obtained without the _iter_() method, except that the next() is constantly called to fetch the next value, and after the _iter_() method is used to become an iterative object, it can be easily fetched with the for in loop.

__getitem__( )

_ The purpose of the getitem_() method is to let instances subscribe elements as list s do. _ The getitem_() method needs to pass in two parameters, one is the instance itself and the other is the subscript. When [] is detected, _getitem_() runs.

>>>class A(object):
...    def __getitem__(self, key):                       # Define a _getitem_ method whose key parameter is the subscript
...        print('Call function __getitem__')
...        print(key + 1)
>>>a = A()
>>>a[1]                                                  # Here we pass a to self and 1 to key.
Call function __getitem__                                # When [] is detected, the _getitem_ method is executed
2                                                        # Returns key+1, or 1 + 1 = 2

Using the characteristics of getitem () method, we can generate a Fibonacci sequence which can be output by subscript.

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

Want a go:

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

If you want to have slicing like list, you need to determine whether the incoming parameters are integers or slices, and then do further output processing. Like this (the following code is from Liao Xuefeng's official website)

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n is index.
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n is sliced.
            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

Operation results:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]

Of course, there is still room for improvement, such as the processing of step parameters, the processing of negative numbers and so on. These can be further improved in the future. Here the value of _getitem_() is briefly introduced.

__setitem__( )

_ Setitem_() also allows instances to subscribe elements like list s and assign values to keys, so three parameters need to be passed in: instance itself, key, and value. When an assignment statement such as []= is detected, the _setitem_() method is executed.

>>>class A(object):
...    def __setitem__(self, key, value):            # Define a _setitem_ method with key parameters as keys and value as values
...        print('Call function __setitem__')
...        print(value)
>>>a = A()
>>>a[1] = 10                                         # Here we pass a to self, 1 to key and 10 to value.
Call function __getitem__                            # When []= is detected, the _setitem_ method is executed
10                                                   # Print value

__delitem__( )

_ Deltem_() is used to delete the element of the specified key, requiring two parameters, self and key, to be passed in. When del is detected, the _delitem_() method is executed.

Combining getitem (), setitem () and delitem (), this paper gives a simple example to help understand.

class A(object):
    def __init__(self, start = 0):
        print('Call function __init__')
        self.start = start
        self.dict = {}                              # Define a dict

    def __getitem__(self, key):                     # Define the method to get the value
        print('Call function __getitem__')
        try:
            return self.dict[key]                   # If you assign a value to the key, return the value corresponding to the key
        except KeyError:
            return key                              # If no key is assigned, the key itself is returned

    def __setitem__(self, key, value):              # Definition of assignment method
        print('Call function __setitem__')
        self.dict[key] = value

    def __delitem__(self, key):                     # Define methods to delete elements
        print('Call function __delitem__')
        del self.dict[key]
a = A()                                             # Create an instance of A
Call function __init__ 
a[1] = 10                                           # Execute the assignment method _setitem__ 
Call function __setitem__ 
a[2] = 20                                           # Execute the assignment method _setitem__
Call function __setitem__ 
a[1]                                                # Execute the value method _getitem_, [1] has a corresponding value of 10
Call function __getitem__ 
10 
a.dict                                              #Existing values in dict attribute
{1: 10, 2: 20} 
del a[1]                                            #Delete the value of key [1] in the dict attribute
Call function __delitem__ 
a.dict
{2: 20} 

__getattr__( )

Errors occur when we try to call an undefined property or method. The purpose of _getattr_() is to execute _getattr_() to try to return another value when calling a property that does not exist.

When _getattr_() is not defined

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

a = Stu('Ming')
>>>a.name
Ming
>>>a.age
AttributeError: 'Stu' object has no attribute 'age'

After the definition of _getattr_()

class Stu(object):
    def __init__(self, name):
        self.name = name
    
    def __getattr__(self, attr):
        return 1
>>>a.name
Ming
>>>a.age                          # Here age is passed to the second parameter attr of _getattr_().
1

It also returns a function.

class Stu(object):
    def __init__(self, name):
        self.name = name
    
    def __getattr__(self, attr):
        return lambda: 10

Of course, we can refine the definition according to the parameters of _getattr_(), like this:

class Stu(object):

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

__call__( )

_ The call_() method makes an instance callable, and when an attempt is made to call an instance directly, the call_() method is executed.

class Stu(object):
    def __init__(self, name):
        self.name = name
    
    def __call__(self):
        print('I am %s' % self.name)
>>>a = Stu('Ming')
>>>a()                                           # Note the need to add ()
I am Ming

_ call_() also defines parameters so that the instance object can be treated as a function. This blurs the distinction between objects and functions. In fact, many times we need to distinguish whether an object is callable or not, which can be judged by callable() functions.

>>> callable(Stu())
True
>>> callable(max)
True
>>> callable([1, 2])
False
>>> callable(None)
False



Author: Sanbei_
Link: https://www.jianshu.com/p/2e7bcb8e9493
Source: Brief Book
The copyright of the brief book belongs to the author. For any form of reprinting, please contact the author for authorization and indicate the source.

Posted by tristanlee85 on Thu, 02 May 2019 09:10:38 -0700