<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 MetaProgramming In Python
Classes in Python - What is a class in Python?
Step1:  Classes - Nothing but instances of types.  Class technically is a sugar over the native 'type'
What is type in Python?
Step2:  'type' is an important native structure used for creating classes.
Life Cycle involved in a class - Vanilla
Step3:  What is type? 'type' defines how a class behaves in Python.
Got it. Well then - Can I change 'how' a class behaves in Python? - MetaClasses
Metaclasses
Step4:  LifeCycle with Metaclasses
Step5:  Pattern 1 
Step6:  Pattern 2 
Step7:  Pattern 3 
Step8:  Pattern 4 
Step9:  Pattern 5 
<ASSISTANT_TASK:>
Python Code:
class Test:
    pass
a = Test()
a
type(a)
type(Test)
type(type)
Explanation: MetaProgramming In Python
Classes in Python - What is a class in Python?
End of explanation
type?
TestWithType = type('TestWithType', (object,), {})
type(TestWithType)
ins1 = TestWithType()
type(ins1)
type('TestWithType', (object,), {})()
Explanation: Classes - Nothing but instances of types.  Class technically is a sugar over the native 'type'
What is type in Python?
End of explanation
class TestClass:
    def __new__(cls, *args, **kwargs):
        print('new method called')
        instance = super(TestClass, cls).__new__(cls, *args, **kwargs)
        return instance
    def __call__(self, a, b, c):
        self.call_count += 1
        print('call method called')
        return a * b * c
    def __init__(self):
        self.call_count = 0
        super(TestClass, self).__init__()
        print('init method called')
        
    def get_call_count(self):
        return self.call_count
a = TestClass()
a(1,2,3)
a.get_call_count()
Explanation: 'type' is an important native structure used for creating classes.
Life Cycle involved in a class - Vanilla
End of explanation
class MySingletonMeta(type):
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MySingletonMeta, cls).__call__(*args)
        return cls._instances[cls]
class MySingletonClass(metaclass=MySingletonMeta):
    def __init__(self):
        self.i = 1
a = MySingletonClass()
b = MySingletonClass()
type(a), id(a) , type(b), id(b)
Explanation: What is type? 'type' defines how a class behaves in Python.
Got it. Well then - Can I change 'how' a class behaves in Python? - MetaClasses
Metaclasses
End of explanation
class MyMetaClass(type):
    
    _test_attribute = 1
    def __new__(cls, *args, **kwargs):
        print("metaclass new method called")
        return super(MyMetaClass, cls).__new__(cls, *args, **kwargs)
    
    def __call__(cls, *args, **kwargs):
        print("metaclass call method called")
        return super(MyMetaClass, cls).__call__(*args, **kwargs)
    def __init__(self, *args, **kwargs):
        print("metaclass init method called")
        return super(MyMetaClass, self).__init__(*args, **kwargs)
    
    def test_method_1(self):
        print("MyMetaClass - Test method 1 called")
class MyClass(metaclass=MyMetaClass):
    def __new__(cls, *args, **kwargs):
        print("instance new method called")
        return super(MyClass, cls).__new__(cls, *args, **kwargs)
    
    def __init__(self, *args, **kwargs):
        print("instance init method called")
        return super(MyClass, self).__init__(*args, **kwargs)
ins2 = MyClass()
MyClass._test_attribute
MyClass.__mro__
MyMetaClass.__mro__
Explanation: LifeCycle with Metaclasses
End of explanation
from abc import ABCMeta, ABC, abstractmethod
ABCMeta?
class MyAbstractClass(metaclass=ABCMeta):
    def __init__(self):
        pass
    @abstractmethod
    def my_abstract_method(self):
        pass
MyAbstractClass()
class MyChildClass(MyAbstractClass):
    
    def __init__(self):
        pass
    
    def my_abstract_method(self):
        pass
mcc = MyChildClass()
mcc
Explanation: Pattern 1 : Abstract Classes
End of explanation
class MySingletonABCMeta(ABCMeta):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MySingletonABCMeta, cls).__call__(*args)
        return cls._instances[cls]
class MyAbstractSingletonClass(metaclass=MySingletonABCMeta):
    def __init__(self):
        pass
    @abstractmethod
    def my_abstract_method(self):
        pass
MyAbstractSingletonClass()
class MyAbstractSingletonChild(MyAbstractSingletonClass):
    def __init__(self):
        pass
    
    def my_abstract_method(self):
        pass
a1 = MyAbstractSingletonChild()
b1 = MyAbstractSingletonChild()
type(a1), id(a1), type(b1), id(b1)
Explanation: Pattern 2 : Abstract family of singleton classes - Combine two metaclasses
End of explanation
class MyBeanMeta(type):
    _instances = {}
    def __call__(cls, *args):
        print(args)
        key = tuple((cls, args))
        if key not in cls._instances:
            cls._instances[key] = super(MyBeanMeta, cls).__call__(*args)
        return cls._instances[key]
class MyBeanClass(metaclass=MyBeanMeta):
    def __init__(self, a ):
        self.a = a
bn1 = MyBeanClass(1)
bn2 = MyBeanClass(2)
bn3 = MyBeanClass(3)
bn4 = MyBeanClass(1)
id(bn1), id(bn2), id(bn3), id(bn4)
Explanation: Pattern 3 : Pooled Objects
End of explanation
import logging
logging.basicConfig(filename='example.log', level=logging.INFO)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
class MyLogSingletonMeta(type):
    logger = logging.getLogger('abc')
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super(MyLogSingletonMeta, cls).__call__(*args)
            cls._instances[cls] = instance
            instance.__dict__['logger'] = logging.getLogger('abc')
        return cls._instances[cls]
class MyLogEnabledClass(metaclass=MyLogSingletonMeta):
    def test_function(self):
        self.logger.info('Inside test_function method of Log Enabled class')
        pass    
lec_instance1 = MyLogEnabledClass()
lec_instance2 = MyLogEnabledClass()
lec_instance1.test_function()
print(id(lec_instance1), id(lec_instance2))
!cat example.log
class MyLogger:    
    def __init__(self, logger=None):
        self.logger = logger
        
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            if self.logger is None:
                print(str(func) + " is called")
            else:
                self.logger.info(str(func) + " is called")
            return func(*args, **kwargs)
        return wrapper 
class MyLoggingMeta(type):
    
    def __new__(cls, name, bases, attrs):        
        for item, value in attrs.items():
            if callable(value):
                print("Function item :" + str(item), str(value), type(value))
                attrs[item] = MyLogger()(value)
            else: 
                print(str(item), str(value), type(value))
        return super(MyLoggingMeta, cls).__new__(cls, name, bases, attrs)
class MyClass1(metaclass=MyLoggingMeta):
    def test_m1(self):
        pass
    
    def test_m2(self):
        pass
a= MyClass1()
a.test_m2()
a.test_m1()
Explanation: Pattern 4 : Logging using Metaclasses
End of explanation
class MySealedMeta(type):
    
    def __new__(cls, name, bases, attrs):
        all_metaclasses = [type(x) for x in bases]
        if MySealedMeta in all_metaclasses:
            raise TypeError("Sealed class cannot be sublcassed")
        return super(MySealedMeta, cls).__new__(cls, name, bases, attrs)
    
class MySealedClass(metaclass=MySealedMeta):
    pass
class MyChildOfSealed(MySealedClass):
    pass
Explanation: Pattern 5 : Sealed classes
End of explanation
<END_TASK>