python基础--面向对象

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python基础--面向对象相关的知识,希望对你有一定的参考价值。

面向对象的基础是类

类就是对一类事物的抽象,把一类事物共同的属性方法抽象出来就叫做类,比如人类:可以把身高,体重,年龄等属性抽象出来,属性是固有的,比如人可以说话吃饭走路,这种可以做出具体动作或者叫完成某一类事情的动作就做方法。身高体重年龄说话吃饭走路是人类共有的,把它们抽象出来就就做类。

类的个体就是对象,对象是具体的,实实在在的事物,比如根据上面的属性方法实例化一个人,实例化后此人就具有上面的属性和方法。

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

面向对象的特点:

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。

数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。

方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

实例变量:定义在方法中的变量,只作用于当前实例的类。

继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。

实例化:创建一个类的实例,类的具体对象。

方法:类中定义的函数。

对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

对象可以包含任意数量和类型的数据。

类定义

class Obj(object):#使用关键字class定义类,然后跟类名(Obj)小括号,小括号里的是基类,所有类都继承与它。(可以省略不写)

简单里的例子:

class Obj(object):

    print("hello")

ob = Obj()#类名加括号就是实例化

技术分享图片

技术分享图片


类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过实例访问属性与方法也可以通过类名访问其属性。

技术分享图片

技术分享图片

类对象支持两种操作:属性引用和实例化。

属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。

类对象创建后,类命名空间中所有的命名都是有效属性名。

很多类都在对象创建时初始化状态:

    class Student(object):#类名Student首字母通常大写

    def __init__(self, name, score,gender):#__init__()叫构造方法,用来初始化状态,实例化类的时候自动调用

    self.name = name

    self.score = score

    self.gender = gender

    def print_score(self):

    print('%s: %s' % (self.name, self.score))

    def get_gender(self):

    print("%s" % (self.gender))


技术分享图片

技术分享图片


在python当中一切皆对象,对象(实例)本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址也不会一样。

在类内部定义的属性属于类本身的,由操作系统只分配一块内存空间,大家公用这一块内存空间,实例化后操作系统会给每个对象分配一个地址空间

class People(object):

n = 123 #类变量(存在于类的内部,每个实例化的对象都可以共用)

name = "我是类name"

def __init__(self, name, age, sex):#构造函数 在实例化时做一些类的初始化的工作(默认自动调用)

self.name = name #r1.name=name实例变量(静态属性),作用域就是实例本身(存在于实例对象中)

self.age = age

self.sex = sex

def talk(self,speak):

print("%s say %s world" %(self.name,speak))

def run(self):

print("%s runing"%self.name)

p1 = People("Jack",20,"M")

print(p1.name)#打印传入的名字Jack

print(p1.n)#共用的属性

p1.talk('hello')

p2 = People("Mike",21,"M")

print(id(p1))

print(id(p2))

print(id(People))


技术分享图片

技术分享图片

打印出共有的属性n

技术分享图片

技术分享图片

同一个地址,说明是直接使用的类属性

每产生一个对象会对应三个属性:id、类型type和数值

id可以理解为在内存当中的位置(其实不是,id实际指向的是对象的地址)

is是身份运算符,其中id对应的就是身份。

id相同,数值肯定相同;id不相同,数值可以相同。

x = 10

print(id(x))

print(type(x))

print(x)

y = 10

print(id(y))

print(type(y))

print(y)

#判断x和y的内存地址是否相同

print(x is y)

#判断x和y的数值是否相同

print(x == y)


技术分享图片

技术分享图片

创建一个类就会创建一个类的命名空间,用来存储类中定义的所有名字,这些名字称为类的属性:而类中有两种属性:数据属性和函数属性,其中类的数据属性是共享给所有对象的,而类的函数属性是绑定到所有对象的。

创建一个对象(实例)就会创建一个对象(实例)的命名空间,存放对象(实例)的名字,称为对象(实例)的属性

在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常。

类的相关方法(定义一个类,也会产生自己的命名空间)

类名.__name__   # 类的名字(字符串)

类名.__doc__    # 类的文档字符串

类名.__base__   # 类的第一个父类(在讲继承时会讲)

类名.__bases__  # 类所有父类构成的元组(在讲继承时会讲)

类名.__dict__   # 类的字典属性、名称空间

类名.__module__ # 类定义所在的模块

类名.__class__  # 实例对应的类(仅新式类中)

构造函数

构造函数就是在构造对象的同时被对象自动调用,完成对事物的初始化,一个类只要生成一个类对象,它一定会调用构造函数.

特点:

Python提供的构造函数式 __init__();

一个类中只能有一个构造函数

不能有返回值

可以用它来为每个实例定制属性

不管写不写都会有构造函数,意思就是__init__()方法是可选的,如果不提供,Python 会给出默认的__init__方法


技术分享图片

class People(object):

n = 123 #类变量(存在于类的内部,每个实例化的对象都可以共用)

name = "我是类name"

def __init__(self, name, age, sex):#构造函数 在实例化时做一些类的初始化的工作(默认自动调用)

self.name = name #r1.name=name实例变量(静态属性),作用域就是实例本身(存在于实例对象中)

self.age = age

self.sex = sex

print("构造函数执行")

def talk(self,speak):

print("%s say %s world" %(self.name,speak))

def run(self):

print("%s runing"%self.name)

p1 = People("Jack",20,"M")


技术分享图片

技术分享图片


只是实例化对象没有调用任何属性和方法,构造函数自定执行了。

析构函数:

与构造函数对应的是析构函数,在python中的“__del__”就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。

__del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数

如果要显式的调用析构函数,可以使用del关键字,方式如下:

del对象名

当对象调用完后主要用来销毁对象,回收内存,我们可以用来关闭打开的数据库或者临时打开的文件等操作。

class People(object):

n = 123 #类变量(存在于类的内部,每个实例化的对象都可以共用)

name = "我是类name"

def __init__(self, name, age, sex):#构造函数 在实例化时做一些类的初始化的工作(默认自动调用)

self.name = name #r1.name=name实例变量(静态属性),作用域就是实例本身(存在于实例对象中)

self.age = age

self.sex = sex

print("构造函数执行")

def talk(self,speak):

print("%s say %s world" %(self.name,speak))

def run(self):

print("%s runing"%self.name)

def __del__(self):

print("析构函数执行")

p1 = People("Jack",20,"M")

技术分享图片

技术分享图片

封装

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承

在开发项目中有时候会用到一些相同的东西,如果每个类都写一遍,那么就会产生冗余,可以把这些提取出来,这就会用到继承。

一个类从已有的类那里获得其已有的属性与方法,叫做类的继承,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

class People:

# 定义基本属性

name = ''

age = 0

# 定义构造方法

def __init__(self, n, a):

self.name = n

self.age = a

def speak(self):

print("%s 说: 我 %d 岁。" % (self.name, self.age))

# 实例化类

p = People('Jack', 10)

p.speak()

class Student(People):

grade = ''

def __init__(self,n,a,g):

#调用父类的构函

People.__init__(self,n,a)

self.grade = g

def run(self):#增加新的类方法

print("%s 在跑步" %self.name)

p1 = Student('jack',10,1)

p1.run()

子类


技术分享图片

技术分享图片


如果父类的方法无法满足当前要求可以在子类重写父类方法,这就是方法重写,方法重写指在子类中重新定义父类中已有的方法。

例如:

class People:

# 定义基本属性

name = ''

age = 0

# 定义构造方法

def __init__(self, n, a):

self.name = n

self.age = a

def speak(self):

print("%s 说: 我 %d 岁。" % (self.name, self.age))

# 实例化类

p = People('Jack', 10)

p.speak()

class Student(People):

grade = ''

def __init__(self,n,a,g):

#调用父类的构函

People.__init__(self,n,a)

self.grade = g

#覆写父类的方法

def speak(self):

print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))

p1 = Student('jack',10,1)

p1.speak()

父类


技术分享图片


子类

技术分享图片技术分享图片

运行结果:

技术分享图片

在实例化一个类的时候,先在本类中寻找方法,如果没有就去父类找

class People(object):

def __init__(self,name,age):

self.name = name

self.age = age

self.tell()

def tell(self):

print("%s---%s"%(self.name,self.age))

class Student(People):

def tell(self):

print("子类重新定义")

student = Student("Jack",20)


技术分享图片

技术分享图片

技术分享图片


技术分享图片

多继承

在项目中有时候会用继承多个类

例如:

class People:

#定义基本属性

name = ''

age = 0

#定义构造方法

def __init__(self,n,a):

self.name = n

self.age = a

def speak(self):

print("%s 说: 我 %d 岁。" %(self.name,self.age))

class Student():

def __init__(self,g):

self.grade = g

def tell(self):

print("我在读 %d 年级"%self.grade)

class sample(People, Student):

def __init__(self, n, a, g):

People.__init__(self, n, a)

Student.__init__(self, g)

test = sample("Tim", 25, 2)

test.speak()

test.tell()


技术分享图片

技术分享图片

sample 没有定义方法直接继承People和Student两个类

Python类分为两种,一种叫经典类,一种叫新式类。两种都支持多继承。

py2 经典类是按深度优先来继承的,新式类是按广度优先来继承的

py3 经典类和新式类都是统一按广度优先来继承的

例如:

ABCD四个类,B和C都继承A,D继承B和C,那么他的继承规则是首先在D中寻找,找不到去B中找,然后是C最后是A


技术分享图片

class A():

def __init__(self):

print("A")

class B(A):

#pass

def __init__(self):

print("B")

class C(A):

#pass

def __init__(self):

print("C")

class D(B,C):

#pass

def __init__(self):

print("D")

d = D()

默认输出D(D 类中执行构造函数)


技术分享图片


技术分享图片

D直接继承BC类

技术分享图片

技术分享图片


B中没找到,会去C中找

技术分享图片

技术分享图片

如果C也没找到去A中找

技术分享图片

技术分享图片

在python2.7中

class B(A):

pass

# def __init__(self):

# print("B")

class C(A):

#pass

def __init__(self):

print("C")

class D(B,C):

pass

# def __init__(self):

# print("D")

d = D()

打印出的是A


技术分享图片

技术分享图片

私有变量

如果某个属性不想在外不能直接访问可以设置为私有的变量

python中双下划线可以定义私有变量


技术分享图片

在外部不能访问,访问会出错 

技术分享图片


如果要访问可以使用内部的方式来访问(应用就是在实际项目中要加个限制条件访问,例如只有老师可以看学生的成绩,成绩就要设置为私有属性,查看是要判断是否为老师才有权查看)在内部写一个公开函数来调用

class People(object):

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

self.name = name

self.age = age

self.tell()

self.__sex=sex#定义私有变量

def tell(self):

print("%s---%s"%(self.name,self.age))

def get_sex(self):

print("性别为:%s" %self.__sex)

p1 = People("Jack",20,"M")

print(p1.name)

#print(p1.sex)

p1.get_sex()


技术分享图片


技术分享图片


私有方法

class People(object):

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

self.name = name

self.age = age

self.tell()

self.__sex=sex#定义私有变量

def tell(self):

print("%s---%s"%(self.name,self.age))

def get_sex(self):

print("性别为:%s" %self.__sex)

def __speak(self):

print('我是私有方法')

def get_speakfun(self):

return self.__speak()

p1 = People("Jack",20,"M")

p1.get_speakfun()


技术分享图片


技术分享图片


如果要访问也是在内部加一个公开的方法来访问 

技术分享图片

技术分享图片

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;

多态

多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定

class Animal(object):

def run(self):

print('Animal is running...')

class Dog(Animal):

pass

#print("dog")

class Cat(Animal):

pass

#print("cat")

dog = Dog()

dog.run()

cat = Cat()

cat.run()


技术分享图片

技术分享图片


这样调用后输出的都是一样的信息,实际开发中需求肯定不会是这样的,比如那种动物调用父类就打印那种动物的运动

把代码改为:

class Animal(object):

def run(self):

print('Animal is running...')

class Dog(Animal):

def run(self):

print("dog is running")

class Cat(Animal):

def run(self):

print("cat is running")

dog = Dog()

dog.run()

cat = Cat()

cat.run()

技术分享图片

技术分享图片

这样在使用的时候每种动物表现的都不一样,这就是多态

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

定义一个class的时候,实际上就定义了一种数据类型。自己定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

例如:

class Animal(object):

def run(self):

print('Animal is running...')

class Dog(Animal):

def run(self):

print("dog is running")

class Cat(Animal):

def run(self):

print("cat is running")

dog = Dog()

dog.run()

cat = Cat()

cat.run()

a = list() # a是list类型

b = Animal() # b是Animal类型

c = Dog() # c是Dog类型

print(isinstance(a, list))

print(isinstance(b, Animal))

print(isinstance(c, Dog))

技术分享图片

技术分享图片

可以看到a、b、c确实对应着list、Animal、Dog这3种类型。 

技术分享图片

技术分享图片

c既是Dog类型也是Animal类型,这是因为Dog继承了Animal 所以c有这两种类型的属性

那么

b = Animal()

print(isinstance(b, Dog))


技术分享图片

技术分享图片

反过来就不可以了

当传入Animal的实例时,打印run_twice()


技术分享图片

技术分享图片

可以看出新增子类的时候,不必对run_twice()做任何修改,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat……时,我们只需要接收Animal类型就可以了,因为Dog、Cat……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

继承还可以一级一级地继承下来


以上是关于python基础--面向对象的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

python基础之面向对象

python——面向对象基础

Python面向对象基础

Python面向对象基础