python中的类及继承

本篇博文部分内容采自https://www.cnblogs.com/CYHISTW/p/10966448.html

一.类及相关定义

:python中类通过class来定义,一般格式为class A(object) 来完成定义,定义完的class就称为类对象。其中object为类A所要继承的类的名称,若A没有需要继承的类,那么在定义时这对括号可写可不写,括号如果写了,那么里面写不写object也无所谓

实例对象:将类对象赋给某个变量,那么该变量就为这个类的实例对象。实例对象由类定义中的方法属性组成,在实例化时,括号一定要写上,否则赋值的就是一个类对象了

class Person() :
   def name(self):
      print('小朋友')
>>> a = Person()            #Person是类对象,a是实例对象
>>> a.name()                #a.name = Person().name,相当于实例化对象调用方法name
小朋友


>>> b = Person #这里Person没有括号,后面调用b.name()就提示错误 #Person没加括号是没有创造新的实例 #Person只表示符变为b,b.name()相当于Person.name() #方法name有参数self,意味着需要创建实例才能调用。见6,所以报错 >>> b.name() TypeError: name() missing 1 required positional argument: 'self'
>>> c = Person() >>> c.name #c.name() 和c.name 有什么区别? #c=Person()虽然创建了实例 #但是c.name并不是调用实例方法,只是调用其属性,见4 #而c.name的属性本身就是一个类方法对象,所以才会返回对象 运行: >>> <bound method Person.name of <__main__.Person object at 0x000001C8A1DC3D48>>

以上代码参考:https://blog.csdn.net/qq_42647903/article/details/102619460

 

方法:类中的方法要通过def进行定义,定义格式为def name(self,a,b...n)其中方法中的第一个参数必须为self,后面的则为自己设定的参数,这样做是为了区分哪个实例对象调用了方法。未实例化的类对象直接调用方法会因为缺少参数而报错,要调用方法用class.name()【相比于调用属性多了个括号】参数中有self也说明了要想使用该方法,一定要先实例化才行

class A:
    def do(self):
        print('do')
b = A()             //如果将此改为b=A.do()则会报错缺少重要参数self,这是因为我们还未实例化A,就将A的方法给使用了
b.do()       

>>>do

python中的方法也可分为实例方法和类方法,里面所有的def定义都是实例方法,因为他有参数self,那么类方法该如何定义呢?我们可以通过标记 @classmethod将方法绑定到类上,而非实例对象上。

class Person(object):
    count = 0
    @classmethod
    def how_many(cls):          //第一个参数一定要为cls,表示传递的是类本身
        return cls.count

    def __init__(self, name):
        self.name = name
        Person.count = Person.count + 1

print (Person.how_many())
p1 = Person('Bob')
print (Person.how_many())

>>>0
>>>1

 

属性:属性可以分为类属性和实例属性,有点类似C中局部变量和全局变量。类中的属性可以直接定义无需关键字,而调用属性则用class.name,name即为类中的属性名称。

修改属性的方法

(1)使用类或实例直接操作类型:

由于python是一个动态语言,所以我们可以在写好了类之后再在类中进行动态的添加类属性,对于实例属性也可以这样做。比如我们在A类中增加属性size,在A的实例化对象中b中增加属性size。但是值得注意的是,如果我们直接修改A类的属性值,那么即使不修改实例化的属性值,实例化也可以正常输出,但如果我们只修改实例化的属性值,那么类的属性值以及其他未修改的实例化的属性值都不会改变。

class A:
    name = 'bob'
b = A()
b.size = 7          //如果只删去次行代码,那么两个print都会正常输出
A.size = 7          //如果只删去此行代码,那么A.size就会报错,但b.size不会
print(A.size)
print(b.size)

>>>7
>>>7

当然我们还可以通过实例对象来修改类属性。如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。

class People(object):
      country = 'china'  # 类属性
  
print(People.country)   #china
p = People()
print(p.country)    #china
p.country = 'japan' 
print(p.country)  # 实例属性会屏蔽掉同名的类属性:japan
print(People.country)   #china
del p.country  # 删除实例属性
print(p.country)    #实例属性被删除后,再调用同名称的属性,会调用类属性:china

(2)python内置函数操作 

1)getattr(obj, name[, default]):访问对象的属性,如果存在返回对象属性的值,否则抛出AttributeError异常。
2)hasattr(obj,name):检查是否存在某个属性,存在返回True,否则返回False。
3)setattr(obj,name,value):设置一个属性。如果属性不存在,会创建一个新属性,该函数无返回值。若存在则更新这个值。
4)delattr(obj, name):删除属性,如果属性不存在则抛出AttributeError异常,该函数也无返回值。

(3)使用@property装饰器操作类属性。

 

Python中的内置类属性 

__init__:我们知道了我们虽然定义的类只有一个(即A),但我们通过A实例化的对象却可以有无穷多个,对于这些实例化对象,既然他们都是由一个类所实例化得来的,那么他们应该有一些共同的属性,因此就要用到我们的__init__()方法了。__init__方法的第一个参数必须是self,后面的参数则由我们自己设置,我们每次实例化的时候都会自动调用__init__()方法,因此在创建实例时__init__后面的参数一定要也要一同输入,否则会报错。(注意:__init__前后都是双下划线)

class A:
    temperature = ‘37’                    //类的属性值,每个A的实例化后,其属性temperature都是37
def __init__(self,name,size,color): self.name = name //实例化的属性值,每个A的实例化中name,size,color都可以不同,通过实例化输入的参数确定 self.size = size self.color = color A.number = '7' b = A(1,2,3)
print(A.number)
print(b.name)

>>>7
>>>1

当然还有另外一种定义方式,即先定义,但不传参数。这种就有点像老师在改卷子前已经有了一个花名册,当分数出来时再登记上去,在这里即为在实例化以后,再把数据传入。这种方式在实例化的时候就不可以传参数

class Student:
    def __init__(self):
        self.name = None          //把none分别改为susan和87,则不需要下面的赋值也可以得到结果
        self.score = None

a = Student()
a.name = 'susan'
a.score = '87'
print(a.name,a.score)

>>> susan 87

 

__call__:可以将一个类对象当做函数进行调用,调用的函数功能由__call__提供,该类中的其他函数不可以用

class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def my_print(self,):
        print("a = ", self.a, "b = ", self.b)

    def __call__(self, *args, **kwargs):
        self.a = args[0]
        self.b = args[1]
        print("call: a = ", self.a, "b = ", self.b)

if __name__ == "__main__":
    demo = Demo(10, 20)
    demo.my_print()

    demo(50, 60)


>>> a =  10 b =  20
>>> call: a =  50 b =  60

 代码采自 https://www.cnblogs.com/SBJBA/p/11355412.html

 

属性的其他设置

(1)访问限制

我们对一个实例绑定很多属性,但有的属性并不想让其他人访问,则可以通过设置属性名来限制某些属性的访问

如果一个属性以单下划线开头,那么其表示程序员不想让外部访问,但程序对其可以进行访问

如果一个属性以双下划线开头,那么其表示无法被外界访问

class Person(object):
    def __init__(self, name):
        self.name = name
        self._title = 'Mr'
        self.__job = 'Student'

p1=Person("zhangsan")

print(p1.name,'\n')
print(p1._title,'\n')
try:
    print(p1.__job)
except:
    print("Error")


>>>zhangsan
>>>Mr
>>>Error

但以双下划线开头的变量虽然不能从外部访问,但其可以通过内部来进行访问

class Person(object):

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

    def get_name(self):
        return self.__name

p2=Person("Bob")
print(p2.get_name())

>>>Bob

如果强行想从外部访问其实也可以,双下划线开头的实例变量之所以外部不能访问是因为python解释器对外把__job变量改为了_Person__job,所以可以通过_Person__job来访问__job变量

print(p1._Person__job)    //输出为Student

注意避免以下错误:

p1.__job = "teacher"
print(p1.__job)                //teacher
print(p1._Person__job)         //Student

表面上看,外部代码成功设置了__job变量,但实际上__job变量和class内部的__job变量不是一个变量,内部的__job变量已经被python解释器自动改成了_Person__job,而外部代码给p1增加了一个__job变量。

 

(2)参数定义

python中定义参数时,有的时候传入参数与定义的参数个数是相同的,但有的时候我们不知道传入的参数有几个,因此我们可以通过*args来将传入的参数不管多少都接受,并形成一个元组,如果传入的是关键字形式的参数,则应将形参设置为**kwargs,则会将关键字生成为一个字典

def func(*args,**kwargs):
    print(args)
    print(kwargs)

func(1,2,3,x=1,y=2)

 

>>>(1, 2, 3)

>>>{'x': 1, 'y': 2}

 

二.类的继承

当我们需要构建一个类,而这个类有部分属性或方法在其他类里面已经存在了,那么我们就可以从其它类里面继承我们所要的功能,被继承的类叫父类,继承的类叫子类。如下,Cat类就继承了Animal的所有属性和方法,想调用时直接调用就好了。(参考博客:https://www.cnblogs.com/cccy0/p/9040192.html

class Animal:
    def heshui(self):
        print('动物正在喝水')

class Cat(Animal):
    pass

cat = Cat() cat.heshui()
>>> 动物正在喝水

如果我们在Cat类里面也写一个heshui方法,那么我们就可以重写一下Cat类。但当我们调用子类的方法时,如果该方法与父类的方法重名了,那么我们优先调用的是子类的方法,父类的方法被我们屏蔽了。

class Animal:
    def heshui(self):
        print('动物正在喝水')

class Cat(Animal):
    def heshui(self):
        print('猫正在喝水')
cat = Cat()
cat.heshui()

>>>猫正在喝水

那如果我们此时想要调用父类方法怎么办呢,就可以通过super方法来继承父类

class Animal:
    def heshui(self):
        print('动物正在喝水')

class Cat(Animal):
    def heshui(self):
        super().heshui()
cat = Cat()
cat.heshui()

>>>动物正在喝水

super函数是用于调用父类(超类)的一个方法,其语法定义为:

super(type[, object-or-type])

其中type为子类名,后面object-or-self一般填入self参数,当然在python3 中,直接写成super()也可以。如果子类自己定义了__init__,那么他是不可以直接调用父类__init__中的属性,如果想调用,则可以写成super.__init__(),这样父类__init__的所有属性都会被继承。

class D:
    def __init__(self):
        self.name = 'susan'
        self.size = 'big'
        self.num = '6'
class E(D):
    def __init__(self):
        super().__init__()

a = E()
print(a.num,a.name,a.size)

值得注意的是,在继承的时候,如果继承的父类中的属性值是从外界传递进来的那种而不是一开始就已经一定在了类中,那么我们继承的时候也要相应地进行调整

class D:
    def __init__(self,name,size,num):
        self.name = name
        self.size = size
        self.num = num
class E(D):
    def __init__(self,name,size,num,score):     //继承时要把父类中的所有__init__中的类型都继承,如果只写name,size,那么程序就会报错说少一个参数,同理下面super的__init__中也要把所有父类的属性都标明,不可以少标
        super().__init__(name,size,num)      
        self.score = score
a = E('susan','17','big','87')
print(a.score,a.name,a.size)

以上这两种继承方式,都只能把父类的属性全部继承,但是有的时候我们只需要继承父类中的一部分属性,改怎么办呢?有两种方法:

第一种:子类不想继承的变量,用父类的私有变量存储。

class father():
    def __init__(self):
        self.a = 1
        self.__b = 2
    def foo(self):
        pass

class child():
    def __init__(self):
        super().__init__()

print('a',child().a)             //可以看到a可以继承,但是__b不可以
print('b',child().__b)

>>>AttributeError: 'child' object has no attribute '__b'

第二种:子类想继承的变量,放在父类的类变量中,其余为成员变量(或局部变量)。

class father():
    a = 1
    def __init__(self):
        self.__b = 2
    def foo(self):
        pass

class child(father):
    def __init__(self):
        pass

print('a',child().a)
print(child().b)

>>>AttributeError: 'child' object has no attribute 'b'

以上两种方法的原理是如果子类有了__init__后就不能继承父类__init__中的属性,除非使用super.__init__(),而就算是super.__init__()也不能继承其中的私有属性,其实本质上就是如果子类继承了一个父类,那么子类在定义的时候,其他继承了相同的父类的子类也可以被调用到该子类。子类继承父类,子类实例化只会调用自己的构造方法,父类的构造方法不会调用,或者说子类重写了父类的构造方法,所以父类在构造方法中定义的默认属性也无法继承。

class A:
    name = '7'
    def do(self):
        print('do')
        
class B(A):
    def __init__(self):
        super().__init__()
        self.name = A.name

class C(A):
    def __init__(self):
        super().__init__()
        self.size= B.name

d = C()
print(d.size)

>>>7

例如上面的例子,虽然C继承的是A类,在定义C的size时赋值的是B的name,但是由于B也是通过A赋值来的,那么在C中被赋值的size其实就是A的,所以这样就不会报错。再看一个例子:

class LinearModule(torch.nn.Module):
    def __init__(self):
        super(LinearModule,self).__init__()
        self.linear = torch.nn.Linear(1,1)
    def forward(self,x):
        y_pred = self.linear(x)
        return y_pred

其中torch.nn.Linear是继承自torch.nn.Module,所以self.linear可以被赋值,不过为什么后面为什么self.linear又可以被当做一个函数来使用,是因为torch.nn.Linear中有__call__方法,可以把一个对象当做函数看待。

python中的继承分为两种,一种是单继承,以上所有的例子使用的都是单继承,还有一种是多继承,接下来就介绍一下多继承。多继承中最为核心的问题是如何寻找父类,如果父类之间的方法属性不冲突,那么继承的时候就可以将父类的属性和方法全部继承

 

class Father:
  def hobby(self): 
    print("love to play video game.")
 
class Mother: def cook(self): print("love to cook anything.") class Son(Father, Mother): pass son = Son() son.hobby() son.cook()

>>>
love to play video game.
>>>love to cook anything.

 

如果继承的多个父类方法属性正好名字一样,则会优先选择最先被继承的方法,即从左到右依次检索

class Base(object):
    def play(self):
        print('Base is playing!')
 
class A(Base):  # 继承Base
    def play(self):  # 自动覆盖父类的此方法
        print('A is playing')
         
class B(Base):  # 继承Base
    def play(self):
        print('B is playing')
         
class C(A, B):  # 继承A,B 
    pass
 
c = C()
c.play()

>>>A is playing

当然在python3中,类被创建时会自动创建方法解析顺序mro,object是所有类的父类,运行mro()方法可以查看继承顺序,例如

print(A.mro())
>>>[<class '__main__.A'>, <class '__main__.Base'>, <class 'object'>]

多重继承不得不再次提到super方法,Python中的super()方法设计目的是用来解决多重继承时父类的查找问题,所以在单重继承中用不用 super 都没关系;但是,使用 super() 是一个好的习惯。super()的好处就是可以避免直接使用父类的名字.主要用于多重继承。事实上,super函数返回的代理对象是一个bultin class super,正如它的名字所指,类super代理了子类的父类。在单继承关系中,super代理的类很容易找到,就是子类的唯一父类;但是在多继承关系中,super除了能代理子类的父类外,还有可能代理子类的兄弟类。往深挖涉及到方法解析顺序,菱形继承等问题,暂且先不继续深挖了。

 

三.类的多态

多态:是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承)

多态性多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

总结就是即为   多态性:一个接口,多种实现

class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('say miaomiao')

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

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

c = Cat()
d = Dog()
p = Pig()

def func(obj):
    obj.talk()

func(c)
func(d)
func(p)

------------------------------

>>> say miaomiao
>>> say wangwang
>>> say aoao

再举个比较贴切的例子,在我们玩角色扮演游戏的时候,由于游戏并不知道玩家会选择哪个类型的角色,因此会提供多个角色以供选择,只有当玩家开始玩的时候才知道选择的角色,这个过程中,游戏就是这个函数,很多角色都可以玩表现的就是多态。

多态的缺点

调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景。鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”(以上来源自:https://zhuanlan.zhihu.com/p/88402677

 

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有正确"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

class Duck():
    def walk(self):
         print('I walk like a duck')
    def swim(self):
         print('i swim like a duck')

class Person():
    def walk(self):
       print('this one walk like a duck') 
    def swim(self):
       print('this man swim like a duck')

可以很明显的看出,Person类拥有跟Duck类一样的方法,当有一个函数调用Duck类,并利用到了两个方法walk()和swim()。我们传入Person类也一样可以运行,函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用。

 

posted @ 2021-02-17 20:07  gausstu  阅读(924)  评论(0)    收藏  举报