Python-元類

exec模塊

什麼是exec模塊?

exec 模塊 是Python內置的一個模塊數據庫

exec模塊的做用?

exec 模塊能夠把 「字符串形式的」 Python代碼 添加到全局名稱空間或局部名稱空間中app

exec模塊怎麼用?

直接調用 exec()code

須要傳三個參數:orm

  • 參數1:字符串形式的Python代碼
  • 參數2:全局名稱空間 字典
  • 參數3:局部名稱空間 字典
# 全局名稱空間
'''
文本形式的python代碼:
    以下:
'''
code = '''
global x
global y

x = 10
y = 20
def func():
    pass
'''

# 全局名稱空間
global_dict = {"x":200}

# 局部名稱空間
local_dict = {}

# 傳三個參數:
# 參數1:字符串形式的Python代碼
# 參數2:全局名稱空間
# 參數3:局部名稱空間
exec(code,global_dict,local_dict)

print(global_dict)      # {'x': 10,....}
print(local_dict)       # {'func': <function func at 0x0000000001D01E18>}

建立類的兩種方式

一、 用 class 關鍵字建立對象

# 建立類的第一種方式:
class Test:
    country = "china"

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def speak(self):
        print("speak chinese")

p1 = Test("qinyj",18)
print(Test)

二、 手動調用 type() 實例化出來獲得一個自定義的類繼承

# 建立類的第二種方式:
class_name = "Test"
class_base = (object,)
class_dict = {}
code = '''
name = "Test"
def __init__(self,name,age):
    self.name = name
    self.age = age
def test(self):
    print("from Test.test...")
'''

# 使用exec 模塊,目的:將字符串形式的Python代碼執行封裝到類的名稱空間中
exec(code,{},class_dict)
'''
type源碼:
    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
    參數說明:
        what --> 類名
        bases --> 基類/父類
        dict --> 類的名稱空間
'''
Test = type(class_name,class_base,class_dict)
print(Test)

元類介紹

什麼是元類?

元類就是類的類,咱們自定義的類的類是type,type就是全部類的類,type就是一個元類內存

元類的做用?

元類能夠幫咱們控制類的建立ci

元類能夠幫咱們控制類的調用資源

怎麼用元類?

一、 自定義一個元類,繼承type,派生出本身的屬性和方法

二、 給須要使用的類,經過metaclass 指定自定義的元類

自定義一個元類

首先咱們自定義一個元類必需要繼承type類,而後重寫裏面的方法。

# 自定義一個元類
class MyMeta(type):
    # 子類的方法與父類的方法同樣,優先用子類的,子類覆蓋父類的__init__方法
    # 控制了子類的定義方式
    def __init__(self,class_name,class_base,class_dict):
        if not class_name.istitle():
            raise TypeError("類的首字母必須大寫")

        if not class_dict.get("__doc__"):
            raise TypeError("類的內部必需要寫註釋")
        super().__init__(class_name, class_base, class_dict)

    # 模擬type元類內部作的事情
    # 元類觸發的__call__能夠控制類的調用,調用__call__會觸發如下兩點:
    # 一、會調用__new__產生一個空對象
    # 二、會執行__init__(),把參數傳過去,再將實例化出來的對象返回給自定義的類
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        obj.__init__(*args, **kwargs)
        return obj

    # 能夠經過元類內部的__new__控制對象的建立
    # def __new__(cls, *args, **kwargs):
    #     pass

# 首先自定義一個類,
# 由於Foo類繼承了元類,必須手動繼承object
class Foo(object,metaclass=MyMeta):
    '''
    若是定義的類名首字母沒有大寫則會報錯:
        TypeError: 類的首字母必須大寫
    若是不寫註釋則會報錯:
        TypeError: 類的內部必需要寫註釋
    註釋:這是一個Foo類
    '''
    x = 10
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def f1(self):
        print("from Foo.f1")

foo = Foo("qinyj",18)       # 調用Foo對象,會觸發type的__call__方法
# print(foo.f1())

ORM

什麼是ORM?

ORM:對象關係映射 --> 映射到數據庫MySQL中的數據表

Python中 MySQL數據庫中
類名 表名
對象 一條記錄
對象.屬性 字段

這裏模擬Django的ORM,爲了將數據庫的增、刪、改、查,所有封裝成一個個方法,好比:save,delete,update,select

在優酷項目中實現元類

'''
ORM:對象關係映射:----》映射到數據MySQL中的數據表
類名--》表名
對象--》一條記錄
對象.屬性--》字段

模擬Django的ORM,爲了將數據庫的增、刪、改、查所有封裝成一個個的方法:
    好比:save、delete、uptate、select
'''
class Field:
    def __init__(self,name,column_type,primary_key,default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default

# int類型
class IntegerField(Field):
    def __init__(self,name,column_type="int",primary_key=False,default=0):
        super().__init__(name,column_type,primary_key,default)

# str類型
class StringField(Field):
    def __init__(self,name,column_type="varchar(64)",primary_key=False,default=None):
        super().__init__(name,column_type,primary_key,default)


'''
問題1:解決代碼冗餘問題:好比有100張表,須要寫100個__init__
解決1:使用繼承,繼承父類,繼承一個dict

問題2:沒法預測每一張表的字段是什麼,沒法經過父類的__init__解決問題
解決2:經過繼承字典,內部的__init__,能夠接受任意個數的關鍵字參數

問題3:繼承字典的類實例化的對象,沒法經過對象.屬性的方式存值
解決3:對象和字典的屬性時兩個不一樣的名稱空間,經過在類內部實現魔法方法
        __getattr__、__setattr__ 實現字典與對象的名稱空間的屬性相通,如出一轍,
        而且具有字典原有的特性,取值方式和字典同樣
'''


'''
建立元類,元類須要作的事情:
    一、一張表必須有一個表名
    二、一張數據表必須有一個主鍵,而且主鍵必須是惟一的
    三、將數據表中全部的字段對象,都存放在一個獨立的字典中
        存不是目的,取才是目的
'''
class OrmMetaClass(type):
    # 元類  實現__new__方法
    def __new__(cls, class_name,class_base,class_dict):

        # 過濾 Models 類
        if class_name == "Models":
            # 什麼事情都不作,原路返回
            return type.__new__(cls, class_name,class_base,class_dict)

        # 獲取數據表的表名
        table_name = class_dict.get("table_name",class_name)

        # 定義主鍵的中間變量
        primary_key = None

        # 定義字典,存放數據表的字段對象
        mappings = {}

        # 遍歷類名稱空間中的全部屬性
        for key,value in class_dict.items():
            # 過濾不想要的屬性
            if isinstance(value,Field):
                mappings[key] = value
                # 判斷是不是添加了主鍵
                if value.primary_key:

                    # 判斷主鍵的中間變量是否存在
                    # 若是已經有了就拋異常,只能有一個主鍵
                    if primary_key:
                        raise TypeError("只能有一個主鍵")

                    # 若主鍵中間的變量沒有值,則給中間變量賦值
                    primary_key = value.name
        # 若是上述遍歷 發現沒有定義主鍵,則拋異常必須有一個主鍵
        if not primary_key:
            raise TypeError("必須有一個主鍵")

        # 循環遍歷 把類的名稱空間中多餘重複的屬性刪除掉,節省內存資源
        for key in mappings.keys():
            class_dict.pop(key)

        # 給類的名稱空間添加表名、主鍵、存放字段對象 屬性。
        class_dict["table_name"] = table_name
        class_dict["primary_key"] = primary_key
        class_dict["mappings"] = mappings
        return type.__new__(cls, class_name,class_base,class_dict)


class Models(dict,metaclass=OrmMetaClass):
    def __getattr__(self, item):
        # print(item,"在調用對象.屬性沒有屬性值得時候觸發")
        return self.get(item)

    def __setattr__(self, key, value):
        # print(key,value)
        self[key] = value

# 建立用戶表類
class User(Models):
    user_id = IntegerField(name="user_id",primary_key=True)
    user_name = StringField(name="name")
    pwd = StringField(name="pwd")


# 建立電影類
class Movies(Models):
    movie_id = IntegerField(name="movie_id",primary_key=True)
    movie_name = StringField(name="movie_name")


user = User(id="001",name="qinyj",pwd="123")
print(user)
# 經過在類內部實現魔法方法,讓對象.屬性獲得的值和用字典獲得的值名稱空間相通,取到的值如出一轍,
# print(user.get("id"))
# user.age = 18
# print(user.age)

movie = Movies(id="002",movie_name="真實寫真")
print(movie)
相關文章
相關標籤/搜索