python 基础 day13类与对象

面向对象编程

面向对象编程(简称 OOP),是一种封装代码的方法。将数据扔进列表中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。

代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的。


面向对象的常用术语:
  • 类:可以理解是一个模板,通过它可以创建出无数个具体实例。通过其创造无数个实例,特征各不相同(这一过程又称为类的实例化)。
  • 对象:类并不能直接使用,通过类创建出的实例(又称对象)才能使用。
  • 属性:类中的所有变量称为属性。
  • 方法:类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个 self 参数

对象=属性+方法

class:定义类

   Python 中使用类的顺序是:先创建(定义)类,然后再创建类的实例对象,通过实例对象实现特定的功能。

  Python 中,创建一个类使用 class 关键字实现,其基本语法格式如下:

class 类名:
          零个到多个类属性...
          零个到多个类方法...
  • 类中属性方法顺序不做要求,其中可以相互调用
  • Python的类名必须是由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。以区别函数的函数名
  • 如果不为类定义任何属性和方法,那么这个类就相当于一个空类,如果空类不需要其他可执行语句,则可使用 pass 语句作为占位符。
class Mon:
    pass

定义一个有意义的类

class Mon:
    hair='abc'
    def say(self,content):
        print(content)

 创建了一个名为Mon的类,在类中有一个hair类的属性。
 根据定义属性位置的不同,在各个类方法之外定义的称为类属性或类变量(如 hair 属性),而在类方法中定义的属性称为实例属性(或实例变量)
  Person 类中还创建了一个 say() 类方法,该方法包含两个参数,分别是 self 和 content。可以肯定的是,content 参数就只是一个普通参数,没有特殊含义,但 self 比较特殊,并不是普通的参数,

注意,更确切地说,say() 是一个实例方法,除此之外,Python 类中还可以定义类方法和静态方法,这 3 种类方法的区别和具体用法

self用法

 self类似于c++中的this指针,在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。

class Dog:
    def __init__(self):
        print("正在执行构造方法")
    # 定义一个jump()实例方法
    def jump(self):
        print("正在执行jump方法")

   Python 要求,类方法(构造方法和实例方法)中至少要包含一个参数,但并没有规定此参数的名称(完全可以叫任意参数名),为了更好的阅读性,约定第一个参数为self

   同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此,Python解释器就能知道到底要操作哪个对象的方法了。

对于构造方法来说,self 参数(第一个参数)代表该构造方法正在初始化的对象。

  因此,程序在调用实例方法和构造方法时,不需要为第一个参数传值。

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()
dog1 = Dog()
dog1.run()
dog2 = Dog()
dog2.run()~

    上面代码中,jump() 和 run() 中的 self 代表该方法的调用者,即谁在调用该方法,那么 self 就代表谁,因此,该程序的运行结果为:

<__main__.Dog object at 0x00000276B14B12B0> 在调用构造方法
<__main__.Dog object at 0x00000276B14B12B0> 正在执行run方法
<__main__.Dog object at 0x00000276B14B12B0> 正在执行jump方法
<__main__.Dog object at 0x00000276B14B1F28> 在调用构造方法
<__main__.Dog object at 0x00000276B14B1F28> 正在执行run方法
<__main__.Dog object at 0x00000276B14B1F28> 正在执行jump方法~

    当一个 Dog 对象调用 run() 方法时,run() 方法需要依赖该对象自己的 jump() 方法。在现实世界里,对象的一个方法依赖另一个方法的情形很常见,例如,吃饭方法依赖拿筷子方法,写程序方法依赖敲键盘方法,这种依赖都是同一个对象的两个方法之间的依赖。

   注意,当 Python 对象的一个方法调用另一个方法时,不可以省略 self。也就是说,将上面的 run()方法改为如下形式是不正确的

def run():
    # 省略self,代码会报错
    self.jump()
    print("正在执行run方法")~
    #TypeError: run() takes 0 positional arguments but 1 was given

当 self 参数作为对象的默认引用时,程序可以像访问普通变量一样来访问这个 self 参数,甚至可以把 self 参数当成实例方法的返回值。
class ReturnSelf :
    def grow(self):
        if hasattr(self, 'age'):
            self.age += 1
        else:
            self.age = 1
        # return self返回调用该方法的对象
        return self
rs = ReturnSelf()
# 可以连续调用同一个方法
rs.grow().grow().grow()
print("rs的age属性值是:", rs.age)~
#rs的age属性值是: 3

_init_()类构造方法

 在创建类时,我们可以手动添加一个 _init_() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
 构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。Python 类中,手动添加构造方法的语法格式如下:

    def __init__(self,...):
            代码块

注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。在类的方法中有很多类似的方法
 在_init_() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。

class Mon:
    hair='abc'
    def __init__(self):
        print('content')
a=Mon()#content

注意,如果开发者没有为该类定义任何构造方法,那么 Python 会自动为该类创建一个只包含 self 参数的默认的构造方法。

 在创建a这个对象时,隐式调用了类的构造方法。
 不仅如此,在 _init_() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割。

class Mon:
    hair='abc'
    def __init__(self,name,age):
        print("这个人的名字是:",name," 年龄为:",age)
a=Mon("老胡",19)#这个人的名字是: 老胡  年龄为: 19

注意,由于创建对象时会调用类的构造方法,如果构造函数有多个参数时,需要手动传递参数,传递方式如代码中所示(后续章节会做详细讲解)。

 注意,虽然构造方法中有 self、name、age 3 个参数,但实际需要传参的仅有 name 和 age,也就是说,self 不需要手动传递参数。
关于self参数,在创建类对象时,无需给 self 传参即可。

类对象的创建

 class 语句只能创建一个类,而无法创建类的对象,因此要想使用已创建好的类,还需要手动创建类的对象,创建类对象的过程又称为类的实例化。
对已创建的类进行实例化,其语法格式如下:

类名(参数)

 当创建类时,若没有显式创建 _init()_ 构造方法或者该构造方法中只有一个 self 参数,则创建类对象时的参数可以省略不写。

例如,如下代码创建了名为 Python 的类,并对其进行了实例化:

class Person :
    '''这是一个学习Python定义的一个Person类'''
    # 下面定义了2个类变量
    name = "zhangsan"
    age = "20"
    def __init__(self,name,age):
        #下面定义 2 个实例变量
        self.name = name
        self.age = age
        print("这个人的名字是:",name," 年龄为:",age)
    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)
# 将该Person对象赋给p变量
p = Person("张三",20)

  由于构造方法除 self 参数外,还包含 2 个参数,且这 2 个参数没有设置默认参数,因此在实例化类对象时,需要传入相应的 name 值和 age 值(self 参数是特殊参数,不需要手动传值,Python会自动传给它值)。

类对象的使用

类对象大概有以下几种作用:

  • 操作对象的实例变量,包括访问、修改实例变量的值、以及给对象添加或删除实例变量)。

  • 调用对象的方法,包括调用对象的方法,已经给对象动态添加方法。

  • 类对象访问变量或方法
    使用已创建好的类对象访问类中实例变量的语法格式如下

对象名.变量名

使用类对象调用类中方法的语法格式如下:

对象名.方法名(参数)

 下面代码通过 Person 对象来调用 Person 的实例变量和方法

# 输出p的name、age实例变量
print(p.name, p.age)
# 访问p的name实例变量,直接为该实例变量赋值
p.name = '李刚'
# 调用p的say()方法,声明say()方法时定义了2个形参,但第一个形参(self)不需要传值,因此调用该方法只需为第二个形参指定一个值
p.say('Python语言很简单,学习很容易!')
# 再次输出p的name、age实例变量
print(p.name, p.age)
# 输出p的name、age实例变量
print(p.name, p.age) 
# 访问p的name实例变量,直接为该实例变量赋值
p.name = '李刚'
# 调用p的say()方法,声明say()方法时定义了2个形参,但第一个形参(self)不需要传值,因此调用该方法只需为第二个形参指定一个值
p.say('Python语言很简单,学习很容易!')
# 再次输出p的name、age实例变量
print(p.name, p.age)

结果

张三 20
Python语言很简单,学习很容易!
李刚 20
李刚 20
Python语言很简单,学习很容易!
李刚 20


  • 对已创建对象动态添加变量,只需要对新变量进行赋值,例如:
# 为p对象增加一个skills实例变量
p.skills = ['programming', 'swimming']
print(p.skills)#['programming', 'swimming']

  为 p 对象动态增加了一个 skills 实例变量,即只要对 p 对象的 skills 实例变量赋值就是新增一个实例变量。也可以动态删除实例变量,使用 del 语句即可删除,例如如下代码:

# 删除p对象的name实例变量
del p.name
# 再次访问p的name实例变量
print(p.name)
#zhangsan

  程序中调用 del 删除了 p 对象的 name 实例变量,但由于类中还有同名的 name 类变量,因此程序不会报错

  • 给类对象添加方法
     为 p 对象动态增加的方法,Python 不会自动将调用者自动绑定到第一个参数(即使将第一个参数命名为 self 也没用)。
# 先定义一个函数
def info(self):
    print("---info函数---", self)
# 使用info对p的foo方法赋值(动态绑定方法)
p.foo = info
# Python不会自动将调用者绑定到第一个参数,
# 因此程序需要手动将调用者绑定为第一个参数
p.foo(p)  # ①

# 使用lambda表达式为p对象的bar方法赋值(动态绑定方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p) # ②

    上面的第 5 行和第 11 行代码分别使用函数、lambda 表达式为 p 对象动态增加了方法,但对于动态增加的方法,Python 不会自动将方法调用者绑定到它们的第一个参数,因此程序必须手动为第一个参数传入参数值,如上面程序中 ① 号、② 号代码所示。


  如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于 types 模块下的 MethodType 进行包装。例如如下代码:

def intro_func(self, content):
    print("我是一个人,信息为:%s" % content)
# 导入MethodType
from types import MethodType
# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
p.intro = MethodType(intro_func, p)
# 第一个参数已经绑定了,无需传入
p.intro("生活在别处")

  正如从上面代码所看到的,通过 MethodType 包装 intr_func 函数之后(包装时指定了将该函数的第一个参数绑定为 p),为 p 对象动态增加的 intro() 方法的第一个参数己经绑定,因此程序通过 p 调用 intro() 方法时无须传入第一个参数,就像定义类时己经定义了 intro() 方法一样。

共有和私有

  一般面向对象的编程语言都会区分共有和私有,在python中默认对象的属性和方法都是公开的,可以通过点操作符进行访问
但是为了实现类似私有变量的特征,python内部才有了一种name mangling(名字改编)的技术
定义私有变量只要在变量名或者函数名前面加“__”两个下划线,那么这个函数或者变量就成了私有的。

class Play:
    name=1
    __gef="ah"
p=Play()
print(p.name)#1
print(p.__gef)#AttributeError: 'Play' object has no attribute '__gef'

这样外部将变量名隐藏起来了,如果需要访问需要从内部进行

class Play:
    def __init__(self,name):
        self.__name=name
    def getName(self):
        return self.__name
p=Play('laohu')
print(p.getName())#laohu

也可以通过“_类名__变量名”访问双下划线开头的私有变量

#python目前的私有都是伪私有,python的类没有权限控制
print(p._Play__name)#laohu

继承

    继承,即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
    Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类、超类)。子类继承父类的语法是:在定义子类时,将多个父类放在子类之后的圆括号里。语法格式如下:

 class 类名(父类1, 父类2, ...):
      #类定义部分

注意,Python 的继承是多继承机制,即一个子类可以同时拥有多个直接父类。

如果在定义一个 Python 类时,并未显式指定这个类的直接父类,则这个类默认继承 object 类。object 类是所有类的父类,要么是直接父类,要么是间接父类。

从子类的角度来看,子类扩展(extend)了父类;但从父类的角度来看,父类派生(derive)出子类。也就是说,扩展和派生所描述的是同一个动作,只是观察角度不同而已

class Fruit:
    def info(self):
        print("我是一个水果!重%g克" % self.weight)

class Food:
    def taste(self):
        print("不同食物的口感不同")

# 定义Apple类,继承了Fruit和Food类
class Apple(Fruit, Food):
    pass

# 创建Apple对象
a = Apple()
a.weight = 5.6
# 调用Apple对象的info()方法
a.info()
# 调用Apple对象的taste()方法
a.taste()

注意的是,如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性:

class Apple(Fruit, Food):
    def taste(self):
        print("我不好吃,不脆不甜")
a.taste()#  我不好吃,不脆不甜      

调用未绑定的父类方法

子类包含与父类同名的方法的现象被称为方法重写(Override),也被称为方法覆盖。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。

在子类中调用重写之后的方法,Python 总是会执行子类重写的方法,不会执行父类中被重写的方法。如果需要在子类之中调用父类被重写的实例方法,怎么做?

Python 类相当于类空间,因此 Python 类中的方法本质上相当于类空间内的函数。所以,即使是实例方法,Python 也允许通过类名调用。区别在于:在通过类名调用实例方法时,Python 不会为实例方法的第一个参数 self 自动绑定参数值,而是需要程序显式绑定第一个参数 self。这种机制被称为未绑定方法。

通过使用未绑定方法即可在子类中再次调用父类中被重写的方法

class BaseClass:
    def foo (self):
        print('父类中定义的foo方法')
class SubClass(BaseClass):
    # 重写父类的foo方法
    def foo (self):
        print('子类重写父类中的foo方法')
    def bar (self):
        print('执行bar方法')
        # 直接执行foo方法,将会调用子类重写之后的foo()方法
        self.foo()
        # 使用类名调用实例方法(未绑定方法)调用父类被重写的方法
        BaseClass.foo(self)
sc = SubClass()
sc.bar()

执行bar方法
子类重写父类中的foo方法
父类中定义的foo方法

上面程序中 SubClass 继承了 BaseClass 类,并重写了父类的 foo() 方法。接下来程序在 SubClass 类中定义了 bar() 方法,该方法的第 11 行代码直接通过 self 调用 foo() 方法,Python 将会执行子类重写之后的 foo() 方法;第 13 行代码通过未绑定方法显式调用 BaseClass 中的 foo 实例方法,井显式为第一个参数 self 绑定参数值,这就实现了调用父类中被重写的方法。

super函数

Python 的子类也会继承得到父类的构造方法,但如果子类有多个直接父类,那么会优先选择排在最前面的父类的构造方法。

注意,当子类继承多个父类是,super() 函数只能用来调用第一个父类的构造方法,而其它父类的构造方法只能使用未绑定的方式调用。

class Employee :
    def __init__ (self, salary):
        self.salary = salary
    def work (self):
        print('普通员工正在写代码,工资是:', self.salary)
class Customer:
    def __init__ (self, favorite, address):
        self.favorite = favorite
        self.address = address
    def info (self):
        print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))
# Manager继承了Employee、Customer
class Manager(Employee, Customer):
    # 重写父类的构造方法
    def __init__(self, salary, favorite, address):
        print('--Manager的构造方法--')
        # 通过super()函数调用父类的构造方法
        super().__init__(salary)
        # 与上一行代码的效果相同
        #super(Manager, self).__init__(salary)
        # 使用未绑定方法调用父类的构造方法
        Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work()  #①
m.info()  #②

上面程序中,第 7、11 行代码分别示范了两种方式调用父类的构造方法。通过这种方式,Manager 类重写了父类的构造方法,并在构造方法中显式调用了父类的两个构造方法执行初始化,这样两个父类中的实例变量都能被初始化。

–Manager的构造方法–
普通员工正在写代码,工资是:2500。
我是一个顾客,我的爱好是:IT产品,地址是广州

从上面的运行结果可以看到,此时程序中 ①、② 号代码都可正常运行,这正是程序对 Manager 重写的构造方法分别调用了两个父类的构造方法来完成初始化的结果

多重继承

大部分面向对象的编程语言(除了 C++)都只支持单继承,而不支持多继承,这是由于多继承不仅增加了编程的复杂度,而且很容易导致一些莫名的错误。

Python 虽然在语法上明确支持多继承,但通常推荐如果不是很有必要,则尽量不要使用多继承,而是使用单继承,这样可以保证编程思路更清晰,而且可以避免很多麻烦。

当一个子类有多个直接父类时,该子类会继承得到所有父类的方法,但是,如果多个父类中包含了同名的方法,此时会发生什么呢?此时排在前面的父类中的方法会“遮蔽”排在后面的父类中的同名方法。例如:

class Item:
    def info (self):
        print("Item中方法:", '这是一个商品')
class Product:
    def info (self):
        print("Product中方法:", '这是一个工业产品')
class Mouse(Item, Product): # ①
    pass
m = Mouse()
m.info()#Item中方法: 这是一个商品
#

组合

class Turtle:
    def __init__(self, x):
        self.num = x


class Fish:
    def __init__(self, x):
        self.num =3* x


class Pool:
    def __init__(self, x, y):
        self.turtle = Turtle(x)
        self.fish = Fish(y)

    def print_num(self):
        print("水池里面有乌龟%s只,小鱼%s条" % (self.turtle.num, self.fish.num))


p = Pool(2, 3)
p.print_num()
# 水池里面有乌龟2只,小鱼9条

类,类对象和实例对象

https://img-blog.csdnimg.cn/20191102142001735.png
类对象:创建一个类,其实也是一个对象也在内存开辟了一块空间,称为类对象,类对象只有一个

实例对象:就是通过实例化类创建的对象,称为实例对象,实例对象可以有多个

类属性:类里面方法外面定义的变量称为类属性。类属性所属于类对象并且多个实例对象之间共享同一个类属性,说白了就是类属性所有的通过该类实例化的对象都能共享。

实例属性:实例属性和具体的某个实例对象有关系,并且一个实例对象和另外一个实例对象是不共享属性的,说白了实例属性只能在自己的对象里面使用,其他的对象不能直接使用,因为self是谁调用,它的值就属于该对象。

  • 类属性:类外面,可以通过实例对象.类属性和类名.类属性进行调用。类里面,通过self.类属性和类名.类属性进行调用。
  • 实例属性 :类外面,可以通过实例对象.实例属性调用。类里面,通过self.实例属性调用。
  • 实例属性就相当于局部变量。出了这个类或者这个类的实例对象,就没有作用了。
  • 类属性就相当于类里面的全局变量,可以和这个类的所有实例对象共享。

绑定

Python 严格要求方法需要有实例才能被调用,这种限制其实就是 Python 所谓的绑定概念。
Python 对象的数据属性通常存储在名为.__ dict__的字典中,我们可以直接访问__dict__,或利用 Python 的内置函数vars()获取.__ dict__。

常用内置函数

1.issubclass(class, classinfo)
用于判断参数 class 是否是类型参数 classinfo 的子类

  • 一个类被认为是其自身的子类。
  • classinfo可以是类对象的元组,只要class是其中任何一个候选类的子类,则返回True
class A:
    pass
class B(A):
    pass
print(issubclass(B, A))# True     

2.isinstance(object, classinfo)
如果第一参数(object)是第二个参数( classinfo)的实例对象,则返回True

  • 如果object是classinfo的子类的一个实例,也符合条件
  • 如果第一个参数不是对象,则永远返回False。
  • classinfo可以是类对象组成的元组,只要class是其中任何一个候选类的子类,则返回True
  • 如果第二个参数不是类或者由类对象组成的元组,会抛出一个TypeError异常。

3.hasattr(object, name)
用于判断对象是否包含对应的属性。
4.getattr(object, name[, default])
用于返回一个对象属性值
5.setattr(object, name, value)
对应函数 getattr(),用于设置属性值,该属性不一定是存在的。
7.delattr(object, name)用于删除属性。
8.class property([fget[, fset[, fdel[, doc]]]])
用于在新式类中返回属性值。

  • fget – 获取属性值的函数
  • fset – 设置属性值的函数
  • fdel – 删除属性值函数
  • doc – 属性描述信息
发布了47 篇原创文章 · 获赞 5 · 访问量 1930

猜你喜欢

转载自blog.csdn.net/Pang_ling/article/details/102864030