面向对象

63 阅读8分钟

面向对象就类似于现实中的填写信息,先要设计表格内容,在打印表格,最后填写表格,在程序中也是如此,类比如下

image.png 知道类是什么之后,接下来我们要学会怎么去定义一个类,定义类的格式是

class 类名称:
    类的属性
    类的行为

这是定义一个类的格式,定义完类接下来就是要去创建一个类对象了,就相当于指定好了表格,接下来要去打印,创建类对象的格式是

对象=类名称()

定义在类里的函数统一称为方法,定义一个方法的格式是

def 方法名(self,形参1,.....,形参N)

可以看到,在方法定义的参数列表中,有一个:self关键字self关键字是成员方法定义的时候,必须填写的。 它用来表示类对象自身的意思 当我们使用类对象调用方法的是,self会自动被python传入 在方法内部,想要访问类的成员变量,必须使用self 可以看到,在方法定义的参数列表中,有一个:self关键字self关键字是成员方法定义的时候,必须填写的。 它用来表示类对象自身的意思 当我们使用类对象调用方法的是,self会自动被python传入 在方法内部,想要访问类的成员变量,必须使用self

image.png 尽管 self 必须存在,但是在写参数是可以当作是透明的

image.png 当调用 self 时,无需传参,所以传入参数时,第一个传入的参数是 mag 而不是给 self

关于类和对象的关系大致跟以下差不多,我们设计的类只能当作是一种图纸,所以我们要基于图纸生产实体(对象),对能正常使用,这种就是面向对象编程,设计类,基于类创建对象,由对象做具体的工作

image.png 具体的操作可以通过以下代码去实现

# 设计一个闹钟
class Clock:
    id=None
    price=None

    def ring1(self):
        import winsound
        winsound.Beep(500,3000)

    def ring2(self):
        import winsound
        winsound.Beep(1000, 3000)
# 创建2个闹钟对象并让其工作
clock1=Clock()
clock1.id="00303"
clock1.price=19.9
print(f'闹钟的ID:{clock1.id},价格{clock1.price}')
clock1.ring1()
clock2=Clock()
clock2.id="00404"
clock2.price=9.9
print(f'闹钟的ID:{clock2.id},价格{clock2.price}')
clock2.ring2()

可以看出在创建对象时,我们可以创建多个对象,且互不影响

在定义一个类时,类里面有许多内置的方法,我们称之为魔术方法,以下是几种常见的魔术方法

image.png

①__str__ 字符串方法 当类对象需要被转换为字符串之时,会输出如上结果(内存地址) 内存地址没有多大作用,我们可以通过str方法,控制类转换为字符串的行为。

class Student5:
    def __init__(self,name,age):
        self.name=name
        self.age=age
stu_4=Student5("邓紫棋",18)
print(stu_4)

通过以上代码打出的是它的内存地址,此时可以使用 str 魔术方法对其进行改造

class Student5:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    # str魔术方法
    def __str__(self):
        return f"Student5类对象:name:{self.name},age:{self.age}"
stu_4=Student5("邓紫棋",18)
print(stu_4)

改造完之后打印出的就是通过 str 方法返回的数据,输出结果如下

image.png ②__lt__ 小于符号比较方法 在面向对象的知识中,直接对两个对象进行比较是不可以的,所以我们需要通过lt方法 lt 的传入参数中的 other 表示另一个类对象

class Student6:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __lt__(self,other):
        return self.age<other.age # 返回值是Ture或False
stu1=Student6("贝克汉姆",21)
stu2=Student6("阿尔瓦雷斯",18)
print(stu1<stu2)
print(stu1>stu2)

③__le__ 小于等于比较符号方法 lt方法只能比较大于或者小于,带等号的情况是无法进行比较的,所以我们需要使用le方法,使用方法同 lt,只是多加了个等号

class Student7:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __le__(self,other):
        return self.age<other.age # 返回值是Ture或False
stu1=Student7("亚马尔",17)
stu2=Student7("库库雷利亚",18)
print(stu1<=stu2)
print(stu1>=stu2)

④__eq__ 比较运算符实现方法 eq只能比较等号,返回的值是布尔类型,如果没有使用eq方法时,直接比较也是会返回值的,只会返回False,因为不使用eq方法比较的是地址,使用方法同以上两种

当我们将现实世界事物的属性和行为封装到类中,描述为成员变量和成员方法,这种就是封装 在封装时,我们可以定义私有成员和私有方法,私有成员不能够被用户直接使用,如果像正常对象那样使用私有方法时,系统会报错,调用私有成员进行赋值时不报错但是无效, 就类似于手机的属性,只有厂家能够更改,我们无法更改,私有成员和私有方法也是这样,只能在类内部使用,在外部无法使用 定义私有成员的方式非常简单,只需要: 私有成员变量:变量名以开头(2个下划线)私有成员方法:方法名以开头(2个下划线)即可完成私有成员的设置

class Phone:
    # 定义私有成员
    __current_voltage=1
    # 定义私有方法
    def __keep_single_core(self):
        print("让CPU单核运行")
    # 定义一个公开方法去调用私有方法
    def call_by_5G(self):
        if self.__current_voltage >=1:
            print("5G通话已开启")
        else:
            self.__keep_single_core()
            print("你的电压不够,无法开启5G通话")
phone=Phone()
phone.call_by_5G()

此时的系统是不报错的,说明可以正常运行

私有成员是内部才能使用的,放在外部是无法使用的

私有成员的意义是提供仅供内部成员使用的属性和方法,而不对外开放(类对象无法使用)

在面向对象中还有一个特点就是继承,继承就是定义一个新的类,这个新的类继承了老的类的内容,新的类就被称为子类,老的类就被称为父类,继承也分为单继承和多继承,以下是单继承的写法

class 类名(父类名):
    类内容体

括号内的就是父类,重新写的类名就是子类,子类可以继承父类的内容,以上这种继承方法称为单继承 多继承就是一个子类可以继承多个父类,书写格式为

class 类名(父类1,父类2.....):
    新增内容提/pass

当定义一个类之后不想写内容,可以使用 pass

注意,当有两个同名的方法时,默认从左到右的顺序,谁先继承用谁的方法

image.png 以上 Phone 和 NFCreader 中都含有名为 producer 的方法,当调用 producer 时,调用的是 Phone 中的,因为先继承 Phone,先继承谁,谁的优先级越高. 子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写。 即:在子类中重新定义同名的属性或方法即可。

需要复写时,只需要重新定义这个成员

# 复写父类成员
class Phone6(Phone5):
    # 在子类中调用父类的成员变量
    five=phone5.nfc
    nfc="第六代"
    def call_by_5g(self):
        # 在子类中调用父类的成员方法
        phone5.call_by_5g()
        print("5g已升级,现在是6g")
phone6=Phone6()
print(phone6.nfc)
print(phone6.call_by_5g())

一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员如果需要使用被复写的父类的成员,需要特殊的调用方式:

方式1: 调用父类成员 使用成员变量:父类名.成员变量 使用成员方法:父类名.成员方法(self)

方式2: 使用super()调用父类成员使用成员变量:super().成员变量使用成员方法:super().成员方法()

面向对象三大特点的最后一个就是多态,多态就是以父类做定义声明,以子类做实际工作,用以获得同一行为,不同状态

# 多态
class Animal:
    def speak(self):
        pass
class Dog(Animal):
    def speak(self):
        print("汪汪汪")
class Cat(Animal):
    def speak(self):
        print("喵喵喵")
def make_noise(animal:Animal):
    animal.speak()
dog=Dog()
cat=Cat()
# 传入子类对象
make_noise(dog)
make_noise(cat)

此时定义的第一个 Animal 是一个空实现,就相当于一个接口,确定有哪些方法,具体的方法由子类来确定,这种写法称为接口或者抽象类,方法体是空实现(pass)的就被称之为空实现,接口就相当于是大老板指定一些指标计划,接下来交给相关部门去具体实现,接口只是梯控抽象方法,具体的问题需要子类去解决