Python中的类和对象 面向对象编程

鳄鱼君

发表文章数:642

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python » Python中的类和对象 面向对象编程

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景类,并基于这些类来创建对象。编写类时,我们定义的一大类对象都有通用的行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。

创建和使用类

使用类几乎可以模拟任何东西。列如我现在想模仿人。每个人都具有一些属性,例如姓名、年龄,每个人都需要吃饭、睡觉。由于这是一个共性,我们的People类将包含它们。编写这个类后,我们将使用它来创建表示特定小狗的实例!

创建People类

#类名的命名规范跟标识符一样,这里就严格按照驼峰命名
class People():
    """一次模拟人的简单练习""" 
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def eat(self):
        """每个人都需要吃饭"""
        print(self.name.title()+"is now eatting!")
    def sleep(self):
        """每个人都需要睡觉"""
        print(self.name.title()+"is now sleeping")

代码解释:

  1. 定义一个名为People的类。根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。
  2. 方法__init__():类中的函数称为方法;一切函数的都适用于方法,唯一重要的差别是调用方法的方式。__init__()是一个特殊的方法,每当你根据People类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
  3. 方法__init__():包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个__init__()方法来创建People实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
  4. 接下来创建People实例时,Python将调用People类的方法__init__()。我们将通过实参向People()传递名字和年龄;self会自动传递,因此我们不需要传递它。每当我们根据People类创建实例时,都只需给最后两个形参(name和age)提供值。
  5. 方法__init__():定义了两个变量,并都有前缀self。以self为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例。self.age = age的作用与此类似。像这样可通过实例访问的变量称为属性
  6. People类还定义了另外两个方法:eat()和sleep()。由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self。我们后面将创建的实例能够访问这些方法,换句话说,所有的人都会需要吃饭和睡觉。eat()和sleep()方法仅作模拟打印操作,其他不多介绍!

根据类创建实例

有了People类,接下来创建一个表示特定人的实例(People类的代码省略):

class People():
    ...
        
people=People('eyujun',18) # 类实例化
print('My name is %s and My age is %s' % (people.name.title(),people.age))

代码解释:

  1. 我们让Python创建一个人名为’eyujun’、年龄为18。遇到这行代码时,Python使用实参’eyujun’和18调用People类中的方法__init__()
  2. 方法__init__()创建一个表示特定人的实例,并使用我们提供的值来设置属性name和age。
  3. 方法__init__()并未显式地包含return语句,但Python自动返回一个表示这个人的实例。我们将这个实例存储在变量people中。在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如People)指的是类,而小写的名称(如people)指的是根据类创建的实例

1.访问属性

要访问实例的属性,可使用.点(句点)表示法。:

people.name.title()
people.age

在这里,Python先找到实例people,再查找与这个实例相关联的属性name。在People类中引用这个属性时,使用的是self.name。我们使用同样的方法来获取属性age的值。people.name.title()将people的属性name的值’eyujun’改为首字母大写的;people.age将people的属性age的值18转换为字符串。

2.调用方法

根据People类创建实例后,就可以使用句点表示法来调用People类中定义的任何方法。下面来让这个人可以吃饭睡觉:

class People():
    ...
        
people=People('eyujun',18)
people.eat()
people.sleep()

要调用方法,可指定实例的名称(这里是people)和要调用的方法,并用句点分隔它们。遇到代码people.eat()时,Python在类People中查找方法eat()并运行其代码。Python以同样的方式解读代码people.sleep()。

3.创建多个实例

可按照需求根据类创建任意数量的实例。那么我们再创建一个名为people1的实例:

class People:
    ...

people=People('eyujun',18)
people.eat()
people.sleep()

people1=People('lucy',18)
people1.eat()
people1.sleep()

这个实例中,我们创建了两条人,分别名为Eyujun和Lucy。每个人都是一个独立的实例,有自己的一组属性(姓名和年龄),能够执行相同的操作(吃饭和睡觉)

面向对象编程

通过上面的练习,我们基本可以创建类并且可以对类进行实例化,接下来我们介绍一些专业术语,更有助于理解!

面向对象编程
OOP编程是利用”类”和”对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率,另外,基于面向对象的程序可以让他人更加容易理解你的代码逻辑,从而使开发变得更从容,那么就严格要求在今后的代码编写时使用面向对象编程。
class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象都具备的属性。共同的方法
object 对象
一个对象即是一个类的实例化后的实例,一个类必须经过实例化后才可以在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之间有共性,亦有不同,这是个很好的列子来说明。
encapsulation 封装
在类中对数据的赋值、内部调用对外部是透明的,这是类变成了一个胶囊或者容器,里面包含着类的数据和方法
inheritance 继承
一个类可以派生出子类,在这个父类定义的属性、方法自动被子类继承
polymorphism 多态
态是面向对象的重要特征,就是”一个接口,多种实现”,指一个父类(基类)中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态

按照类定义的描述,那么一切皆为对象,可以分类,只要是对象,就肯定属于某种品类,那么就肯定会有属性了。

属性
类中的所有变量称为属性。
方法
类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个 self 参数(后续会做详细介绍)。
析构函数
在实例释放、销毁的时候执行,通常用于做一些收尾工作,如关闭一些数据库连接,打开临时文件

我们现在已经基本知道了一个类中该含有什么,创建对象之后,我们就可以来操作对象,包括对象的实例变量,访问、修改实例变量的值、以及给对象添加或删除实例变量;调用对象的方法,包括调用对象的方法,已经给对象动态添加方法。

class People:
    n='我是一个类变量' #类变量
    name='wo shi she'
    def __init__(self,name,age): #传入name,age参数
        #构造函数,几乎每个类都需要这样写
        #在实例化时做一些类的初始化工作
        self.name=name #实例变量(静态属性),作用域就是实例本身
        self.age=age
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))

#实例化
r1=People('zjj',18) #把一个类变成一个具体对象的过程叫实例化(初始化)
r2=People('wff',20)
r1.talk() #实例化之后,我们可以借助r1来调用talk方法
print(r1.name,r1.n) #先找实例本身,没有就去类里面寻找
#实例里面没有n,所以去类里面寻找,实例本身就有name,所以打印name

r1.name='我修改了实例变量'  #我们可以修改实例变量
print(r1.name,r1.age)

r1.salary=120000 #我们可以添加一些实例变量
print(r1.name,r1.age,r1.salary)

del r1.salsary #删除实例变量
print(r1.salary)
print(r2.name,r2.age) #我们修改的是r1,r2和r2是互不影响的

r1.n="我修改了类变量"
print(r1.n) #r1中修改了类变量,打印
print(r2.n) #r2中类变量n,没有被修改
#r1和r2是互不影响的,你只是在r1中修改了类变量

解释一下第一个参数self,同一个类可以产生多个对象r1和r2,甚至更多,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象,就像我们每个人都有自己的名字一样,Python解释器就能知道到底要操作哪个对象的方法了。反过来如果不加就会报错,因为程序找不到你这个人叫什么名字,就是不知道要操作那个对象

class Dog:
    def __init__(self):
        print(self,"在调用构造方法")
    # 定义一个jump()方法
    def jump(self):
        print(self,"正在执行jump方法")
    # 定义一个run()方法,run()方法需要借助jump()方法
    def run(self):
        print(self,"正在执行run方法")
        # 使用self参数引用调用run()方法的对象
        self.jump()
r1 = Dog() #r1和r2中的self就是一个标识作用,两个是相互独立,不一样的
r1.run()
r2 = Dog() #若果缺少self,就会报错找不到
r2.run()

r1和r2就相当于我们在内存中开辟了两个不同的对象空间,那么self就是起到标识作用,自己尝试。

我们再来看看析构函数和私有属性

class People:
    def __init__(self,name,age,salary): #传入name,age参数
        self.name=name #实例变量(静态属性),作用域就是实例本身
        self.age=age
        self.__salary=salary #私有属性
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))
    def get_salary(self): 
        #我们在内部也是可以修改私有属性的
        print('我们可以在类中定义一个方法,来获取%s属性'% self.__salary)
    def __del__(self):
        print('我是一个析构函数,不管发生什么我都会执行....')
r1=People('zjj',18,120000) #实例化
#我们只是对类进行实例化,并没有调用方法,但是析构函数会执行

r1.talk() #如果调用了类的方法,那么析构函数总会在最后执行

#print(r1.salary) #我们打印私有属性,报错no attribute,没有这个属性

r1.get_salary() #但是我们可以在类里面构建一个方法来访问

我们再来看一下简单的继承

class People:
    def __init__(self,name,age,salary): #传入name,age参数
        self.name=name #实例变量(静态属性),作用域就是实例本身
        self.age=age
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))
class Men(People):
    pass      #Men类继承了People,这个就是继承
class Women(People): #我们在写一个Women类,继承于People
    def eatting(self):  #在此基础上,我们新加入了一个eatting方法
        print('%s is eatting...'% self.name)
#r2=Men() #因为People类本身就有参数,所以不传入参数会报错
r1=Men('wff',20,123000) #Men类里面什么也没有但它继承于People,所以它同样拥有People的方法
r1.talk()

a1=Women('zjj',18,1200000)
a1.eatting()  #Women类不仅有People的方法,还增加了自己的方法
a1.talk()

往深处延伸一点,我们在子类中创建和父类一样的方法,那么就等于重写了父类的方法,那么我们不想这么做,我们想对父类中的方法来加入一些新的功能,但是不能修改掉父类的方法,怎么做呢?

class People:
    def __init__(self,name,age,salary): #传入name,age参数
        self.name=name #实例变量(静态属性),作用域就是实例本身
        self.age=age
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))
class Men(People):
    def talk(self):
        print("%s is changed the parent class's method"% self.name)
class Women(People):
    def talk(self):
        People.talk(self) #这样就会把父类原本的方法保留
        print("%s is changed the parent class's method"% self.name)
r1=Men('zjj',18,1200000)
r1.talk()  #很显然子类中的talk方法会将父类中的talk方法重写

r2=Women('wff',20,12000)
r2.talk()

我们在继承父类的时候,父类是有参数的,我们想在子类中增添一些参数怎么办,这时候不能再父类中增加参数,这样就显得过于局限,我的另个子类不想要这个参数怎么办,所以我们只有在子类中单独增加需要的属性,这样才显得合理嘛!

class People:
    def __init__(self,name,age): #传入name,age参数
        self.name=name
        self.age=age
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))
class Men(People):
    def __init__(self,name,age,salary): #我们在子类中初始化
        People.__init__(self,name,age) #父类中已经存在name,age,所以这步就是把父类的参数传过来
        #super(Men,self).__init__(name,age) #可以采用这种方法来继承父类中的构造函数
        self.salary=salary #只需要写salary
    def talk(self):
        print("%s have %s salary in monthly"% (self.name,self.salary))
r1=Men('zjj',18,120000)
r1.talk()

我们可以看到,上面的实例中采用了两种方法来继承父类的属性,由于类具有继承的特点,父类可以派生很多的子类,那么现在因为老板要求要把People类名修改掉,那么可能这个父类有很多个子类,这样就需要逐个修改,或许你会替换掉,没事的,但是类可以继承多个父类,那么还需要继承父类的函数,反正说这么多就是要你使用super这种方法。

class People(object): #新式类
    def __init__(self,name,age):
        self.name=name
        self.age=age
        self.friends=[] #创建一个朋友的列表
    def talk(self):
        print('%s can talking with women.... he is %s years old'% (self.name,self.age))
class Relation(object):
    def make_friends(self,obj):  #Relation类中没有name
        print('%s is making friends with %s'% (self.name,obj.name))
        self.friends.append(obj)  #我们把
class Men(People,Relation): #寻找方法就是先找People类,存在name属性
    def __init__(self,name,age,salary):
        super(Men,self).__init__(name,age) #新式类写法
        self.salary=salary #只需要写salary
    def talk(self):
        print("%s have %s salary in monthly"% (self.name,self.salary))
r1=Men('zjj',18,120000)
r2=Men('wff',20,12000)
r1.make_friends(r2) #我们没有在Relation类中定义name参数,
print(r1.friends)

经典类和新式类的区别

class A:
    def __init__(self):
        print('A')
class B(A):
   
    def __init__(self):
        print('B')
class C(A):
    def __init__(self):
        print('C')
class D(B,C):
    pass
    #def __init__(self):
        #print('D')
r1=D()
#广度优先D B C A
#深度优先D B A C 因为B是继承A的,B中没有可以去A中寻找

在py2中经典类是按照深度优先来继承的,新式类是按广度优先来继承的;在py3中,经典类和新式类都是同一按照广度优先来继承的。理解了这个之后,我们再来看一个实例,如果你能够看懂,就说明你已经基本知道怎么去写一个类了

class School(object):
    #定义一个类,构造函数里面包含学校名称,地址,学生列表,老师列表
    def __init__(self,name,address):
        self.name=name
        self.address=address
        self.students=[]
        self.staffs=[]
    def registered(self,stu_name):
        #定义一个方法,学校可以为学生办理注册手续,把学生加入到学生列表里面
        print('为%s 学员办理注册手续'% stu_name.name)
        self.students.append(stu_name)
    def hire(self,tea_staffs): #定义一个方法,学校可以进行招工,加入到老师列表里面
        print('雇佣了一位%s老师'% tea_staffs.name)
        self.staffs.append(tea_staffs)
        #tea_staffs.name是一个字符串,我们修改name属性之后,是体现不到的,这就失去了联系
        #tea_staffs可以说是一个内存地址,修改name属性之后,列表里面的元素也会相应改变的
class SchoolMembers(object):
    def __init__(self,name,age,gender):
        self.name=name
        self.age=age
        self.gender=gender

class Teachers(SchoolMembers):
    #定义一个老师的类,继承父类构造方法,并且加入职业,就是教什么的
    def __init__(self,name,age,gender,salary,profess):
        super(Teachers,self).__init__(name,age,gender)
        self.salary=salary
        self.profess=profess
    def introduct(self): #定义一个方法,介绍老师的相关信息
        print('''
        ---info of :%s----
        Name:%s
        Age:%s
        Gender:%s
        Salary:%s
        Profess:%s
        '''% (self.name,self.name,self.age,self.gender,self.salary,self.profess))
    def teaches(self):
        print('%s teaches me %s'%(self.name,self.profess))

class Students(SchoolMembers):
    #定义一个学生类,继承父类的构造方法,并且加入学生的学号
    def __init__(self,name,age,gender,stu_id):
        super(Students,self).__init__(name,age,gender)
        self.stu_id=stu_id
    def introduct(self): #定义一个方法,用来介绍学生的相关信息
        print('''
        ---info of :%s----
        Name:%s
        Age:%s
        Gender:%s
        Stu_id:%s
        '''% (self.name,self.name,self.age,self.gender,self.stu_id))
    def learning(self):
        print('%s is learning ...his student munber is %s'% (self.name,self.stu_id))
school=School('郑州大学','郑州') #实例化一个学校

t1=Teachers('baikun',35,'men',4000,'体育') #实例化一个体育老师,也就是我们的老师
t2=Teachers('xuanhui',30,'women',3400,'数控编程') #实例化一个数控编程老师

s1=Students('zjj',18,'men',181305142) #一个学生,就是我
s2=Students('wff',20,'women',181305143) #另一个学生,女生

t1.introduct() #调用introduct方法,介绍一下老师
s1.introduct() #介绍一下学生

school.registered(s1) #学生在郑州大学进行注册
school.hire(t1) #郑州大学招了一位新教师

print(school.students) #打印学校招工的所有老师
print(school.staffs) #打印学校注册的学生,返回的是一个内存地址

print(school.students[0].name) #可以这样来打印老师名字
print(school.staffs[0].name)

接着是类的多态

class Animal:
    def __init__(self,name):
        self.name=name
class Dog(Animal):
    def talk(self):
        print('wang wang .....')
class Cat(Animal):
    def talk(self):
        print('miao miao ....')
        
def animal(obj): #我们在外面定义了一个函数,函数会执行talk()方法
    obj.talk()
a=Dog('zjj')
b=Cat('wff')
# a.talk() #原本我们可以这样调用talk()方法
# b.talk()
animal(a) #但是我们现在可以这样调用,这就叫做同一个接口多种实现,但是有点牵强
animal(b) #animal就是同一个接口,多种形态

我们来说一下静态方法,它只是名义上归类管理,实际上在静态方法里访问不了类或实例中的任何属性。

class Animal:
    def __init__(self,name):
        self.name=name
class Dog(Animal):
    #静态方法不需要传入self参数
    @staticmethod #静态方法,就是跟类没有关系,是个函数
    def talk(): #说是跟类没有关系,但是需要通过类来调用
        print('wang wang .....') #加入self参数就会报错
    # @staticmethod
    # def talk(self):
    #     print('wang wang...%s'% self.name)
a=Dog('zjj')
a.talk()
#a.talk(a) 但是我们可以把实例本身传进去,这样你想要加入self,或者访问实例属性也是可以的,仿佛没什么用

有静态方法,就有类方法,我们再来看一下类方法,类方法只能访问变量,不能访问实例变量。

class Animal:
    n='我是一个类变量'
    def __init__(self,name):
        self.name=name
class Dog(Animal):
    @classmethod
    def talk(self): #类方法
        print('wang %s wang .....'% self.name) #我们访问实例变量,会报错
        #print('wang %s wang .....'% self.n) #我们访问类变量,没有问题
a=Dog('zjj')
a.talk()

还有属性方法,就是把一个方法变成静态属性,那么一个属性再调用是就不需要加()了

class Animal:
    n='我是一个类变量'
    def __init__(self,name):
        self.name=name
class Dog(Animal):
    @property
    def eat(self):
        print('%s is eatting .....' % self.name)
a=Dog('zjj')
a.eat()  #调用eat()方法,显然会报错
a.eat #访问属性,而不是访问eat()方法
#这时候我们就不能传参数,也不能对属性的值进行更改,其实想要传参数也是可以的,我们后面会说到

属性方法调用时默认是不能够传参数的,但是如果上面的eat方法中正好有参数,那么我们就必须传参数,怎么做呢?

class Dog(object):
    def __init__(self,name):
        self.name=name
        self.__food=None
    @property #把一个方法变成一个属性
    def eat(self):
        print('%s is eatting %s.....' % (self.name,self.__food))
    @eat.setter #对属性进行赋值
    def eat(self,food):
        print('set to food:',food)
        self.__food=food #我们可以采用这种方法,来把参数传给Dog类,私有属性在外部是访问不了的
    @eat.deleter #删除属性方法中的参数
    def eat(self): #定义了这个方法之后就可以使用del a.eat来删除属性
        del self.__food
        print('已经删除')
a=Dog('zjj')
a.eat
a.eat='baozi' #这样我们就可以设定参数,但是好像跟类的属性没有什么联系
# del a.name  #我们可以对类的属性进行删除操作
# print(a.name)

#del a.eat #但是我们不能仅仅采用这种方式来删除属性方法,会报错

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《Python中的类和对象 面向对象编程》 发布于2019-12-12

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

6 + 2 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册