学习笔记

全民一起玩Python【4】(面向对象原理与语法)

1.class定义对象,self指向自身

可以通过class 语句来定义类,给类起名字的规则,与变量、函数等命名规则相同,不过按照业界习惯,类名一般不采用下划线,而是把每个单词的首字母大写,也就是驼峰命名法。

创建对象的语法是:【对象变量名=类名()】

创建对象后,还可以自己给对象增加属性【对象名.自定义属性名=属性内容】

class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hello(self):
        print(f'大家好,我是{self.name},今年{self.age}岁')

#创建一个Person对象
person=Person('李灵杰','25')

#调用对象的方法
person.say_hello()

#可以为对象增加属性
person.h=170
print(person.h)

需要注意的是,当调用一个对象的某个方法时,Python会自动为其添加一个参数,并且排在第一位,比如代码写的是【m1.add(5,3)】,则实际执行的是【m1.add(自动参数,5,3)】

该自动参数代表的,就是调用该方法的哪个对象自身

比如下面的代码中,调用对象是没有填写参数,Python却报了错:

class 类:
    def prt():
        print(123)
对象=类()
对象.prt()    
#TypeError: 类.prt() takes 0 positional arguments but 1 was given
#类型错误:类.prt()接受0个位置参数,但提供了1个参数。

也就是说,class中定义的方法,第一个参数self(也可以写成其他,但约定俗称写成self),就是该对象自身,

也就是说对象在执行自己的方法时,又将自己作为参数传给了自己这个方法

如果类中定义的方法有多个参数,self要放在第一位,否则会将第一位当成self

class 类:
    def prt(a):
        print(a.数值)
对象=类()
对象.数值=12
对象.prt()    #12
class MiShu:
    def say_hello(self):
        print('变量self的地址是:',id(self))
    def do_add(self,x,y):
        print(f'{self.name}计算出的结果是{x+y}')

if __name__ =='__main__':
    m1=MiShu()
    m2=MiShu()

    m1.name='l'
    m2.name='c'

    m1.do_add(500,20)      #l计算出的结果是520
    m2.do_add(1000,314) #c计算出的结果是1314
    #仅填写了两个参数,但是类中自定义的方法,加上self却一共有3个参数

m2.do_add()仅填写了两个参数,但是在但是类中自定义的方法,加上self却一共有3个参数,为什么能正常运行,chatGPT给出的回答是:

在Python中,类的方法中的第一个参数通常被命名为self,它表示对当前对象的引用。当你调用一个类的方法时,Python会自动将调用该方法的对象作为第一个参数传递给该方法,也就是self。所以在你的代码中,即使你在调用m2.do_add(1000, 314)时只提供了两个参数,Python仍然会将m2对象作为self参数传递给do_add方法,因此方法调用可以正常执行。

实际上,m2.do_add(1000, 314)这一行的执行等同于MiShu.do_add(m2, 1000, 314),其中m2会被传递给方法作为self参数,而1000和314则分别作为x和y参数传递给方法。

总结起来,当你在类的方法中定义了self参数时,Python会在方法调用时自动传递当前对象作为self参数,即使你没有显式地提供该参数。这就是为什么你的代码能够正常运行的原因。

2.构造方法统一属性定义,封装对象不惧万马奔腾

定义一个类时,可以使用构造方法,写法是【def __init__(self,参数…)】

在创建类的实例时,构造方法会被自动调用,用于对实例的初始化操作,为对象提供初始状态

在构造方法中,可以使用参数来接收外部传入的赋值,并将其赋给实例变量,构造方法还可以执行其他任何你认为在对象创建时需要执行的操作

定义了初始值后,后期也可以自己给对象重新赋值

class 类:
        def __init__(self):
                self.name='初始名字'
        def say_hello(self):
                print(self.name)
对象1=类()
对象1.say_hello() #初始名字

对象1.name='重新定义的名字'
对象1.say_hello() #重新定义的名字
class Mishu:
    def __init__(self,n,a,i):
        self.name=n
        self.age=a
        self.id=i

    def say_hello(self):
        print(f'我是{self.name},工号{self.id}')

    def do_add(self,x,y):
        print(f'{self.name}算好了,计算结果是{x+y}')

if __name__=='__main__':
    m1=Mishu('a',30,'a01')
    m2=Mishu('b',20,'b02')

    m1.say_hello()  #我是a,工号a01
    m2.say_hello()  #我是b,工号b02

创建一个跑马的程序

import time
import random

name_1='海洋饼干'
speed_1=5
loc_1=0

while True:
    r=round(random.random())
    loc_1=loc_1+speed_1*r
    print(r)
    print(f'{name_1}已经跑到了{loc_1}米处!')
    time.sleep(1)

以上例子中,如果有许多匹吗,需要重复性地创建许多个变量,可以通过创建类的方式来精简

import random
import time

class Horse:
    def __init__(self,n,s):
        self.name=n
        self.speed=s
        self.loc=0

    def run(self):
        self.loc+=round(self.speed*random.random())
        print(f'{self.name}已经跑到了{self.loc}米处!')

if __name__=='__main__':
    h1=Horse('海洋饼干',6)

    while True:
        h1.run()
        time.sleep(1)

3.面向对象便于分工协作,成员私有避免乱写乱读

可以import其他Python文件中的类,比如【from py文件 import 类名】,Python文件不需要.py

创建类时,可以为属性前面加上两条下划线,比如【self.__属性=abc】将该属性定义为私有属性,意味着该属性只能被函数内部的方法所调用,创建类后不能自己手动修改

Python支持动态添加属性,在创建一个对象后,还可以通过【对象.自定义属性名】来添加属性

值得了解的是,动态添加属性时,如果将属性名前面加两条下划线,并不会修改私有属性,而是相当于创建一个新的属性,因为私有属性在Python中,属性名前后都有两条下划线,也就是说私有属性的真实名字是【__属性名__】

设置了私有属性后,想要读取私有属性的数值,可以使用getter方法

可以在class类自定义代码中,编写一个方法,根据用户指定的参数读写该class中的某个私有属性。

由于该方法位于class内部,所以可以合法的读写该私有属性,同时该方法本身并非私有,因而可以被其他外部程序合法调用

class Test:
    def __init__(self,n,x):
        self.__name=n
        self.x=x

    def get_loc(self):
        return self.__name  #创建类时,加上一个调用私有变量的方法

t1=Test('t1',1)
print(t1.x)

t1name=t1.get_loc() #调用私有变量的方法
print(t1name)   #t1

可以通过setter方法来修改私有属性

class Test:
    def __init__(self,n,x):
        self.__name=n
        self.x=x

    def get_loc(self):
        return self.__name  #创建类时,加上一个调用私有变量的方法

    def set_loc(self,x):    #加上一个修改私有变量的方法
        self.__name=x



t1=Test('t1',1)
print(t1.x)

t1.set_loc('修改后的名字')    #调用 修改私有变量 方法


t1name=t1.get_loc() #读取私有变量的方法
print(t1name)   #修改后的名字

设置私有变量后又加上setter方法,可以在一些场景下,控制变量的输入范围

class Student:
    def __init__(self,n,id):
        self.name=n
        self.id=id
        self.__age=16

    def say_hello(self):
        print(f'我是{self.name},今年{self.__age}岁')

    def set_age(self,a):
        if 12<a<30:
            self.__age=a
        else:
            print('年龄不在许可的范围内')


std1=Student('li','a13')
std1.set_age(25)    #如果不再许可的范围内会弹出提示并且不允许修改
std1.say_hello()

如果自己创建了一个类,里面的属性不希望别人随意修改,就可以将其设置为私有属性

4.pygame支持2D游戏,做动画只需边写边擦

此处为语雀视频卡片,点击链接查看:[11.4]–第三十六回 pygame支持2D游戏,做动画只需边写边擦.mp4

5.方法多态打破种类隔离,只看功能不分你我高低

此处为语雀视频卡片,点击链接查看:[11.5]–第三十七回 方法多态打破种类隔离,只看功能不分你我高低.mp4

6.继承父类得到创业资本,覆盖方法走出自我新篇

一个类可以继承另一个类的属性和方法,只要在自定义类时,在类名后面加上一个括号,括号里写上要继承的类名,新创建的类就能获得指定类的属性(不严谨的理解)和方法

所谓子类继承父类的属性,实质上是子类继承了父类的【__init__】,因而在创建时即动态添加与父类一样的属性

私有属性无法继承(不严谨的说法)

如果子类继承父类,有给子类添加了自己的属性,那么父类的属性就无法被继承

新创建的类也可以添加自己的属性和方法,如果定义的方法的名字与父类相同,但功能不同,相当于对继承来的方法进行了修改,改成了自己的方法,也就是进行了方法的覆盖

class A:
    def __init__(self):
        self.money=1000000000
        self.house=100
    def my_small_goal(self):
        print('先赚一个亿')

class B(A):
    pass;

if __name__=='__main__':
    x=B()

    print(x.money)  #1000000000
    print(x.house)  #1000000000
    x.my_small_goal()   #先赚一个亿

isinstance()函数可以检查一个对象是否属于某个类型,或者判断A对象是否是B对象的子类,语法是isinstance(A,B),返回True或False,值得了解的是,第二个参数允许是元组,如果A参数是元组中任意一个类型,那也会返回True

7.多继承自动获得多方法,父属性并未传给子类别

此处为语雀视频卡片,点击链接查看:[11.7]–第三十九回 多继承自动获得多方法,父属性并未传给子类别.mp4

所谓子类继承父类的属性,实质上是子类继承了父类的【__init__】,因而在创建时即动态添加与父类一样的属性

B类继承A类后,给B添加自己的属性,然后调用A类属性时会报错

class A:
    def __init__(self):
        self.money=1000000000
        self.house=100
    def my_small_goal(self):
        print('先赚一个亿')

class B(A):
    def __init__(self):
        self.test='test'    #继承A类后,添加自己的属性,然后调用A类属性时会报错

if __name__=='__main__':
    x=B()

    print(x.money)   #继承A类后,添加自己的属性,然后调用A类属性时会报错
    #AttributeError: 'B' object has no attribute 'money'
    print(x.house)  #继承A类后,添加自己的属性,然后调用A类属性时会报错
    x.my_small_goal()   #先赚一个亿

如果父类的__init__中定义了私有属性,子类自己定义的方法中无法调用该属性

继承私有属性,私有属性的名称需要加上父类属性的前缀(真实名称)

class 有钱人:
    def __init__(self,n,m):
        self.__money=m
        self.name=n

    def 交个朋友(self):
        print(f'{self.name}说:这些钱拿去花:{self.__money/200}')

class 炫富者(有钱人):
    def 炫富(self):
        print(f'我叫{self.name},这些钱都是我的:',self._有钱人__money*10)    #继承私有属性,私有属性的名称需要加上父类属性的前缀(真实名称)

if __name__ =='__main__':
    a=炫富者('张三',1000000)
    a.交个朋友()
    a.炫富()

    b=炫富者('王五',2000000)
    b.交个朋友()

如果子类和父类都有同名方法,怎样才能在子类中调用这个方法,从而减少重复代码?

可以写【父类名.方法名(self,*)】,也可以写【super().方法名(*)】

子类可以继承多个父类,只要在继承时,在括号里将不同的父类名字用逗号隔开

如果从多个父类继承方法,而这些父类中存在同名的方法,会优先继承括号中排在前面的父类

8.指定对象作为参数,相互调用实现协同

此处为语雀视频卡片,点击链接查看:[11.8]–第四十回 指定对象作为参数,相互调用实现协同.mp4

class Role:
    def __init__(self,nm,pwr,bld):
        self.name=nm
        self.power=pwr
        self.blood=bld

    def attack(self,enemy):
        print(f'{self.name}对{enemy.name}发起{self.power}点攻击')
        enemy.hurt(self.power)

    def hurt(self,p):
        self.blood-=p
        print(f'{self.name}血量降低到{self.blood}')

    def die(self):
        print(f'{self.name}卒...')

r1=Role('王昭君',10,100)
r2=Role('程咬金',5,200)

print(f'大家好,我是{r1.name},我很能打')
print(f'大家好,我是{r2.name},我血很多')

r1.attack(r2)
r2.attack(r1)
r1.attack(r2)
r2.attack(r1)

改进的写法

from time import sleep
from random import sample

class Role:
    def __init__(self,nm,pwr,bld):
        self.name=nm
        self.power=pwr
        self.blood=bld

    def attack(self,enemy):
        print(f'{self.name}对{enemy.name}发起{self.power}点攻击')
        enemy.hurt(self.power)

    def hurt(self,p):
        self.blood-=p
        print(f'{self.name}血量降低到{self.blood}')

    def die(self):
        print(f'{self.name}卒...')

roles=[
    Role('王昭君',10,100),
    Role('程咬金',5,200),
    Role('妲己',12,70)
]

while len(roles)>1:
    本轮对手=sample(roles,2)
    r1=本轮对手[0]
    r2=本轮对手[1]

    r1.attack(r2)

    if r2.blood<=0:
        r2.die()
        roles.remove(r2)

    #sleep(0.2)

print(f'最终获胜者——{roles[0].name}')

'''
程咬金对妲己发起5点攻击
妲己血量降低到65
妲己对程咬金发起12点攻击
程咬金血量降低到188
妲己对王昭君发起12点攻击
王昭君血量降低到88
王昭君对妲己发起10点攻击
妲己血量降低到55
王昭君对程咬金发起10点攻击
程咬金血量降低到178
程咬金对王昭君发起5点攻击
王昭君血量降低到83
程咬金对王昭君发起5点攻击
王昭君血量降低到78
王昭君对妲己发起10点攻击
妲己血量降低到45
妲己对程咬金发起12点攻击
程咬金血量降低到166
王昭君对妲己发起10点攻击
妲己血量降低到35
王昭君对程咬金发起10点攻击
程咬金血量降低到156
妲己对程咬金发起12点攻击
程咬金血量降低到144
妲己对王昭君发起12点攻击
王昭君血量降低到66
程咬金对王昭君发起5点攻击
王昭君血量降低到61
王昭君对程咬金发起10点攻击
程咬金血量降低到134
程咬金对王昭君发起5点攻击
王昭君血量降低到56
妲己对程咬金发起12点攻击
程咬金血量降低到122
妲己对程咬金发起12点攻击
程咬金血量降低到110
王昭君对程咬金发起10点攻击
程咬金血量降低到100
程咬金对妲己发起5点攻击
妲己血量降低到30
王昭君对妲己发起10点攻击
妲己血量降低到20
妲己对程咬金发起12点攻击
程咬金血量降低到88
妲己对王昭君发起12点攻击
王昭君血量降低到44
王昭君对妲己发起10点攻击
妲己血量降低到10
王昭君对妲己发起10点攻击
妲己血量降低到0
妲己卒...
王昭君对程咬金发起10点攻击
程咬金血量降低到78
程咬金对王昭君发起5点攻击
王昭君血量降低到39
王昭君对程咬金发起10点攻击
程咬金血量降低到68
王昭君对程咬金发起10点攻击
程咬金血量降低到58
王昭君对程咬金发起10点攻击
程咬金血量降低到48
王昭君对程咬金发起10点攻击
程咬金血量降低到38
王昭君对程咬金发起10点攻击
程咬金血量降低到28
王昭君对程咬金发起10点攻击
程咬金血量降低到18
程咬金对王昭君发起5点攻击
王昭君血量降低到34
王昭君对程咬金发起10点攻击
程咬金血量降低到8
王昭君对程咬金发起10点攻击
程咬金血量降低到-2
程咬金卒...
最终获胜者——王昭君
'''

9.寻常只道Python好,不识对象是真身

dir()与help()可以一查看一个类的所有属性和方法

发表回复