精华内容
下载资源
问答
  • 也带一波(纯Python方向的可以选择性跳过,也可以当扩展) 其实通俗讲就是, 给原有对象动态的添加一些额外的职责 (毕竟动不动就改类你让其他调用的人咋办?也不符合开放封闭原则是吧~) 举个简单的例子:( ...
     

    上次知识回顾:https://www.cnblogs.com/dotnetcrazy/p/9278573.html

    代码裤子:https://github.com/lotapp/BaseCode

    在线编程:https://mybinder.org/v2/gh/lotapp/BaseCode/master

    在线预览http://github.lesschina.com/python/base/ext/基础拓展.html

    终于期末考试结束了,聪明的小明同学现在当然是美滋滋的过暑假了,左手一只瓜,右手一本书~正在给老乡小张同学拓展他研究多日的知识点

    1.NetCore装饰器模式

    装饰器这次从C#开始引入,上次刚讲迭代器模式,这次把装饰器模式也带一波(纯Python方向的可以选择性跳过,也可以当扩展)

    其实通俗讲就是,给原有对象动态的添加一些额外的职责(毕竟动不动就改类你让其他调用的人咋办?也不符合开放封闭原则是吧~)

    举个简单的例子:(https://github.com/lotapp/BaseCode/tree/master/netcore/3_Ext/Decorators)

    BaseComponent.cs

    /// <summary>
    /// 组件的抽象父类
    /// </summary>
    public abstract class BaseComponent
    {
        /// <summary>
        /// 定义一个登录的抽象方法
        /// 其他方法,这边省略
        /// </summary>
        public abstract string Login();
    }
    

    LoginComponent.cs

    /// <summary>
    /// 默认登录组件(账号+密码)
    /// 其他方法省略
    /// 友情提醒一下,抽象类里面可以定义非抽象方法
    /// </summary>
    public class LoginComponent : BaseComponent
    {
        public override string Login()
        {
            return "默认账号密码登录";
        }
    }
    

    默认调用:

    static void Main(string[] args)
    {
        var obj = new LoginComponent();
        var str = obj.Login();
        Console.WriteLine(str);
    }
    

    如果这时候平台需要添加微信第三方登录,怎么办?一般都是用继承来解决,其实还可以通过灵活的装饰器来解决:(好处可以自己体会)

    先定义一个通用装饰器(不一定针对登录,注册等等只要在BaseComponent中的都能用)

    /// <summary>
    /// 装饰器
    /// </summary>
    public class BaseDecorator : BaseComponent
    {
        protected BaseComponent _component;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="obj">登录组件对象</param>
        protected BaseDecorator(BaseComponent obj)
        {
            this._component = obj;
        }
        public override string Login()
        {
            string str = string.Empty;
            if (_component != null) str = _component.Login();
            return str;
        }
    }
    

    现在根据需求添加微信登录:(符合开放封闭原则)

    /// <summary>
    /// 默认登录组件(账号+密码)
    /// 其他方法省略
    /// </summary>
    public class WeChatLoginDecorator : BaseDecorator
    {
        public WeChatLoginDecorator(BaseComponent obj) : base(obj)
        {
        }
        /// <summary>
        /// 添加微信第三方登录
        /// </summary>
        /// <returns></returns>
        public string WeChatLogin()
        {
            return "add WeChatLogin";
        }
    }
    

    调用:(原有系统该怎么用就怎么用,新系统可以使用装饰器来添加新功能)

    static void Main(string[] args)
    {
        #region 登录模块V2
        // 实例化登录装饰器
        var loginDecorator = new WeChatLoginDecorator(new LoginComponent());
        // 原有的登录方法
        var str1 = loginDecorator.Login();
        // 现在新增的登录方法
        var str2 = loginDecorator.WeChatLogin();
        Console.WriteLine($"{str1}\n{str2}");
        #endregion
    }
    

    结果:

    默认账号密码登录
    add WeChatLogin

    如果再加入QQ和新浪登录的功能就再添加一个V3版本的装饰器,继承当时V2版本的登录即可(版本迭代特别方便)

    /// <summary>
    /// 默认登录组件(账号+密码)
    /// 其他方法省略
    /// </summary>
    public class LoginDecoratorV3 : WeChatLoginDecorator
    {
        public LoginDecoratorV3(BaseComponent obj) : base(obj)
        {
        }
    
        /// <summary>
        /// 添加QQ登录
        /// </summary>
        /// <returns></returns>
        public string QQLogin()
        {
            return "add QQLogin";
        }
    
        /// <summary>
        /// 添加新浪登录
        /// </summary>
        /// <returns></returns>
        public string SinaLogin()
        {
            return "add SinaLogin";
        }
    }
    

    调用:

    static void Main(string[] args)
    {
        #region 登录模块V3
        // 实例化登录装饰器
        var loginDecoratorV3 = new LoginDecoratorV3(new LoginComponent());
        // 原有的登录方法
        var v1 = loginDecoratorV3.Login();
        // 第二个版本迭代中的微信登录
        var v2 = loginDecoratorV3.WeChatLogin();
        // 新增的QQ和新浪登录
        var qqLogin = loginDecoratorV3.QQLogin();
        var sinaLogin = loginDecoratorV3.SinaLogin();
        Console.WriteLine($"{v1}\n{v2}\n{qqLogin}\n{sinaLogin}");
        #endregion
    }
    

    结果:

    默认账号密码登录
    add WeChatLogin
    add QQLogin
    add SinaLogin

    其实还有很多用处,比如原有系统缓存这块当时考虑不到,现在并发来了,已经上线了,原有代码又不太敢大幅度修改,这时候装饰器就很方便的给某些功能添加点缓存、测试、日记等等系列功能(AOP里面很多这种概念)

    实际场景说的已经很明白了,其他的自己摸索一下吧

     

    2.Python装饰器

    那Python怎么实现装饰器呢?小胖问道。

    小明屁颠屁颠的跑过去说道,通过闭包咯~(闭包如果忘了,可以回顾一下)

    2.1.装饰器引入

    来看一个应用场景,以前老版本系统因为并发比较小,没考虑到缓存

    def get_data():
        print("直接数据库读取数据")
    
    def main():
        get_data()
    
    if __name__ == '__main__':
        main()
    

    在不修改原有代码的前提下咋办?我们参照C#和Java写下如下代码:

    In [1]:
    # 添加一个闭包
    def cache(func):
        def decorator():
            print("给功能添加了缓存")
            if True:
                pass
            else:
                func()# 如果缓存失效则读取数据库获取新的数据
        return decorator
    
    def get_data():
        print("直接数据库读取数据")
    
    def main():
        f1 = cache(get_data)
        f1()
        print(type(f1))
    
    if __name__ == '__main__':
        main()
    
     
    给功能添加了缓存
    <class 'function'>
    
     

    小张问道:“怎么也这么麻烦啊,C#的那个我就有点晕了,怎么Python也这样啊?”f1 = cache(get_data) f1()

    小明哈哈一笑道:“人生苦短,我用Python~这句话可不是随便说着玩的,来来来,看看Python的语法糖”:

    In [2]:
    def cache(func):
        def wrapper():
            print("给功能添加了缓存")
            if True:
                pass
            else:
                func()  # 如果缓存失效则读取数据库获取新的数据
        return wrapper
    
    @cache
    def get_data():
        print("直接数据库读取数据")
    
    def main():
        get_data()
    
    if __name__ == '__main__':
        main()
    
     
    给功能添加了缓存
    
     

    其实

    @cache
    def get_data()
    

    等价于

    # 把f1改成函数名字罢了。可以这么理解:get_data重写指向了一个新函数
    get_data = cache(get_data)
    

    小张同学瞪了瞪眼睛,努力回想着以前的知识点,然后脱口而出:“这不是我们之前讲的属性装饰器吗?而且好方便啊,这完全符合开放封闭原则啊!“

    class Student(object):
        def __init__(self, name, age):
            # 一般需要用到的属性都直接放在__init__里面了
            self.name = name
            self.age = age
    
        @property
        def name(self):
            return self.__name
    
        @name.setter
        def name(self, name):
            self.__name = name
    
        @property
        def age(self):
            return self.__age
    
        @age.setter
        def age(self, age):
            if age > 0:
                self.__age = age
            else:
                print("age must > 0")
    
        def show(self):
            print("name:%s,age:%s" % (self.name, self.age))
    

    小明也愣了愣,说道:”也对哦,你不说我都忘了,我们学习面向对象三大特性的时候经常用呢,怪不得这么熟悉呢“

    随后又嘀咕了一句:”我怎么不知道开放封闭原则...“

    小张嘲笑道:”这你都不知道?对扩展开放,对已经实现的代码封闭嘛~“

    In [3]:
    # 需要注意一点
    def cache(func):
        print("装饰器开始装饰")
        def wrapper():
                print("给功能添加了缓存")
                if True:
                    pass
                else:
                    func()  # 如果缓存失效则读取数据库获取新的数据
        return wrapper
    
    @cache # 当你写这个的时候,装饰器就开始装饰了,闭包里面的功能是你调用的时候执行
    def get_data():
        print("直接数据库读取数据")
    
     
    装饰器开始装饰
    
     

    2.2.多个装饰器

    小明赶紧扯开话题,”咳咳,我们接下来我们接着讲装饰器"

    小张问道,像上面那个第三方登录的案例,想加多少加多少,Python怎么办呢?

    小明一笑而过~

    现在项目又升级了,要求每次调用都要打印一下日记信息,方便以后纠错,小张先用自己的理解打下了这段代码,然后像小明请教:

    In [4]:
    def log(func):
        def wrapper():
            print("输出日记信息")
            cache(func)()
        return wrapper
        
    def cache(func):
        def wrapper():
            print("给功能添加了缓存")
            if True:
                pass
            else:
                func()  # 如果缓存失效则读取数据库获取新的数据
        return wrapper
    
    @log
    def get_data():
        print("直接数据库读取数据")
    
    def main():
        get_data()
    
    if __name__ == '__main__':
        main()
    
     
    输出日记信息
    给功能添加了缓存
    
     

    小明刚美滋滋的喝着口口可乐呢,看到代码后一不小心喷了小张一脸,然后尴尬的说道:“Python又不是只能装饰一个装饰器,来看看我的代码”:

    In [5]:
    def log(func):
        print("开始装饰Log模块")
        def wrapper():
            print("输出日记信息")
            func()
        return wrapper
    
    def cache(func):
        print("开始装饰Cache模块")
        def wrapper():
            print("给功能添加了缓存")
            if True:
                pass
            else:
                func()  # 如果缓存失效则读取数据库获取新的数据
        return wrapper
    
    @log
    @cache
    def get_data():
        print("直接数据库读取数据")
    
    def main():
        get_data()
    
    if __name__ == '__main__':
        main()
    
     
    开始装饰Cache模块
    开始装饰Log模块
    输出日记信息
    给功能添加了缓存
    
     

    小张耐心的看完了代码,然后说道:“咦,我发现它装饰的时候是从下往上装饰,执行的时候是从上往下啊?执行的时候程序本来就是从上往下,按照道理应该是从上往下装饰啊?”

    小明神秘的说道:“你猜啊~你可以把它理解为寄快递和拆快递

    小张兴奋的跳起来了:

    装饰器:装快递,先包装里面的物品,然后再加个盒子。执行装饰器:拆快递,先拆外面的包装再拆里面的~简直妙不可言啊

    2.3.带参装饰器

    小明继续讲述他哥哥的血泪历史:

    需求时刻在变,系统使用范围更广了,为了不砸场子,抠门的老板决定每年多花5W在技术研发的硬件支持上,这下子技术部老开心了,想想以前前端只能通过CDN和HTTP请求来缓存,后端只能依赖页面缓存和数据库缓存就心塞,于是赶紧新增加一台Redis的云服务器。为了以后和现在缓存代码得变一变了,需要支持指定的缓存数据库:(如果不是维护别人搞的老项目,你这么玩保证被打死,开发的时候老老实实的工厂模式搞起)

    带参数的装饰器一般都是用来记录logo日记比较多,自己开发知道debug模式,生产指定except模式等等

    In [6]:
    # 可以理解为,在原来的外面套了一层
    def cache(cache_name):
        def decorator(func):
            def wrapper():
                if cache_name == "redis":
                    print("给功能添加了Redis缓存")
                elif cache_name == "memcache":
                    pass
                else:
                    func()
            return wrapper
        return decorator
    
    @cache("redis") # 相当于是:get_data = cache(”redis“)(get_data)
    def get_data():
        print("直接数据库读取数据")
    
    def main():
        get_data()
    
    if __name__ == '__main__':
        main()
    
     
    给功能添加了Redis缓存
    
     

    小张很高兴,然后练了练手,然后质问小明道:”你是不是藏了一手!“

    代码如下:

    In [7]:
    def log(func):
        def inner():
            print("%s log_info..." % func.__name__)
            func()
        return inner
    
    @log
    def login_in(name_str, pass_str):
        return "欢迎登录:%s" % (name_str)
    
    @log
    def login_out():
        print("已经退出登录")
    
    @log
    def get_data(id):
        print("%s:data xxx" % id)
    
    def main():
        login_out()
        get_data(1)
        print(login_in("小明", "xxx"))
    
    if __name__ == '__main__':
        main()
    
     
    login_out log_info...
    已经退出登录
    
     
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-7-dcb695819107> in <module>()
         23 
         24 if __name__ == '__main__':
    ---> 25main()
    
    <ipython-input-7-dcb695819107> in main()
         19 def main():
         20     login_out()
    ---> 21get_data(1)
         22     print(login_in("小明", "xxx"))
         23 
    
    TypeError: inner() takes 0 positional arguments but 1 was given
     

    2.4.通用装饰器

    小明尴尬的笑了下,然后赶紧倾囊相授,定义一个通用的装饰器:(传参数就在外面套一层)

    def log(func):
        @functools.wraps(func) # 签名下面一个案例就会讲
        def wrapper(*args,**kv):
            """可变参 + 关键字参数"""
            print("%s log_info..." % func.__name__)
            return func(*args,**kv)
        return wrapper
    

    这部分知识如果忘记了可以回顾一下,我们之前讲的函数系列:https://www.cnblogs.com/dotnetcrazy/p/9175950.html

    In [8]:
    def log(func):
        # 可变参 + 关键字参数
        def wrapper(*args,**kv):
            print("%s log_info..." % func.__name__)
            return func(*args,**kv)
        return wrapper
    
    @log
    def login_in(name_str, pass_str):
        return "欢迎登录:%s" % (name_str)
    
    @log
    def login_out():
        print("已经退出登录")
    
    @log
    def get_data(id):
        print("%s:data xxx" % id)
    
    def main():
        login_out()
        get_data(1)
        print(login_in("小明", "xxx"))
    
    if __name__ == '__main__':
        main()
    
     
    login_out log_info...
    已经退出登录
    get_data log_info...
    1:data xxx
    login_in log_info...
    欢迎登录:小明
    
     

    2.5.扩展补充

    其实装饰器可以做很多事情,比如强制类型检测等,先看几个扩展:

    1.装饰器方法签名的问题

    成也装饰器,败也装饰器,来个案例看看,装饰器装饰的函数真的就对原函数没点影响?

    In [9]:
    # 添加一个闭包
    def cache(func):
        def wrapper(*args,**kv):
            if True:
                print("缓存尚未失效:直接返回缓存数据")
            else:
                func(*args,**kv)
        return wrapper
    
    def get_data(id):
        """获取数据"""
        print("通过%d直接数据库读取数据"%id)
    
    In [10]:
    # 进行装饰
    get_data = cache(get_data)
    # 调用原有名称的函数
    get_data(110)
    # 发现虽然函数调用时候的名字没有变
    # 但是内部签名却变成了闭包里面的函数名了
    print(get_data.__name__)
    print(get_data.__doc__)
    # print(get_data.__annotations__)
    
     
    缓存尚未失效:直接返回缓存数据
    wrapper
    None
    
     

    发现虽然函数调用时候的名字没有变,但是内部签名却变成了闭包里面的函数名了!

    玩过逆向的人都知道,像你修改了apk文件,它看似一样,但签名就变了,得再处理才可能绕过原来的一些自效验的验证措施

    这边一样的道理,你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字、文档字符串、注解和参数签名都丢失了。

    functools里面的wraps就帮我们干了这个事情(之前讲模块的时候引入了functools,随后讲衍生的时候用了里面的偏函数,这边讲讲wraps

    上面代码改改:

    In [11]:
    from functools import wraps
    
    # 添加一个闭包
    def cache(func):
        @wraps(func)
        def wrapper(*args,**kv):
            if True:
                print("缓存尚未失效:直接返回缓存数据")
            else:
                func(*args,**kv)
        return wrapper
    
    def get_data(id):
        """获取数据"""
        print("通过%d直接数据库读取数据"%id)
    
    # 进行装饰
    get_data = cache(get_data)
    # 调用原有名称的函数
    get_data(110)
    # 签名已然一致
    print(get_data.__name__)
    print(get_data.__doc__)
    # print(get_data.__annotations__)
    
     
    缓存尚未失效:直接返回缓存数据
    get_data
    获取数据
    
     

    另外:@wraps有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数,eg:

    In [12]:
    get_data.__wrapped__(100)
    
     
    通过100直接数据库读取数据
    
     

    2.装饰器传参的扩展(可传可不传)

    In [13]:
    import logging
    from functools import wraps, partial
    
    def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
        if func is None:
            return partial(logged, level=level, name=name, message=message)
    
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    
    @logged
    def add(x, y):
        return x + y
    
    @logged(level=logging.CRITICAL, name='测试')
    def get_data():
        print("读数据ing")
    
    def main():
        add(1,2)
        get_data()
    
    if __name__ == '__main__':
        main()
    
     
    get_data
    
     
    读数据ing
    
     

    3.类中定义装饰器

    在类里面定义装饰器很简单,但是你首先要确认它的使用方式。比如到底是作为一个实例方法还是类方法:(别忘记写selfcls

    In [14]:
    from functools import wraps
    
    class A(object):
        # 实例方法
        def decorator1(self, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print("实例方法装饰器")
                return func(*args, **kwargs)
            return wrapper
    
        # 类方法
        @classmethod
        def decorator2(cls, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print("类方法装饰器")
                return func(*args, **kwargs)
            return wrapper
    
    In [15]:
    # 装饰方式不一样
    a = A()
    @a.decorator1 # 实例方法调用
    def test1():
        pass
    
    @A.decorator2 # 类方法调用
    def test2():
        pass
    
    In [16]:
    # 调用一下
    test1()
    test2()
    
     
    实例方法装饰器
    类方法装饰器
    
     

    在涉及到继承的时候。 例如,假设你想让在A中定义的装饰器作用在子类B中。你需要像下面这样写:

    class B(A):
        @A.decorator2
        def test(self):
            pass
    

    也就是说,装饰器要被定义成类方法并且你必须显式的使用父类名去调用它。

    你不能使用 @B.decorator2 ,因为在方法定义时,这个类B还没有被创建。

    4.类装饰器

    看这个之前,我们先来看看怎么把类当函数一样使用:

    In [17]:
    class A(object):
        def __call__(self):
            print("让类对象能像函数一样调用的~魔法方法")
    
    def main():
        a = A()
        a()
    
    if __name__ == '__main__':
        main()
    
     
    让类对象能像函数一样调用的~魔法方法
    
     

    重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

    装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。

    在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的

    用类来实现呢?我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果

    我们拿之前说的通用装饰器的例子继续说:(一般来说装饰器就定义成方法,然后给需要添加的函数或者类方法添加就基本够用了

    In [18]:
    from functools import wraps
    
    class Log(object):
        def __init__(self, func):
            wraps(func)(self)  # @wraps(func) 访问不到,所以用这种方式
            self.__func = func
    
        def __call__(self, *args, **kvs):
            print("%s log_info..." % self.__func.__name__)
            return self.__func(*args, **kvs)
    
    @Log # 相当于 login_in=Log(login_in)
    def login_in(name_str, pass_str):
        return "欢迎登录:%s" % (name_str)
    
    @Log
    def login_out():
        print("已经退出登录")
    
    @Log
    def get_data(id):
        print("%s:data xxx" % id)
    
    def main():
        login_out()
        get_data(1)
        print(login_in("小明", "xxx"))
    
    if __name__ == '__main__':
        main()
    
     
    login_out log_info...
    已经退出登录
    get_data log_info...
    1:data xxx
    login_in log_info...
    欢迎登录:小明
    
     

    对类进行装饰的测试:(以上一个案例为例)

    装饰实例方法的时候容易出现莫名其妙的错误,所以一般加上get方法(反射系列的稍后会讲)

    eg:show() missing 1 required positional argument: 'self'

    完整写法:(你可以去除__get__试试)

    In [19]:
    import types
    from functools import wraps
    
    class Log(object):
        def __init__(self, func):
            wraps(func)(self)  # @wraps(func) 访问不到,所以用这种方式
            self.__func = func
    
        def __call__(self, *args, **kvs):
            print("%s log_info..." % self.__func.__name__)
            return self.__func(*args, **kvs)
    
        # 装饰实例方法的时候容易出现莫名其妙的错误,所以一般加上get方法
        # eg:show() missing 1 required positional argument: 'self'
        def __get__(self, instance, cls):
            if instance is None:
                return self
            else:
                return types.MethodType(self, instance)
    
    class LoginComponent(object):
        def __init__(self, name):
            self.__name = name
    
        @Log
        def show(self):
            """实例方法"""
            print("欢迎你:%s" % self.__name)
    
        @classmethod
        @Log  # 写在下面("从下往上装,从上往下拆")
        def login_in(cls):
            """类方法"""
            print("登录ing")
    
        @staticmethod
        @Log
        def show_news():
            """静态方法"""
            print("今天的新闻是...")
    
    def main():
        LoginComponent.login_in()
        LoginComponent.show_news()
        login = LoginComponent("小明")
        login.show()
    
    if __name__ == '__main__':
        main()
    
     
    login_in log_info...
    登录ing
    show_news log_info...
    今天的新闻是...
    show log_info...
    欢迎你:小明
    
     

    更多的可以参考如下链接:

    详解Python装饰器

    将装饰器定义为类

    Python中的__init__()和__call__()函数

    python中装饰器的使用和类装饰器在类中方法的使用


    3.面向对象系列扩展

    看着小张准备回家换衣服了,小明有点失落,又有点孤单,于是说道:“逗逼张,你还要听吗?我准备讲类相关的知识了,这些可是我课后自学的哦~”

    小张转了转身,一念间就留了下来~

    3.1.动态添加属性和方法

    类相关的基础知识如果忘记,可以查看之前的文章:https://www.cnblogs.com/dotnetcrazy/p/9202988.html

    当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性:

    In [20]:
    # 定义一个类
    class Person(object):
        def __init__(self, name):
            self.__name = name
    
        def show(self):
            print("中国欢迎你~", self.__name)
    
    In [21]:
    xiaoming = Person("小明")
    xiaoming.show() # 正常调用
    
    # 给实例动态添加一个属性
    xiaoming.age = 22
    print(xiaoming.age)
    
     
    中国欢迎你~ 小明
    22
    
    In [22]:
    # 其他实例是访问不到这个属性的
    xiaopan = Person("小潘")
    xiaopan.age
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-22-efcec543fe3f> in <module>()
          1 # 其他实例是访问不到这个属性的
          2 xiaopan = Person("小潘")
    ----> 3xiaopan.age
    
    AttributeError: 'Person' object has no attribute 'age'
     

    "这个以前不是讲过嘛,动态添加属性,还有没有啥我不知道的知识了?"小张不屑的说道.

    小明故作悬疑,抬头看着小张说道:“你知道怎么添加类属性吗?知道怎么添加方法吗?”

    小张沉默不语,默默的看着小明讲课,随后心里想到:“这个坑货,话也不说全,还好现在是夏天,不然我早着凉了”

    要想添加其他实例都可以访问的属性,可以给类添加一个类属性,用法和上面差不多,只是把对象改成类。

    来看个案例:

    In [23]:
    # 给类动态添加一个属性
    Person.age = 22
    
    xiaoming = Person("小明")
    print(xiaoming.age)
    
    xiaopan = Person("小潘")
    print(xiaopan.age)
    
     
    22
    22
    
     
    1.添加实例方法

    小张,还记得讲装饰器的时候有这么一句代码吗?

    types.MethodType(self, instance)
    

    小张:"记得当时用类装饰实例方法的时候出现了问题,然后才加的?"

    对头,以上面Person类为例,来一起看怎么动态添加方法

    In [24]:
    import types
    
    class Person(object):
        def __init__(self, name):
            self.__name = name
    
    def test(self):
        print("测试一下")
    
    def main():
        xiaoming = Person("小明")
        xiaoming.test = types.MethodType(test, xiaoming)
        xiaoming.test()
    
    if __name__ == '__main__':
        main()
    
     
    测试一下
    
     

    你可以思考一下,为什么必须通过types.MethodType才行?(提示:self

    注意一点,当你在新方法中调用类中私有方法时就会出问题

    其实这个本质相当于通过实例对象调用里面公开属性

    In [25]:
    import types
    
    class Person(object):
        def __init__(self, name):
            self.__name = name
    
    # 一样的代码,只是调用了私有属性
    def test(self):
        print("中国欢迎你,%s" % self.__name)
    
    def main():
        xiaoming = Person("小明")
        xiaoming.test = types.MethodType(test, xiaoming)
        xiaoming.test() # 其实这个本质相当于通过实例对象调用里面公开属性
    
    if __name__ == '__main__':
        main()
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-25-2bf92b457fc8> in <module>()
         15 
         16 if __name__ == '__main__':
    ---> 17main()
    
    <ipython-input-25-2bf92b457fc8> in main()
         12     xiaoming = Person("小明")
         13     xiaoming.test = types.MethodType(test, xiaoming)
    ---> 14xiaoming.test() # 其实这个本质相当于通过实例对象调用里面公开属性
         15 
         16 if __name__ == '__main__':
    
    <ipython-input-25-2bf92b457fc8> in test(self)
          7 # 一样的代码,只是调用了私有属性
          8 def test(self):
    ----> 9print("中国欢迎你,%s" % self.__name)
         10 
         11 def main():
    
    AttributeError: 'Person' object has no attribute '__name'
     
    2.添加类方法和静态方法

    看一下类方法和静态方法的案例:

    In [26]:
    # 类方法案例
    class Person(object):
        pass
    
    @classmethod
    def test(cls):
        print(cls)
    
    def main():
        Person.test = test # 直接赋值即可
        xiaoming = Person()
        xiaoming.test()
    
    if __name__ == '__main__':
        main()
    
     
    <class '__main__.Person'>
    
    In [27]:
    # 静态方法案例
    class Person(object):
        pass
    
    @staticmethod
    def test():
        print("test")
    
    def main():
        Person.test = test
        xiaoming = Person()
        xiaoming.test()
    
    if __name__ == '__main__':
        main()
    
     
    test
    
     

    3.2.__slots__

    这下小张急了,怎么又和上次讲得模块一样,无法无天了啊?有没有办法限制一下呢?

    小明哈哈一笑,娓娓道来:

    1.指定实例属性

    如果我们想要限制实例的属性怎么办?比如,只允许添加指定属性和方法?

    In [28]:
    # 定义一个类
    class Person(object):
        __slots__ = ("age", "name")  # 用tuple定义允许绑定的属性名称
    
        def show(self):
            print("中国欢迎你~")
    
    xiaoming = Person()
    xiaoming.name="小明"
    xiaoming.age = 22
    xiaoming.qq = 110 # 不允许的属性就添加不了
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-28-2f9e13cdc435> in <module>()
          9 xiaoming.name="小明"
         10 xiaoming.age = 22
    ---> 11xiaoming.qq = 110 # 不允许的属性就添加不了
    
    AttributeError: 'Person' object has no attribute 'qq'
     

    说几个测试后的结论:

    1. __slots__不一定是元组,你用列表也一样(推荐和官方一致)
    2. 如果你定义的私有属性不在元组内,也会报错
    In [29]:
    # 列表定义__slots__不会报错
    class Person(object):
        __slots__ = ["__name", "age", "gender"]
    
        def __init__(self, name):
            self.__name = name
    
        def show(self):
            print("中国欢迎你~")
    
    
    xiaoming = Person("小明")
    xiaoming.age = 22
    xiaoming.gender = "男"
    
    In [30]:
    # 注意一个东西,如果你定义的私有属性不在元组内,也会报错
    class Person(object):
        __slots__ = ("age")
    
        def __init__(self, name):
            self.__name = name
    
        def show(self):
            print("中国欢迎你~")
    
    xiaoming = Person("小明")
    xiaoming.age = 22
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-30-0b85ac9c18af> in <module>()
          9         print("中国欢迎你~")
         10 
    ---> 11xiaoming = Person("小明")
         12 xiaoming.age = 22
    
    <ipython-input-30-0b85ac9c18af> in __init__(self, name)
          4 
          5     def __init__(self, name):
    ----> 6self.__name = name
          7 
          8     def show(self):
    
    AttributeError: 'Person' object has no attribute '_Person__name'
     

    2.指定实例“方法”

    这个限制对实例方法一样有效,再复习下给实例对象添加方法:

    import types
    
    class Person(object):
        __slots__ = ("__name", "age", "test")
    
        def __init__(self, name):
            self.__name = name
        def show(self):
            print("中国欢迎你~")
    
    def test(self):
        print("test")
    
    xiaoming = Person("小明")
    xiaoming.age = 22
    xiaoming.test = types.MethodType(test, xiaoming)
    xiaoming.test()
    

    看看被限制之后:(Python中定义的方法相当于定义了一个属性,然后指向了定义的函数)

    In [31]:
    # 这个限制对实例方法一样有效
    import types
    
    class Person(object):
        __slots__ = ("__name", "age")
    
        def __init__(self, name):
            self.__name = name
        def show(self):
            print("中国欢迎你~")
    
    def test(self):
        print("test")
    
    xiaoming = Person("小明")
    xiaoming.age = 22
    xiaoming.test = types.MethodType(test, xiaoming)
    xiaoming.test()
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-31-d1bab7d57b40> in <module>()
         15 xiaoming = Person("小明")
         16 xiaoming.age = 22
    ---> 17xiaoming.test = types.MethodType(test, xiaoming)
         18 xiaoming.test()
    
    AttributeError: 'Person' object has no attribute 'test'
     

    小明讲得唾沫横飞,然后故作神秘的和小张说道:

    3.扩展:看看对类有啥影响

    测试结果:不影响

    In [32]:
    # 类方法案例
    class Person(object):
        __slots__ = ("name", "age")
        pass
    
    @classmethod
    def test1(cls):
        print("类方法")
    
    @staticmethod
    def test2():
        print("静态方法")
    
    def main():
        Person.qq = 110
        Person.test1 = test1  # 类方法
        Person.test2 = test2  # 静态方法
        xiaoming = Person()
        print(xiaoming.qq)
        xiaoming.test1()
        xiaoming.test2()
    
    if __name__ == '__main__':
        main()
    
     
    110
    类方法
    静态方法
    
     

    扩展:__getattribute__属性拦截器

    有点像C#里面的Attribute标签,AOP其实就是这类的思想

    更多可以参考如下链接:

    动态添加属性和方法

    反射以及魔法方法相关内容

    制定类以及魔法方法相关内容

    In [33]:
    class Person(object):
        def __init__(self, name):
            self.__name = name
    
        def show(self):
            print(self.__name)
    
        # 属性拦截器里面不要调用self.方法 or self.属性
        def __getattribute__(self, obj):
            print("obj:", obj)
            if obj == "show":
                print("do something")
            elif obj == "_Person__name":  # 注意这种情况,如果你想要访问私有属性,需要写出类名.属性
                print("Log info : xxx")
            return object.__getattribute__(self, obj) # 你重写了属性、方法获取的方式,别忘记返回对应的属性
    
    def main():
        p = Person("小明")
        p.show()
    
    if __name__ == '__main__':
        main()
    
     
    obj: show
    do something
    obj: _Person__name
    Log info : xxx
    小明
    
     

    3.3.元类系列

    小张一脸懵逼的看着小明,然后说道:”就没有类似于C#里面的反射机制?“

    小明背着手,缓缓的绕着小张走了一圈,那眼神仿佛是在看一件工艺艺术品一样,然后随口说道:

    3.3.1.type动态创建类

    前面我们讲过了type()函数可以查看一个类型或变量的类型。比如说:

    Person是一个class,它的类型就是type,而xiaoming是一个实例,它的类型就是class Person

    看个例子:

    In [34]:
    class Person(object):
        pass
    
    def main():
        xiaoming = Person()
        print(type(Person))
        print(type(xiaoming))
    
    if __name__ == '__main__':
        main()
    
     
    <class 'type'>
    <class '__main__.Person'>
    
     

    其实还可以通过 __class__ 来查看创建对象的是谁:

    In [35]:
    class Person(object):
        pass
    
    def main():
        xiaoming = Person()
        print(Person.__class__)
        print(xiaoming.__class__)
    
    if __name__ == '__main__':
        main()
    
     
    <class 'type'>
    <class '__main__.Person'>
    
     

    小张被小明看的发毛,然后赶紧扯开话题说道:”怎么都是type?难道这个就是接下来准备讲的内容?“

    小明点头说道:”是滴~“

    我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数

    那怎么创建呢?以上面那个案例为摸版,来个案例:

    类名 = type("类名", 父类们的Tuple, Dict)

    In [36]:
    def main():
        Person = type("Person", (object, ), {})
        xiaoming = Person()
        print(Person.__class__)
        print(xiaoming.__class__)
    
    if __name__ == '__main__':
        main()
    
     
    <class 'type'>
    <class '__main__.Person'>
    
     

    小张感叹道:”Python的这种‘反射’太过简单了吧,我直接都可以写案例了“

    比如,实现如下内容:

    In [37]:
    class Person(object):
        def show(self):
            print("父类方法:mmd")
    
    class Student(Person):
        gender = "男"
    
        def __init__(self, name):
            self.__name = name
    
        def eat(self):
            print("%s实例方法:大口吃饭" % self.__name)
    
        @classmethod
        def run(cls):
            print("我是类方法:跑着上课")
    
        @staticmethod
        def sleep():
            print("静态方法:晚安")
    
    def main():
        print(Student.gender)
        xiaoming = Student("小明")
        xiaoming.show()
        xiaoming.eat()
        xiaoming.run()
        xiaoming.sleep()
    
    if __name__ == '__main__':
        main()
    
     
    男
    父类方法:mmd
    小明实例方法:大口吃饭
    我是类方法:跑着上课
    静态方法:晚安
    
    In [38]:
    def show(self):
        print("父类方法:mmd")
    
    def __init__(self, name):
        self.__name = name
    
    def eat(self):
        print("%s实例方法:大口吃饭" % self.__name)
    
    @classmethod
    def run(cls):
        print("我是类方法:跑着上课")
    
    @staticmethod
    def sleep():
        print("静态方法:晚安")
    
    def main():
        Person = type("Person", (object, ), {"show": show})
        Student = type(
            "Student", (Person, ), {
                "gender": "男",
                "__init__": __init__,
                "eat": eat,
                "run": run,
                "sleep": sleep
            })
        
        print(Student.gender)
        xiaoming = Student("小明")
        xiaoming.show()
        xiaoming.eat()
        xiaoming.run()
        xiaoming.sleep()
    
    if __name__ == '__main__':
        main()
    
     
    男
    父类方法:mmd
    小明实例方法:大口吃饭
    我是类方法:跑着上课
    静态方法:晚安
    
     

    3.3.2.元类~metaclass

    小明又仔细端详了小张一次,然后继续讲到:

    当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

    但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

    总的流程就是:先定义metaclass,再创建类,最后创建实例

    type就是Python在背后用来创建所有类的那个元类


    小张有点恐慌的看了一眼小明,然后继续听讲

    Python2是看看类里面有没有__metaclass__这个属性,有就通过它指向的函数或者方法来创建类

    Python3简化了一下,在Class定义的时候就可以指定了,eg:class Person(object, metaclass=type)

    In [39]:
    # 这三个参数其实就是type对应的三个参数
    def create_class(name, bases, attrs):
        attrs["name"] = "小明"
        return type(name, bases, attrs)
    
    class Person(object, metaclass=create_class):
        pass
    
    def main():
        # 判断一个对象有没有某个属性
        hasattr(Person, "name")
        print(Person.name)
    
    if __name__ == '__main__':
        main()
    
     
    小明
    
     

    其实原类有点像刚刚讲的属性拦截器了,大概流程如下:

    1. 拦截类的创建
    2. 修改类
    3. 返回修改之后的类

    来一个正规化的写法,eg:给MyList添加一个add方法(list是append方法,别混淆了)

    In [40]:
    # metaclass是类的模板,所以必须从`type`类型派生:
    class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
            attrs['add'] = lambda self, value: self.append(value)
            return type.__new__(cls, name, bases, attrs)
    
    class MyList(list, metaclass=ListMetaclass):
        pass
    
    def main():
        mylist = MyList()
        mylist.add("mmd")
        print(mylist)
    
    if __name__ == '__main__':
        main()
    
     
    ['mmd']
    
     

    元类一般ORM用的比较多(映射),如果你不编写ORM框架的话,基本上用不到

    这方面可以参考这篇文章:尝试编写一个ORM框架


    3.4.枚举类

    枚举类经常用,代码也很简单,继承一下Enum类就可以了,unique用来防止重复的(重复会提示你)

    In [41]:
    from enum import Enum, unique
    
    @unique
    class StatusEnum(Enum):
        # 待审核状态(0)默认
        Pendding = 0
    
        # 审核已通过(1)正常
        Normal = 1
    
        # 审核不通过(2)未删
        Cancel = 2
    
        # 已删除状态(99)假删
        Delete = 99
    
    # 调用:
    StatusEnum.Delete
    
    Out[41]:
    <StatusEnum.Delete: 99>
    In [42]:
    # 重复项测试
    from enum import Enum, unique
    
    @unique
    class StatusEnum(Enum):
        # 审核已通过(1)正常
        Normal = 1
        # 已删除状态(99)假删
        Delete = 99
        # 重复测试
        Test = 99
    
    # 调用:
    StatusEnum.Delete
    
     
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-42-6a79f45cf1d9> in <module>()
          3 
          4 @unique
    ----> 5class StatusEnum(Enum):
          6     # 审核已通过(1)正常
          7     Normal = 1
    
    ~/anaconda3/lib/python3.6/enum.py in unique(enumeration)
        832                 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
        833         raise ValueError('duplicate values found in %r: %s' %
    --> 834                 (enumeration, alias_details))
        835     return enumeration
        836 
    
    ValueError: duplicate values found in <enum 'StatusEnum'>: Test -> Delete
     

    3.5.垃圾回收

    之前写的文章里面有提到过,可以简单回顾一下:(可变类型和不可变类型 引用数的引入)

    其实程序员基本上关注,实在要关注的就是怎么显示回收:

    import gc # 需要导入gc模块
    
    print(gc.collect()) # 显式垃圾回收
    print(gc.garbage)   # 看回收了哪些
    

    先看看之前讲可变类型和不可变类型说的一句话:

    Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同的变量,实际上都指向同个内存地址

    看个案例:

    In [2]:
    a=10
    b=10
    c=10
    print(id(a))
    print(id(b))
    print(id(c))
    
     
    94747627400000
    94747627400000
    94747627400000
    
     

    上面的ID都一样,那较短到底是多短呢?

    先贴一下逆天的测试结果:(不要在编辑器里面测试,建议进入官方的python3交互模式,用vscode测试的结果不准)

    1. 小整数[-5,257)共用对象,常驻内存不在这个范围内的均创建一个新的对象
    2. 单个字符共用对象,常驻内存
    3. 字符串:
      • 英文单词,共用对象,引用计数为0就删除
      • 英文中有空格(英文句子、词组),不共用,引用计数为0的时候就删掉
      • 中文字符串:不共用,引用计数为0的时候就删掉

    其实也很好理解,第一个范围是程序员经常用的范围,字符串系列嘛就更正常了,老外肯定不管中文什么的,要是中国人发明的可以常用汉字常驻内存^_^ 然后一篇文章里面单词出现频率肯定比词组和句子高,所以都能解释通了

    来简单验证一下:

    图片

    In [1]:
    # 257的时候就取不到了,这时候都是不同的ID
    # 这个就是所谓的大整数了(每一个大整数,均创建一个新的对象)
    a=257
    b=257
    c=257
    print(id(a))
    print(id(b))
    print(id(c))
    
     
    140602139583728
    140602139584112
    140602139583792
    
    In [2]:
    # 单个字符
    d='a'
    e='a'
    f='a'
    print(id(d))
    print(id(e))
    print(id(f))
    
     
    140602366927792
    140602366927792
    140602366927792
    
    In [3]:
    # 英文单词
    str1 = "dog"
    str2 = "dog"
    str3 = "dog"
    print(id(str1))
    print(id(str2))
    print(id(str3))
    
     
    140602139175376
    140602139175376
    140602139175376
    
    In [4]:
    # 英文中有空格(句子,词组)
    str4 = "big dog"
    str5 = "big dog"
    str6 = "big dog"
    print(id(str4))
    print(id(str5))
    print(id(str6))
    
     
    140602139174984
    140602139174816
    140602139175544
    
    In [5]:
    # 不共享对象,计数为0就删除
    str7 = "明"
    str8 = "明"
    str9 = "明"
    print(id(str7))
    print(id(str8))
    print(id(str9))
    
     
    140602139296272
    140602139296352
    140602139296192
    
    In [6]:
    str10 = "小明"
    str11 = "小明"
    str12 = "小明"
    print(id(str10))
    print(id(str11))
    print(id(str12))
    
     
    140602139147320
    140602139146616
    140602139146792
    
    In [7]:
    str13 = "小 明"
    str14 = "小 明"
    str15 = "小 明"
    print(id(str10))
    print(id(str11))
    print(id(str12))
    
     
    140602139147320
    140602139146616
    140602139146792
    
     

    再说说查看引用的时候注意一下:sys.getrefcount的参数object也会占1个引用计数(sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1)

    这个是Python主要的一种垃圾回收方式(计数引用),看看源码:

    参考链接:https://github.com/python/cpython/blob/master/Include/object.h

    // 实际上没有任何东西被声明为PyObject,但是每个指向Python对象的指针都可以强制转换为PyObject(这是手工制作的继承)
    typedef struct _object {
        _PyObject_HEAD_EXTRA
        Py_ssize_t ob_refcnt; /* 引用计数 */
        struct _typeobject *ob_type;
    } PyObject;
    
    // 类似地,每个指向可变大小Python对象的指针都可以转换为PyVarObject
    typedef struct {
        PyObject ob_base;
        Py_ssize_t ob_size; /* 可变变量引用计数 */
    } PyVarObject;
    
    In [1]:
    # 引用计数
    import sys
    
    
    # 定义一个临时类
    class Temp(object):
        def __del__(self):
            print("你被干掉了")
    
    
    t1 = Temp()
    print(sys.getrefcount(t1))  #(结果比实际引用大1)【object也会占1个引用计数】
    
    t2 = t1
    print(sys.getrefcount(t1))
    print(sys.getrefcount(t2))
    
    del t1
    print(sys.getrefcount(t2))
    # sys.getrefcount(t1)#被删掉自然没有了
    
    del t2
    print("-" * 10)
    
     
    2
    3
    3
    2
    你被干掉了
    ----------
    
     

    引用计数基本上可以解决大部分的问题,用起来比较简单,而且实时性比较高(一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时)

    但对于循环引用,或者对于像双向链表这样的方式,就算引用对象删除了,它的计数还是1(相互引用嘛)

    所以Python解释器用了另一种方法解决这个:

    分代回收(隔代回收)

    Python解释器设置了某些阀值,当达到了阀值就进行第一轮回收(大概是有循环引用的-1,然后看两个相互引用的对象现在的引用结果是不是都是0,如果都是0说明没有外部引用,那就是垃圾了),不是垃圾的移到第二个链表里面,当第二轮达到阀值的时候,进行第二轮回收(一轮的也回收下),不是垃圾的"老对象"移到第三个链表里面,当第三轮达到阀值的时候统统回收一波)

    gc.get_count() 获取当前自动执行垃圾回收的计数器

    gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率(可以自己设置)默认是:(700, 10, 10)

    来看看阀值情况:

    In [1]:
    import gc
    
    print(gc.get_count())
    print(gc.get_threshold())
    
     
    (234, 8, 1)
    (700, 10, 10)
    
     

    比如你新创建了1000个对象,才释放20个,就已经超过默认的700阀值,Python第一代检测就上场了(以此类推)

    一般能活到最后的都不大可能是垃圾了,比如配置文件之类的,基本上不太改动的(越老越成精嘛)

    小张若有所思的说道:

    1. 当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
    2. 当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
    3. 当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

    小明左右端详小张,终于忍不住说出了那句话:“小张,你能不能..."

    话没说完就被小张打断了:”我是男的,不搞基!就是搞基也只喜欢我们班的培哥!“

    小明吃惊的说道:”你想啥呢?我只是看你骨骼清奇,想要收你为徒罢了...“

    (完)


    经典引用:(参考1 参考2

    在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。
    
    从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。
    
    刚刚说到的例子中,我们以一个不是很常见的情况结尾:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。
    
    这就是为什么Python要引入Generational GC算法的原因!
    
    Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表。
    
    因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
    
    随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
    
    通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。

    参考链接:

    Python垃圾回收机制详解

    经典之~画说 Ruby 与 Python 垃圾回收

    使用 GC、Objgraph 干掉 Python 内存泄露与循环引用

    转载于:https://www.cnblogs.com/dotnetcrazy/p/9333792.html

    展开全文
  • gcd(欧几里得算法辗转相除法): ...之前一直愚蠢地以为辗转相除法输进去时 a 要大于 b ,现在发现事实上如果 a 小于 b,那第一次就会先交换 a b。 1 #include<stdio.h> 2 #define l...

    gcd(欧几里得算法辗转相除法):

    gcd ( a , b )= d ;

    即 d = gcd ( a , b ) = gcd ( b , a mod b );以此式进行递归即可。

    之前一直愚蠢地以为辗转相除法输进去时 a 要大于 b ,现在发现事实上如果 a 小于 b,那第一次就会先交换 a 与 b。

     1 #include<stdio.h>
     2 #define ll long long 
     3 
     4 ll gcd(ll a,ll b){
     5     return b==0?a:gcd(b,a%b);
     6 }
     7 
     8 int main(){
     9     ll a,b;
    10     while(scanf("%lld%lld",&a,&b)!=EOF){
    11         printf("%lld\n",gcd(a,b));
    12     //    printf("%lld\n",a>b?gcd(a,b):gcd(b,a));
    13     }
    14     return 0;
    15 }
    View Code

    在原基础上改成循环之后的GCD:

     

    1 ll gcd(ll a,ll b){
    2     for(;a>0&&b>0;a>b?a%=b:b%=a);
    3     return a+b;
    4 }

    这个代码是针对非负数范围的,但除此之外我还纠结了很久,在非负数的范围内(long long内)与普通递归的gcd对拍并没有发现问题,一直做题的时候也没有发现有什么问题,但是刷到一题UVA10325,经测试数据中没有给0或负数,但是用这个WA用递归版的AC,并不知道为什么。

    所以……还是库函数/递归保平安吧

     

    拓展欧几里得:

    当 gcd ( a , b )= d 时,求绝对值和最小的 x , y 使得 x * a + y * b = d ;

    d = gcd ( a , b ) = gcd ( b , a mod b );

    设:

    x1 * a + y1 * b = d ;        ①

    x2 * b + y2 * ( a mod b ) = d ;   ②

    因为 a mod b = a - ( a / b )* b;  ③(除法为整除)

    将③代入①整理得:

    y2 * a + ( x2 - ( a / b ) * y2 ) * b = d; ④

    由①和④整理得:

    x1 = y2 ;

    y1 = x2 - ( a / b ) * y2;

    将此结论代入递归函数既得。

     1 #include<stdio.h>
     2 #define ll long long
     3 
     4 void gcd(ll a,ll b,ll& d,ll& x,ll& y){
     5     if(!b){d=a;x=1;y=0;}
     6     else {gcd(b,a%b,d,y,x);y-=x*(a/b);}
     7 }
     8 
     9 int main(){
    10     ll a,b,d,x,y;
    11     while(scanf("%lld%lld",&a,&b)!=EOF){
    12         gcd(a,b,d,x,y);
    13         printf("%lld*%lld+%lld*%lld=%lld\n",a,x,b,y,d);
    14     }
    15     return 0;
    16 }
    View Code

     

    拓展欧几里得求逆元:

    当 a 与 b 互素时有 gcd ( a , b ) = 1 ;

    即得: a * x + b * y = 1;

    a * x ≡ 1 ( mod b );

    由于 a 与 b 互素,同余式两边可以同除 a ,得:

    1 * x ≡ 1 / a (mod b);

    因此 x 是 a mod b 的逆元;

     

    递归方法计算:

     1 #include<stdio.h>
     2 #define ll long long
     3 
     4 ll gcd(ll a,ll b,ll &d,ll& x,ll& y){
     5     if(!b){
     6         d=a;
     7         x=1;
     8         y=0;
     9         return x;
    10     }
    11     else{
    12         gcd(b,a%b,d,y,x);
    13         y-=x*(a/b);
    14     }
    15     return x;
    16 }
    17 
    18 int main(){
    19     ll a,b,d,x,y;
    20     while(scanf("%lld%lld",&a,&b)!=EOF){
    21         x=gcd(a,b,d,x,y);
    22         printf("a:%lld->x:%lld\n",a,x);
    23 //        printf("a:%lld->x:%lld\nb:%lld->y:%lld\n",a,x,b,y);
    24     }
    25     return 0;
    26 }
    View Code

     

    循环方法计算:

     1 #include<stdio.h>
     2 
     3 int main(){
     4     int a,b;
     5     while(scanf("%d%d",&a,&b)!=EOF){
     6         int x=1,y=0,t;
     7 
     8         {
     9             if(a!=1&&b!=1){
    10                 int b0=b,q;
    11                 while(a>1){
    12                     q=a/b0;
    13                     t=b0;b0=a%b0;a=t;
    14                     t=y;y=x-q*y;x=t;
    15                 }
    16                 if(x<0)x+=b;
    17             }
    18         }
    19 
    20         printf("a:%d->x:%d\n",a,x);
    21     }
    22     return 0;
    23 }
    View Code

     

     1 ll gcd(ll a,ll b){
     2     if(a!=1&&b!=1){
     3         int b0=b,q,t,x=1,y=0;
     4         while(a>1){
     5             q=a/b0;
     6             t=b0;b0=a%b0;a=t;
     7             t=y;y=x-q*y;x=t;
     8         }
     9         if(x<0)x+=b;
    10     }
    11     return x;
    12 }

     

    转载于:https://www.cnblogs.com/cenariusxz/p/4323872.html

    展开全文
  • <scripttype="text/JavaScript">//jQuery插件的写法(需要传入操作对象...(function($){//PI_TestPlugIn为插件名称,也是插件的操作对象//为了不会其它插件名重复,这里我使用PlugIn的缩写PI_来定义插件对象...

    <script type="text/JavaScript">
        
            //jQuery插件的写法(需要传入操作对象)
            ;(function($)
            {
                //PI_TestPlugIn为插件名称,也是插件的操作对象
                //为了不会与其它插件名重复,这里我使用PlugIn的缩写PI_来定义插件对象前缀
                $.fn.PI_TestPlugIn=   
                {
                    //该插件的基本信息
                    Info:{
                        Name: "TestPlugIn",
                        Ver: "1.0.0.0",
                        Corp: "Lzhdim",
                        Author: "lzhdim",
                        Date: "2010-01-01 08:00:00",
                        Copyright: "Copyright @ 2000-2010 Lzhdim Technology Software All Rights Reserved",
                        License: "GPL"
                    },
                    //具有参数的函数对象,这里参数是一个对象,具有属性
                    FunctionWithParams:function(paramObj)
                    {
                        //使用参数,是否使用默认值
                        var params = paramObj ? paramObj : new function(){
                                                               param1= "1";
                                                               param2= "2";
                                                           };
                        
                                                       
                        return this.Info.Name + ".FunctionWithParamObject";
                    },
                    //具有参数的函数对象,这里参数是一个变量
                    FunctionWithParam:function(varparam)
                    {
                        //使用参数,是否使用默认值
                        var param = varparam ? varparam : null;
                                                       
                                                       
                        return this.Info.Name + ".FunctionWithParam";
                    },
                    //不具有参数的函数对象
                    FunctionWithOutParam:function()
                    {
                        return  this.Info.Name + ".FunctionWithOutParam";
                    }
                };
            })(jQuery);
            
            
            //jQuery拓展函数的写法(不需要传入操作对象),即API函数
            ;(function($)
            {
                $.extend({
                    //PIF_TestExtendFunction为拓展函数的操作对象
                    //为了不会与其它插件名重复,这里我使用PlugIn的缩写PI_来定义插件对象前缀
                    PIF_TestExtendFunction:
                    {
                        //该拓展函数的基本信息
                        Info:{
                            Name: "TestExtendFunction",
                            Ver: "1.0.0.0",
                            Corp: "Lzhdim",
                            Author: "lzhdim",
                            Date: "2010-01-01 08:00:00",
                            Copyright: "Copyright @ 2000-2010 Lzhdim Technology Software All Rights Reserved",
                            License: "GPL"
                        },
                        //具有参数的函数对象
                        FunctionWithParams:function(paramObj)
                        {
                            //使用参数,是否使用默认值
                            var params = paramObj ? paramObj : {
                                                               param1: "1",
                                                               param2: "2"
                                                           };
                                                       
                                                       
                            return this.Info.Name + ".FunctionWithParamObect";
                        },
                        //具有参数的函数对象,这里参数是一个变量
                        FunctionWithParam: function (varparam) {
                            //使用参数,是否使用默认值
                            var param = varparam ? varparam : null;


                            return this.Info.Name + ".FunctionWithParam";
                        },
                        //不具有参数的函数对象
                        FunctionWithOutParam:function()
                        {
                            return this.Info.Name + ".FunctionWithOutParam";
                        }
                    }
                });
            })(jQuery);



            $(function () 
            {
                //测试插件
                var params = 
                {
                    param1: "3",
                    param2: "4"
                };
                
                alert($(this).PI_TestPlugIn.FunctionWithParams(params));

                alert($.PIF_TestExtendFunction.FunctionWithOutParam());
            });
            
            
        </script>

    转载于:https://www.cnblogs.com/cxxjohnson/p/5589335.html

    展开全文
  • 目录数组的扩展forEach遍历map方法Array.form()Array.of()copyWithin()find()和findIndex()keys(),values(),entries()配合for...of循环includes()includes方法indexOf()方法的区别对象扩展方法Object.assign()浅...

    数组的扩展

    forEach遍历

    //第一个参数为函数
        //函数的第一个参数 是遍历的当前元素
        //函数的第二个参数 当前元素的下标
        //函数的第三个元素 数组本身。
    //第二个参数为:函数中this指向。
    arr.forEach(function(item,index,arr){
    	
    },obj);
    

    小练习:将数组中的参数写入页面中(重点是看forEach中第二个参数(函数中this指向。)

    <body>
        <ul>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </body>
    <script>
        var arr = [
            "千与千寻",
            "肖申克的救赎",
            "谁先爱上他的",
            "哈尔的移动城堡",
            "海上钢琴师"
        ]
        var lis = document.querySelectorAll("li")
        arr.forEach(function (item, index, self) {
           //  console.log("电影名:"+item+",下标为:"+index);
            this[index].innerHTML = item;
        },lis);
    </script>
    

    map方法

    map 映射含义,用法与forEach基本类似。

    可以在函数中定义返回值。返回是一个数组。

    var arr = [
        {name:"千与千寻",score:"8.6"},
        {name:"肖申克的救赎",score:"9.6"},
        {name:"谁先爱上他的",score:"8.3"},
        {name:"哈尔的移动城堡",score:"8.9"},
        {name:"海上钢琴师",score:"9.0"}
    ]
    var arr2 = arr.map(function(item){
        return item.score;
         //等价于    return item["score"];
    });
    console.log(arr2);// 能够得到这里面的每一个值 "8.6", "9.6", "8.3", "8.9", "9.0"
    

    数组 中 map方法与forEach遍历的比较,

    <script>
        var array = ["wo", "shi", "zhao"];
        array.forEach(function (item, index, arr) {
           console.log(item + "  " + index);
            //console.log(arr);
        });
        array.map(function (item, index, arr) {
            console.log(item + "  " + index);
            //console.log(arr);
        });
    </script>
    

    在这里插入图片描述在这里插入图片描述

    Array.form()

    Array.form方法可以将类似数组的对象转为真正的数组。比如:DOM获取的伪数组,arguments对象。

    <body>
        <ul>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </body>
     <script>
        let lis = document.querySelectorAll("li");
        console.log( lis);//输出的是li 的一个伪数组
        var arr = Array.from(lis);//转换为真正数组
        console.log( arr);//这时获取到的li是一个真正的数组
        //用typeof检测时发现都为对象 是因为数组也是对象
    </script> 
    

    小练习:将arguments的参数(是一个伪数组)排序

    <!-- <script>
       //获取arguments的参数是一个伪数组
        function sort() {
            var args = Array.from(arguments);//转换成了一个真正的数组,
            return args.sort();//这样就能使用数组的排序方法sort()
        }
        console.log(sort(6, 3, 5));
    </script> -->
    

    这里在对比一下

    <!-- <script>
        let likeArr = {
            length: 3,//这个设置要有,否则输出一个空数组
            "0": "hello",
            "1": "word",
            "2": "你好"
        }
        console.log(likeArr);//{0: "hello", 1: "word", 2: "你好", length: 3}
        let arry = Array.from(likeArr);
        console.log(arry);
        //上下这两种输出都是一个真正的数组,结构样式一样
        var arr = ["hello", "word", "你好"];
        console.log(arr);
    </script> -->
    

    在这里插入图片描述

    Array.of()

    Array.of方法将一组值,转换为数组。

    <script>
        let arr = Array.of( 3);
        console.log(arr);//这时只有一个参数,就输出数组,数组里只有一个参数
    
        let arr2 = new Array(3);//传统数组方法当数组里面只有一个参数时,输出的不是这个数组的内容,而是设置这个数组的长度
        console.log(arr2);//输出  length: 3
        //========================================================
        let arr3 = new Array(1,2,3);
        console.log(arr3);//输出数组  [1, 2, 3]
       //Array.of方法
        let arr4 =  Array.of(1,2,3);
        console.log(arr4);//输出数组  [1, 2, 3]
    </script>
    

    copyWithin()

    将指定位置的内容复制到其他位置(复制会覆盖原有的内容)然后返回当前数组。

    <script>
        //target:必需,从该位置进行覆盖
    //start:可选,从该位置开始读取数据,默认为0。如果为负数,表示倒数。
    //end:可选 到该位置结束读取数据,默认为数组的长度。如果为负数,表示倒数、
      var arr=[0,1,2,3,4]
        // var arr1= arr.copyWithin(1);
        // console.log(arr1);//[0, 0, 1, 2, 3]
      //  var arr2= arr.copyWithin(1,3);
      //  console.log(arr2);//[0, 3, 4, 3, 4]
        // var arr3= arr.copyWithin(1,2,3);
        // console.log(arr3);//[0, 2, 2, 3, 4]
        // var arr4= arr.copyWithin(1,-2,4);
        // console.log(arr4);[0, 3, 2, 3, 4]
        这几种情况不能同时释放观察,因为同时释放原来的数组值就被改变,复制会覆盖原有的内容
    </script>
    

    find()和findIndex()

    find方法,找到第一个符合条件的数组元素。
    arr.find(function(item,indedx,arr){
    //条件判断
    })
    小练习:(下面没有输出-9,因为它不是第一次出现)

    <script>
        var arr = [1, 6, -10, -9].find(function (item) {
            return item < 0;
        })
        // ES6箭头函数写法
      //  var arr = [1, 6, -10, -9].find(item => item < 0)
        console.log(arr);//-10
    </script>
    

    findIndex,找到第一个符合条件的数组元素的下标。

    var arr = [1, 6, -10, -9].findIndex(item => item < 0)
        console.log(arr);//2
    

    keys(),values(),entries()配合for…of循环

    这三个方法主要用于数组的遍历。配合for…of循环。

    keys():对键名的遍历

    values():对键值的遍历

    entries():对键值对的遍历。

    格式:
    //item 指遍历的当前元素
    //arr 指遍历的数组
    for (const item of arr) {
    	console.log(item);
    }
    
    var arr = ["a","b"]
    for (const item of arr.keys()) {
    	console.log(item);
    }
    // 0 1
    for (const item of arr.values()) {
    	console.log(item);
    }
    // a b
    for (const item of arr.entries()) {
    	console.log(item);
    }
    //[0,"a"] [1,"b"]
    

    includes()

    该方法可以判断数组是否包含指定的值。
    格式

    //arr:数组
    //value:判断该数组中是否包含该值
    //target:从数组的指定位置开始查找。
    arr.includes(value,target)
    

    返回值:布尔值 true为包含,false 不包含

    console.log( [1,2,3].includes(2)); //true
    console.log( [1,2,3].includes("2")); //false
    console.log([1,2,3].includes(2,1));//true
    console.log([1,2,3].includes(2,2));//false
    

    小练习:

    var arr = [1,2,3]
    var index = arr.indexOf(2);//利用的是indexOf()方法找不到值返回-1
    if(index !== -1){
    	console.log("该值已包含");
    }else{
    	console.log("该值不包含");
    }
    显示该值已包含
    var arr = [1,2,3]
    var index = arr.indexOf(2);
    

    includes方法与indexOf()方法的区别

    1. indexOf返回的是下标。 includes返回的是布尔值
    2. indexOf用来判断是否包含不够语义化,不够直观。
    3. indexOf 对于NaN是有误判的。
     <script>这样写就没有了误判
        var arr = [1, 2, 3,NaN]
    //    var index = arr.indexOf(NaN);//该值不包含(可是明明有)
        var index = arr.includes(NaN);
        if (index) {
            console.log("该值已包含");
        } else {
            console.log("该值不包含");
        }
    </script> 
    

    对象的拓展

    一、对象的属性和方法简介写法:

    ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

    属性名和属性值一样时,可以只写一个(语法自动补足)

    // 方法:es6
    const o = {
      method() {
        return "Hello!";
      }
    };
    
    // 等同于原来
    
    const o = {
      method: function() {
        return "Hello!";
      }
    };
    

    打印对象用简洁表示法需要加大括号

    let user = {
      name: 'test'
    };
    
    let foo = {
      bar: 'baz'
    };
    
    console.log(user, foo)//原来写法
    // {name: "test"} {bar: "baz"}
    console.log({user, foo})//es6写法
    // {user: {name: "test"}, foo: {bar: "baz"}}
    

    在这里插入图片描述
    更多es6详细接地气记载访问
    https://www.cnblogs.com/marvintang1001/p/11809665.html

    1.对象属性的简洁表示法

    ES6可以在对象的大括号中,直接写入变量 和函数,作为对象的属性和方法。如果属性名与变量名相同,则可以简写,只写属性名即可。

    var name = "赫本"
    var obj = {name}
    //相当于
    var obj = {name:name}
    console.log(obj.name); //赫本
    
    <script>
        function fn(a, b) {
            return {
                a,
                b
            };
            /*等价于   return {
                   a: a,
                   b: b
               };*/
        }
        var obj = fn("hello", "word");
        console.log(obj); //{a: "hello", b: "word"}
    </script>
    

    方法的简写

    在对象中,可以对方法进行简写

    <script>
        let obj = {
                fn() {
                    console.log("今晚九点,海岛集合。");
                }
            }
            //相等于
            /* let obj = {
                 fn: function() {
                     console.log("今晚九点,海岛集合。");
                 }
             }*/
        obj.fn();
    </script>
    

    简写只适用于普通函数方法,构造函数不能简写。

    2.属性名表达式

    拆分的功能,如果是对象,则将对象中的所有属性遍历出来,如果是数组,则将所有的元素遍历出来

    <!-- <script>
        let a = {
            x: 1,
            y: 2
        }
        let b = {
            ...a
        }
        console.log(b);// {x: 1, y: 2}
    </script> -->
    <script>
        let arr = [10, 20, 30];
        let c = {
            ...arr
        }
        console.log(c["0"]);//10
    </script>
    

    对象的扩展方法

    Object.assign()用于对对象的合并

    格式
    //第一个参数后面的所有参数,合并到第一个参数中。
    Object.assign(target,obj1,obj2)
    
    <script>
        第一个参数后面的所有参数,合并到第一个参数中。
        const target = { name: '奥黛丽·赫本' }
        const obj1 = { age: '20' }
        Object.assign(target, obj1);
        console.log(target);//输出一个改变后的对象    {age: "20" name: "奥黛丽·赫本"}
    </script>
    

    同名属性

    <script>
         let target = { name: '奥黛丽·赫本' }
        let obj1 = {name: '张三' }
        Object.assign(target, obj1);
        console.log(target);//{name: '张三'}
        //遇到同名属性,则后面的参数对象中的属性会将前面的属性覆盖。
     </script>
    

    用在数组上

    用在数组上时,会将数组视为对象。

    <script>
        //Object.assign() 用在数组上时,会将数组视为对象。
        var arr1 = [10, 20, 30];
       /*等价于 var  arr1 = {
            "0":"a",
            "1":"b",
            "2":30
        }*/
         var arr2 = ["a", "b"];
        /*等价于    var arr2 = {
            0: "a",
            1: "b"
        }*/
        Object.assign(arr1, arr2);
        console.log(arr1);//a,b,30
    
    </script>
    

    浅拷贝

    对基本类型变量,浅拷贝是对值的拷贝,(会影响被拷贝的值)没有深拷贝的概念。
    对引用类型来说,浅拷贝是对对象地址的拷贝,并没有开辟新的栈,复制的结果是两个对象指向同一个栈的地址,彼此之间的操作会互相影响,

    <script>
        //#### 浅拷贝只拷贝复合数据的内存地址。拷贝后的变量仍然指向原本的对象。
        const target = {
            name: "奥黛丽·赫本",
            sex: "女",
            son: {
                name: "卢卡·多蒂",
                age: "40"
            }
        };
        let obj1 = {
            name: "奥黛丽",
            age: "20",
            son: {
                name: "卢卡·多蒂",
                age: "40"
            }
        }
        Object.assign(target, obj1);
        console.log(target);
        /*合并后的对象输出的为 {age: "20"
            name: "奥黛丽"
             sex: "女"
             son: {name: "卢卡·多蒂", age: 39}}
         我们看到加上来的对象替换了原来名字相同的对象    这里age: 就该为40,只不过 这样的演示不了(下面的40是对的)  
             */
        console.log(target.son.age);//40   
        /*  因为这个我们明白,原来的值没有被改变,他们得到的这个对象是这个对象的内存地址,
        改变的也是也是这个对象的内存地址的内容,没有真正的改变这个对象的内容
        */
        obj1.son.age = 39;//作用  改变里面的值
        console.log(target.son.age);//39       值改变了,是对内存地址的改变,不是对堆中数据的改变
    </script>
    

    深拷贝

    深拷贝则是开辟新的栈,拷贝后的对象与原来的对象是完全隔离,互不影响

    assign对象(合并对象)扩展方法就是浅拷贝。也就是说,如果被合并的对象中有一个属性为对象,那么target对象拷贝得到的是这个对象的内存地址。

    <script>
        let obj1 = {
            name: 'tom',
            age: 18,
            sex: '男',
            children: {
                name: '张三',
                age: 200
            }
        }
        let obj2 = {
                sleep: '睡觉'
            }
            //  合并对象
            // let res = Object.assign(target, [obj1, obj2, ....]);
        let res = Object.assign({}, obj1)
       // console.log(res);
        console.log(obj1, obj2);
        obj1.name = 'zhangsan';
        console.log(obj1, obj2);
    </script>
    

    合并对象 也能够实现对象的拷贝 深拷贝是狭义(只能拷贝一层); 不能实现真正的深拷贝(对象嵌套的形式)

    js 的深拷贝 经典的算法 递归算法实现

    总要有一个出口

    function deepObj(obj) {
                let newObj = {}
                for (let key in obj) {
                    if (obj[key] instanceof Object) {
                        newObj[key] = deepObj(obj[key])
                    } else {
                        newObj[key] = obj[key]
                    }
                }
                return newObj
            }
    
            let res = deepObj(obj1)
            console.log(res); //age变为3400
            obj1.children.name = '2348758475';
            res.children.age = 3400;
            console.log(res); //age变为3400
            console.log(obj1); age仍为200
    

    这样就完成了深拷贝,拷贝后的对象与原来的对象是完全隔离

    展开全文
  • ES6 总结(二)          内容大纲:           1、数值扩展 ... 3、Math 对象的扩展 1、数值扩展 ... Number.EPSILON ,属性表示 1 大于 1 的最小浮点
  • 在通信类软件中,常常会数据库打交道。由于需求变化,或者是程序优化升级等原因,对数据表字段进行扩展是常有的事情。这就要求开发者必须熟练掌握对数据表字段进行扩展的操作流程。     ...
  • 数据类型扩展与变量

    2020-10-22 17:09:27
    数据类型扩展 import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer; import javax.lang.model.element.NestingKind; public class Demo03 { public static void main(String[] args) { //整数拓展...
  • 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,在Kotlin中就用到了扩展函数或者属性。委托模式已经证明...
  • 更新ing 欧几里得 欧几里得算法: 又叫辗转相除法, 用于计算两个数的最大公约数, 在算最大公约数, 以及扩展欧几里得算法中都会用到 ...拓展欧几里得 求的是ax + by = gcd(a, b) 的值 int gcd(int a,int b){ return...
  • 如何设计一款USB TYPEC转HDMI和VGA双转多功能他拓展坞或者扩展器方案设计,台湾安格目前出了两款可以设计这类拓展坞的方案产品AG9320和AG9321,AG9320主要使用于单转音视频数据转换器方案,AG9321除了可以转换音视频...
  • 1.ES6 随记(1)-- let const 2.ES6 随记(2)--解构赋值 3.ES6 随记(3.1)--字符串的拓展 4.ES6 随记(3.2)-- 正则的拓展 & 数值的拓展 5.ES6 随记(3.3)-- 数组的拓展 4. 拓展 e. 函数的拓展 ...
  • 卡尔曼滤波与拓展卡尔曼滤波

    千次阅读 2018-02-19 23:27:46
    从上个世纪卡尔曼滤波理论被提出,卡尔曼滤波在控制论信息论的连接上做出了卓越的贡献。为了得出准确的下一时刻状态真值,我们常常使用卡尔曼滤波、扩展卡尔曼滤波、无迹卡尔曼滤波、粒子滤波等等方法,这些方法在...
  • 拓展欧几里得算法能够在辗转相除的同时求出一组 x , y x,y x , y 使得 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) a x + b y = g c d ( a , b ) 。 怎么在辗转相除的时候求呢?我们要看 g c d ( b , a % b ) gcd...
  • 基于Steiner最小树模型建立裂纹拓展与能量传播的关系,指出裂纹的贯通拓展是沿着耗能最小而最快释放能量的路径。并建立相似模型试验中的真实裂隙数学裂隙模型,将问题定义为约束型的Steiner树问题。覆岩破坏形式遵循...
  • 扩展运算符怎么运算 两者不同在哪里 1、赋值运算符包括"基本赋值运算符"和“扩展赋值运算符" 赋值含义: public class OperatorTest04{ public static void main(String[]args){ //赋值运算符“="右边优先级比较高...
  • php有两个Memcached的拓展程序,MemcachedMemcache,首先,他们都是Memcached缓存服务器的客户端,他们二者的区别在于1,...2,Memcached是基于原生的c的libmemcached的拓展,只有面向对象的接口, Memcache 扩展
  •   Kotlin 提供了拓展具有新功能的类的能力,而不必从类继承或使用设计模式(如Decorator),这是通过称为拓展的特殊声明来实现的。例如,你可以从无法修改的第三方库位类编写新函数。这些函可以按照通常的方式调用...
  • Kotlin 中类的扩展方法并不是在原类的内部进行拓展,通过反编译为Java代码,可以发现,其原理是使用装饰模式,对源类实例的操作和包装,其实际相当于我们在 Java中定义的工具类方法,并且该工具类方法是使用调用者为...
  • 在Visual Studio中的扩展与更新中安装插件后,显示[禁用],重新安装后仍然不能使用,但是VS默认安装的扩展却可以正常使用。 这里需要注意下方显示“当前不允许加载每用户扩展”,点击“启用每用户拓展的加载”...
  • 首先,什么是 Spring 的扩展点呢?IOC 容器对 bean 的管理从 BeanDefinition 的注册到实例化再到依赖注入,有自己一套默认的流程,而 Spring 预留的扩展点允许某些 bean 干预这个预设的流程。 下面来看一个例子: ...
  • 本文的主要目的是针对类的加载的一个扩展,主要讲讲类拓展和分类的底层实现原理 【面试题】类扩展 分类 的区别 1、category 类别、分类 专门用来给类添加新的方法 不能给类添加成员属性,添加了成员属性,也...
  • 本文主要是详解iOS类别(Category)与扩展(Extension,匿名类别),请登陆疯狂软件教育官网。Category在iOS开发中使用非常频繁。尤其是在为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最大...
  • KMP 模式匹配算法与扩展 KMP 相关 前言 本文导览 KMP 算法简称 Knuth-Morris-Pratt Algorithm (中译:克努特——莫里斯——普拉特操作), 主要用于解决字符串中的模式匹配问题,本文将介绍以下几点 字符串的模式...
  • weui 给我们提供一套很棒的组件,它微信原生视觉体验一致。很多业务场景下使用weui的组件开发速度快同时用户体验也很赞! 在使用扩展组件tabs 过程中遇到许多的问题,耽误不少时间~~真是要命呀。温馨提示:认真看...
  • oc 类别 扩展

    千次阅读 2016-05-05 17:04:07
    尤其是在为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最大程度的体现了Objective-C的动态语言特性。 #import @interface NSObject (Category) - (void)myMethod; @end ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,084
精华内容 433
关键字:

扩展与拓展