动静态方法 封装 隐藏 伪装 继承 多态 魔法 反射 元类

动静态方法

绑定方法与非绑定方法的使用:若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法,需要引用类则将其定义成类方法,无需引用类或对象则将其定义成静态方法

动态方法(绑定方法)

绑定给对象的方法

'''
类中定义的函数默认绑定给对象方法 
对象来调用,会把对象自己当成第一个参数传给方法的第一个形参self
类来调用绑定给对象的方法:类名来调用有几个参数填几个参数
'''
class Studen:
    school_name='清华'#属性公共数据

	def __init__(self,name,age,gender):# 初始化方法,当类被加括号调用的时候,会自动触发这个函数的执行
        self.name=name
        self.age=age
        self.gender=gender
        
    #下面函数为绑定给对象的方法
    def choice_course(self):#对象调用第一个参数自动传入对象自己
        print(f'学生{self.name}正在选课')
        
stu=Studen('jack',16,'male')
print(stu.__dict__)#{'name': 'jack', 'age': 16, 'gender': 'male'}
stu.choice_course()#学生jack正在选课#对象调用会自动将对象自己当第一个参数传入


class Student():
    school_name='清华'

    def choice_course(self):
        print('对象的方法')
Student.choice_course(123)#对象的方法#类来调用绑定给对象的方法有几个参数填几个参数包括形参self,这里随便填个参数

绑定给类的方法

'''
被@classmethod修饰的函数默认绑定给类的方法
类调用会把类名当成第一个参数传给方法的第一个形参cls
对象调用会自动将产生该对象的类当成第一个参数传入
'''
class Student():
    school_name='清华'

    @classmethod#此时,该方法已经绑定给了类,而不是对象的方法
    def func(cls):#cls:类名
        print('猜猜猜',cls)

Student.func()#猜猜猜 <class '__main__.Student'>
#类来调用相当于把自身当成第一个参数传入func(Student)
obj=Student()
obj.func()#猜猜猜 <class '__main__.Student'>
#对象调用会自动将产生该对象的类当成第一个参数传入func(Student)
class Mysql():
    port = 3306
    ip = '127.0.0.1'

    @classmethod
    def from_func(cls):
        return f'ip:{cls.ip} port:{cls.port}'

res=Mysql.from_func()#调用函数
print(res)#ip:127.0.0.1 port:3306
#settings.py:
ip=3306
port='127.0.0.1'

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

    @classmethod
    def from_func(cls):
        return cls(settings.ip,settings.port)#实例化对象
res=Mysql.from_func()#调用函数
print(res)#<__main__.Mysql object at 0x000002962A9891C0>
print(res.port,res.ip)#127.0.0.1 3306    

静态方法(非绑定方法)

'''
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法,该方法不绑定给对象,也不绑定给类,无论是类还是对象调用它就是一个普通函数,因而没有自动传值则调用时就需要传对应数量的参数
'''
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.id = self.get_id()#调用get_id函数,此时self为调用者或是对象或是类,具体看谁调用

    @staticmethod#该方法为静态方法,既不绑定给对象也不绑定给类
    def get_id():
        import uuid
        return uuid.uuid4()

stu=Student('kevin',19)
print(stu.id)#8f93dbc3-825b-4cb6-8a79-77c1220c71eb
print(stu.create_id())#4906415d-bfda-455a-8171-09e9b7195192
print(Student.create_id())#637c5403-88fe-405d-ae77-05e4b574dd0e
import random
class Users():
    def __init__(self,name,password):
        self.name=name
        self.password=password

    @staticmethod
    def get_code(n):
        code=''
        for i in range(n):
            random_int = str(random.randint(0, 9))
            random_upper = chr(random.randint(65, 90))
            random_lower = chr(random.randint(97, 122))
            temp =random.choice([random_lower,random_upper,random_int])
            code +=temp
        return code
user=Users('kevin',123)
print(user.get_code(5))#KM4mR,调用被静态方法修饰的函数对象则需要传参
print(Users.get_code(4))#Rn1F,调用被静态方法修饰的函数类也需要传参


        '''如果方法里面用到了对象,又用到了类,最好绑定给对象'''
        # 如何通过对象得到这个对象的类:
        print(self.__class__)
        cls = self.__class__ # cls = Student

三大特征之封装

封装:即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用

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

作用:保护数据安全性、提高代码复用性、降低耦合度等
在Python中,我们可以通过以下方式来实现封装:

1.在类定义阶段,使用__对属性或方法进行命名,以表示其为私有属性或私有方法,仅允许在类内部使用。
2.使用@property装饰器将类的方法转换为只读属性。
3.使用setter和getter函数对类的属性进行封装。

隐藏属性

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

如何隐藏属性

'''
    1. 在类定义阶段,名字前面有__那么该名字就被隐藏起来,无法直接访问(本质上隐藏属性发生了变形:_类名__属性名\方法名),通过类名\对象名._类名__数据功能名才能访问
    2. 隐藏属性既可以隐藏类属性、类(对象)方法、对象属性
    3. 隐藏对外不对内,在类的内部没有隐藏,通过正常的调用方法来调用即可
'''
class Myclass:
    school_name='清华'
    #python变量的命名数字、字母、下划线任意的组合
    _='嘿嘿嘿'
    _name='tony'
    __age=18#__开头的变量名无法直接访问
    
    def __init__(self,name,age):
        self.__name=name#对象属性也可以拥有隐藏的属性
        self.__age=age
    
    def __choice_course(self):#变量名前面加两个__那么该名字就被隐藏起来
        print('baby正在选课')
        
    def get_info(self):
		self.__choice_course()#在类体代码中直接可以使用隐藏的名字    

print(Myclass._Myclass__age)#18 访问隐藏的数据
Myclass.__hobby='music'
print(Myclass.__hobby)#music
obj =Myclass()
obj.get_info()#baby正在选课
obj.__addr='上海'
print(obj.__addr)#上海
'''在类调用阶段给类添加属性给对象增加数据的方式,使用__是无法隐藏的,依然可以得到结果,只有在类定义阶段名字加__才能被隐藏'''

开放接口

'''
由于隐藏属性是对外不对内的,我们要在内部开放一个接口来返回类内部被隐藏的属性,可以更好的对外做限制
''' 
class Person:
    def __init__(self,name,age,hobby):
        self.__name=name#对象也可以拥有隐藏的属性
        self.__age=age
        self.__hobby=hobby

    def get_info(self):
        #类体代码中可以直接使用隐藏的名字
        print(f"""
        姓名:{self.__name}
        年龄:{self.__age}
        爱好:{self.__hobby}
        """)

    #隐藏的属性让用户无法直接修改名字,这里我们可以做些额外的限制开放修改的接口。可以自定义很多功能,例如:
    def set_name(self,new_name):
        if len(new_name)==0:
            raise ValueError('名字不能为空')
        if new_name.isdigit():
            raise ValueError('名字不能是数字')
        self.__name=new_name

obj=Person('jason',18,'read')
obj.get_info()
#输出结果
#姓名:jason
#年龄:18
#爱好:read
obj.set_name('')#抛出异常ValueError: 名字不能为空
obj.set_name('tony')
obj.get_info()
#输出结果
#姓名:tony
#年龄:18
#爱好:read
#要求修改国名
class Student():
    __country='CHN'

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

    def set_country(self,country_name):
        if not isinstance(country_name,str) or not country_name.isalpha():#isinstance判断数据类型、isalpha判断是否字母
            print('格式错误')
            return
        Student.__country=country_name#修改属性

    def get_country(self):
        return self.__country

stu=Student('kevin',18)
stu.set_country('1')
print(stu.get_country())#格式错误&CHN
stu.set_country('China')
print(stu.get_country())#China

伪装property装饰器

伪装:把类里面的方法伪装成类里面的属性
如何伪装:被@property修饰的方法就变成伪装属性
@property把方法伪装成属性之后,以后再调用方法的时候,就不用加括号了,只需要对象点属性名即可
eg:
class C:
	def func(self):pass
obj=C()
obj.func()#没被伪装调用方法
obj.func#经过伪装的调用方法
  • 伪装方式一
'''
因被@property修饰的方法对象无法进行修改删除,若想删除修改则需要额外在删除修改的函数方法上添加以下装饰器
修改:@被伪装属性的方法名.setter
删除:@被伪装属性的方法名.deleter
'''
class Student():
    __country = 'CHINA'
    __city = 'Shanghai'

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

    @property  # 把方法伪装成属性来使用
    def country(self):
        return self.__country

    @country.setter  # 修改的
    def country(self, country_name):
        if not type(country_name) is str:
            return
        Student.__country = country_name

    @country.deleter  # 删除的
    def country(self):
        del Student.__country
 
stu=Student('KEVIN',19)
print(stu.country)#CHINA,伪装后通过访问属性方式调用函数,
stu.country='china'#修改
print(stu.country)#china
del stu.country#删除成功
  • 伪装方式二
"""这种方式,是有顺序要求的,必须是以获取>设置>删除为顺序"""
class Student():
    __country = 'China'
    __city = 'shanghai'

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

    def get_country(self):
        return self.__country

    def set_country(self,country_name):
        if not isinstance(country_name,str) or not country_name.isalpha():
            print('格式有误')
            return
        Student.__country=country_name

    def del_country(self):
        del Student.__country
        print('删除成功')
    
    country = property(get_country, set_country, del_country)#必须按照此顺序编写

stu=Student('KEVIN',18)
print(stu.country)#China
stu.country='1'
print(stu.country)#格式有误&China
del stu.country #删除成功  
class Bmi():
    def __init__(self, weight, height):
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / self.height ** 2
stu = Bmi(70, 1.8)
# print(stu.get_bmi())添加property前调用方式
print(stu.bmi) # 添加property后调用方式,伪装成属性访问方法

三大特征之继承

	"""
		1. 什么是继承?
			继承就是新建类的一种方式,新建出来的类被称为子类或者派生类,被继承的类称为父类或者基类
			子类可以继承父类的所有属性和方法
		2. 为什么要用继承?
			类:解决了对象与对象之间的代码冗余问题
			继承:解决了类与类之间的代码冗余问题
			子类就拥有了父类的所有数据和方法
		3. 怎么使用继承?
			经典类:没有继承object类的子子孙孙类都是经典类
			新式类:继承了object类的子子孙孙类都是新式类
			只有在python2中才区分经典类和新式类,在python3中,全部都是新式类
		    1. 在子类括号里面写上继承的父类名字,括号内可以写多个继承的父类
		    2. 子类可以遗传父类的所有属性和方法
		    3. 子类继承父类之后,可以使用父类的所有方法和属性,父类不能使用子类的
            
         4. 查看继承的类
         	print(类名.__bases__)
         	
         5. 继承后对象属性查找顺序
         对象自身>产生对象的类>产生对象的类所继承的类
	"""
class Student():
    school='SH'
    def __init__(self,name,age,gender):
        self.name=name
        self.age=age
        self.gender=gender

    def course(self):
        print(f'{self.name}在选课')

class Teacher():
    school='SH'
    def __init__(self,name,age,gender,level):
        self.name=name
        self.age=age
        self.gender=gender
        self.level=level

    def score(self):
        print(f'{self.name}在评分'
# 上述代码太过冗余,继承则能解决类之间代码冗余问题
class People():#定义父类,子类相似属性代码都写到父类中,用来给子类继承;
    school='SH'
    def __init__(self,name,age,gender):
        self.name=name
        self.age=age
        self.gender=gender

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

    def course(self):
        return f'{self.name}在选课'

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

    def score(self):
        return f'{self.name}在评分'

stu=Student('KEVIN',18,'MALE')
print(stu.course())#KEVIN在选课
teacher=Teacher('tony',38,'MALE',10)
print(teacher.score())#tony在评分

单继承下的属性查找

单继承子类只继承一个父类,括号中的类名只能写一个
'''
    单继承下的属性查找顺序:
        1、先从对象自己的名称空间中查找
        2、找不到再从产生这个对象的类中查找
        3、类中找不到再去继承的父类中查找
        一直找到没有父类为止,再找不到就报错了
'''
class Foo():
    def f1(self):
        print('from Foo.f1')

    def f2(self):
        print('from Foo.f2')
        self.f1()  #obj调用  此时的self就是obj

class Bar(Foo):
    def f1(self):
        print('from Bar.f1')
obj = Bar()
obj.f2()
>>from Foo.f2
  from Bar.f1
class Foo():
    def __f1(self):            # _Foo__f1()
        print('from Foo.f1')

    def f2(self):
        print('from Foo.f2')
        self.__f1()            # self._Foo__f1()隐藏属性在什么类里面 _的一定就是当下的属性名,隐藏属性对外不对内

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

obj = Bar()
obj.f2()

>>from Foo.f2
  from Bar.f1


多继承下的属性查找


#多继承就是括号里面可以有多个父类,继承的父类中也可以有多个父类
非菱形查找:按照继承类从从左往右的顺序一个个分支去查找
菱形查找:
1. 经典类(仅py2中存在)
	# 查找顺序:多继承情况下 在要查找属性不存在时,会按照深度优先的方式查找,从继承的类中从左往右依次查找,从第一个分支一直找到顶点
    不继承object或者其子类的类
2. 新式类(python3)
	# 查找顺序:多继承情况下 在要查找属性不存在时,会按照广度优先的方式查找,从继承的类中从左往右依次查找,从最后一个分支找到顶点
    所有类默认都继承object
class G: # 在python2中,未继承object的类及其子类,都是经典类
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试
在py3中查找
class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证

super关键字

派生的核心思想是子类继承了父类还可以增加新的功能

  • 例1
#用super调父类的方法super().__init__(name,age,gender),自己在拓展增加写新的功能self.sid=sid
class People():
    def __init__(self,name,age,gender):
        self.name=name
        self.age=age
        self.gender=gender
        
class Student(People):
    def __init__(self,name,age,gender,sid):
        super().__init__(name,age,gender)#子类调用父类的方法
        self.sid=sid
        
class Teacher(People):
    def __init__(self,name,age,gender,level):
        super().__init__(name,age,gender)#子类调用父类的方法
        self.level=level
    
stu1=Student('kevin',19,'male',11) 
print(stu1.__dict__)#{'name': 'kevin', 'age': 19, 'gender': 'male', 'sid':11}
tea1=Teacher('tony',29,'male',1)
print(tea1.__dict__)#{'name': 'tony', 'age': 29, 'gender': 'male', 'level':1}
  • 例2
class People():
    school = 'SH'

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


class Student(People):
    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        super(Student, self).__init__(name, age, gender)  # 不能说继承的就是父类,不严谨   
        '''    
        1、这样的调用方式,其实是不依赖于继承的,指名道姓的调用父类__init__方法
         People.__init__(self, name, age, gender)  
        2、在py3中的写法,super依赖于继承,self参数不写
         super().__init__(name, age, gender)
        3、在py2中的写法,super依赖于继承
         super(Student, self).__init__(name, age, gender)
        以上三种方法使用其中一种即可
        super(Student, self) 返回的是一个特殊对象
        super关键字的查找顺序是按照类的mro列表顺序依次查找
        '''
        self.courses = course

    def choose_course(self):
        pass
    
tea=Teacher('yy',19,'male',10)
print(tea.__dict__)#{'name': 'yy', 'age': 19, 'gender': 'male', 'level': 10}
stu=Student('kk',19,'male')
print(stu.__dict__)#{'name': 'kk', 'age': 19, 'gender': 'male', 'course': []}
stu.course.append('python')
print(stu.__dict__)#{'name': 'kk', 'age': 19, 'gender': 'male', 'course': ['python']}
  • 例3
class Mylist(list):
    def append(self,values):#重写了append方法
        if values =='name':#如果追加的值为'name',则输出提示信息并返回
            print('name不能追加')
            return

        super().append(values)#否则调用父类的append方法进行追加操作
        print(f'{values}追加成功')
        
obj=Mylist()
print(obj,type(obj))#[] <class '__main__.Mylist'>
obj.append('name')#name不能追加
obj.append(123)#123追加成功
obj.append(456)#456追加成功
print(obj)#[123, 456]

mro列表

用于解决多继承时方法调用顺序问题

'''
遇到多继承中出现了super关键字,如何更准确的知道它的查找顺序,我们需要打印出类的mro列表;
输出mro列表:类名.mro()\类名.__mro__
查看一个类的mro列表, 看一个类的mro列表,要从起始类开始看
'''
class A():
    def test(self):
        super().test()

class B():
    def test(self):
        print('from B')

class C(A,B):
    pass

c=C()
c.test() #from B
print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

a=A()
print(A.mro())#[<class '__main__.A'>, <class 'object'>]
a.test()#报错AttributeError: 'super' object has no attribute 'test'

Mixins机制

Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的

class Vehicle:  # 交通工具
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")

class CivilAircraft(Vehicle):  # 民航飞机
    pass

class Helicopter(Vehicle):  # 直升飞机
    pass

class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
    pass

如上所示民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车显然不应该拥有此功能

class Vehicle:  # 交通工具
    pass

class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")

class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass

class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass

class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路

上面的CivilAircraft、Helicopter类实现了多继承,不过它们继承的第一个类我们起名为FlyableMixin,这个类是一个Mixin类表示其作为功能的辅类,而不是作为主类

  • 使用Mixin类注意点

    对于mixin类的命名方式一般以 Mixin, able, ible 为后缀

    继承的位置在主类左边

三大特征之多态

多态与多态性

多态指的是一类事物有多种形态,比如动物有多种形态:猫、狗、猪;比如水有多种形态:液态、气态、固态
多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用

class Animal():  
    def speak(self):  
        pass 
        
class People(Animal):
	def speak(self):  #父类中的方法名speak一定要有
	print('你好')
    
class Cat(Animal):
	def speak(self):  #父类中的方法名speak一定要有
	print('喵喵')  
        
stu=People()
stu.speak()
>>你好

多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名

强制约束#不推荐使用
"""
1.父类只能被继承不能被调用产生对象
2.父类里面的方法不再实现具体的功能,父类属性和方法不是让子类来继承的
3.定义继承父类的子类中一定要有父类这个方法,抽象类中的父类强制限制子类的行为
"""

import abc  # 1.导入模块,abc为abstract class  抽象类的简写

class Animal(metaclass=abc.ABCMeta):# 2.指定metaclass属性将类设置为抽象类,抽象类用来约束子类的,不能实例化
    @abc.abstractmethod # 3.抽象方法,该装饰器限制子类必须定义有一个名为speak的方法 
    def speak(self):  
        pass 

class People(Animal):
	def speak(self):  #父类中的方法名speak一定要有
	print('你好')
    
class Cat(Animal):#父类Animal只能被继承不能被调用产生对象
	def speak(self):  #父类中的方法名speak一定要有
	print('喵喵') 
    
obj=Animal()#报错,不可再实例化对象        
obj=People()
obj.speak()#你好

鸭子类型

不强制约束(鸭子类型)# 推荐使用
"鸭子类型:它是一种动态的编程风格,我们更多关注的是对象的方法或属性,而不是对象的类型,我们不用在使用type或者isinstance查出它的类型了,通过多态提高了灵活性,多态带来的特性在不考虑对象的数据类型下,直接调用的对应方法"

class People():
    def speak(self):
        print('你好')

class Dog():
    def speak(self):
        print('旺旺')

class Cat():
    def speak(self):
        print('喵喵')

def animal(obj):
    return obj.speak()

xiaohong=People()
xiaohuang=Dog()
xiaoju=Cat()

animal(xiaohong)#你好
animal(xiaohuang)#旺旺
animal(xiaoju)#喵喵

魔法

Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类,这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发

  • __new__
"""当Python实例化一个对象时,首先调用__new__()方法构造一个类的实例,并为其分配对应类型的内存空间,该实例的内存地址就是它的唯一标识符。然后再调用__init__()方法对实例进行初始化,通常是对该实例的属性进行初始化"""
class Person(object):
    
    def __new__(cls):
        print("__new__ called")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__ called")
		  
a = Person()
		
"""输出结果
__new__ called
__init__ called
"""
  • __init__
'''初始化方法,调用类的时候自动触发,给对象添加数据时自动触发,里面有一个self参数,用来接收对象的'''
class C(object):
    def __init__(self,name):
        self.name=name
        print('init')
obj=C('kevin') #init  
  • __str__,__repr__
"""1.打印对象或者输出对象时自动触发
   2.必须有返回值,且返回值为字符串类型,否则报错
   3.若两者同时存在类中,只会执行__str__,执行优先级高于__repr__
   4.一般情况都使用__str__,偶尔会遇到__repr__
   """
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return 'repr执行了'
        # return 123 必须返回字符串类型
    
    def __str__(self):
        print('我执行了!!!')
        return '字符串类型'

stu = Student('kevin', 20)
print(stu)# 我执行了!!! 字符串类型
print('from repr',repr(stu)) #repr执行了
print('from str',str(stu))# 我执行了!!! 字符串类型
  • __del__
"""1.删除对象的时候自动触发
   2.当程序全部执行完毕,也会自动调用触发
   3.一般用在回收系统资源的时候使用"""
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age   
    
    def __del__(self):
        print('触发了')
        
tu = Student('kevin',20)
del stu #触发了  # 1. 删除对象的时候自动触发__del__方法
print(123)#123 触发了 # 2. 当程序全部执行完毕,也会自动调用触发__del__
  • __enter__
'''当对象被当做with上下文管理操作的开始自动触发,并且该方法返回什么,as后面的变量名就接收到什么'''
class Open():
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print('首先执行enter')
        return '我返回啥f就接收啥'

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('最后执行exit')

with Open('a.txt') as f:
    print('其次执行with语句')
print(f)
"""输出结果
首先执行enter
其次执行with语句
最后执行exit
我返回啥f就接收啥
"""
  • __exit__
'''当对象参与的with语句执行完毕后自动触发'''
class Open:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):#三个参数分别代表异常类型、异常值和追溯信息
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)#<class 'AttributeError'>
        print(exc_val)#<class 'AttributeError'>
        print(exc_tb)#<traceback object at 0x0000022ACA8824C0>
       #return True 如果__exit()返回值为True,那么异常会被清空,with后的语句正常执行

with Open('a.txt') as f:
    print('=====>执行代码块')
    raise TypeError('***着火啦,救火啊***')  # 它是主动抛出异常
print('0'*100)  #0重复100次,抛出异常后此行代码就不执行,如果__exit()返回值为True,那么此行代码就会执行

总结:__enter__,__exit__
1、出现with语句,首先对象的__enter__被触发,有返回值则赋值给as声明的变量
2、其次开始执行with代码块
3、with中代码块执行完毕后再执行__exit__方法
4、当with代码块出现异常,在__exit__方法中能够得到异常的信息(异常类型、异常值、追溯信息)
如果在__exit__方法中没有return True,则with语句后的代码块不再执行;当有return True时不再抛出异常,with语句后的代码块正常执行

  • __getattr__,__setattr__,__delattr__
"""__getattr__当调用属性且使用句点符查找不存在属性时触发
   __setattr__当使用句点符修改属性或者增加属性值时自动触发,因此不能再该方法内用句点符方式赋值 self.name = value 会死循环,而是用self.__dict__[key] =value方式增加属性
   __delattr__当使用句点符删除属性时会自动触发,因此不能再该方法内用句点符方式删值 sdel self.item 会死循环,而是用self.__dict__.pop(item)方式删除属性"""

class Foo:
    x = 1
    
    def __getattr__(self, item):# 查找的属性不存在的时候会执行
        print('item:%s' % item)
        print('----> from getattr:你找的属性不存在')

    def __setattr__(self, key, value):#当属性不存在时添加、修改属性就会执行
        print('----> from setattr')
        print(key,value)
       # self.key=value #无限递归了
        self.__dict__['z']=2 #用此方式增加属性
        
    def __delattr__(self, item): #del 删除属性的时候会触发
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop('z')
obj =Foo(1)
obj.z =1
del obj.z
print(obj.__dict__)#{}
  • __setitem__,__getitem__,__delitem__
"""__getitem__当属性通过中括号取值时会自动触发
   __setitem__当属性通过中括号赋值时自动触发
   __delitem__当属性通过中括号删除值时自动触发"""
class Foo:
    def __init__(self, name):
        self.name = name

    def __getitem__(self, item):#对象通过中括号取值时会自动触发__getitem__
        print(item)#name
        print('__getitem__执行了')
        print(self.__dict__[item])#kevin


    def __setitem__(self, key, value):# 对象通过中括号改值的时候,会自动触发的,并且,key就是中括号中得key,value就是=右边的值
        print(key, value)#age 19
        print('__setitem__执行了')
        self.__dict__[key] = value

    def __delitem__(self, key):#del对象中括号属性时删除
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)

obj=Foo('kevin')
print(obj.name)#kevin
print(obj['name'])
obj['name']#__getitem__执行了
obj['age']=19#__setitem__执行了
del obj['age']#del obj.key时我执行
  • __call__
"""当给对象加括号调用时自动触发,__call__方法返回什么对象调用之后就返回什么
后续课程flask框架源码的时候,__call__方法就是入口
"""
class Foo:
    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):# 对象加括号的时候会自动触发执行
        print('__call__执行了')
        print(args,kwargs)
        return 123 

obj1=Foo()
print(obj1(123,321,name='kevin'))
"""输出结果
__call__执行了
(123, 321) {'name': 'kevin'}
123
"""
  • isinstance
"""判断对象是否为某个类的实例,判断某个数据值是不是某个数据类型"""
class Student():
    def __init__(self,name):
        self.name=name
stu =Student('kevin')
print(isinstance(stu,Student))#True
print(isinstance('123',str))#True
  • issubclass
"""判断一个类是否某个类的子类"""
class Student(object):
    def __init__(self,name):
        self.name=name
stu =Student('kevin')
print(issubclass(Student,object)) #True
  • __doc__
"""查看类内部的注释"""
class Foo:
    """这是注释"""
    pass

class Bar(Foo):
    pass

print(Foo.__doc__)#这是注释
print(Bar.__doc__)#None  #该属性无法继承给子类

反射

在Python中,反射指的是'利用字符串来操作对象的属性和方法

涉及到四个内置函数的使用:
1.hasattr()#判断对象是否有某个字符串对应的方法名    
2.getattr()#根据字符串获取对象对应的属性名(值)或方法名(函数内存地址)
    '''如果第二个参数是属性的话,直接返回属性的值
   如果属性不存在会报错
   如果写了第三个参数,当属性不存在的时候,会把第三个值返回
   如果第二个参数是函数名,那么返回的就是函数内存地址,如果想调用直接返回结果加括号
	'''
3.setattr()#根据字符串给对象添加或修改数据
"""
给对象增加属性, 属性名不存在则增加属性
给对象增加属性,属性名存在则修改属性
"""
4.delattr()#根据字符串删除对象数据名或属性名,不可删除对象的类名字
class C1:
    school_name='小姐姐学院'

    def choice_course(self):
        print('大宝贝在选课')

obj =C1()

print(hasattr(obj,'school_name'))#True
print(getattr(obj,'school_name'))#小姐姐学院
print(getattr(obj,'choice_course'))#<bound method C1.choice_course of <__main__.C1 object at 0x000001C950B95F40>>
print(getattr(obj,'schoolname'))#属性不存在报错
print(getattr(obj,'school_name',999))#小姐姐学院#写了第三个参数,属性存在返回属性名,属性名不存在返回第三个参数
print(getattr(obj,'schoolname','不存在'))#不存在

setattr(obj,'name','jason')
print(obj.__dict__)#{'name': 'jason'}
setattr(obj,'name','123')
print(obj.__dict__)#{'name': '123'}

delattr(obj,'name')
print(obj.__dict__)#{}
delattr(obj,'school_name')#报错,不可删除对象所属类中的名字
  • 例1
#需求判断用户输入的名字是否可以被使用(存在)
input输入的是字符串类型,而属性名方法名变量名却不是字符串类型,反射则可以解决此问题利用字符串来操作对象的数据和方法
class C1():
    school_name='小姐姐学院'

    def choice_course(self):
        print('大宝贝在敲代码')

obj =C1()

while True:
    target_name =input('请输入想要操作的名字:').strip()
    if hasattr(obj,target_name):
        print('系统中有该方法')
        data_or_func =getattr(obj,target_name)##获取名字对应的属性值/函数地址
        if callable(data_or_func):##callable判断对象是否可调用
            print('本次执行的是%s方法'% target_name)
            data_or_func()#调用函数
        else:
            print('本次执行的是%s属性'% target_name)
            print(data_or_func)

    else:
        print('系统中没有该名字')
 
#输出结果
请输入想要操作的名字:xxx
系统中没有该名字
请输入想要操作的名字:school_name
系统中有该方法
本次执行的是school_name属性
小姐姐学院
请输入想要操作的名字:choice_course
系统中有该方法
本次执行的是choice_course方法
大宝贝在敲代码
  • 例2
#需求打印配置文件中大写的变量名
settings.py文件
NAME='kevin'
AGE=18
desc= ABC
info= 123

执行文件
import settings
print(dir(settings))#查看settings文件可使用的名字

useful_dict={}
for name in dir(settings):
    if name.isupper():
        useful_dict[name] =getattr(settings,name)
print(useful_dict) #{'NAME':'kevin','AGE':18 }       
        

while True:
    target_name=input('输入方法名')
    if hasattr(settings, target_name):
        print(getattr(settings, target_name))
    else:
        print('该模块没有该方法名')
  • 例3
class FtpServer:
    def serve_forever(self):
        while True:
            inp = input('input your cmd>>: ').strip()  #  get a.txt  put a.txt
            cmd, file = inp.split()  #['get', 'a.txt']

            #  hasattr(self, 'get')
            #  hasattr(self, 'put')
            if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性

                # getattr(self, ’get‘) get或者put的内存地址
                func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性
                func(file) #调用get或者put函数

    def get(self, file):
        print('Downloading %s...' % file)

    def put(self, file):
        print('Uploading %s...' % file)

server = FtpServer()
server.serve_forever()
  • 补充
1. 以上4个方法中得第一个参数也可以写模块名
import time
time.sleep(3)

res=getattr(time,'sleep')
print(res)#<built-in function sleep>
res(3)#调用sleep函数

2. __import__
# 通过字符串的形式导入模块
time=__import__('time')
time.sleep()

random = __import__("random")
res=random.randint(0,9)
print(res)#7
#父类限制子类行为,在父类中会用raise方法提示
class Animal():
    def speak(self):
        raise Exception('必须实现speak方法')

class People(Animal):
    def speak(self):#子类必须使用父类的方法,强制限制
        pass

obj=People()
obj.speak()

元类

简介:即产生类的类

下示得出结果所有类都为type类产生,这个类称为元类

print(type(123)) #<class 'int'>
print(type([123])) #<class 'list'>
print(type(int)) #<class 'type'>
print(type(list)) #<class 'type'>

class A():
    pass
print(type(A)) #<class 'type'>,用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type

print(type(type)) #<class 'type'>

"综上得出结果所有类都为type类产生,这个类称为元类"

元类与类.jpg

产生类的两种方式

  • class关键字

    class关键字在帮我们创建类时,必然帮我们调用了元类,那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分

    1、类名class_name='StanfordTeacher'
    
    2、基类们class_bases=(object,)
    
    3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的
    
    
    class StanfordTeacher():
           pass 
    
  • type关键字

    由于所有类都是有type类造出来的,所以我们也可以使用type类造出来一个新类

    type(object_or_name, bases, dict)
        # 这三个条件就是造出来类的三个要素.
    type("类名", "父类", "类的名称空间")
    

自定义元类

元类是造出类的类,所以,我们可以对类进行高度的定制化

举个例子:我让你写出来的类名必须是首字母大写?
思考:类是如何产生的?类是由元类产生的type类,如果想改类的行为,应该在元类里的__init__方法中改.
	对象是如何产生的? 是执行了产生这个对象的类内部的__init__方法
			
推导:想改父类的__init__方法, 但是又不能修改源代码,所以我们可以写出来一个子类来继承父类type,在子类里面写代码,然后再重新执行一些父类的__init__方法.

class MyTypeClass(type):
    def __init__(self, cls_name, cls_bases=None, cls_dict=None):
        print(cls_name, cls_bases, cls_dict)  # C1 () {'__module__': '__main__', '__qualname__': 'C1'}

        # 限制类名必须是首字母大写
        if not cls_name.istitle():
            raise Exception("类名必须是首字母大写")
        super().__init__(cls_name, cls_bases=None, cls_dict=None)
class C1(metaclass=MyTypeClass):
    school = 'Sh'

class a(metaclass=MyTypeClass):#元类不能直接继承
    pass

# obj = C1()
obj = a()
#限制传参的方式必须是关键字传参
思考:对象加括号会自动调用类的__call__方法,并且,__call__方法里面返回什么,那么,对象加括号的位置就是什么,类名()会怎么样?
推导:类名(),应该是会执行产生类的类中的__call__方法,而这个类恰好是元类type
class MyClass(type):
    def __call__(self, *args, **kwargs):
        print("__call__ 执行了")
        print(args, kwargs)


        if args:#限制传参的方式必须是关键字传参,只需要args是空就行
            raise Exception("传参必须是关键字传参")

        super(MyClass, self).__call__(*args, **kwargs)


class C1(metaclass=MyClass):
    def __init__(self, name):
        print("__init__ 执行了")
        # self.name = name

"""看C1类里面的__init__方法和元类里面的__call__方法的执行顺序"""
# 得出结论,其实在执行类的__init__之前,先执行了__call__方法

obj = C1(name='kevin')  # 都是位置传参,强制要求必须是关键字传参
posted @ 2023-06-26 21:51  爆炸小龙虾  阅读(37)  评论(0)    收藏  举报