# !/usr/bin/env python
# -*- coding:utf-8 -*-

# 1、__len__


class A:

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

    def __len__(self):
        print('你使用了 len(对象) ,我被触发啦')
        print(self.__dict__)
        return len(self.__dict__)


a = A('albert', 17)
# 使用内置函数  len(类的实例对象) 就会触发对象属于的类中的 __len__方法,
print(len(a))


# 2、__str__

class B:

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

    def __str__(self):
        print('你使用了 str(对象) ,我被触发啦')
        return f'姓名:{},年龄:{self.age}'


b1 = B('don', 19)
b2 = B('hugu', 5)
# 如果直接打印对象,得到的是对象的内存地址。
# 但是如果类中实现了 __str__方法,那么在打印 对象 时,就会触发 __str__, 默认输出__str__方法的返回值
# str(类的对象) 也会触发 对象属于的类的 __str__方法
print(b1)  # 姓名:don,年龄:19
print(b2)

str(b1)

# 3、__repr__:与__str__类似,但是优先级比 __str__低

print('我的名字:%r' % ('albert'))  # 我的名字:'albert'
print('我的名字:%s' % ('albert'))  # 我的名字:albert

print(repr('Hello'))  # 'Hello'


class C:

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

    def __repr__(self):
        print('你使用了 repr(对象) ,我被触发啦')
        return f'姓名:{},年龄:{self.age}'


c1 = C('don12', 109)
# 如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出__repr__方法的返回值。
print(c1)  # 姓名:don12,年龄:109


# 4、__call__:对象() 自动触发对象从属于类(父类)的__call__方法
class D:
    def __init__(self, name):
         = name

    def __call__(self, *args, **kwargs):
        print('你使用了 对象() ,我被触发啦')


d = D('hello')
# 对象后面加括号,触发__call__执行。
d()


# 5、__eq__: 两个对象进行比较的时候,会触发__eq__
class E(object):
    def __init__(self):
        self.a = 1
        self.b = 2

    def __eq__(self, obj):  # self 接收第一个对象x, obj 接收第二个对象y
        if self.a == obj.a and self.b == obj.b:
            return True


x = E()
y = E()
print(x == y)

# 6、__del__:析构方法
"""
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,
因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
"""

# 7、__new__: 创造并返回一个新对象   (new一个对象  构造方法)

class F(object):

    def __init__(self):

        self.x = 1
        print('in init function')

    def __new__(cls, *args, **kwargs):
        print('in new function')
        return object.__new__(F)  # 把派生类F传给基类object的 __new__方法,得到一个对象空间内存地址,地址返回给对象,__init__方法给对象空间封装属性(__init__方法里面,不写return的原因)


# 对象是object类的__new__方法 产生了一个对象.
f = F()

"""
类名(),执行如下两步:
1. 先触发 object的__new__方法,此方法在内存中开辟一个对象空间.
2. 执行__init__方法,给对象封装属性.
"""
print(f)


# Python 设计模式: 单例模式 (重中之重)
# 单例模式:一个类只允许实例化一个对象。(类实例化出的对象都在一个内存空间里)

# 一般 实例化出的对象地址,不一样
class E:
    pass


obj = E()
print(obj)  # <__main__.F object at 0x00000115A2867820>
obj1 = E()
print(obj1)   # <__main__.E object at 0x00000115A28677C0>
obj2 = E()
print(obj2)   # <__main__.E object at 0x00000115A2867790>


# 重写父类__new__方法,实现单例模式
class F:
    # 定义一个私有变量,用来存 对象的内存空间地址
    __instance = None

    def __init__(self, name):
         = name

    # 重写了父类的 __new__方法,只开辟出一个内存空间地址
    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = object.__new__(cls)
        return cls.__instance


obj3 = F('albert')
print(obj3)  # <__main__.F object at 0x00000115A2867700>
obj4 = F('dou')
print(obj4)  # <__main__.F object at 0x00000115A2867700>

print()  # dou
print()  # dou

"""
单例模式具体分析:
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
【采用单例模式动机、原因】
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
【单例模式优缺点】
【优点】
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
【缺点】
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
"""

# 8、__item__系列
# __getitem__  __setitem__  __delitem__  对对象做类似于字典的(增删改查)操作时,会触发__item__系列
class Foo:
    def __init__(self, name):
         = name

    def __getitem__(self, item):
        print(item)
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        print('进行了键值对的设置 我被触发了')
        self.__dict__[key]=value

    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)

    # 删除对象属性时,就会触发__delattr__
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)


f1=Foo('albert')

# 对一个 对象 进行类似 字典的取值时,就会触发 __getitem__方法,并把键传给 item
f1['name']

# 对一个 对象 进行类似 字典的设置键和值时,就会触发 __setitem__方法
f1['age1'] = 19

# 对一个 对象 进行类似 字典的删除时,就会触发 __delitem__方法
del f1.age1

# f1['name']='alex'
# print(f1.__dict__)


# 9、__enter__ __exit__:  with 上下文管理

class H:

    def __init__(self, text):
        self.text = text

    def __enter__(self):  # 开启上下文管理器对象时触发此方法
        self.text = self.text + '您来啦'  # 第一步
        print(11111)
        return self  # 这里必须返回self!!! 将实例化的对象返回f1

    def __exit__(self, exc_type, exc_val, exc_tb):  # 执行完上下文管理器对象f1时触发此方法
        print(333)  # 第三步
        self.text = self.text + ',这就走啦'

# 实例化对象的第二种方式: 必须基于 __enter__ 以及 __exit__这个两个方法.
with H('大爷') as f11:
    print(2222)
    print(f11.text)  # 第二步
print(f11.text)  # 第四步


# 10、__iter__


class R:

    def __init__(self,name):
         = name

    def __iter__(self):
        for i in range(10):
            yield i


obj = R('Albert')  # obj 一个可迭代对象
print('__iter__' in dir(obj))
for i in obj:
    print(i)