python面向对象编程

面向过程的编程思想

  指的是解决问题的步骤,类似设计一条流水线,是一种机械式的思维方式

  优点:复杂的问题流程化,简单化

  缺点:可扩展性差

面向对象的编程思想

  对象就是特征和技能的结合体,

  (个人理解:操作一个个对象,给它方法和属性,让它完成某个目标,不去关注它完成目标的过程)

  优点:可扩展性强

  缺点:编程的复杂度要高与面向过程

1、类

  对象是特征与技能的结合体,而类则是一系列对象相同特征与技能的结合体

  强调:

    1. 对象是具体存在的事物,而类则是一个抽象的概念

    2. 站在不同的角度总结出的类与对象不同

  在程序中:先定义类,后调用类产生对象

  (个人理解:类,同类 ,相同的属性, 一种抽象的概念,把一个个对象中具有的相同属性和方法,

        拿出来,单独放在一个地方,这个地方就是类)

# 定义类
class Student:
    # 相同的特征
    school = "NO.4 school"
    # 相同的技能
    def study(self):
        print("good good study.")

# 类是一系列对象相同特征(变量)和技能(函数的结合体,即类体中就是变量和函数)
# 但类体中可以存在任意python代码
# 类体代码会再类定义阶段立即执行,会产生一个类名称空间,用来将执行过程中产生的名字都丢进去

#
print(Student.__dict__)# 查看类的名称空间
print(Student.school) # print(Student.__dict__['school'])
print(Student.study)

# 修改
Student.school = "University" # Student.__dict__['school'] = 'University'
Student.country = "China"    # Student.__dict__["country"] = "China"
del Student.country             # del Student.__dict__["country"]

# 技能(函数)的使用
Student.study(123)
类的使用

  总结:

    1. 类的本质就是一个名称空间,存放着变量名和函数名

    2. 用途:当作名称空间从内部取出名字来使用,或调用类产生对象

2、对象

  调用类产生对象,对象的本质也是名称空间

  调用类的过程称之为类的实例化,调用类的返回值称之为类的一个对象/实例

class Student:
    # 相同的特征
    school = "NO.4 school"

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

    # 相同的技能
    def study(self):
        print("good good study.")

stu1 = Student('sun', 22, 'boy')
# 调用类后发生了:
# 1. 先产生一个空对象stu1,然后返回
# 2. 触发类中函数__init__的执行,将对象本身同调用类括号内指定的参数一同传入__init__内

# __init__的功能:是在实例化时就为对象初始自己独有的特征
# 注意:不能有返回值,不然报错
类的实例化到底发生了什么事

  属性的查找顺序:

    先从对象中查找,没有,就到类中查找,还没有就报错

  类中定义的属性是所有对象共享的,对象可以来用,类也可以用

    对象修改类定义的属性,结果是,在自己的名称空间内重新添加了一个新的属性

    类修改自己定义的属性,结果是,所有对象调用时,数据都会发生改变

# 统计实例了对象的个数
class Student:
    # 相同的特征
    school = "NO.4 school"
    
    count = 0

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
        Student.count += 1

    # 相同的技能
    def study(self):
        print("good good study.")

stu1 = Student('aa', 12, 'boy')
stu2 = Student('bb', 12, 'girl')
stu3 = Student('cc', 15, 'boy')
print(Student.count, stu1.count, stu2.count, stu3.count)
# 结果是:3   3   3   3
统计实例了对象的个数

  绑定方法:

    类中定义的函数是类的函数属性,类可以使用,但是绑定给对象用的

class Student:
    # 相同的特征
    school = "NO.4 school"

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

    # 相同的技能
    def study(self, x):
        print("good good study.%s"%self.name)

stu1 = Student('aa', 12, 'boy')
# 类来使用
# Student.study(stu1,123)

# 对象调用
# 对象调用时,会把自己作为第一个参数传入
# 若需要传其它参数,可以直接在括号内填入
stu1.study(123)
方法的使用

3、继承与派生

  继承是一种新建类的方式,新建的类称之为子类/派生类,被继承的类称之为父类/基类/超类

  python中继承特点:

    1. 子类可以重用父类的属性

    2. python中一个子类可以同时继承多个父类

    3. 在继承背景下,类分为新式类,经典类

      新式类:但凡继承了object的类以及类的子类

               python3中一个类即便没有显示继承任何类,默认会继承object

          即python3中所有的类都是新式类

      经典类: 没有继承object类以及该类的子类

          在python2中才区分新式类与经典类

          在python2中一个类如果没有显示地继承任何类,也不会继承object

  为什么要用继承:减少类与类之间的代码冗余

  查看自己有哪些父类:__bases__

# 第一种方式:指名道姓地引用某一个类地函数
# 使用这种方式,和继承无关
# 访问的是类的函数,没有自动传值的效果

class People:
    school = 'No.4 school'

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

class Student():
    def __init__(self, name, age, sex, score=0):
        # 使用父类的属性
        People.__init__(self,name,age,sex)
        # 自己派生新的属性
        self.score = score

    def study(self):
        print('%s is studying.' % self.name)

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

    def score(self,stu,num):
        stu.score=num

stu1=Student('sun',28,'male')
print(stu1.__dict__)
stu1.study()
tea1=Teacher('aaa',18,'male',10)
print(tea1.__dict__)
子类派生新属性和使用父类属性的方式一
# 第二种方式:super()必须在类中使用(使用了后面的MRO列表)
# 在python2中,super(自己的类名, 自己的对象)
# 在python3中,super()
# 调用该函数会得到一个特殊的对象,该对象专门用来访问父类中的属性!!!完全参照mro列表!!!
# 不会根据有没有实际的继承关系
# 总结:
# 1. 严格依赖继承的mro列表
# 2. 访问是绑定方法,有自动传值的效果

class A:
    def f1(self):
        print('A.f1')
        super().f2()

class B:
    def f2(self):
        print('B.f2')

class C(A,B):
    def f2(self):
        print('C.f2')

obj=C()
print(C.mro())
obj.f1()
'''
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
A.f1
B.f2
'''

# 两种方式最好不要混着用
使用父类属性方式二(重点)

  1. 单继承下属性查找

    优先级:对象-> 对象的类->父类->....->object

class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1() #obj.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()
'''
Foo.f2
Bar.f1
'''
查找示例

  2. 多继承下的属性查找

    如果一个子类继承多个分支(分支没有共同继承一个非object的类)

    这时新式类和经典类的查找顺序是一样的。

    此时属性的查找优先级是:对象—对象的类—按照从左到右的顺序一个分支一个分支的找



# # 第四层:
# class G:
#     # x = 'G'
#     pass
#
# # 第三层
# class E(G):
#     # x = 'E'
#     pass
#
# class F:
#     # x = 'F'
#     pass
#
# # 第二层
# class B(E):
#     # x = 'B'
#     pass
#
# class C(F):
#     # x = 'C'
#     pass
#
# class D:
#     # x = 'D'
#     pass
#
# # 第一层
# class A(B, C, D):
#     # x = 'A'
#     pass
#
# obj = A()
# # obj.x = 111
# print(obj.x)
查找示例

    如果出现菱形继承:

      新式类:广度优先查找,从左到右一个分支一个分支查找,在最后一个分支才去查找顶级类

      经典类:深度优先查找,从左到右一个分支一个分支的查找,在第一个分支就查找顶级类


#对于新式类,python会通过C3线性算法计算出一个MRO表,
# 表中是一个简单的所有基类的线性顺序列表,如:

>>> A.mro() #等同于A.__mro__
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]

MRO遵循三条准则:
1. 子类会先于父类查找
2. 多个父类会根据它们在列表中的顺序被检查
3. 如果对下一个类存在两个以上合法的选择,选择第一个类
继承原理(python3)

  组合

     组合指的是某一个对象拥有一个属性,该属性的值是另外一个类的对象

    为何使用组合:

       通过某一对象添加属性(属性的值是另外一个类的对象)的方式,

      可以间接的将两个类组合在一起,减少代码的冗余

4、多态

  多态指的是同一种/类事物的不同形态

  多态性:在多态的背景下,可以不用考虑对象具体类型的前提下而直接使用对象

  多态性的精髓:统一

# 第一种方式:
# 有python提供比较硬性的方式来同一接口,(不推荐使用你)
import abc
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def speak(self):
        pass
    @abc.abstractmethod
    def run(self):
        pass

# Animal() # 父类只是用来建立规范的,不能用来实例化的,更无需实现内部的方法
class People(Animal):
    def speak(self):
        print('say hello')
    def run(self):
        pass

class Dog(Animal):
    def speak(self):
        print('汪汪汪')
    def run(self):
        pass

class Pig(Animal):
    def speak(self):
        print('哼哼哼')
    def run(self):
        pass

obj1=People()
obj2=Dog()
obj3=Pig()
# 实例化对象,不会报错,
# 但当你调用任何方法时,如果类中没有规定的speak,run方法时就会报错
# 这样就可以达到强制统一接口的作用
多态的使用方式一
# 第二种方式
# 人为的统一接口,不做任何强制措施
# 就是python俗称的鸭子类型
class Disk:
    def read(self):
        print('Disk read')
    def write(self):
        print('Disk write')

class Memory:
    def read(self):
        print('Mem read')
    def write(self):
        print('Mem write')

class Cpu:
    def read(self):
        print('Cpu read')
    def write(self):
        print('Cpu write')

obj1=Disk()
obj2=Memory()
obj3=Cpu()
obj1.read()
obj2.read()
obj3.read()
方式二

5、封装

  封:往容器/名称空间里存入名字

  装:代表将存放于名称空间中的名字给藏起来,这种隐藏是对外部不对内

  如何封装:

    在类定义的属性或方法前加__开头(没有__结尾)

  总结:

    1. __开头的属性实现隐藏仅仅是一种语法意义上的变形,并不会真的限制类外部的使用

    (封装的方法:__func变成了_类名__func)

    2. 该变形操作只在类定义阶段时执行一次,类定义阶段后新增的__开头的属性不会变形

    3. 如果父类不想子类覆盖自己的属性,可以在属性前加上__开头

class Foo:
    __x=111 # _Foo__x
    __y=222 # _Foo__y

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

    def __func(self): #_Foo__func
        print('func')

    def get_info(self):
        print(self.__name,self.__age,self.__x) #print(self._Foo__name,self._Foo__age,self._Foo__x)
# print(Foo.__x)
# print(Foo.__func)
# print(Foo.__dict__)
# print(Foo._Foo__x)
# print(Foo._Foo__y)
# Foo.__z=333
# print(Foo.__dict__)
# print(Foo.__z)

# 不想让子类覆盖父类的方法
class Foo:
    def __f1(self): #_Foo__f1
        print('Foo.f1')
    def f2(self):
        print('Foo.f2')
        self.__f1() #obj._Foo__f1()

class Bar(Foo):
    def __f1(self): # _Bar__f1
        print('Bar.f1')

obj=Bar()
obj.f2()
'''
Foo.f2
Bar.f1
'''
封装的使用

  为什么封装:

    对数据属性的封装:将数据属性隐藏起来,类外部无法直接操作属性,需要类内部开辟一个接口,

    让外部的使用可以间接地操作属性,可以在接口内定义任意的控制逻辑

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

    def tell_info(self):
        print('<name:%s age:%s>'  %(self.__name,self.__age))

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

obj=People('sun',18)
obj.tell_info()

# obj.set_info(123,19)
obj.set_info('sun','18')
# 上述两个修改都不会成功
示例

    对函数的封装:隔离复杂度

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()
View Code

6、property装饰器

  用来将类内的函数属性伪装成数据属性

# 使用
class People:
    def __init__(self,name):
        self.__name=name

    @property
    def name(self):
        return '<名字:%s>' %self.__name

    @name.setter
    def name(self,obj):
        if type(obj) is not str:
            print('name必须为str类型')
            return
        self.__name=obj

    @name.deleter
    def name(self):
        del self.__name

obj=People('sun')
# 查看
# print(obj.name)
# 修改
# obj.name='SUN'
# obj.name=123
# print(obj.name)
# 删除
del obj.name
print(obj.__dict__)

# 以前property设置方法
class People:
    def __init__(self,name):
        self.__name=name

    def get_name(self):
        return '<名字:%s>' %self.__name

    def set_name(self,obj):
        if type(obj) is not str:
            print('name必须为str类型')
            return
        self.__name=obj

    def del_name(self):
        del self.__name

    name=property(get_name,set_name,del_name)

obj=People('sun')
print(obj.__dict__)
使用示例

7、类中定义方法

  绑定方法

    绑定给谁就应该由谁来调用,谁来调用就会将谁作为第一个参数传入

    绑定给对象的方法:类中定义的函数默认就是绑定给对象的

    绑定给类的方法:为类中定义的函数加上一个装饰器classmethod, (对象也可以调用,

            但传入的第一个参数的值任然是类,一般不会这样使用)

  非绑定方法

    既不与类绑定,又不与对象绑定,意味着对象和类都可以来调用,无论谁来调用都是一个普通的函数,

    没有自动传值的效果,使用@staticmethod装饰器

# class Foo:
#     def f1(self):
#         print(self)
#
#     @classmethod
#     def f2(cls):
#         print(cls)
#
#     @staticmethod
#     def f3(x,y):
#         print('f3',x+y)

# 绑定给对象的方法
# obj=Foo()
# print(obj.f1)
# obj.f1()
# 结果是:
# <bound method Foo.f1 of <__main__.Foo object at 0x000001F2732CF0F0>>
# <__main__.Foo object at 0x000001F2732CF0F0>

# 绑定给类的方法使用
# # print(Foo.f2)
# # Foo.f2()
# # print(obj.f2)
# # obj.f2()
# 结果是:
# <bound method Foo.f2 of <class '__main__.Foo'>>
# <class '__main__.Foo'>
# <bound method Foo.f2 of <class '__main__.Foo'>>
# <class '__main__.Foo'>

# 非绑定方法的使用
# print(Foo.f3)
# print(obj.f3)
# Foo.f3(1,2)
# obj.f3(3,4)
# 结果是:
# <function Foo.f3 at 0x0000023DA02F3950>
# <function Foo.f3 at 0x0000023DA02F3950>
# f3 3
# f3 7
使用示例
# 应用
# setting.py
IP = "127.0.0.1"
PORT = 3306

# run.py
import settings
class MySql:
    def __init__(self, ip, port):
        self.id = self.create_id()
        self.ip = ip
        self.port = port

    def tell_info(self):
        print('<id:%s ip:%s port:%s>' % (self.id, self.ip, self.port))

    @classmethod
    def from_conf(cls):
        return cls(settings.IP, settings.PORT)

    @staticmethod
    def create_id():
        import uuid
        return uuid.uuid4()

# obj1=MySql('1.1.1.1',3306)
# obj1.tell_info()
# 绑定给类的方法提供了另外一种实例化对象的方式
obj2 = MySql.from_conf()
# 查看非绑定方法的结果
obj2.tell_info()
应用

8、内置方法

  1. isinstance和insubclass

    isinstance 判断一个对象是否是一个类的实例,issubclass 判断是否是子类

isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
要判断两个类型是否相同推荐使用 isinstance()

示例:
class A:
    pass
class B(A):
    pass
isinstance(A(), A)    # returns True
type(A()) == A        # returns True
isinstance(B(), A)    # returns True
type(B()) == A        # returns False
isinstance和type的区别
class A:
    pass
class B(A):
    pass
issubclass(B, A)
# 注:issubclass内的参数必须是一个类,不然会报错
issubclass使用

  2. 反射

    通过字符串映射到对象或类的属性(主要和input连用)

# hasattr 判断对象或类是否有字符串对应的方法或属性
hasattr(obj, ;'name') # 'name' in obj.__dict__
getattr(obj, 'name') # obj.__dict__['name']
setattr(obj, 'age', 18) # obj.sex = 18
delattr(obj, 'name')
print(obj.__dict__)
使用方法

  3. 属性attr方法

    作用:如果属性查找在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,

                         那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。

    __setattr__: 添加/修改属性会触发它的执行

    __delattr__: 删除属性的时候会触发

    __getattr__: 只有在使用点调用属性且属性不存在的时候才会触发

  4. item方法

    __getitem__: 使用类似字典的方式得到数据

    __setitem__:

    __delitem__:

    def __getitem__(self, item):
        return self.__dict__get(item)                      
    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)

obj["name"]
obj["sex"] = 19
del obj["sex"]
使用方式

  5. __dir__()

    获取对象所有的属性名和方法名(包括继承类中的属性和方法),对象使用方式a.__dir__(),类使用方式Bar.__dir__(a);执行dir(object)也是调用的对象的__dir__()方法。

class Foo:
    def foo(self):
        pass

class Bar(Foo):
    count = 0

    def __init__(self):
        self.name = "sun"

    def bar(self):
        pass

a = Bar()
print(a.__dir__())

# 结果
"""
['name', '__module__', 'count', '__init__', 'bar', '__doc__', 'foo', '__dict__', '__weakref__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
"""

  6. __dict__

    1. 类的__dict__得到是类本身(不包括继承的类)相关属性和方法,对象的__dict__得到的是仅仅是对象的属性,即self.xxx  

class Foo:
    def foo(self):
        pass

class Bar(Foo):
    """this is class bar"""
    count = 0

    def __init__(self):
        self.name = "sun"

    def bar(self):
        pass

a = Bar()
print(Bar.__dict__)
print(a.__dict__)

# 结果
"""
{'__module__': '__main__', '__doc__': 'this is class bar', 'count': 0, '__init__': <function Bar.__init__ at 0x7f05f5aec8c8>, 'bar': <function Bar.bar at 0x7f05f5aec950>}
{'name': 'sun'}
"""

    2. 一些内置的数据类型是没有__dict__属性的,如:整数、字典、列表等

    3. 如果类继承了父类的属性,类的__dict__没有变化,对象的__dict__则增加了父类的属性

class Foo:
    def __init__(self):
        self.cls = "Foo"

    def foo(self):
        pass


class Bar(Foo):
    """this is class bar"""
    count = 0

    def __init__(self):
        self.name = "sun"
        super(Bar, self).__init__()

    def bar(self):
        pass

a = Bar()
print(Bar.__dict__)
print(a.__dict__)

# 结果
"""
{'__module__': '__main__', '__doc__': 'this is class bar', 'count': 0, '__init__': <function Bar.__init__ at 0x7f9df8c00950>, 'bar': <function Bar.bar at 0x7f9df8c009d8>}
{'name': 'sun', 'cls': 'Foo'}
"""

    4. 函数vars()和__dict__的效果一样,都是返回对象或类的属性和属性值的字典对象

  7. 其它

    __str__: 会在打印对象时触发,可以定义对象被打印时的输出信息,返回必须是字符串

    __del__: 回收资源的操作,在对象被删掉前或程序结束后执行,可以用来回收对象意外其它相关资源

    __call__: 在对象被调用时会自动触发方法

    __eq__: 当两个对象作比较时a1==a2触发

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __eq__(self, obj):
        if self.name == obj.name and self.sex==obj.sex:
            return True
给了一个列表的对象,去掉里面属性相同的重发对象

9、异常

  异常是错误发生的信号,程序出错就会产生异常,如果该异常没有被程序处理,异常就会抛出,程序终止。

  异常信息包含三部分:

    traceback 异常的追踪信息

    异常的类型

    异常的内容

  为什么要处理异常

    避免程序因为异常而崩溃,所以在应用程序中应该对异常进行处理,从而增强程序的健壮性

  分类:

    语法上错误,在程序执行前就应该立即修正

    逻辑上的错误,可提前预防的错误,使用if判断,如果不能预知,就用try,except

try:
    code.....
except NameError as e:
    # e是错误信息
    # 当抛出的异常是NameError时,执行该代码快
    code.....
except NameError2:
    # 多分支
    code....

# 可以写在一行
except (NameError, NameError2)

# 万能异常
Exception

# else 当没有发现异常时,执行else内的内容
try:
    .....
else:
    ....

# finally: 最后执行,无论有没有异常
异常处理方法try
# 自定义异常类型
class MyException(BaseException):
    def __str__(self): # 打印异常值
        return ""

# 报异常
raise MyException("我定义的异常")
自定异常类型
assert 条件,条件不成立,则抛出异常
断言

10、元类

  原因:python中一切皆对象

  什么是元类:

    内置的元类是type,由元类实例化得到自定义的类,自定义的类实例化得到对象

  自定义类的三个组成部分:

    1. 类名

    2. 类的基类们

    3. 类的名称空间

  class创建自定义类的底层原理:

    1. 拿到类名

    2. 拿到类的基类们

    3. 拿到类的名称空间(使用exec,执行字符串)

    4. 调用元类实例化得到自定义的类,

class_name = "Student"
class_bases = (object,)(必须是元组)
class_namespace = {}
class_body = """
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def study(self):
        print("%s is studying"%self.name)
"""
exec(class_body, {}, class_namespace)

Student = type(class_name, class_bases, class_namespace)
stu1 = Student('sun', 22)
创建自定义类的另一种方式
# 只有继承了type的类才能称为自定义元类,否则就是一个普通的类
class Mymeta(type):
    def __init__(cls, class_name, class_bases, class_namespace):
        pass
  
class Student(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self):
        print("%s is studying"%self.name)
自定义元类
# 使用自定义元类给自定义类加上限制
# 类名必须是驼峰体,即首字母必须大写
# 必须有注释
class Mymeta(type):
    def __init__(cls, class_name, class_bases, class_namespace):
        # 类名必须大写
        if not class_name.istitle():
            raise TypeError("类名首字母必须大写")
        doc = class_namespace.get("__doc__")
        if not (doc and doc.strip()):
            raise TypeError("必须要有注释,且注释不能为空")

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

    def study(self):
        print("%s is studying"%self.name)
自定义元类的使用
# 元类中__call__的使用
# 类的实例化过程即类的调用Student("sun", 22)
# 1. 先产生一个空对象
# 2. 执行__init__,完成对象的初始属性操作
# 3. 返回初始化好的对象
# 因此:类的实例化即调用元类的__call__方法
class Mymeta(type):
    def __init__(cls, class_name, class_bases, class_namespace):
        # 类名必须大写
        pass
    def __call__(self, *args,**kwargs):#self是Student 
        obj = self.__new__(self) # 实际上使用的是:object.__new__(self)
        self.__init__(obj, *args, **kwargs)
        # 可以对新产生的对象加上限制
        # 比如控制对象属性全部隐藏(隐藏后方法中若是需要使用必须是self.__name)
        obj.__dict__ = {("_%s__%s"%(self.__name__, k)): v for k,v in obj.__dict__.items()}
        return obj

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

    def study(self):
        print("%s is studying"%self.__name)

stu1 = Student("sun", 20)
stu1.study()
元类中__call__的使用
# 使用自定义元类后属性的查找顺序
# 先对象层:对象 -> 父类们 -> object
# 后元类层:Mymeta -> type

# 为什么不使用object.__new__(self),而使用self.__new__(self)
# 虽然上述效果一样,但使用self.__new__(self),查找属性时会顺着mro列表查找最后找到object中,
# 如果直接使用object.__new__(self),会直接跳过中间的父类们,拿不到中间对属性的操作

# Mymeta也是一个对象,那么Mymeta之所以可以调用,一定是在元类type中有一个__call__方法
class Mymeta(type):
    def __new__(cls, *args, **kwargs):
        """自定义生成新对象的格式"""
        obj = type.__new__(cls, *args, **kwargs) # 必须按照这种方式传值
        print(obj.__dict__)
        return obj  # 得到对象必须返回, 才能执行后面的__init__

    def __init__(cls, class_name, class_bases, class_namespace):
        print("init.....")

class Student(metaclass=Mymeta):   # 相当于Student=Mymeta("Student", (object, ), {...})
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self):
        print("%s is studying"%self.name)

print(type(Mymeta))
# <class 'type'>
# 产生Student的过程就是调用Mymeta,那调用Mymeta一定是执行了type中的__call__方法
# class type:
#     def __call__(self, *args, **kwargs): #self=<class '__main__.Mymeta'>
#         obj=self.__new__(self,*args,**kwargs) # 产生Mymeta的一个对象
#         self.__init__(obj,*args,**kwargs)
#         return obj
强调,强调,强调
class Mymeta(type):
    def __new__(cls, class_name:str, class_bases:tuple, class_namespace:dict):
        """自定义生成新对象的格式"""
        # 把自定义类的数据属性变大写
        new_class_namespace = {}
        for k, v in class_namespace.items(): #type: str
            if not callable(v) and not k.startswith("__"):
                new_class_namespace[k.upper()] = v
            else:
                new_class_namespace[k] = v
        obj = type.__new__(cls, class_name, class_bases, new_class_namespace) # 必须按照这种方式传值
        return obj  # 得到对象必须返回, 才能执行后面的__init__

    def __init__(cls, class_name, class_bases, class_namespace):
        pass

class Student(metaclass=Mymeta):   # 相当于Student=Mymeta("Student", (object, ), {...})
    school = "No.4 school"
    classnoom = ""
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self):
        print("%s is studying"%self.name)

print(Student.__dict__)
# 结果:
"""
{'__module__': '__main__', 
'SCHOOL': 'No.4 school', 
'CLASSNOOM': '',
 '__init__': <function Student.__init__ at 0x00000288A1082840>, 
'study': <function Student.study at 0x00000288A10828C8>, 
'__dict__': <attribute '__dict__' of 'Student' objects>,
'__weakref__': <attribute '__weakref__' of 'Student' objects>,
'__doc__': None}
"""

# 对象的使用也必须使用大写
stu1 = Student("sun", 23)
print(stu1.SCHOOL)
# 结果:No.4 school
实例一:在元类中控制自定义类的数据属性都变成大写
# 要求:
# 1. 自定义类实例化是必须以关键字传参(对__call__操作)
# 2. key作为自定义类对象的属性,且所有属性变大写 
class Mymeta(type):
    def __init__(cls, class_name, class_bases, class_namespace):
        pass

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("必须使用关键字传值")
        obj = self.__new__(self)
        for k, v in kwargs.items():
            obj.__dict__[k.upper()] = v
        return obj

class Student(metaclass=Mymeta):   # 相当于Student=Mymeta("Student", (object, ), {...})

    def study(self):
        print("%s is studying"%self.name)

stu1 = Student(name='sun', age=23)
print(stu1.__dict__)
print(stu1.NAME)
实例二:在元类中控制自定义的类无需__init__方法
# 多次实例化的结果指向同一个实例,用于节省空间
# 如果从配置文件中读取配置进行实例化,在配置相同的情况下,就没有必要产生重复对象浪费内存了

# settings.py文件下的内容
PORT = 3306
HOST= "127.0.0.1"

#第一种方法,在类中实现单例模式
import settings
class Mysql:
    __instance = None
    
    def __init__(self, host, port):
        self.host = host
        self.ip = port

    @classmethod
    def singleton(cls):
        if not cls.__instance:
            cls.__instance = cls(settings.HOST, settings.PORT)
        return cls.__instance
    
# 当直接调用类的方法singleton得到的对象是单例模式
obj1 = Mysql.singleton()
obj2 = Mysql.singleton()
# 但实例化得到的对象又是一个新的对象
obj3 = Mysql("127.0.0.1", 3306)

# 第二种方法,使用装饰器实现单例
from functools import wraps
import settings
def singleton(cls):
    __instance = cls(settings.HOST, settings.PORT)
    @wraps(cls)
    def wrapper(*args, **kwargs):
        if args or kwargs:
            obj = cls(*args, **kwargs)
            return obj
        return __instance
    return wrapper

@singleton
class Mysql:
    def __init__(self, host, port):
        self.host = host
        self.ip = port

# 当不传参数时可以实现单例,obj1和obj2的内存地址都是一样
obj1 = Mysql()
obj2 = Mysql()
# 当传入参数时,又是另一个新对象
obj3 = Mysql("127.0.0.1", 3306)

#  第三种方法:使用元类
import settings
class Mymeta(type):
    def __init__(cls, class_name, class_bases, class_namespace):
        # 事先从配置文件中取数据造一个Mysql对象
        cls.__instance = cls(settings.HOST, settings.PORT)
        super().__init__(class_name, class_bases, class_namespace)

    def __call__(self, *args, **kwargs):
        # 当传入参数时重新创建一个对象
        if args or kwargs:
            obj = self.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj
        return self.__instance
    
class Mysql:
    def __init__(self, host, port):
        self.host = host
        self.port = port

# 当不传参数时可以实现单例,obj1和obj2的内存地址都是一样
obj1 = Mysql()
obj2 = Mysql()
# 当传入参数时,又是另一个新对象
obj3 = Mysql("127.0.0.1", 3306)

# 第四种方式,使用导入模块的方式
# 导入模块,第一导入会执行模块内的代码生成名称空间
# 之后再次导入,都会指向这个名称空间

# 创建一个单例模块
# singleton.py模块
import settings
class Mysql:
    def __init__(self, host, port):
        self.host = host
        self.port = port

instance=MySQL(settings.HOST,settings.PORT)

# 多次导入
def f1():
    from singleton import instance
    print(instance)

def f2():
    from singleton import instance,Mysql
    print(instance)
    obj=MySQL('127.0.0.1',3306)
    print(obj)

f1()
f2()
# 结果:两个instance的内存地址一样,和obj的地址不一样
实例三:单例模式(四种方式)

 

posted @ 2019-04-06 17:21  yw_sun  阅读(196)  评论(0)    收藏  举报