python类(class)-面向对象

面型对象的三大特性

  • 封装
  • 确保对象中的数据安全
  • 继承
  • 保证了对象的可扩展性
  • 多态
  • 保证了程序的灵活性

类的封装

  • 封装是面向对象的三大特性之一

  • 封装是指隐藏对象中一些不希望被外部所访问的属性或方法

  • 如果要隐藏一个类型的属性

    将对象的属性名,修改为一个外部不知道的名字 (差不多就是自己骗自己这种感觉,外部还是能改的)

  • 如何获取(修改)对象中的属性

    需要提供一个getter和setter方法,使外部可以访问到属性
    getter 获取对象中指定的属性 (get_属性名)
    setter 用来设置对象的指定属性(set_属性名)

  • 使用封装确实增加了类的定义复杂程度,但是它也确保了数据的安全性
    1.隐藏了属性名,使调用者无法随意的修改对象中的属性
    2.增加了getter和setter的方法,很好的控制了属性是否是只读的
    3.使用setter方法设置属性,可以增加数据的验证,确保数据的值是正确的的
    4.使用getter方法获取属性,使用stter方法设置属性可以在读取属性和修改属性的同时做一些其他的处理
    5.使用getter方法可以表示一些计算的属性,比如说在获取属性时,会自动输出一个属性,或者做一些运算等

  • 如果希望属性是只读的,则可以直接去掉setter方法

  • 如果希望属性不能被外部访问,则可以直接去掉getter方法

    # 定义一个狗类
    class Dog():
        # 定义初始化方法
        def __init__(self,name,old,gen):
    	  # 在里面定义属性
          self.hidden_name = name
          self.hidden_old = old
          self.hidden_gen = gen
    
        # 定义修改name属性的方法
        def set_name(self,name):
            self.hidden_name = name
    
        # 定义查看name属性的方法    
        def get_name(self):
            return self.hidden_name
    
        # 定义修改old属性的方法
        def set_old(self,old):
            # 年龄没有负数,所以这里要用一个判定如果对方修改的年龄小于0则不作任何操作
            if old >= 0 :
                self.hidden_old  = old   
        # 定义查看old属性的方法
        def get_old(self):
            return self.hidden_old
            
        # 定义修改gen属性的方法
        def set_gen(self,gen):
            self.hidden_gen = gen
        # 定义查看gen属性的方法
        def get_gen(self):
            return self.hidden_gen
    
        # 定义的通用方法
        def jiao(self):
            print('%s正在叫'%self.hidden_name)
    
        def pao(self):
            print('%s正在跑'%self.hidden_name)
    
        def shui(self):
            print('%s正在睡觉'%self.hidden_name)
    
    white = Dog('小白','1','man')
    white.jiao()
    
    white.pao()
    
    white.shui()
    
    
    # 这里是white这个对象在调用dog类中的方法,跑,叫,睡觉这些
    
    # 这个是调用方法来修改white的属性,这个属性如果设置不可查看或者不可修改,可以把上面查看和修改的方法去掉
    print('修改姓名前',white.get_name())
    white.set_name('小黑')
    print('修改姓名后',white.get_name())
    
    # 输出结果:
    	# 修改姓名前 小白
    	# 修改姓名后 小黑
    
    # 属性被修改了

类的一些补充

  • 可以为对象的属性使用双下划线开头 __xxx

  • 双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问

  • 双下划线开头的是隐藏属性,隐藏属性只不过是python自动为属性改了一个名字

  • __属性名 实际上是将名字修改为了 类名.__属性名

    这种的其实还是在外部可以改内部的属性值的

类中的装饰器

  • property装饰器用来将一个方法,装换为对象的属性
  • 添加property装饰器后,我们就可以像调用属性一样,调用方法
  • 作用

    把方法变为属性,只可以用来访问

    class student():
    
        def __init__(self,name,age,score):
            self.name = name
            self.age = age
            self.score = score
        @property         # 加入装饰器后
        def get_name(self):
            return self.name
        @property          # 加入装饰器后
        def get_age(self):
            return self.age
        @property         # 加入装饰器后 
        def get_course(self):
            return max(self.score)
        
    f1 = student('kkk',17,(100,97,98))
    f2 = student('iii',11,(70,60,10))
    
    print(f1.get_name)           # 调用方法
    print(f1.get_age)            # 调用方法             
    print(f1.get_course)
    
    # 加入装饰器后和调用属性差不多,就是调用方法的()没了
  • setter装饰器
  • @属性名.setter装饰器
  • 作用

    把方法变为属性,对象可以直接赋值来修改

  • 这个装饰器是在property下一级的,没有property就不可以用这个装饰器
  • (这两个装饰器是一起用的,property的用来访问,setter的用来修改)
    # 定义一个类
    class Person:
    	# 初始化类的属性
        def __init__(self,name):
            self._name = name
    	# 定义带有property装饰器的方法
        @property
        def get_name(self):
            return self._name
    	# 定义带有 setter装饰器的方法 (这里的属性名是property装饰器下的方法,因为是修改get_name下的属性)
        @get_name.setter
        def set_name(self,name):
            self._name = name
    
    # 创建一个类的实例 并给f1的name属性赋值  
    f1 = Person('lmk')
    # 先打印看一下f1的属性值
    print(f1.get_name)
    # 结果是 lmk
    
    # 修改 f1的name属性 (通过setter装饰器修改的)
    f1.set_name = 'kml'
    # 打印修改后的结果
    print(f1.get_name)
    # 结果是 kml
    
    # 这样调用方法就好像调用属性一样,   
    		# 但是setter这个装饰器
    		# self._name = name	
    		# name必须加上_让她变成隐藏的属性才能用,要不然会报错
    

继承

  • 有一个类能够实现我们需要的大部分功能,但是不能实现全部功能

  • 如何让这个类实现全部功能?

    1. 直接修改这个类,在类中添加我们需要的功能,
      修改起来会比较麻烦,并且会违反ocp原则
    2. 直接创建一个新类,
      创建一个新类比较麻烦,并且会出现大量的复制粘贴,出现大量的重复代码
    3. 直接从一个类中来继承他的属性和方法
  • 继承是面向对象的三大特性之一

  • 通过继承,我们可以使一个类获取到其他类中的属性和方法

  • 在定义类时,可以在类名后的括号中指定当前类的父类,(超类,基类,super)

  • 子类(衍生类)可以直接继承父类中的所有属性和方法

  • 通过继承可以直接让子类获取到父类的方法或属性,避免编写重复性的代码,并且也符合ocp原则,

  • 所以我们经常需要通过继承来对一个类进行扩展

    #定义一个父类
    class lmk:
    	def run(self):
    		print('动物会跑~~~')
    
    	def sleep(self):
    		print('动物睡觉~~~')
    
    # 定义子类 继承父类
    class kml(lmk):
    	def bark(self):
    		print('www')
    
    a = kml()
    
    a.run() 
    a.sleep()
    a.bark()
    # 定义父类后,在用子类继承父类,父类的所有属性和方法,在子类都可以使用,并且在子类中还可以扩展方法,
    # 在子类的方法,父类是不可以使用的
    		
    # 在创建子类时,如果省略不写父类,则默认父类为 object
    # object是所有类的父类,所有类都继承字object

    在创建子类时,如果省略不写父类,则默认父类为 object
    object是所有类的父类,所有类都继承字object

  • issubclass() 检查一个类是否是另一个类的子类

    • 参数, 1,子类 2,父类
      print(issubclass(kml,lmk))
      # 返回结果是 true
  • isinstance() 用来检查一个对象是否是一个类的实例

    • 参数,1,对象, 类
      print(isinstance(a,lmk))
      print(isinstance(a,kml))
      # 这两个结果都是一样的
      
      # 所有的对象都是object的实例
      # print(isinstance(a,object))
      # 返回true

继承-重写

  • 如果在子类中有和父类同名的方法,则,通过子类实例去调用方法时,
  • 会调用子类的方法,而不是父类的方法,这个特点我们叫做方法的重写,(覆盖,override)
    #定义一个父类
    class lmk:
    	def run(self):
    		print('动物会跑~~~')
    
    	def sleep(self):
    		print('动物睡觉~~~')
    # 定义子类
    
    class kml(lmk):
    
    	# 在子类添加一个run方法
    	def run(self):
          	print('动物会跳')
    
    	def bark(self):
    		print('www')
    
    a = kml()
    # 调用run方法
    print(a.run())
    # 输出结果   动物会跳
    
    # 如果调用父类的方法,则输出结果还是动物会跑
  • 当我们调用一个对象的方法时,
  • 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
  • 如果没有,则去当前对象的父类中寻找,如果父类中有,则直接调用父类的方法,
  • 如果父类没有,则去父类的父类中寻找,以此类推,直到找到object,如果依然没有找到,就报错

子类在调用方法是会优先在当前类中寻找有没有这个方法,没有就往上找

super()

  • 子类继承父类,父类的所有方法都会被子类继承,包括特殊方法,也可以重写特殊方法
  • super()可以获取当前类的父类
  • 并且通过super()返回对象调用父类方法时,不需要传递self
  • 格式:
    super().__init__(参数1,参数2)
    # 定义一个父类
    class hello:
    	def __init__(self,name,age):
    		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
    	@name.setter
    	def name(self,age):
    		self._age =  age
    
    #定义一个子类
    class kml(hello):
    	def __init__(self,name,age,hhh):
    	    # 通过super()函数接收父类的特殊方法,所以子类在创建对象时,把这个两个参数传递给父类(name,age)
    		super().__init__(name,age) 
      		self._hhh = hhh
    	def get_hhh(self):
    		return self.hhh
    
    # 创建子类的一个对象 
    a = kml('kkk',18,'hello')
    # kkk是父类的name 18是父类的age hello是子类的hhh
    
    print(a._name)
    print(a._age)
    print(a._hhh)

多重继承

  • python是支持多重继承的,也就是我们可以为一个类同时指定多个父类

  • 可以在类名()后面添加多个类,实现多重继承

  • 多重继承,会使子类同时拥有多个父类,并且会获取到所有父类中的方法

  • 在开发中如果没有特殊的情况,应该尽量避免使用多重继承,因为多重继承会让我们的代码过于复杂

  • 如果多个父类中有同名的方法,则会在第一个父类中寻找,然后找第二个一次类推

  • 也就是父类中有同名的方法前面的父类会覆盖后面父类的同名方法

  • 类名.__bases__

  • 这个属性会获取当前类的所有父类

    print(kml.__bases__)
    # (<class '__main__.hello'>,)
    # 父类的顺序是在继承括号中先写那个父类那个父类就是前面的父类

多态

  • 多态是面向对象的三大特性之一

  • 多态从字面理解就是多种形态

  • 就是一个对象可以以不同的形态去呈现

    # 定义两个类
    class A:
        def __init__(self,name):
            self._name = name
    
        @property
        def name(self):
            return self._name
            
        @name.setter
        def name(self,name):
            self._name = name   
    
    class B:
        def __init__(self,name):
            self._name = name
    
        def __len__(self):
            return 10            # 通过len()来返回的值,return定什么值,len()函数就返回什么值
    
        @property
        def name(self):
            return self._name
            
        @name.setter
        def name(self,name):
            self._name = name   
    
    # 定义一个函数
    def say_hello(obj):
        print('你好 %s'%obj.name)
    
    # 对于这个函数来说只要对象中含有name属性,它就可以作为参数传递
    # 这个函数不会考虑对象的类型,只要有name属性即可
    
    # 再定义一个函数
    def say_hello_2(obj):
        # 做类型检查
        if isinstance(obj , A):
            print('你好 %s'%obj.name) 
    # 在这个函数中我们做了一个类型检查,也就是obj是A类型的对象时,才可以正常使用
    # 其他类型的对象都无法使用该函数,这个函数就违反了多态
    # 违反了多态的函数,只适用于一种类型的对象,无法处理其他类型的对象,这样导致函数的适应性非常差
  • 上面代码的len()

    之所以一个对象能通过len()来获取长度,是因为对象中具有一个特殊方法__len__
    换句话说,只要对象中具有__len__特殊方法,就可以通过len()来获取它的长度
    这个是输出类中定义对象的len

  • 如果打印对象的属性的话该多长还是多长

    print(len(b.name))

类中的属性和方法

  • 属性
  • 类属性
  • 实例属性
  • 方法
  • 类方法
  • 实例方法
  • 静态方法

  • 类属性,

    直接在类中定义的属性

  • 类属性可以通过类或类的实例对象访问到

  • 但是类属性只能通过类本身修改,无法通过实例对象修改,实例对象只能是覆盖类的属性,就是给实例对象重新赋值

  • 实例属性

    通过实例对象添加的属性属于实例属性

  • 实例属性只能通过实例对象修改和访问,类对象无法修改

  • __init__方法是定义实例属性的位置

    例如
    class kml:
       pass
    
    a = kml()	
    a.age = 19   # 这就是实例属性
  • 实例方法,

    在类中定义,以self为第一个参数的方法都是实例方法

  • 实例方法在调用时,python会将调用对象作为self传入

  • 实例方法可以通过实例和类去调用

  • 当通过实例去调用时会自动将当前调用对象作为self传入

  • 当通过类去调用时,不会自动传递self,此时我们必须手动传递self

    class kml:
    	def test(self):
           	print('这是test方法~~~ ' , self)
    
    a = kml()
    a.tset() # 通过实例对象调用
    kml.test(a) # 通过类调用  必须传入一个实例对象
    
    # a.test() 和 kml.test(a) 是相等的
    # 一个是通过实例来调用方法 ,解释器默认传入调用的对象
    # 一个是通过类来调用方法并把实例参数传入
    
  • 类方法

    在类内部使用@classmethod来修饰的方法属于类方法

  • 类方法的第一个参数是cls 也会被自动传递,cls就是当前类对象

  • 类方法和实例方法的区别,实例方法的第一个参数是self,而类方法的第一个参数是cls

  • 类方法可以通过类去调用,也可以通过实例去调用,没有区别

    class kml:
    	@classmethod
    	def lei(cls):
    		print('这是类方法')
    kml.lei()
    a.lei()
    # 类和对象都可以进行调用
  • 静态方法

    在类中使用@staticmethod来修饰的方法数据静态方法

  • 静态方法不需要指定任何的默认参数

  • 静态方法可以通过类和实例去调用

    静态方法,基本上就是一个和当前类无关的方法,他只是一个保存到当期类中的函数

  • 静态方法一般都是一些工具方法,和当前类无关

    class kml:
    	@staticmethod
    	def jing_tai():
    		print('这是一个静态方法')
    			
    kml.jing_tai()
    a.jing_tai()
    # 类和对象都可以调用 并且不用传递任何参数 (没有self 没有 cls)

垃圾回收

  • 程序在运行过程中会产生垃圾
  • 并且会影响程序运行的运行性能,所以这些垃圾必须被清理
  • 在程序中没有被引用的对象就是垃圾,所谓垃圾回收就是将垃圾对象从内存中删除
  • 在python中有自动的垃圾回收机制,它会自动将这些没有被引用的对象删除
  • 所以我们不用手动处理垃圾回收

python的垃圾回收机制

class A:
    def __init__(self):
        self.name = 'A类'

    # del是一个特殊方法,它会在对象被垃圾回收前调用
    def __del__(self):
        print('A()对象被删除了~~~',self)

a = A()
b = a       # 又使用一个变量b,来引用a对应的对象

print(a.name)
input('回车键退出...')

# 回车后python才会进行垃圾回收 在a回收之前会执行 __del__这个特殊方法
# 输出结果 A类
		# A()对象被删除了~~~
class A:
    def __init__(self):
        self.name = 'A类'

    # del是一个特殊方法,它会在对象被垃圾回收前调用
    def __del__(self):
        print('A()对象被删除了~~~',self)

a = A()
b = a       # 又使用一个变量b,来引用a对应的对象

print(a.name)

a = None # 将a设置为了None,此时没有任何的变量对A()对象进行引用,它就是变成了垃圾
b = None
# del a
# del b
input('回车键退出...')
# 结果是 程序还在 input阻塞 A类就被垃圾回收了

# 这个实例是 在回车之前A类就被删除了 因为 我们将a这个实例对象设置为了 None
# 这里注释的 del a  del b 和 把对象改为 None的效果是一样的

类中垃圾回收的特殊方法

  • del这个特殊方法,会在对象被垃圾回收前调用

    • 调用后,这个空着的对象就会被删除
  • 怎么分辨这个对象是不是垃圾呢

    • 如果这个对象有变量去调用它就不是,如果这个变量在后面有重新赋值了,有没有其他变量来引用这个对象,那么就是垃圾

类中的所有特殊方法

  • 特殊方法都是使用__开头和结尾的

  • 特殊方法一般不需要我们手动调用,需要在一些特殊情况下执行

  • str
    __str__(self)

  • 这个特殊方法会在尝试将对象转换为字符串的时候调用

  • 它的作用可以用来指定对象转换为,字符串的结果

  • 比如print(a) a是实例对象

  • 如果不定义__str__ 只会返回父类名称和内存地址

  • 定义了以后会返回你所指定的对象(字符串)

    class A:
        def __str__(self):
            return '这是一个A类'
    
    a = A()
    print(a)
    # 打印结果
        # 这是一个A
  • repr
    __repr__(self)

  • 这个方法会在对当前使用repr()函数时调用

  • 它的作用是指定对象在交互界面直接输出结果

    class A:
        def __repr__(self):
            return '你使用了repr()函数'
    
    a = A()
    print(repr(a))
    # 打印结果
       # 你使用了repr()函数
  • str 和 repr
    __str__ and __repr__这两个方法都是用于显示的, str是面向用户的 而repr是面向开发人员的

  • 打印类的时候默认是将类转换为str形式

  • new
    new(self)

    • 是在新式类中出现的方法,就是用来创建对象的 ,一般不用这个变量
  • 运算方法

    # object.__add__(self, other) 加
    # object.__sub__(self, other) 减
    # object.__mul__(self, other)  乘法
    # object.__matmul__(self, other) @
    # object.__truediv__(self, other)  /
    # object.__floordiv__(self, other) //
    # object.__mod__(self, other)     %
    # object.__divmod__(self, other) divmod, divmod(a, b) = (a/b, a%b)
    # object.__pow__(self, other[, modulo])  **
    # object.__lshift__(self, other) <<
    # object.__rshift__(self, other) >>
    # object.__and__(self, other)  &
    # object.__xor__(self, other) ^
    # object.__or__(self, other) |
  • 比较方法

    # object.__lt__(self, other) 小于 <
    # object.__le__(self, other) 小于等于 <=
    # object.__eq__(self, other) 等于 ==
    # object.__ne__(self, other) 不等于 !=
    # object.__gt__(self, other) 大于 >
    # object.__ge__(self, other) 大于等于 >=
  • 举例__gt__这个方法

  • gt会在对象做大于比较时调用,该方法的返回值将会作为比较的结果

  • 需要两个参数,一个self表示当前对象,other表示和当前对象比较的对象

  • 也就是 如果是大于的话 就返回前面得参数就大于后面的参数

    class A:
        def __init__(self,name,age):
            self.name = name
            self.age = age
    
        # 定义gt方法  
        def __gt__(self,other):
            # 如果 self 大于 other 返回true 否则返回 false
            return self.age > other.age   # 这里如果写小于的话 就直接返回false 因为 是否大于
    
    		# 在啰嗦一遍,创建一个gt方法,如果前面的age>后面的age则就返回true反之就反之就返回false
    
    # 创建实例对象
    a = A('lmk',18)
    b = A('kml',16)
    
    # 这里调用gt方法,传入要比较的对象
    print(a.__gt__(b))
    
    
    # 输出结果是 true 
    
  • __len__是获取长度的函数

  • 在类中定义__len__方法,在这个方法内写什么,print(len(对象名或者类名)) 就是什么,因为不是打印的属性

    class A:
        def __init__(self,name):
            self.name = name
    	# 定义 len方法
        def __len__(self):
            return 10  # 直接返回10 
    
    
    a = A('lmk')
    # a对象调用 len方法 就直接返回 上面定义的10
    print(a.__len__())
    
  • __bool__可以通过bool来指定对象转换为布尔值的情况

  • 这个方法可以指定这个类返回的bool值是true还是false

    class A:
        def __init__(self,age):
            self.age = age
    
        def __bool__(self):
    	# 可以用来指定条件,如果 *** 大于 一个数字,则返回true,反之就返回false
      	    return self.age > 17	
    
    
    a = A(18)
    
    print(a.__bool__())
    # 这里 a对象传入了18 大于 类中的17 所以 返回结果是 true 如果小于就返回 false

这些特殊方法可以配合多态进行使用
https://www.jianshu.com/p/882492a9fa99 python 类的运算方法博客可以参考


本博客所有文章是以学习为目的,如果有不对的地方可以一起交流沟通共同学习 邮箱:1248287831@qq.com!