天天看點

Python進階 -- 13 元類、ORM一、元類二、ORM

一、元類

1、了解類也是對象

        Python中的類同樣也是一種對象,隻要你使用關鍵字class,Python解釋器在執行的時候就會建立一個對象。這個對象(類對象)擁有建立對象(執行個體對象)的能力。但是,它的本質仍然是一個對象

2、動态的建立類

        因為類也是對象,你可以在運作時動态的建立它們,就像其他任何對象一樣。首先,你可以在函數中建立類,使用class關鍵字即可。代碼如下:

def create_class(name):
    if name == "AAA":
        class AAA(object):
            print("建立AAA類")
        return AAA
    elif name == "BBB":
        class BBB(object):
            print("建立BBB類")
        return BBB


# 調用create_class方法,并傳遞參數
# 想要建立AAA對象,需要傳遞對應的參數
AAA_class = create_class("AAA")
print(AAA_class)

# 可以通過AAA_class這個類建立示例對象
print(AAA_class())

"""
    列印結果:

        建立AAA類
        <class '__main__.create_class.<locals>.AAA'>
        <__main__.create_class.<locals>.AAA object at 0x000002235BBBA860>

"""
           

        但這還不夠動态,因為你仍然需要自己編寫整個類的代碼。由于類也是對象,是以它們必須是通過什麼東西來生成的才對。當你使用class關鍵字時,Python解釋器自動建立這個對象。但就和Python中的大多數事情一樣,Python仍然提供給你手動處理的方法。此時需要用到Python的内置函數:type

        type不僅僅可以傳回一個對象的類型,同時,還可以用來建立一個類,即type就是元類(元類可以建立元類,元類也可以建立其他所有的類)

3、使用type建立類

        type還有一種完全不同的功能,動态的建立類。

        type可以接受一個類的描述作為參數,然後傳回一個類。(要知道,根據傳入參數的不同,同一個函數擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向後相容性)

type建立類的文法:

type(類名, 由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
           
# 使用class關鍵詞定義一個類
class Test:
    pass 

# 以上使用關鍵詞定義一個類的方式,可以是用type如下定義
# 類名叫做Test2,父類為空,屬性為空
Test2 = type("Test2", (), {})
           

使用help測試兩個類,結果如下:

help(Test)
"""
	Help on class Test in module __main__:

	class Test(builtins.object)
	 |  # 使用class關鍵詞定義一個類
	 |
	 |  Data descriptors defined here:
	 |
	 |  __dict__
	 |      dictionary for instance variables (if defined)
	 |
	 |  __weakref__
	 |      list of weak references to the object (if defined)
"""


help(Test2)
"""
	Help on class Test2 in module __main__:

	class Test2(builtins.object)
	 |  Data descriptors defined here:
	 |  
	 |  __dict__
	 |      dictionary for instance variables (if defined)
	 |  
	 |  __weakref__
	 |      list of weak references to the object (if defined)
"""
           

4、使用type建立帶有屬性的類

# 使用type建立一個帶有屬性的類
# 類名叫做Test,繼承自object,有一個屬性為bar=True
Test = type("Test", (object, ), {"bar" : True})

"""
    以上可以翻譯為:
        class Test(object):
            bar = True
"""

# 建立一個子類,繼承Test
Child_Class = type("Child_Class", (Test,), {})


help(Test)
"""
    Help on class Test in module __main__:

    class Test(builtins.object)
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  bar = True
"""


help(Child_Class)
"""
    class Child_Class(Test)
     |  Method resolution order:
     |      Child_Class
     |      Test
     |      builtins.object
     |  
     |  Data descriptors inherited from Test:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from Test:
     |  
     |  bar = True
"""
           

注意:type的第2個參數,元組中是父類的名字,而不是字元串;添加的屬性是類屬性,并不是執行個體屬性

5、使用type建立帶有方法的類

(1)、添加執行個體方法

# 使用type建立一個帶有屬性的類
# 類名叫做Test,繼承自object,有一個屬性為bar=True
Test = type("Test", (object, ), {"bar" : True})

"""
    以上可以翻譯為:
        class Test(object):
            bar = True
"""

# 建立一個子類,繼承Test
Child_Class = type("Child_Class", (Test,), {})


# 定義一個普通的函數
def func1(self):
    print("這是一個方法:", self.bar)

# 使用type建立一個類,讓func1指向上面定義的方法
Test1 = type("Test1", (Child_Class, ), {"func1": func1})

# 使用hasattr方法判斷Test1中是否有func1這個屬性
print(hasattr(Test1, "func1"))  # True
help(Test1)
"""
    Help on class Test1 in module __main__:

    class Test1(Child_Class)
     |  Method resolution order:
     |      Test1
     |      Child_Class
     |      Test
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  func1(self)
     |      # 定義一個普通的函數
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from Test:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from Test:
     |  
     |  bar = True
"""

# 使用hasattr方法判斷Child_Class中是否有func1這個屬性
print(hasattr(Child_Class, "func1"))    # False
           

(2)、添加靜态方法

# 使用type建立一個帶有屬性的類
# 類名叫做Test,繼承自object,有一個屬性為bar=True
Test = type("Test", (object, ), {"bar" : True})

"""
    以上可以翻譯為:
        class Test(object):
            bar = True
"""

# 建立一個子類,繼承Test
Child_Class = type("Child_Class", (Test,), {})

# 定義一個普通的函數
def func1(self):
    print("這是一個方法:", self.bar)

# 定義一個靜态方法
@staticmethod
def test_static():
    print("這是一個靜态方法")

Test1 = type("Test1", (Child_Class, ), {"func1": func1, "test_static": test_static})

test1 = Test1()
test1.test_static()     # 這是一個靜态方法
test1.func1()           # 這是一個方法: True

           

(3)、添加類方法

# 使用type建立一個帶有屬性的類
# 類名叫做Test,繼承自object,有一個屬性為bar=True
Test = type("Test", (object, ), {"bar" : True})

"""
    以上可以翻譯為:
        class Test(object):
            bar = True
"""

# 建立一個子類,繼承Test
Child_Class = type("Child_Class", (Test,), {})

# 定義一個普通的函數
def func1(self):
    print("這是一個方法:", self.bar)

# 定義一個靜态方法
@staticmethod
def test_static():
    print("這是一個靜态方法")

# 定義一個類方法
@classmethod
def test_classmethod(cls):
    print(cls.bar)

Test1 = type("Test1", (Child_Class, ), {"func1": func1, "test_static": test_static, "test_classmethod": test_classmethod})

test1 = Test1()
test1.test_classmethod()           # True
           

較為完整的使用type建立類的方式:

class A(object):
    num = 100

def print_b(self):
    print(self.num)

@staticmethod
def print_static():
    print("----haha-----")

@classmethod
def print_class(cls):
    print(cls.num)

B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class})
b = B()
b.print_b()
b.print_static()
b.print_class()
""" 結果
    100
    ----haha-----
    100  """
           

6、什麼是元類

        元類就是用來建立這些類(對象)的,元類就是類的類;函數type實際上是一個元類,type就是Python在背後用來建立所有類的元類;Python中所有的東西(對象),包括整數、字元串、函數以及類,它們全部都是對象,而且它們都是從一個類建立而來,這個類就是type。

7、__methodclass__屬性(python2中的用法)

在定義一個類的時候為其添加__metaclass__屬性。如下:

class Foo(object):
    __metaclass__ = 一個類的引用
    ...
           

        Python就會用元類來建立類Foo。小心點,這裡面有些技巧。你首先寫下class Foo(object),但是類Foo還沒有在記憶體中建立。Python會在類的定義中尋找__metaclass__屬性,如果找到了,Python就會用它來建立類Foo,如果沒有找到,就會用内建的type來建立這個類。

當寫了一個普通的建立類的方法時,代碼如下:

class Foo():
    pass
           

Python做了如下的操作:

        Foo中有__metaclass__這個屬性嗎?如果是,Python會通過__metaclass__建立一個名字為Foo的類(對象)

        如果Python沒有找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。

        如果Python在任何父類中都找不到__metaclass__,它就會在子產品層次中去尋找__metaclass__,并嘗試做同樣的操作。

        如果還是找不到__metaclass__,Python就會用内置的type來建立這個類對象。

8、自定義元類

自定義元類的主要目的就是為了當建立類時能夠主動的改變類。

使用函數來當作元類:

# 自定義一個将所有屬性都變成大寫字母的方法
# class_name : 類名
# class_parents : 父類元組
# class_attr : 屬性字典
def upper_attr(class_name, class_parents, class_attr):

    #周遊屬性字典,把不是__開頭的屬性名字變為大寫
    new_attr = {}
    for name,value in class_attr.items():
        if not name.startswith("__"):
            new_attr[name.upper()] = value

    #調用type來建立一個類
    return type(class_name, class_parents, new_attr)

# 如果定義類的屬性中有metaclass這個屬性,則會自動使用metaclass對應的方法去建立目前的類
class Foo(object, metaclass=upper_attr):
    bar = 'bip'

print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))

f = Foo()
print(f.BAR)
           

使用類當作元類:

class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被調用的特殊方法
    # __new__是用來建立對象并傳回之的方法
    # 而__init__隻是用來将傳入的參數初始化給對象
    # 你很少用到__new__,除非你希望能夠控制對象的建立
    # 這裡,建立的對象是類,我們希望能夠自定義它,是以我們這裡改寫__new__
    # 如果你希望的話,你也可以在__init__中做些事情
    # 還有一些進階的用法會涉及到改寫__call__特殊方法,但是我們這裡不用
    def __new__(cls, class_name, class_parents, class_attr):
        # 周遊屬性字典,把不是__開頭的屬性名字變為大寫
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # 方法1:通過'type'來做類對象的建立
        return type(class_name, class_parents, new_attr)

        # 方法2:複用type.__new__方法
        # 這就是基本的OOP程式設計,沒什麼魔法
        # return type.__new__(cls, class_name, class_parents, new_attr)

# python3的用法
class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

# python2的用法
# class Foo(object):
#     __metaclass__ = UpperAttrMetaClass
#     bar = 'bip'


print(hasattr(Foo, 'bar'))
# 輸出: False
print(hasattr(Foo, 'BAR'))
# 輸出:True

f = Foo()
print(f.BAR)
# 輸出:'bip'
           

9、元類總結

        就元類本身而言,它們的作用很簡單,如下:攔截類的建立、修改類、傳回修改之後的類

        "元類就是深度的魔法,99%的使用者應該根本不必為此操心。如果你想搞清楚究竟是否需要用到元類,那麼你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什麼,而且根本不需要解釋為什麼要用元類。" —— Python界的領袖 Tim Peters

二、ORM

1、ORM是什麼

        ORM 是 python程式設計語言後端web架構 Django的核心思想,“Object Relational Mapping”,即對象-關系映射,簡稱ORM。

        一個句話了解就是:建立一個執行個體對象,用建立它的類名當做資料表名,用建立它的類屬性對應資料表的字段,當對這個執行個體對象操作時,能夠對應MySQL語句。

2、通過元類實作簡單的ORM中的insert功能

# 建立一個元類,一個類繼承了type,就是元類
class ModelMetaClass(type):
    # cls:類對象   name:類名   bases:父類元組  attrs:屬性字典
    def __new__(cls, name, bases, attrs):
        mapping = dict()
        # 判斷是否需要儲存
        for k, v in attrs.items():
            # 判斷是否是指定的StringField或者IntegerField的執行個體對象
            # 即:判斷v是否是一個元組
            if isinstance(v, tuple):
                print("Found mapping : %s ==> %s" %(k, v))
                # 将k 和 v 放到mapping中
                mapping[k] = v

        # 根據mapping中的key删除attrs中的對應的屬性字典
        for k in mapping.keys():
            attrs.pop(k)

        # 将之前的屬性和字段對應關系{"uid":('uid','int unsigned'),...}放入屬性字典中
        attrs["__mappings__"] = mapping     # 儲存屬性和列的映射關系
        attrs["__table__"] = name   # 讓表名和類名一緻
        # 使用type建立類
        return type.__new__(cls, name, bases, attrs)


# 實體類
class User(metaclass = ModelMetaClass):
    uid = ("uid", "int unsigned")
    name = ("username", "varchar(20)")
    password = ("password", "varchar(30)")
    email = ("email", "varchar(40)")

    """
        當實體類使用ModelMetaClass這個元類建立執行個體對象之後,以上的屬性就相當于變為如下屬性:

        __mappings__ = {
            "uid":("uid", "int unsigned"),
            "name":("username", "varchar(20)"),
            "password":("password", "varchar(30)"),
            "email":("email", "varchar(40)")
        }
        __table__ = "User"

    """
    def __init__(self, **kwargs):
        # 建立執行個體對象的時候,kwargs 為: uid = xxx , name = xxx , password = xxx , email = xxx  
        for name, value in kwargs.items():
            # 将kwagrs中沒個對應的參數名和value放入執行個體對象的屬性和對應的值中
            setattr(self, name, value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0]) # 建立字段名稱清單
            args.append(getattr(self, k, None)) # 根據k(uid)擷取執行個體對象中屬性的值,并放入參數清單中

        # 編寫sql, ",".join(清單):該方法将清單中的沒個元素取出,并用,号隔開,并傳回字元串
        # sql = """insert into %s (%s) values (%s)""" % (self.__table__, ",".join(fields), ",".join([str(i) for i in args]))
        # 以上寫sql的方法會出現資料類型錯誤的問題,即生成的sql如下:
        # insert into User (uid,username,password,email) values (1,小名同學,admin123,[email protected])
        # 無法在mysql中直接運作
        args_temp = list()
        for temp in args:
            # 判斷資料類型
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append("""'%s'""" % temp)
        sql = """insert into %s (%s) values (%s)""" % (self.__table__, ",".join(fields), ",".join(args_temp))
        print(sql)


# 實踐以上代碼
u = User(uid=1, name='小名同學', password='admin123', email='[email protected]')
u.save()

"""
    列印結果如下:

        Found mapping : uid ==> ('uid', 'int unsigned')
        Found mapping : name ==> ('username', 'varchar(20)')
        Found mapping : password ==> ('password', 'varchar(30)')
        Found mapping : email ==> ('email', 'varchar(40)')
        insert into User (uid,username,password,email) values (1,'小名同學','admin123','[email protected]')

"""
           

3、将類中除了定義屬性的其他方法抽取到父類中

# 建立一個元類,一個類繼承了type,就是元類
class ModelMetaClass(type):
    # cls:類對象   name:類名   bases:父類元組  attrs:屬性字典
    def __new__(cls, name, bases, attrs):
        mapping = dict()
        # 判斷是否需要儲存
        for k, v in attrs.items():
            # 判斷是否是指定的StringField或者IntegerField的執行個體對象
            # 即:判斷v是否是一個元組
            if isinstance(v, tuple):
                print("Found mapping : %s ==> %s" %(k, v))
                # 将k 和 v 放到mapping中
                mapping[k] = v

        # 根據mapping中的key删除attrs中的對應的屬性字典
        for k in mapping.keys():
            attrs.pop(k)

        # 将之前的屬性和字段對應關系{"uid":('uid','int unsigned'),...}放入屬性字典中
        attrs["__mappings__"] = mapping     # 儲存屬性和列的映射關系
        attrs["__table__"] = name   # 讓表名和類名一緻
        # 使用type建立類
        return type.__new__(cls, name, bases, attrs)

# 定義基類
class Model(object, metaclass=ModelMetaClass):
    def __init__(self, **kwargs):
        # 建立執行個體對象的時候,kwargs 為: uid = xxx , name = xxx , password = xxx , email = xxx  
        for name, value in kwargs.items():
            # 将kwagrs中沒個對應的參數名和value放入執行個體對象的屬性和對應的值中
            setattr(self, name, value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0]) # 建立字段名稱清單
            args.append(getattr(self, k, None)) # 根據k(uid)擷取執行個體對象中屬性的值,并放入參數清單中

        # 編寫sql, ",".join(清單):該方法将清單中的沒個元素取出,并用,号隔開,并傳回字元串
        # sql = """insert into %s (%s) values (%s)""" % (self.__table__, ",".join(fields), ",".join([str(i) for i in args]))
        # 以上寫sql的方法會出現資料類型錯誤的問題,即生成的sql如下:
        # insert into User (uid,username,password,email) values (1,小名同學,admin123,[email protected])
        # 無法在mysql中直接運作
        args_temp = list()
        for temp in args:
            # 判斷資料類型
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append("""'%s'""" % temp)
        sql = """insert into %s (%s) values (%s)""" % (self.__table__, ",".join(fields), ",".join(args_temp))
        print(sql)



# 實體類
class User(Model):
    uid = ("uid", "int unsigned")
    name = ("username", "varchar(20)")
    password = ("password", "varchar(30)")
    email = ("email", "varchar(40)")

    """
        當實體類使用ModelMetaClass這個元類建立執行個體對象之後,以上的屬性就相當于變為如下屬性:

        __mappings__ = {
            "uid":("uid", "int unsigned"),
            "name":("username", "varchar(20)"),
            "password":("password", "varchar(30)"),
            "email":("email", "varchar(40)")
        }
        __table__ = "User"

    """

class Cate(Model):
    cid = ("cid", "varchar(10)")
    cname = ("cname", "varchar(30")
   

# 實踐以上代碼
u = User(uid=1, name='小名同學', password='admin123', email='[email protected]')
u.save()

c = Cate(cid='001', cname="商品")
c.save()

"""
    列印結果如下:

        insert into User (uid,username,password,email) values (1,'小名同學','admin123','[email protected]')

        insert into Cate (cid,cname) values ('001','商品')

"""
           

繼續閱讀