048-007. using metaclass
@ # Metaclass is class which creates class # In metaclass, there are 2 kind # First one is way of dynamically creating class by using "type" # Second one is way of implementing metaclass by inheriting "type" @ # We make class by specifying "name of class as string", "base class as tuple", "property and method(which can be denoted as lambda expression) as dictionary" inside of type # type can be used when you find type of object(class) # type can be used when you create class class = type('name of class', tuple_of_base_class, dictionary_for_property_and_method) # I create class "Hello" by using "type" Hello = type('Hello', (), {}) print(Hello) # output: # <class '__main__.Hello'> # I create "Hello instance" by using class "Hello" h = Hello() print(h) # output: # <__main__.Hello object at 0x029B4750> @ # type('Hello', (), {}) creates class named Hello, whose class doesn't have base class, whose class doesn't have property and method # You can create instance from this class @ # You can put property and method into class # You can use inheritance # Let's try make class which has "property and method", "base class" # The following code creates class which inherits "list", which has desc property and replace() # First, I need to define replace() which will be put into class def replace(self, old, new): while old in self: self[self.index(old)] = new # I create AdvancedList class # This class inherits list # This class has property desc and replace() AdvancedList = type('AdvancedList', (list, ), { 'desc': 'upgraded list', 'replace': replace }) # I give list to AdvancedList instance x = AdvancedList([1, 2, 3, 1, 2, 3, 1, 2, 3]) # I replace 1 with 100 in list which AdvancedList instance has x.replace(1, 100) print(x) # output: # [100, 2, 3, 100, 2, 3, 100, 2, 3] print(x.desc) # upgraded list @ # Let's talk about __new__() in metaclass # If class inherits "type", it becomes metaclass # You can add property and method by using __new__() into class which will be newly created class name_of_metaclass(type): def __new__(metacls, name, bases, namespace): code @ # MakeCalc class inherits "type", so MakeCalc class becomes metaclass class MakeCalc(type): # This __new__() is invoked when you newly create class def __new__(metacls, name, bases, namespace): # This adds value of desc attribute into newly created class namespace['desc'] = 'calculating class' # This add method as lambda expression into newly created class namespace['add'] = lambda self, a, b: a + b # This invokes __new__() of "base class type" return type.__new__(metacls, name, bases, namespace) # I create Calc class by metaclass MakeCalc Calc = MakeCalc('Calc', (), {}) # I create instance c of Calc class c = Calc() # I print value of desc attribute from Calc instance c print(c.desc) # I invoke method which is registered onto attribute of add print(c.add(1, 2)) @ # __new__() is invoked when you create new class from metaclass as if you do "Calc = MakeCalc('Calc', (), {})" # Therefore, you can add value of attribute and method onto attribute by using __new__() without using "Calc = MakeCalc('Calc', (), {})" syntanx # In this case, we tried to add method as lambda expression "lambda self, a, b: a + b" # But note that, since we should designate 1st parameter as "self", we also designate 1st argument as "self" in lambda expression @ # Metaclass is mainly used when you control action of class @ # The following is the way of singleton which always creates only one instance from class # Singleton class inherits "type", so Singleton class becomes metaclass class Singleton(type): # __instances is attribute on which you will store instance of Singleton class __instances = {} # This is __call__() which is invoked when you create instance from Singleton class def __call__(cls, *args, **kwargs): # This code checks if you already created instace from Singleton class if cls not in cls.__instances: # If you didn't create instance yet from Singleton class, # this code create instance and store that instance onto "__instances dictionary" cls.__instances[cls] = super().__call__(*args, **kwargs) # If you already created instance, # this returns created instance which is stored onto "__instances dictionary" This returns newly created instance return cls.__instances[cls] # I create Hello class and I configure metaclass as Singleton class class Hello(metaclass=Singleton): # I do nothing here pass # I create instance a from Hello class a = Hello() # 클래스 Hello로 인스턴스 a 생성 # I create instance b from Hello class b = Hello() # I check if instance a equals to instance b print(a is b) # output: # true @ # 1. You create Singleton metaclass which inherits "type" # 1. You create Hello class with configuring "class Hello(metaclass=Singleton):" # 1 Metaclass Singleton can control Hello class @ # __call__() is generally invoked when you invoke instance by using parenthesis # However, if you implement __call__() inside of metaclass which inherits "type", when you create instance from class(Hello class) which uses metaclass(Singleton class), __call__() is invoked (when you invoke class by parenthesis) # In this case, when you create instance by using "Hello()", __call__() is invoked in Singleton class # And in __call__(), it checks if instance already is created or not @ # Famous example of metaclass is abc.ABCMeta which is explained in "038-006. using abstract class" # Abstract class which uses abc.ABCMeta should have only list of method # And derived class from abstract class should implement body of method from list of method # In other words, metaclass(abc.ABCMeta) controls action of abstract class and its derived class