Metaclass

来源:https://www.cnblogs.com/fengqiang626/archive/2020/07/15/13308630.html
-Advertisement-
Play Games

1、一切皆對象 一、 類也是對象 在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段,在Python中這一點仍然成立。但是,Python中的類還遠不止如此。類同樣也是一種對象。只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個對象。下麵的代碼段: class MyCl ...


目錄

1、一切皆對象

一、 類也是對象

在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段,在Python中這一點仍然成立。但是,Python中的類還遠不止如此。類同樣也是一種對象。只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個對象。下麵的代碼段:

class MyClass(object):
    pass

將在記憶體中創建一個對象,名字就是MyClass。這個對象(類)自身擁有創建對象(類實例)的能力,而這就是為什麼它是一個類的原因。但是,它的本質仍然是一個對象,於是你可以對它做如下的操作:

你可以將它賦值給一個變數, 你可以拷貝它, 你可以為它增加屬性, 你可以將它作為函數參數進行傳遞。

在 python 中有兩種對象:

  • 類型(類,新的版本中類和類型是一樣的)對象:可以被實例化和繼承
  • 非類型(實例)對象:不可以被實例和繼承

python 中一切皆為對象:

在python里,int整形是對象,整數2也是對象,你定義的函數啊,類啊都是對象,你定義的變數也是對象。總之,你在python里能用到的都可以稱之為對象。

二、type和object

明白了python中一切皆對象之後,再瞭解一下python中對象之間的兩種關係

面向對象體系中的兩種關係:

  • 父子關係:通常描述為“子類是一種父類”
  • 類型實例關係:這種關係存在於兩個對象中,其中一個對象(實例)是另一個對象(類型)的具體實現。

python中萬物皆對象,一個python對象可能擁有兩個屬性,__class____bases____class__ 表示這個對象是誰創建的,__bases__ 表示一個類的父類是誰們。__class__和type()函數效果一樣。

class MyClass:
    pass

MyClass.__class__
#Out: type

MyClass.__bases__
#Out: (object,)

int.__class__
#Out: type

int.__bases__
#Out: (object,)

object.__class__  #object是type的實例,object創建自type
#Out: type

object.__bases__  #object沒有超類,它本身是所以對象的超類
#Out: ()

type.__class__    #type創建自本身
#Out: type

type.__bases__    #type繼承自object,即object是type的超類
#Out: (object,)  
  • type為對象的頂點,所有對象都創建自type。
  • object為類繼承的頂點,所有類都繼承自object。

三、元類、類、實例

  • object是所有類的超類,而且type也是繼承自object;所有對象創建自type,而且object也是type的實例。
  • “type是object的類型,同時,object又是type的超類”,那到底是先有object還是先有type呢?這就像“先有雞還是先有蛋問題”。
  • object和type是python中的兩個源對象,事實上,它們是互相依賴對方來定義,所以它們不能分開而論。
  • 通過這兩個源對象可以繁育出一堆對象:list、tuple、dict等。元類就是這些類的類,這些類是元類的實例。
l1=list()
l1.__class__      #l是list的實例

#Out: list
list.__class__   #list是type的實例

#Out: type
l1.__bases__      #實例沒有超類

#AttributeError: 'list' object has no attribute '__bases__'
list.__bases__   

#Out: (object,)
l2=[1,2,3]        #l1是利用"類型名()"的方式創建實例,l2是利用python內置類型創造實例,比l1的創建速度要快

2、metaclass

一、type--“造物的上帝”

就像str是用來創建字元串對象的類,int是用來創建整數對象的類,而type就是創建類對象的類。

類本身不過是一個名為 type 類的實例。在 Python的類型世界里,type這個類就是造物的上帝。

用戶自定義類,只不過是type類的__call__運算符的重載。當我們定義一個類的語句結束時,

真正發生的情況,是 Python 調用 type 的__call__運算符。簡單來說,當你定義一個類時,寫成下麵時:

class MyClass:
  data = 1

Python 真正執行的是下麵這段代碼:

class = type(classname, superclasses, attributedict)

這裡等號右邊的type(classname, superclasses, attributedict),就是 type 的__call__運算符重載,它會進一步調用:

type.__new__(typeclass, classname, superclasses, attributedict)

type.__init__(class, classname, superclasses, attributedict)

當然,這一切都可以通過代碼驗證,比如下麵這段代碼示例:

class MyClass:
    data = 1

instance = MyClass()

MyClass, instance
#Out: (__main__.MyClass, <__main__.MyClass at 0x4eac358>)

MyClass = type('MyClass', (), {'data': 1})
instance = MyClass()

MyClass, instance
#Out: (__main__.MyClass, <__main__.MyClass at 0x4f915c0>)

instance.data
#Out: 1

由此可見,正常的 MyClass 定義,和手工去調用 type運算符的結果是一樣的。

二、metaclass屬性

metaclass 是 type 的子類,通過替換 type的__call__運算符重載機制,“超越變形”正常的類。

其實,理解了以上幾點,我們就會明白,正是 Python 的類創建機制,給了 metaclass 大展身手的機會。

一旦你把一個類型 MyClass 的 metaclass 設置成 MyMeta,MyClass 就不再由原生的 type創建,而是會調用 MyMeta 的__call__運算符重載。

class = type(classname, superclasses, attributedict) 

# 變為了
class = MyMeta(classname, superclasses, attributedict)
class MyMeta(type):
    def __new__(cls, *args, **kwargs):
        print('===>MyMeta.__new__')
        print(cls.__name__)
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, classname, superclasses, attributedict):
        super().__init__(classname, superclasses, attributedict)
        print('===>MyMeta.__init__')
        print(self.__name__)
        print(attributedict)
        print(self.tag)

    def __call__(self, *args, **kwargs):
        print('===>MyMeta.__call__')
        obj = self.__new__(self, *args, **kwargs)
        self.__init__(self, *args, **kwargs)
        return obj


class Foo(object, metaclass=MyMeta):
    tag = '!Foo'

    def __new__(cls, *args, **kwargs):
        print('===>Foo.__new__')
        return super().__new__(cls)

    def __init__(self, name):
        print('===>Foo.__init__')
        self.name = name

print('test start')
foo = Foo('test')
print('test end')


#輸出
#===>MyMeta.__new__
#MyMeta
#===>MyMeta.__init__
#Foo
#{'tag': '!Foo', '__module__': '__main__', '__init__': <function Foo.__init__ at 0x00000000010F7950>, '__new__': <function Foo.__new__ at 0x00000000010F78C8>, '__qualname__': 'Foo'}
#!Foo
#test start
#===>MyMeta.__call__
#===>Foo.__new__
#===>Foo.__init__
#test end

在創建Foo類的時候,python做瞭如下操作。

  1. Foo中有metaclass這個屬性嗎?如果是,Python會在記憶體中通過metaclass創建一個名字為Foo的類對象(我說的是類對象,請緊跟我的思路)。
  2. 如果Python沒有找到metaclass,它會繼續在父類中尋找metaclass屬性,並嘗試做和前面同樣的操作。
  3. 如果Python在任何父類中都找不到metaclass,它就會在模塊層次中去尋找metaclass,並嘗試做同樣的操作。
  4. 如果還是找不到metaclass,Python就會用內置的type來創建這個類對象。

現在的問題就是,你可以在metaclass中放置些什麼代碼呢?
答案就是:可以創建一個類的東西。那麼什麼可以用來創建一個類呢?type,或者任何使用到type或者子類化type的東西都可以。用類實現可以(比如上面這個例子),用函數實現也可以。但是metaclass必須返回一個類。

def MyMetaFunction(classname, superclasses, attributedict):
    attributedict['year'] = 2019
    return type(classname, superclasses, attributedict)



class Foo(object, metaclass=MyMetaFunction):

    tag = '!Foo'

    def __new__(cls, *args, **kwargs):
        print('===>Foo.__new__')
        return super().__new__(cls)

    def __init__(self, name):
        print('===>Foo.__init__')
        self.name = name

print('test start')
foo = Foo('test')
print('name:%s,tag:%s,year:%s' % (foo.name, foo.tag, foo.year))
print('test end')

#輸出
#test start
#===>Foo.__new__
#===>Foo.__init__
#name:test,tag:!Foo,year:2019
#test end

把上面的例子運行完之後就會明白很多了,正常情況下我們在父類中是不能對子類的屬性進行操作,但是元類可以。換種方式理解:元類、裝飾器、類裝飾器都可以歸為元編程(引用自 python-cook-book 中的一句話)。

3、應用

一、實現ORM

我們通過創建一個類似Django中的ORM來熟悉一下元類的使用,通過元類用來創建API是非常好的選擇,使用元類的編寫很複雜但使用者可以非常簡潔的調用API

#一、首先定義Field類,它負責保存資料庫表的欄位名和欄位類型
class Field(object):

    def __init__(self, name, column_type):
        self.name = name
        self.colmun_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)


class StringField(Field):

    def __init__(self, name):
        super().__init__(name, 'varchar(100)')


class IntegerField(Field):

    def __init__(self, name):
        super().__init__(name, 'bigint')

#二、定義元類,控制Model對象的創建
class ModelMetaClass(type):

    def __new__(cls, name, bases, attrs):

        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
        mappings = dict()

        for k,v in attrs.items():
            #保持類屬性和列的映射關係到mappings字典
            if isinstance(v,Field):
                print('Found mapping:%s==>%s' % (k, v))
                mappings[k] = v
                
        for k in mappings.keys(): #將類屬性移除,是定義的類欄位不污染User類屬性,只在實例中可以訪問這些key
            attrs.pop(k)
        attrs['__table__'] = name.lower()  #假設表名為類名的小寫,創建類時添加一個__table__屬性
        attrs['__mappings__'] = mappings #保持屬性和列的關係映射,創建類時添加一個__mappings__屬性
        return super().__new__(cls, name, bases, attrs)

#三、Model基類
class Model(dict, metaclass=ModelMetaClass):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError("'Model' object has no attribute '%s'" % key)

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

    def save(self):
        fields = []
        params = []
        args = []
        
        for k,v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self,k,None))
            
        sql = 'insert into %s (%s) values (%s)' % (self.__table__,','.join(fields),','.join(params))
        print('SQL:%s' % sql)
        print('ARGS:%s' % str(args))

#我們想創建類似Django的ORM,只要定義欄位就可以實現對資料庫表和欄位的操作
#最後、我們使用定義好的ORM介面,使用起來非常簡單
class User(Model):

    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

user = User(id=1,name='Job',email='[email protected]',password='pw')
user.save()


#輸出
#Found mapping:password==><StringField:password>
#Found mapping:id==><IntegerField:id>
#Found mapping:email==><StringField:email>
#Found mapping:name==><StringField:username>
#SQL:insert into user (email,id,password,username) values (?,?,?,?)
#ARGS:['[email protected]', 1, 'pw', 'Job']

二、單例

依照Python官方文檔的說法,__new__方法主要是當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑。還有就是實現自定義的metaclass。

簡單來說,單例模式的原理就是通過在類屬性中添加一個單例判定位ins_flag,通過這個flag判斷是否已經被實例化過了,如果被實例化過了就返回該實例。

1)、__new__方法實現單例

class Singleton:

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):     #_instance是類(Singleton)對象的一個屬性
            cls._instance= super().__new__(cls, *args, **kwargs)
        return cls._instance #類的__new__方法之後,必鬚生成本類的實例(註意是“本類”的實例)才能自動調用本類的__init__方法進行初始化,否則不會自動調用__init__

class SubSingleton(Singleton):
    pass

s1 = Singleton()
s2 = Singleton()
print(s1 is s2)

ss1 = SubSingleton()
ss2 = SubSingleton()
print(ss1 is ss2)

#輸出
#True
#True

因為重寫__new__方法,所以繼承至Singleton的類,在不重寫__new__的情況下都將是單例模式。 _instance(單下劃線開頭)屬性換成__instance(雙下劃線開頭)會得到不一樣的結果,將無法實現單例模式,如果__instance(雙下劃線開頭)屬性就變成了私有的(其實變成了_Singleton__instance)。

2)、元類實現單例

class SingletonMeta(type):

    def __init__(self,*args,**kwargs):
        self.__instance = None    #這是一個私有屬性來保存屬性,而不會污染Singleton類(其實還是會污染,只是無法直接通過__instance屬性訪問)
        super().__init__(*args,**kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
        return self.__instance

class Singleton(metaclass=SingletonMeta): #在代碼執行到這裡的時候,元類中的__new__方法和__init__方法其實已經被執行了,而不是在Singleton實例化的時候執行。且僅會執行一次。
    pass

class SubSingleton(metaclass=SingletonMeta):
    pass
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)

ss1 = SubSingleton()
ss2 = SubSingleton()
print(ss1 is ss2)

#輸出
#True
#True
  • 我們知道元類(SingletonMeta)生成的實例是一個類(Singleton),而這裡我們僅僅需要對這個實例(Singleton)增加一個屬性(__instance)來判斷和保存生成的單例。想想也知道為一個類添加一個屬性當然是在__init__中實現了。
  • 關於__call__方法的調用,因為Singleton是SingletonMeta的一個實例。所以Singleton()這樣的方式就調用了SingletonMeta的__call__方法。

三、動態載入

YAML是一個家喻戶曉的 Python 工具,可以方便地序列化 / 逆序列化結構數據。YAMLObject 的一個超越變形能力,就是它的任意子類支持序列化和反序列化(serialization & deserialization)。比如說下麵這段代碼(https://pyyaml.org/wiki/PyYAMLDocumentation):

class Monster(yaml.YAMLObject):

  yaml_tag = u'!Monster'
  def __init__(self, name, hp, ac, attacks):
    self.name = name
    self.hp = hp
    self.ac = ac
    self.attacks = attacks

  def __repr__(self):
    return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
       self.__class__.__name__, self.name, self.hp, self.ac,     
       self.attacks)


yaml.load("""
--- !Monster
name: Cave spider
hp: [2,6]    # 2d6
ac: 16
attacks: [BITE, HURT]
""")

Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])

print yaml.dump(Monster(
    name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT']))
# 輸出

!Monster

ac: 16

attacks: [BITE, HURT]

hp: [3, 6]
name: Cave lizard

這裡 YAMLObject 的特異功能體現在哪裡呢?

你看,調用統一的 yaml.load(),就能把任意一個 yaml 序列載入成一個 Python Object;而調用統一的 yaml.dump(),就能把一個 YAMLObject 子類序列化。對於 load() 和 dump() 的使用者來說,他們完全不需要提前知道任何類型信息,這讓超動態配置編程成了可能。聽大神說在他的實戰經驗中,許多大型項目都需要應用這種超動態配置的理念。

比方說,在一個智能語音助手的大型項目中,我們有 1 萬個語音對話場景,每一個場景都是不同團隊開發的。作為智能語音助手的核心團隊成員,我不可能去瞭解每個子場景的實現細節。

在動態配置實驗不同場景時,經常是今天我要實驗場景 A 和 B 的配置,明天實驗 B 和 C 的配置,光配置文件就有幾萬行量級,工作量不可謂不小。而應用這樣的動態配置理念,我就可以讓引擎根據我的文本配置文件,動態載入所需要的 Python 類。

對於 YAML 的使用者,這一點也很方便,你只要簡單地繼承 yaml.YAMLObject,就能讓你的 Python Object 具有序列化和逆序列化能力。是不是相比普通 Python 類,有一點“變態”,有一點“超越”?

YAML 的這種動態序列化 / 逆序列化功能正是用metaclass 實現的。

我們這裡只看 YAMLObject 的 load() 功能。簡單來說,我們需要一個全局的註冊器,讓 YAML 知道,序列化文本中的 !Monster 需要載入成 Monster 這個 Python 類型。

一個很自然的想法就是,那我們建立一個全局變數叫 registry,把所有需要逆序列化的 YAMLObject,都註冊進去。比如下麵這樣:

registry = {}

def add_constructor(target_class):
    registry[target_class.yaml_tag] = target_class

然後,在 Monster 類定義後面加上下麵這行代碼:

add_constructor(Monster)

但這樣的缺點也很明顯,對於 YAML 的使用者來說,每一個 YAML 的可逆序列化的類 Foo 定義後,都需要加上一句話,add_constructor(Foo)。這無疑給開發者增加了麻煩,也更容易出錯,畢竟開發者很容易忘了這一點。

那麼,更優的實現方式是什麼樣呢?如果你看過 YAML 的源碼,就會發現,正是 metaclass 解決了這個問題。

# Python 2/3 相同部分

class YAMLObjectMetaclass(type):

    def __init__(cls, name, bases, kwds):
        super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)

        if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
            cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
    # 省略其餘定義


# Python 3
class YAMLObject(metaclass=YAMLObjectMetaclass):
    yaml_loader = Loader
    # 省略其餘定義


# Python 2
class YAMLObject(object):
    __metaclass__ = YAMLObjectMetaclass

    yaml_loader = Loader
    # 省略其餘定義

你可以發現,YAMLObject 把 metaclass 都聲明成了YAMLObjectMetaclass,利用 YAMLObjectMetaclass 的__init__方法,為所有 YAMLObject 子類偷偷執行add_constructor()。在 YAMLObjectMetaclass 中,下麵這行代碼就是魔法發生的地方:

cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) 

YAML 應用 metaclass,攔截了所有 YAMLObject 子類的定義。也就說說,在你定義任何 YAMLObject 子類時,Python 會強行插入運行下麵這段代碼,把我們之前想要的add_constructor(Foo)給自動加上。

cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)

所以 YAML 的使用者,無需自己去手寫add_constructor(Foo) 。怎麼樣,是不是其實並不複雜?

4、總結

正常情況下我們在父類中是不能對子類的屬性進行操作,但是元類可以,這就使得程式代碼的維護變得困難。metaclass 是 Python 的黑魔法之一,在掌握它之前不要輕易嘗試。感覺上python的規範原則很松,但這也使得python更靈活,對代碼的編寫理應由程式員自己負責,自己寫的代碼還是得自己負責啊。

元類、裝飾器、類裝飾器都可以歸為元編程,它們之間有一些相似點,還是在實際的應用中選擇比較,使用合適的工具進行編程吧。

參考:

https://www.cnblogs.com/tkqasn/p/6524879.html

https://time.geekbang.org/column/article/101288


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本專欄內容均引用《大話設計模式》並做適當修改, 起因 有部分電腦專業的學生或有一定經驗的在職開發者,他們都知道類、方法、構造方法、甚至抽象類、介面等概念,並用各種IDE寫過不少的Windows或Web程式,但是當問到為什麼要用面向對象,它的好處在哪裡,卻沒有人能完整的講出來,多數人的反應是,概念知 ...
  • 關於延遲載入 在 Spring 中,預設情況下所有定的 bean 及其依賴項目都是在應用啟動時創建容器上下文是被初始化的。測試代碼如下: @Slf4j @Configuration public class DemoConfig { public DemoConfig() { log.warn(" ...
  • 一、複習一下前面所學的內容 1.寫出下列字元或者數字的類型以及在printf()函數中使用什麼符號轉換 常量類型轉換說明(%轉換符號) 12 int %d 0X3 unsigned int %#x 'C' char(實際上是int) %c 2.34E07 double %e '\040' char( ...
  • 安裝python的mysqlclient==1.4.6報錯 環境:ubuntu18.04python 3.6.8Django 2.0 想要使用Django來操作MySQL,需要安裝一個驅動程式。在Python3中,選擇用mysqlclient做驅動。 安裝mysqlclient出現報錯: ERROR ...
  • 繼Golang學習系列第二天:變數、常量、數據類型和流程語句之後,今天開始學習數據類型之高級類型: 派生類型。 學過java的人都知道,java其實就8種基本類型:byte、short、int、long、float、double、char、boolean,但它有引用數據類型:字元串、數組、集合、類、 ...
  • 今天我們繼續來學習C語言的入門知識點 11. 作用域規則 任何一種編程中,作用域是程式中定義的變數所存在的區域,超過該區域變數就不能被訪問。C 語言中有三個地方可以聲明變數: 在函數或塊內部的局部變數 在所有函數外部的全局變數 在形式參數的函數參數定義中 讓我們來看看什麼是局部變數、全局變數和形式參 ...
  • 本文源碼:GitHub·點這裡 || GitEE·點這裡 一、集群環境搭建 1、環境概覽 ES版本6.3.2,集群名稱esmaster,虛擬機centos7。 服務群 角色劃分 說明 en-master master 主節點:esnode1 en-node01 slave 從節點:esnode2 e ...
  • 列表生成式即List Comprehensions,是Python內置的非常簡單卻強大的可以用來創建list的生成式。 通常我們在迴圈遍歷一個列表時,都是通過for迴圈來完成 L = [] for i in range(1,11) L.append(x*x) 結果如下: [1,4,9,16,25,3 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...