The __dir__() method  is used to get a list of attributes and methods contained by a given object . The method gets called indirectly when we use the builtin dir() function.

The method is especially useful when a user needs to inspect a particular object using the dir() function. It returns the list of valid attributes and methods of an object making it easier to determine the capabilities of the object and decide what operations can be  performed on it.

Example with a list:

ExampleEdit & Run
L = [1, 2, 3]

print(L.__dir__()) #Same as print(dir(L))

Output:
['__new__', '__repr__', '__hash__', '__getattribute__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__iter__', '__init__', '__len__', '__getitem__', '__setitem__', '__delitem__', '__add__', '__mul__', '__rmul__', '__contains__', '__iadd__', '__imul__', '__reversed__', '__sizeof__', 'clear', 'copy', 'append', 'insert', 'extend', 'pop', 'remove', 'index', 'count', 'reverse', 'sort', '__class_getitem__', '__doc__', '__str__', '__setattr__', '__delattr__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__dir__', '__class__'][Finished in 0.010454321978613734s]

 Example with a custom object:

ExampleEdit & Run
class Example:
    x = 100
    y = 200
    def example_method(self):
        pass

e = Example()

print(e.__dir__())# Same as print(dir(e))
Output:
['__module__', 'x', 'y', 'example_method', '__dict__', '__weakref__', '__doc__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__'][Finished in 0.010405250126495957s]

Overriding the __dir__() Function for custom objects.

All objects have the __dir__ method defined by default. However, classes can override the method to customize the object's available properties and methods. This allows for finer control over which attributes should be exposed and which should remain hidden. It also helps to ensure that only certain attributes are shown in an object's public interface.

Syntax:
class MyClass:
    def __dir__(self):
        #statements

The method takes no arguments and should return a list containing the attributes and methods available on the object that we want exposed when the builtin dir() function is called on the object. 

ExampleEdit & Run
class MyClass:
    def __init__(self): 
         self.a = 100 
         self.b = 200
         self.c = 300
    # overriding the __dir__() method
    def __dir__(self):
         return ['a', 'b', 'c']

c = MyClass()

print(dir(c))
Output:
['a', 'b', 'c'][Finished in 0.010574067011475563s]

A more practical example 

ExampleEdit & Run
class Calculator:
     def add(self, a, b):
         return "%s + %s = %s"%(a, b, a + b)
     def sub(self, a, b):
         return "%s - %s = %s"%(a, b, a - b)
     def mul(self, a, b):
         return "%s x %s = %s"%(a, b, a * b)
     def div(self, a, b):
         return "%s / %s = %s"%(a, b, a / b)
     #Override the __dir__() method
     def __dir__(self):
         return ['add', 'sub', 'mul', 'div']

c = Calculator()
#Check the attributes belonging to a calculator instance
print(dir(c))

#Use the attributes
print(c.add(10, 20))
print(c.sub(20, 10))
print(c.mul(10, 20))
print(c.div(200, 10))
Output:
['add', 'div', 'mul', 'sub']10 + 20 = 3020 - 10 = 1010 x 20 = 200200 / 10 = 20.0[Finished in 0.01124216802418232s]

Note: Any valid list can be returned by __dir__(), however, it is a good practice to ensure that the method returns a list containing relevant attributes which are actually contained by the object. This can help other developers quickly and easily access information about the object. It also helps to ensure that the code can be understood easily and therefore, easy to maintain.