第十八章:面向对象

面向对象

面向对象的三大特征:继承、封装、多态。

编程思想

1.面向过程编程
过程即流程,面向过程就是按照固定的流程解决问题

需要列举出每一步的流程,并且随着步骤的深入,问题的解决越来越简单
ps:提出问题 然后制定出该问题的解决方案

2.面向对象编程

对象即容器,数据与功能的结合体  (python中一切皆对象),如:list 列表就是一个对象,即可以存数据又存在功能。

上述两种编程思想没有优劣之分 需要结合实际需求而定,实际编程两种思想是彼此交融的 只不过占比不同

类与对象

对象:数据与功能的结合体,对象才是核心
类:多个对象相同数据和功能的结合体,类主要就是为了节省代码

创建类与对象

# 调用类中的方法
class Person:
    count = 'China'  # 创造了一个只要是这个类就一定有的属性,这个是类拥有的 而不是属性
                     # 类属性 静态属性
    def __init__(self, *args):  # 初始化方法
        self.name = args[0]     # 底层相当于:self.__dict__['name'] = name
        self.hp = args[1]
        self.aggr = args[2]
        self.sex = args[3]

    def run(self, n):  # 定义了一个方法,一般情况下必须传 self 参数,且必须写在第一个
        # 后面还可以传其他参数,是自由的
        print('%s跑跑跑,跑了%s公里' % (self.name, n))  ##2.接收参数

    def eat(self, food):    # 自定义方法,默认绑定给对象使用的,谁来调谁就是主人公
        print(f'{self.name} 停下来休息,点了一份 {food}')


per = Person('小强', 100, 3, '不祥')	# 实例化

print(Person.__dict__)  # 可以看到 Person 方法中有 run 这个函数(查看所有属性)
print(Person.run)  # 为一个内存地址,<function Person.run at 0x02AA8610>
Person.run(per, 6)  # 此处为什么是 per,因为在赋值使用 Person 函数后 self 的值返回给了 per

# per.run()        # 另一种简便方法  对象名 + 方法名  与 Person.run(per) 等价
per.run(5)  # 传值,小强跑跑跑,跑了5公里
per.eat('葱油饼')  # 小强 停下来休息,点了一份 葱油饼
print(per.count)  # 获取静态属性, China

动静态方法

class My_class:
    def __init__(self):
        pass

    # 1.类中直接定义函数,默认绑定给对象,类调用有几个参数传几个 对象调用第一个参数就是对象自身
    # 绑定给对象的方法:对象调用自动当做第一个参数传入
    def run(self):
        print('run 函数', self)

    # 2.被 @classmethod 修饰的函数,默认绑定给类
    # 类方法:类调用第一个参数就是类自身,对象也可以调用并且会自动将产生该对象的类当做第一个参数传入
    # 绑定给类的方法:类、对象调用自动将类当做第一个参数传入
    @classmethod
    def func(cls):
        print('func 函数', cls)

    # 3.静态方法:普普通通的函数,无论是类还是对象调用,都必须自己手动传参
    @staticmethod
    def func2(a):
        print('func2 函数', a)


obj = My_class()
# 1.绑定给对象的方法
obj.run()   # run 函数 <__main__.My_class object at 0x011A2E80>
My_class.run('123') # run 函数 123
# 2.绑定给类的方法
My_class.func()  # func 函数 <class '__main__.My_class'>
obj.func()  # func 函数 <class '__main__.My_class'>
# 3.静态方法
My_class.func2(123) # func2 函数 123
obj.func2(123)  # func2 函数 123

继承

继承理论

继承是一种创建新类的方式,在 python 中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类

父类必须在子类上面
  一个类 可以被多个类继承
  一个类 可以继承多个父类 —— python 里

继承表示类与类之间资源的从属关系,类 A 继承类 B 就拥有了类 B 中所有的数据和方法使用权限。

继承的本质

对象:数据与功能的结合体
类(子类):多个对象相同数据和功能的结合体
父类:多个类(子类)相同数据和功能结合体
ps:类与父类本质都是为了节省代码

继承本质应该分为两部分:
抽象:将多个类相同的东西抽出来,形成一个新的类 A。
继承:将多个类去继承新的类 A。

对象查找名字的顺序(非常重要)

对象查找名字的顺序:
1.先从自己的名称空间中查找
2.自己没有再去产生该对象的类中查找
3.如果类中也没有 那么直接报错

即:对象自身 >>> 产生对象的类 >>> 父类(从左往右)

菱形继承

广度优先(最后才会找闭环的定点)

class A:
    def func(self):
        print('类 A')

class B(A):
    def func(self):
        print('类 B')

class C(A):
    def func(self):
        print('类 C')

class D(A):
    def func(self):
        print('类 D')

class E(B):
    def func(self):
        print('类 E')

class F(C):
    def func(self):
        print('类 F')

class G(D):
    def func(self):
        print('类 G')

class S1(E, F, G):
    def func(self):
        print('类 S1')


s = S1
print(s.mro())  # 查看类的继承顺序
# [<class '__main__.S1'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>]

非菱形继承

深度优先(从左往右每条道走完为止)

class B:
    def func(self):
        print('类 B')

class C:
    def func(self):
        print('类 C')

class D:
    def func(self):
        print('类 D')

class E(B):
    def func(self):
        print('类 E')

class F(C):
    def func(self):
        print('类 F')

class G(D):
    def func(self):
        print('类 G')

class S1(E, F, G):
    def func(self):
        print('类 S1')


s = S1
print(s.mro())  # 查看类的继承顺序
# [<class '__main__.S1'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class 'object'>]

经典类与新式类

经典类:不继承 object 或者其子类的类
新式类:继承 object 或者其子类的类
在 python2 中有经典类和新式类
在 python3 中只有新式类(所有类默认都继承 object)

ps:以后在定义类的时候,如果没有其他明确的父类,也可以习惯继承 object,兼容 python2。

派生(重要)

当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己的为准了。

父类中没有的属性 在子类中出现 叫做派生属性
父类中没有的方法 在子类中出现 叫做派生方法

只要是子类的对象调用,子类中有的,一定用子类的,子类中没有才找父类的,如果父类也没有则报错。

如果还想用父类的,单独调用父类的:
方法一:父类名.方法名 需要自己传 self 参数
方法二:super().方法名 不需要自己传 self

正常的代码中,单继承 === 减少了代码的重复。

super 的用法

只在新式类中有,python3 中所有类都是新式类
在类中使用不需要传 self 参数。在类外使用,必须要传 类 和 对象,查找自己所在类的父类

class People:
    def __init__(self, name, sex, job):
        self.name = name
        self.sex = sex
        self.job = job


class Student(People):
    def __init__(self, name, sex, job, subject):
        super().__init__(name, sex, job)    # 不需要传 self 了,对于单继承 super 就可以找到父类了
        self.subject = subject  # 派生属性


s = Student('ysg', '男', 'IT', 'python')
print(s.subject)

派生方法实战演练

以 json 为例,默认可以进行序列化的对象如下:

Python JSON
dict object
list, tuple array
str string
int, float number
True true
False false
None null
import json
import datetime

d = {
    't1': datetime.date.today(),
    't2': datetime.datetime.today(),
    't3': 'jason'
}

# ret = json.dumps(d)
# print(ret)  # Type Error: Object of type date is not JSON serializable


# 我们使用继承方法来进行派生操作

class MyJSONEncoder(json.JSONEncoder):
    def default(self, o):
        '''
        派生 default 方法
        :param o: 接收无法被序列化的数据
        :return: 返回可以被序列化的数据
        '''
        try:
            if isinstance(o, datetime.datetime):    # 判断是否是 datetime 类型,如果是则处理成可以被序列化的类型
                return o.strftime('%Y-%m-%d %H:%M:%S')
            elif isinstance(o, datetime.date):
                return o.strftime('%Y-%m-%d ')
        except TypeError:
            print('类型错误')
        return super().default(self, o)     # 最后调用原来的方法,防止有一些原有的功能没有操作

ret = json.dumps(d, cls=MyJSONEncoder)
print(ret)	# {"t1": "2022-11-07 ", "t2": "2022-11-07 15:21:39", "t3": "jason"}

封装

封装:就是将数据和功能 '封装' 起来

广义上面向对象的封装:代码的保护,面向对象的思想本身就是一种,只让自己的对象能调用自己类中的方法
狭义上的封装 —— 面向对象的三大特性之一
属性 和 方法都藏起来 不让你看见

隐藏

隐藏:将数据和功能隐藏起来不让用户直接调用 而是开发一些接口间接调用从而可以在接口内添加额外的操作

class A:
    __name = 'ysg'

    def __init__(self, age, job):
        self.__age = age
        self.job = job

    def __food(self):
        print('this is food')


a = A(16, 'python')
# print(a.__name)     # AttributeError: 'A' object has no attribute '__name'
# print(a.__age)  # AttributeError: 'A' object has no attribute '__age'
print(a.job)    # python

外部调用隐藏属性

# 查看 class A 的属性
# print(A.__dict__)   # {'__module__': '__main__', '_A__name': 'ysg', '__init__': <function A.__init__ at 0x02A58610>, '_A__food': <function A.__food at 0x02A585C8> ...}

# 如上,我们可以大胆的猜测一下,当我们使用双下划线隐藏后,它可能做了某种变形,我们来尝试下
print(a._A__name)   # ysg
print(a._A__food)   # <bound method A.__food of <__main__.A object at 0x00DB3E08>>
a._A__food()    # this is food

# 可以看出,的确是做了变形的处理

接下来看下它有哪些特点:

1. 类中定义的 __name 只能在内部使用,如 self.__name,引用的就是变形的结果
2. 这种变形其实正是针对外部的变形,在外部无法通过 __x 这个名字访问到
3. 在子类定义的 __x 不会覆盖在父类定义的 __x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的

这种变形需要注意的问题是:

1. 这种机制也并没有,真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2. 变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3. 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

我们用一个例子来验证下上面所说的特点3:

class B:
    def __foo(self):
        print('B.foo')

    def bar(self):
        print('B.bar')
        self.__foo()


class C(B):
    def __foo(self):
        print('C.foo')


c = C()
c.bar()


# 结果:
B.bar
B.foo

封装的意义

1. 封装数据属性:明确区分内外,控制外部对隐藏属性的操作

将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制

class People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print('Name:<%s>\nAge:<%s>' % (self.__name, self.__age))

    def set_info(self, name, age):
        if not isinstance(name, str):
            print('名字必须是字符串类型')
            return
        if not isinstance(age, int):
            print('年龄必须是 int 类型')
            return
        self.__name = name
        self.__age = age


p = People('ysg', 16)
p.tell_info()   # Name:<ysg> Age:<16>


p.set_info(123, 18)     # 名字必须是字符串类型
p.tell_info()   # Name:<ysg>    Age:<16>
p.set_info('er', '18')  # 年龄必须是 int 类型
p.tell_info()   # Name:<ysg>  Age:<16>

p.set_info('er', 18)
p.tell_info()   # Name:<er> Age:<18>

2. 封装方法:隔离复杂度

class ATM:
    def __card(self):
        print('插卡')

    def __auth(self):
        print('用户认证')

    def __input(self):
        print('输入取款金额')

    def __print_bill(self):
        print('打印账单')

    def __take_money(self):
        print('取款')

    # 实现了只通过这一个接口来完成上面流程的调用
    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()


a = ATM()
a.withdraw()

3.可扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑

class Room:
    def __init__(self,name,owner,weight,length,height):
        self.name=name
        self.owner=owner
        
        # 这里访问者其实是要结果,值可以隐藏起来
        self.__weight=weight
        self.__length=length
        self.__height=height
    
    #在这里改变运算规则
    def tell_area(self):
        return self.__weight * self.__length * self.__height

r=Room('卫生间','alex',10,10,10)

# print(r.tell_area())

#用户调用方式不变
print(r.tell_area())    # 1000

伪装

伪装:将类里面的方法伪装成类里面的数据

使用方法:在类中定义函数,使用 @property 进行装饰,调用时就可以像调用属性一样,调用函数了。

class Person(object):
    def __init__(self, name, height, weight):
        self.name = name
        self.height = height
        self.weight = weight
    @property
    def BMI(self):
        return self.weight / (self.height ** 2)


p1 = Person('jason', 1.83, 78)
# p1.BMI()  # BMI 应该作为人的基本数据而不是方法
print(p1.BMI)  # 利用装饰器伪装成数据

如果想要对被 @property 装饰的函数进行修改,衍生出了 @被装饰的函数名.setter 和 @被装饰的函数名.deleter (不常用)

class Foo:
    def __init__(self, val):
        self.__NAME = val  # 将属性隐藏起来

    @property
    def name(self):
        return self.__NAME

    @name.setter
    def name(self, value):
        if not isinstance(value, str):  # 在设定值之前进行类型检查
            raise TypeError('%s must be str' % value)
        self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise PermissionError('Can not delete')


f = Foo('jason')
print(f.name)
f.name = 'jason123'
print(f.name)
del f.name
# f.name = 'jason'  # 触发 name.setter 装饰器对应的函数 name(f,’jason')
# f.name = 123  # 触发 name.setter 对应的的函数 name(f,123),抛出异常 TypeError
# del f.name  # 触发 name.deleter 对应的函数 name(f),抛出异常 PermissionError

多态

多态:一种事物的多种形态,如:

水:液态、固态、气态
动物:人、猪、猫、狗

面向对象中多态意思是:一种事物可以有多种形态但是针对相同的功能应该定义相同的方法,
这样无论我们拿到的是哪个具体的事物,都可以通过相同的方法调用功能

python 永远提倡自由简介大方,不约束程序员行为,但是多态提供了约束的方法

下面是基于 abc 模块强制实行的多态,即父类中被 abc.abstractmethod 装饰过的方法,子类中必须实现该方法。

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

People().talk()

鸭子类型

鸭子类型:只要你看上去像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子

优点:松耦合 每个相似的类之间都没有影响
缺点:太随意了,只能靠自觉

反射

利用字符串操作对象的数据和方法
1.hasattr() 重点
判断对象是否含有某个字符串对应的属性名或方法名
2.getattr() 重点
根据字符串获取对象对应的属性名(值)或方法名(函数体代码)
3.setattr()
根据字符串给对象设置或者修改数据
4.delattr()
根据字符串删除对象里面的名字

反射的例子

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

    def talk(self):
        print('Name:%s,Age:%s' % (self.name, self.age))


p = People('ysg', 21)

# 判断 对象 中是否存在该属性,实际判断的是 p.__dict__  ['name']
print(hasattr(p, 'name'))  # 结果:True

# 取到 'name' 中的值
print(getattr(p, 'name', None))     # 结果:ysg
print(getattr(p, 'names', None))    # 结果:None
print(getattr(p, 'talk', None))     # <bound method People.talk of <__main__.People object at 0x0000020EDF705278>>

# 修改 'name' 中的值
setattr(p, 'name', 'ysging')    # p.name = 'ysging'
print(p.name)                   # 结果:ysging

# 删除 'name' 对象
print(p.__dict__)   # {'name': 'ysging', 'age': 21}
delattr(p, 'age')   # del p.age
print(p.__dict__)   # 结果:{'name': 'ysging'}

反射的好处

好处一:实现可插拔机制

有俩程序员,lili 和 egon,lili 在写程序的时候需要用到 egon 所写的类,但是 egon 去跟女朋友度蜜月去了,还没有完成他写的类,lili 想到了反射,使用了反射机制 lili 可以继续完成自己的代码,等 egon 度蜜月回来后再继续完成类的定义并且去实现 lili 想要的功能。

总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

好处二:动态导入模块(基于反射当前模块成员)

魔法对象

魔法方法:类中定义的双下方法都称为魔法方法
不需要人为调用,在特定的条件下会自动触发运行
eg:_init_ 创建空对象之后自动触发给对象添加独有的数据

1.__init__
 	对象 添加独有数据 的时候,自动触发
2.__str__
	对象 被执行打印操作 的时候,自动触发
3.__call__
	对象 加括号 调用的时候,自动触发
4.__getattr__
	对象 点不存在的名字 的时候,自动触发
5.__getattribute__
	对象 点名字就会,自动触发,有它的存在就不会执行上面的 __getattr__
6.__setattr__
	给对象 添加或者修改数据的时候 自动触发,对象.名字 = 值
7.__enter__
	当对象被当做 with 上下文管理操作的开始,自动触发,并且该方法返回什么 as 后面的变量名就会接收到什么
8.__exit__
	with 上下文管理语法运行完毕之后,自动触发(子代码结束)

魔法方法笔试题

题目

1.补全下列代码使得运行不报错即可
class Context:
	pass
with Context() as f:
	f.do_something()

2.自定义字典类型并让字典能够通过句点符的方式操作键值对

解答

# 1.补全下列代码使得运行不报错即可
class Context:
    def do_something(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


with Context() as f:
    f.do_something()
    

# 2.自定义字典类型并让字典能够通过句点符的方式操作键值对
class Mydict(dict):
    def __setattr__(self, key, value):
        self[key] = value
        return super().__setattr__(key, value)

    def __getattr__(self, item):
        return f'{item} 不存在'
        

dit = Mydict()
dit.name = 'ysg'

print(dit)  # {'name': 'ysg'}
print(dit.age)  # item

魔法方法之双下new

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        # 1.产生一个空对象(骨架)
        obj = self.__new__(self)
        # 2.调用__init__给对象添加独有的数据(血肉)
        self.__init__(obj,*args, **kwargs)
        # 3.返回创建好的对象
        return obj


class Student(metaclass=MyMetaClass):
    def __init__(self, name):
        self.name = name

obj = Student('jason')
print(obj.name)
"""
__new__可以产生空对象
"""

元类

我们定义的类其实都是由 type 类产生的:元类(产生类的类)

# 推导步骤1:如何查看数据的数据类型

s1 = 'hello world'
l1 = [11, 22, 33, 44]
d1 = {'name': 'jason', 'pwd': 123}
t1 = (11, 22, 33, 44)
print(type(s1))  # <class 'str'>
print(type(l1))  # <class 'list'>
print(type(d1))  # <class 'dict'>
print(type(t1))  # <class 'tuple'>


# 推导步骤2:其实 type 方法是用来查看产生对象的类名

class A:
    pass

a = A()
print(type(a))  # <class '__main__.A'>

# 推导步骤3:python 中一切皆对象,我们好奇 type 查看类名显示的是什么
print(type(A))  # <class 'type'>
print(type(type))   # <class 'type'>

# 结论:我们定义的类其实都是由 type 类产生的>>>:元类(产生类的类)

创建类的两种方式

使用 type 定义类的三要素:类名、类的基类、类的名称空间

了解知识:名称空间的产生
1.手动写键值对
针对绑定方法不好定义
2.内置方法:exec
能够运行字符串类型的代码并产生名称空间

# 方法一:使用 class 创建类
class Student:
    pass

print(Student)  # <class '__main__.Student'>
print(Student.__dict__) # {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects> ...}


# 方法二:使用 type 元类创建类
class_dic = {}
cls = type('Student', (object,), class_dic)
print(cls)  # <class '__main__.Student'>
print(cls.__dict__) # {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects> ...}

自定义元类——控制类的创建行为

实现检索类名称是否为大写,类中是否写入注释

# 实现检索类名称是否为大写,类中是否写入注释

class MyMateclass(type):
    def __init__(cls, what, bases=None, dict=None):
        # 类名必须为大写
        if not what.istitle():
            raise TypeError('类的首字母必须是大写')
        super().__init__(what, bases, dict)
        # 类中必须写注释
        if '__doc__' not in dict:
            raise TypeError('类中必须写注释')


class A(metaclass=MyMateclass):
    '''
    注释
    '''
    pass

a = A()

自定义元类——控制类的实例化行为

推导
对象加括号,会执行产生该对象类里面的 __call__
类加括号,会执行产生该类的类里面的 __call__

给对象添加独有数据的时候,必须采用关键字参数传参

# 给对象添加独有数据的时候,必须采用关键字参数传参

class MyMateclass(type):
    def __call__(self, *args, **kwargs):
        # print(self) # <class '__main__.A'>
        # print(args) # ('ysg', '25')
        # print(kwargs)   # {}
        if args:
            raise TypeError('传入参数必须采用关键字传参')
        return super().__call__(*args, **kwargs)


class A(metaclass=MyMateclass):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def read_print(self):
        print('read_print', self.name, self.age)    # read_print ysg 25


a = A(name = 'ysg', age = '25')
a.read_print()

设计模式简介

1.设计模式
	前人通过大量的验证创建出来解决一些问题的固定高效方法

2.IT行业
	23种
        创建型  共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
        结构型  共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
        行为型  共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
        
 	ps:课下感兴趣可以简单看看
        
3.单例模式
	类加括号无论执行多少次永远只会产生一个对象
 	目的:
        当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
        如果不做单例 会产生很多无用的对象浪费存储空间
        我们想着使用单例模式 整个程序就用一个对象

单例模式

方法一:基于模块

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

single = Single_info('ysg', 25)


# 调用方式
# 基于模块的单例模式:提前产生一个对象,之后导模块使用

方法二:基于装饰器

def singles(cls):
    insingles = {}
    def __singles_inner(*args, **kwargs):
        if cls not in insingles:
            insingles[cls] = cls(*args, **kwargs)
        return insingles[cls]
    return __singles_inner

@singles
class Singles_test:
    def __init__(self, name, age):
        self.name = name
        self.age = age


s1 = Singles_test('ysg', '123')
s2 = Singles_test('ysg2', '123124')
print(id(s1))   # 48250304
print(id(s2))   # 48250304
print(s1 is s2) # True
print(s1.name, s2.name) # ysg ysg

方法三:基于元类

from threading import RLock

class SingletonType(type):
    single_lock = RLock()

    def __call__(cls, *args, **kwargs):   # 创建cls的对象时候调用
        with SingletonType.single_lock:
            if not hasattr(cls, "_instance"):
                cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)     # 创建cls的对象

        return cls._instance


class Singleton(metaclass=SingletonType):
    def __init__(self, name):
        self.name = name


single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')

print(single_1.name)     # 第1次创建
print(single_2.name)     # 第1次创建
print(single_1 is single_2)     # True

方法四:基于__new__

from threading import RLock

class Singleton(object):
    single_lock = RLock()   # 上锁

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

    def __new__(cls, *args, **kwargs):
        with Singleton.single_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = object.__new__(cls)

        return Singleton._instance

single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')

print(single_1.name)   # 第2次创建
print(single_2.name)   # 第2次创建

方法五:基于类

class Singleton(object):

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

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            cls._instance = Singleton(*args, **kwargs)
        return cls._instance


single_1 = Singleton.instance('第1次创建')
single_2 = Singleton.instance('第2次创建')
print(single_1.name)    # 第1次创建
print(single_2.name)    # 第1次创建
print(single_1 is single_2)     # True

方法六:绑定方法实现单实例

class Singleton:
    __instance = None
    def __init__(self, name):
        self.name = name

    @classmethod
    def single(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = cls('ysg')
        return cls.__instance

s1 = Singleton.single('ysg')
s2 = Singleton.single('ysg2')
print(s1.name)  # ysg
print(s2.name)  # ysg
print(id(s1.name) == id(s2.name))   # True

作业

1、尝试利用反射编写一个简易版本的用户赠删改查功能
class Cmd_use:
    def __init__(self):
        self.shop_dit = {'挂壁面': 3, '印度飞饼': 22, '极品木瓜': 666, '土耳其土豆': 999, '伊拉克拌面': 1000, '董卓戏张飞公仔': 2000, '仿真玩偶': 10000}

    def add(self, key, val):
        self.shop_dit[key] = val

    def cut(self, key):
        print(key)
        self.shop_dit.pop(key)

    def update(self, key, val):
        self.shop_dit[key] = val

    def select(self):
        for key, val in self.shop_dit.items():
            print(key, val)

    def run(self):
        info = '''
        例子:商品名称|价格
        增:add 挂面|3
        删:cut 挂面
        改:update 挂面|6
        查:select
        '''
        print(info)
        while 1:
            put = input('请输入操作指令>>>').strip().split(' ')
            if hasattr(self, put[0]):
                func = getattr(self, put[0])
                if len(put) == 1:
                    func()
                else:
                    val_lit = put[1].split('|')
                    if len(val_lit) == 1:
                        func(val_lit[0])
                    else:
                        func(val_lit[0], val_lit[1])

c = Cmd_use()
c.run()
posted @ 2022-11-02 15:31  亦双弓  阅读(346)  评论(0)    收藏  举报