Sharding the Ceph RADOS Gateway bucket index

Sharding is the process of breaking down data onto multiple locations so as to increase parallelism, as well as distribute load. This is a common feature used in databases. Read more on this at Wikipedia.

The concept of sharding is used in Ceph, for splitting the bucket index in a RADOS Gateway.

RGW or RADOS Gateway keeps an index for all the objects in its buckets for faster and easier lookup. For each RGW bucket created in a pool, the corresponding index is created in the XX.index pool.

For example, for each of the buckets created in .rgw pool, the bucket index is created in .rgw.buckets.index pool. For each bucket, the index is stored in a single RADOS object.

When the number of objects increases, the size of the RADOS object increases as well. Two problems arise due to the increased index size.

  1. RADOS does not work good with large objects since it’s not designed as such. Operations such as recovery, scrubbing etc.. work on a single object. If the object size increases, OSDs may start hitting timeouts because reading a large object may take a long time. This is one of the reason that all RADOS client interfaces such as RBD, RGW, CephFS use a standard 4MB object size.
  2. Since the index is stored in a single RADOS object, only a single operation can be done on it at any given time. When the number of objects increases, the index stored in the RADOS object grows. Since a single index is handling a large number of objects, and there is a chance the number of operations also increase, parallelism is not possible which can end up being a bottleneck. Multiple operations will need to wait in a queue since a single operation is possible at a time.

In order to work around these problems, the bucket index is sharded into multiple parts. Each shard is kept on a separate RADOS object within the index pool.

Sharding is configured with the tunable bucket_index_max_shards . By default, this tunable is set to 0 which means that there are no shards.

How to check if Sharding is set?

  1. List the buckets
    # radosgw-admin metadata bucket list
    [
     "my-new-bucket"
    ]
    
  2. Get information on the bucket in question
    
    # radosgw-admin metadata get bucket:my-new-bucket
    {
        "key": "bucket:my-new-bucket",
        "ver": {
            "tag": "_bGZAVUgayKVwGNgNvI0328G",
            "ver": 1
        },
        "mtime": 1458940225,
        "data": {
            "bucket": {
                "name": "my-new-bucket",
                "pool": ".rgw.buckets",
                "data_extra_pool": ".rgw.buckets.extra",
                "index_pool": ".rgw.buckets.index",
                "marker": "default.2670570.1",
                "bucket_id": "default.2670570.1"
            },
            "owner": "rgw_user",
            "creation_time": 1458940225,
            "linked": "true",
            "has_bucket_info": "false"
        }
    }
    
    
  3. Use the bucket ID to get more information, including the number of shards.
radosgw-admin metadata get bucket.instance:my-new-bucket:default.2670570.1
{
    "key": "bucket.instance:my-new-bucket:default.2670570.1",
    "ver": {
        "tag": "_xILkVKbfQD7reDFSOB4a5VU",
        "ver": 1
    },
    "mtime": 1458940225,
    "data": {
        "bucket_info": {
            "bucket": {
                "name": "my-new-bucket",
                "pool": ".rgw.buckets",
                "data_extra_pool": ".rgw.buckets.extra",
                "index_pool": ".rgw.buckets.index",
                "marker": "default.2670570.1",
                "bucket_id": "default.2670570.1"
            },
            "creation_time": 1458940225,
            "owner": "rgw_user",
            "flags": 0,
            "region": "default",
            "placement_rule": "default-placement",
            "has_instance_obj": "true",
            "quota": {
                "enabled": false,
                "max_size_kb": -1,
                "max_objects": -1
            },
            "num_shards": 0,
            "bi_shard_hash_type": 0
        },
        "attrs": [
            {
                "key": "user.rgw.acl",
                "val": "AgKPAAAAAgIaAAAACAAAAHJnd191c2VyCgAAAEZpcnN0IFVzZXIDA2kAAAABAQAAAAgAAAByZ3dfdXNlcg8AAAABAAAACAAAAHJnd191c2VyAwM6AAAAAgIEAAAAAAAAAAgAAAByZ3dfdXNlcgAAAAAAAAAAAgIEAAAADwAAAAoAAABGaXJzdCBVc2VyAAAAAAAAAAA="
            },
            {
                "key": "user.rgw.idtag",
                "val": ""
            },
            {
                "key": "user.rgw.manifest",
                "val": ""
            }
        ]
    }
}

Note that `num_shards` is set to 0, which means that sharding is not enabled.

How to configure Sharding?

To configure sharding, we need to first dump the region info.

NOTE: By default, RGW has a region named default even if regions are not configured.

# radosgw-admin region get > /tmp/region.txt 

# cat /tmp/region.txt
{
    "name": "default",
    "api_name": "",
    "is_master": "true",
    "endpoints": [],
    "hostnames": [],
    "master_zone": "",
    "zones": [
        {
            "name": "default",
            "endpoints": [],
            "log_meta": "false",
            "log_data": "false",
            "bucket_index_max_shards": 0
        }
    ],
    "placement_targets": [
        {
            "name": "default-placement",
            "tags": []
        }
    ],
    "default_placement": "default-placement"
}

Edit the file /tmp/region.txt, change the value for `bucket_index_max_shards` to the needed shard value (we’re setting it to 8 in this example), and inject it back to the region.

# radosgw-admin region set < /tmp/region.txt
{
    "name": "default",
    "api_name": "",
    "is_master": "true",
    "endpoints": [],
    "hostnames": [],
    "master_zone": "",
    "zones": [
        {
            "name": "default",
            "endpoints": [],
            "log_meta": "false",
            "log_data": "false",
            "bucket_index_max_shards": 8
        }
    ],
    "placement_targets": [
        {
            "name": "default-placement",
            "tags": []
        }
    ],
    "default_placement": "default-placement"
}

Reference:

  1. Red Hat Ceph Storage 1.3 Rados Gateway documentation
  2. https://en.wikipedia.org/wiki/Shard_(database_architecture)

Inheritance and Method overloading – Object Oriented Programming

Inheritance is a usual theme in Object Oriented Programming. Because of Inheritance, the functions/methods defined in parent classes can be called in Child classes which enables code reuse, and several other features. In this article, we try to understand some of those features that come up with Inheritance.

We’ve discussed Abstract Methods in an earlier post, which is a feature part of Inheritance, and can be applied on child classes that inherits from a Parent class.

E the methods which are inherited can also be seen as another feature or possibility in Inheritance. In many cases, it’s required to override or specialize the methods inherited from the Parent class. This is of course possible, and is called as ‘Method Overloading’.

Consider the two classes and its methods defined below:

Example 0:

import abc

class MyClass(object):

    __metaclass__ = abc.ABCMeta

    def __init__(self):
        pass

    def my_set_method(self, value):
        self.value = value

    def my_get_method(self):
        return self.value

    @abc.abstractmethod
    def printdoc(self):
        return

class MyChildClass(MyClass):

    def my_set_method(self, value):
        if not isinstance(value, int):
            value = 0
        super(MyChildClass, self).my_set_method(self)

    def printdoc(self):
        print("\nDocumentation for MyChildClass()")

instance_1 = MyChildClass()
instance_1.my_set_method(10)
print(instance_1.my_get_method())
instance_1.printdoc()

 

We have two classes, the parent class being MyClass and the child class being MyChildClass.

MyClass has three methods defined.

  • my_set_method()
  • my_get_method()
  • printdoc()

The printdoc() method is an Abstract method, and hence should be implemented in the Child class as a mandatory method.

The child class MyChildClass inherits from MyClass and has access to all it’s methods.

Normally, we can just go ahead and use the methods defined in MyClass , in MyChildClass. But there can be situations when we want to improve or build upon the methods inherited. As said earlier, this is called Method Overloading.

MyChildClass extends the parent’s my_set_method() function by it’s own implementation. In this example, it does an additional check to understand if the input value is an int or not, and then calls the my_set_method() of it’s parent class using super. Hence, this method in the child class extends the functionality prior calling method in the parent. A post on super is set for a later time.

Even though this is a trivial example, it helps to understand how the features inherited from other classes can be extended or improved upon via method overloading.

The my_get_method() is not overridden in the child class but still called from the instance, as instance_1.my_get_method(). We’re using it as it is available via Inheritance. Since it’s defined in the parent class, it works in the child class’ instance when called, even if not overridden.

The printdoc() method is an abstract method and hence is mandatory to be implemented in the child class, and can be overridden with what we choose to do.

Inheritance is possible from python builtins, and can be overridden as well. Let’s check out another example:

Example 1:

class MyList(list):

    def __getitem__(self, index):
        if index == 0:
            raise IndexError
        if index &gt; 0:
            index -= 1
        return list.__getitem__(self, index)

    def __setitem__(self, index, value):
        if index == 0:
            raise IndexError
        if index &gt; 0:
            index -= 1
        list.__setitem__(self, index, value)

x = MyList(['a', 'b', 'c'])
print(x)
print("-" * 10)

x.append('d')
print(x)
print("-" * 10)

x.__setitem__(4, 'e')
print(x)
print("-" * 10)

print(x[1])
print(x.__getitem__(1))
print("-" * 10)

print(x[4])
print(x.__getitem__(4))

This outputs:

['a', 'b', 'c']
----------
['a', 'b', 'c', 'd']
----------
['a', 'b', 'c', 'e']
----------
a
a
----------
e
e

How does the code work?

The class MyList() inherits from the builtin list. Because of the inheritance, we can use list’s available magic methods such as __getitem__() , __setitem__() etc..

NOTE: In order to see the available methods in list, use dir(list).

  1. We create two functions/methods named `__getitem__()` and `__setitem__()` to override the inherited methods.
  2. Within these functions/methods, we set our own conditions.
  3. Wie later call the builtin methods directly within these functions, using
    1. list.__getitem__()
    2. list.__setitem__()
  4. We create an instance named x from MyList().
  5. We understand that
    1. x[1] and x.__getitem__(1) are same.
    2. x[4, 'e'] and x.__setitem__(4, 'e') are same.
    3. x.append(f) is same as x.__setitem__(<n>, f) where <n> is the element to the extreme right which the python interpreter iterates and find on its own.

Hence, in Inheritance, child classes can:

  • Inherit from parent classes and use those methods.
    • Parent classes can either be user-defined classes or buitins like list , dict etc..
  • Override (or Overload) an inherited method.
  • Extend an inherited method in its own way.
  • Implement an Abstract method the parent class requires.

Reference:

  1. Python beyond the basics – Object Oriented Programming

 

Abstract Base Classes/Methods – Object Oriented Programming

Abstract classes, in short, are classes that are supposed to be inherited or subclassed, rather than instantiated.

Through Abstract Classes, we can enforce a blueprint on the subclasses that inherit the Abstract Class. This means that Abstract classes can be used to define a set of methods that must be implemented by it subclasses.

Abstract classes are used when working on large projects where classes have to be inherited, and need to strictly follow certain blueprints.

Python supports Abstract Classes via the module abc from version 2.6. Using the abc module, its pretty straight forward to implement an Abstract Class.

Example 0:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
    def get_val(self):
        return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def get_val(self):
        print("\nCalling the get_val() method")
        print("I'm part of the Abstract Methods defined in My_ABC_Class()")
        return self.val

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = MyClass()

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

In the code above, set_val() and get_val() are both abstract methods defined in the Abstract Class My_ABC_Class(). Hence it should be implemented in the child class inheriting from My_ABC_Class().

In the child class MyClass() , we have to strictly define the abstract classes defined in the Parent class. But the child class is free to implement other methods of their own. The hello() method is one such.

This will print :

# python abstractclasses-1.py

Calling the get_val() method
I'm part of the Abstract Methods defined in My_ABC_Class()
10

Calling the hello() method
I'm *not* part of the Abstract Methods defined in My_ABC_Class()

The code gets executed properly even if the  hello() method is not an abstract method.

Let’s check what happens if we don’t implement a method marked as an abstract method, in the child class.

Example 1:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
    def get_val(self):
        return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = MyClass()

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

Example 1 is the same as Example 0 except we don’t have the get_val() method defined in the child class.

This means that we’re breaking the rule of abstraction. Let’s see what happens:

# python abstractclasses-2.py
Traceback (most recent call last):
  File "abstractclasses-2.py", line 50, in
    my_class = MyClass()
TypeError: Can't instantiate abstract class MyClass with abstract methods get_val

The traceback clearly states that the child class MyClass() cannot be instantiated since it does not implement the Abstract methods defined in it’s Parent class.

We mentioned that an Abstract class is supposed to be inherited rather than instantiated. What happens if we try instantiating an Abstract class?

Let’s use the same example, this time we’re instantiating the Abstract class though.

Example 2:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
        def get_val(self):
            return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = My_ABC_Class()    # <- Instantiating the Abstract Class

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

What does this output?

# python abstractclasses-3.py
Traceback (most recent call last):
    File "abstractclasses-3.py", line 54, in <module>
       my_class = My_ABC_Class()
TypeError: Can't instantiate abstract class My_ABC_Class with abstract methods get_val, set_val

As expected, the Python interpreter says that it can’t instantiate the abstract class My_ABC_Class.

Takeaway: 

  1. An Abstract Class is supposed to be inherited, not instantiated.
  2. The Abstraction nomenclature is applied on the methods within a Class.
  3. The abstraction is enforced on methods which are marked with the decorator @abstractmethod or @abc.abstractmethod, depending on how you imported the module, from abc import abstractmethod or import abc.
  4. It is not mandatory to have all the methods defined as abstract methods, in an Abstract Class.
  5. Subclasses/Child classes are enforced to define the methods which are marked with @abstractmethod in the Parent class.
  6. Subclasses are free to create methods of their own, other than the abstract methods enforced by the Parent class.

Reference:

  1. https://pymotw.com/2/abc/
  2. Python beyond the basics – Object Oriented Programming

Instance, Class, and Static methods – Object Oriented Programming

Functions defined under a class are also called methods. Most of the methods are accessed through an instance of the class.

There are three types of methods:

  1. Instance methods
  2. Static methods
  3. Class methods

Both Static methods and Class methods can be called using the @staticmethod and @classmethod syntactic sugar respectively.

Instance methods

Instance methods are also called Bound methods since the instance is bound to the class via self. Read a simple explanation on self here.

Almost all methods are Instance methods since they are accessed through instances.

For example:

class MyClass(object):

def set_val(self, val):
    self.value = val

def get_val(self):
    print(self.value)
    return self.value

a = MyClass()
b = MyClass()

a.set_val(10)
b.set_val(100)

a.get_val()
b.get_val()

The above code snippet shows manipulating the two methods  set_val() and get_val() . These are done through the instances a and b. Hence these methods are called Instance methods.

NOTE: Instance methods have self as their first argument. self is the instance itself.

All methods defined under a class are usually called via the instance instantiated from the class. But there are methods which can work without instantiating an instance.

Class methods and Static methods don’t require an instance, and hence don’t need self as their first argument.

Static methods

Static methods are functions/methods which doesn’t need a binding to a class or an instance.

  1. Static methods, as well as Class methods, don’t require an instance to be called.
  2. Static methods doesn’t need  self or cls as the first argument since it’s not bound to an instance or class.
  3. Static methods are normal functions, but within a class.
  4. Static methods are defined with the keyword @staticmethod above the function/method.
  5. Static methods are usually used to work on Class Attributes.

=============================
A note on class attributes

Attributes set explicitly under a class (not under a function) are called Class Attributes.

For example:

class MyClass(object):
    value = 10

    def my_func(self):
        pass

In the code snippet above, value = 10 is an attribute defined under the class MyClass() and not under any functions/methods. Hence, it’s called a Class attribute.
=============================

Let’s check out an example on static methods and class attributes:

class MyClass(object):
# A class attribute
    count = 0

    def __init__(self, name):
        print("An instance is created!")
        self.name = name
        MyClass.count += 1

    # Our class method
    @staticmethod
    def status():
        print("The total number of instances are ", MyClass.count)

print(MyClass.count)

my_func_1 = MyClass("MyClass 1")
my_func_2 = MyClass("MyClass 2")
my_func_3 = MyClass("MyClass 3")

MyClass.status()
print(MyClass.count)

This prints the following:

# python statismethod.py

0
An instance is created!
An instance is created!
An instance is created!

The total number of instances are 3
3

How does the code work?

  1. The example above has a class  MyClass() with a class attribute count = 0.
  2. An __init__ magic method accepts a name variable.
  3. The __init__ method also increments the count in the count counter at each instantiation.
  4. We define a staticmethod status() which just prints the number of the instances being created. The work done in this method is not necessarily associated with the class or any functions, hence its defined as a staticmethod.
  5. We print the initial value of the counter count via the class, as MyClass.count. This will print 0since the counter is called before any instances are created.
  6. We create three instances from the class  MyClass
  7. We can check the number of instances created through the status() method and the count counter.

Another example:

class Car(object):

    def sound():
        print("vroom!")

The code above shows a method which is common to all the Car instances, and is not limited to a specific instance of Car. Hence, this can be called as a staticmethod since it’s not necessarily bound to a Class or Instance to be called.

class Car(object):

    @staticmethod
    def sound():
        print("vroom!")

Class methods

We can define functions/methods specific to classes. These are called Class methods.

The speciality of a class methods is that an instance is not required to access a class method. It can be called directly via the Class name.

Class methods are used when it’s not necessary to instantiate a class to access a method.

NOTE: A method can be set as a Class method using the decorator @classmethod.

Example:

class MyClass(object):
    value = 10

    @classmethod
    def my_func(cls):
        print("Hello")


NOTE: Class methods have cls as their first argument, instead of self.

Example:

class MyClass(object):
    count = 0

    def __init__(self, val):
        self.val = val
        MyClass.count += 1

    def set_val(self, newval):
        self.val = newval

    def get_val(self):
        return self.val

    @classmethod
    def get_count(cls):
        return cls.count

object_1 = MyClass(10)
print("\nValue of object : %s" % object_1.get_val())
print(MyClass.get_count())

object_2 = MyClass(20)
print("\nValue of object : %s" % object_2.get_val())
print(MyClass.get_count())

object_3 = MyClass(40)
print("\nValue of object : %s" % object_3.get_val())
print(MyClass.get_count())

Here, we use a get_count() function to get the number of times the counter was incremented. The counter is incremented each time an instance is created.

Since the counter is not really tied with the instance but only counts the number of instance, we set it as a classmethod, and calls it each time using MyClass.get_count()when an instance is created. The output looks as following:

# python classmethod.py

Value of object : 10
1

Value of object : 20
2

Value of object : 40
3

 

Courtsey: This was written as part of studying class and static methods. Several articles/videos have helped including but not limited to the following:

  1. https://jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/
  2. Python beyond the basics – Object Oriented Programming – O’Reilly Learning Paths

 

`self` in Python – Object Oriented Programming

This article was long overdue and should have been published before many of the articles in this blog. Better late than never.

self in Python is usually used in an Object Oriented nomenclature, to denote the instance/object created from a Class.

In short, self is the instance itself.

Let’s check the following example:

class MyClass(object):
def __init__(self, name):
        self.name = name
        print("Initiating the instance!")

    def hello(self):
        print(self.name)

myclass = MyClass("Dan Inosanto")

# Calling the `hello` method via the Instance `myclass`
myclass.hello()

# Calling the `hello` method vai the class.
MyClass.hello(myclass)

The code snippet above is trivial and stupid, but I think it gets the idea across.

We have a class named MyClass() which takes a name value as an argument. It also prints the string “Initiating the instance”.  The name value is something that has to be passed while creating an instance.

The function hello() just prints the name value that is passed while instantiating the class MyClass().

We instantiate the class MyClass() as myclass and pass the string  Dan Inosanto as an argument. Read about the great Inosanto here.

Next, we call the hello() method through the instance. ie..

myclass.hello()

This should print the name we passed while instantiating MyClass() as myclass , which should be pretty obvious.

The second and last instruction is doing the same thing, but in a different way.

MyClass.hello(myclass)

Here, we call the class MyClass() directly as well as it’s method hello(). Let’s check out what both prints:

# python /tmp/test.py

Initiating the instance!
Dan Inosanto
Dan Inosanto

As we can see, both prints the same output. This means that :

myclass.hello(self) == MyClass.hello(myclass)

In general, we can say that:

<instance-name>.<method>(self) == <Class>.<method>(<instance-name>)

ie.. The keyword self actually represents the instance being instantiated from the Class. Hence self can be seen as Syntactic sugar.

Magic methods and Syntactic sugar in Python

Magic methods

Magic methods are special methods which can be defined (or already designed and available) to act on objects.

Magic methods start and end with underscores "__", and are not implicitly called by the user even though they can be. Most magic methods are used as syntactic sugar by binding it to more clear/easy_to_understand keywords.

Python is mostly objects and method calls done on objects. Many available functions in Python are actually tied to magic methodsLet’s checkout a few examples.

Example 0:

In [1]: my_var = "Hello!"

In [2]: print(my_var)
Hello!

In [3]: my_var.__repr__()
Out[3]: "'Hello!'"

As we can see, the __repr__() magic method can be called to print the object, ie.. it is bound to the print() keyword.

This is true for many other builtin keywords/operators as well.

Example 1:

In [22]: my_var = "Hello, "
In [23]: my_var1 = "How are you?"

In [24]: my_var + my_var1
Out[24]: 'Hello, How are you?'

In [25]: my_var.__add__(my_var1)
Out[25]: 'Hello, How are you?'

Here, Python interprets the + sign as a mapping to the magic method __add__(), and calls it on the L-value (Left hand object value) my_var, with the R-value (Right hand object value) as the argument.

When a builtin function is called on an object, in many cases it is mapped to the magic method.

Example 2:

In [69]: my_list_1 = ['a', 'b', 'c', 'd']

In [70]: 'a' in my_list_1
Out[70]: True

In [71]: my_list_1.__contains__("a")
Out[71]: True

The in builtin is mapped to the __contains__()method.

The methods available for an object should mostly be dependent on the type of the object.

Example 3:

In [59]: my_num = 1

In [60]: type(my_num)
Out[60]: int

In [61]: my_num.__doc__
Out[61]: Out[61]: "int(x=0) -> int or long\nint(x, base=10) -> int or long\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. ....>>>

In [62]: help(my_num)
class int(object)
| int(x=0) -> int or long
| int(x, base=10) -> int or long
|
| Convert a number or string to an integer, or return 0 if no arguments
| are given. If x is floating point, the conversion truncates towards zero.
| If x is outside the integer range, the function returns a long instead.

From the tests above, we can understand that the help() function is actually mapped to the object.__doc__ magic method. It’s the same doc string that __doc__ and help() uses.

NOTE: Due to the syntax conversion (+ to __add__(),and other conversions), operators like + , in, etc.. are also called Syntactic sugar.

What is Syntactic sugar?

According to Wikipedia, Syntact sugar is:

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express. It makes the language “sweeter” for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

Hence, magic methods can be said to be Syntactic sugar. But it’s not just magic methods that are mapped to syntactic sugar methods, but higher order features such as Decorators are as well.

Example 4: 

def my_decorator(my_function):
    def inner_decorator():
        print("This happened before!")
        my_function()
        print("This happens after ")
        print("This happened at the end!")
    return inner_decorator

def my_decorated():
    print("This happened!")

var = my_decorator(my_decorated)

if __name__ == '__main__':
    var()

The example above borrows from one of the examples in the post on Decorators.

Here, my_decorator() is a decorator and is used to decorate my_decorated(). But rather than calling the decorator function my_decorator() with the argument my_decorated(), the above code can be syntactically sugar-coated as below:

def my_decorator(my_function):
    def inner_decorator():
        print("This happened before!")
        my_function()
        print("This happens after ")
        print("This happened at the end!")
    return inner_decorator

@my_decorator
def my_decorated():
    print("This happened!")

if __name__ == '__main__':
    my_decorated()

Observing both code snippets, the decorator is syntactically sugar coated and called as:

@my_decorator

instead of instantiating the decorator with the function to be decorated as an argument, ie..

var = my_decorator(my_decorated)

A few syntax resolution methods:

  1. ‘name’ in my_list       ->      my_list.__contains__(‘name’)
  2. len(my_list)                  ->      my_list.__len__()
  3. print(my_list)              ->      my_list.__repr__()
  4. my_list == “value”     ->      my_list.__eq__(“value”)
  5. my_list[5]                      ->      my_list.__getitem__(5)
  6. my_list[5:10]                 ->     my_list.__getslice__(5, 10)

NOTE: This article is written from the notes created while learning magic methods. The following articles (along with several others) were referred as part of the process.

  1. A Guide to Python’s Magic Methods, by Rafe Kettler
  2. Special method names, The Official Python 3 documentation