Python学习笔记(十一):类和对象


关于类和对象

  • Python学习笔记(十一):类和对象
  • 一.类变量和实例变量
  • 类变量
  • 实例变量
  • 局部变量
  • 二.函数装饰器
  • 三.property()
  • 四.封装机制
  • 五.继承机制
  • Python的多继承


一.类变量和实例变量

类体中、所有函数之外:此范围定义的变量,称为类属性或类变量

类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量

类体中,所有函数内部:以“变量名=变量值”的方式定义的变量,称为局部变量

类变量

类变量:在类中,但在各个类方法外定义的变量

class test :
    name = "you"     # 定义类变量
    age = "20"
    def say(self, content):     # 定义say实例方法
        print(content)

name 和 age 属于类变量

类变量的特点:所有类的实例化对象都同时共享类变量,类变量在所有实例化对象中是作为公用资源存在的

类方法的调用方式:使用类名直接调用,使用类的实例化对象调用

print(test.name)  #使用类名直接调用

test.name = "youchanwill" #修改类变量的值
print(test.name)

类名不仅可以调用类变量,也可以修改值

类变量为所有实例化对象共有,通过类名修改类变量的值,会影响所有的实例化对象
除了通过类名访问类变量,还可以动态地为类和对象添加类变量

通过类对象是无法修改类变量的,其本质将不再是修改类变量的值,而是在给该对象定义新的实例变量

除了通过类名访问类变量之外,还可以动态地为类和对象添加类变量
TEST = test()
TEST.catalog = 13
print(TEST.catalog)

实例变量

实例变量:在任意类方法内部,以“self.变量名”的方式定义的变量

只作用于调用方法的对象,实例变量只能通过对象名访问,无法通过类名访问

class test :
    def __init__(self):
        self.name = "you"
        self.age = "20"
    def say(self):     # 定义say实例方法
        self.catalog = 7
        
name、age以及 catalog 都是实例变量
__init__() 函数在创建类对象时会自动调用,而say()方法需要类对象手动调用

test类的类对象都会包含name和age实例变量,只有调用了say()方法的类对象,才包含catalog实例变量

TEST = test() #只有调用say(),才会拥有catalog实例变量
TEST.say()
print(TEST.catalog)
通过类对象可以访问类变量,但无法修改类变量的值

通过类对象修改类变量的值,不是给类变量赋值,而是定义新的实例变量

Python只支持为特定的对象添加实例变量
TEST.money = 30
print(TEST.money)

局部变量

局部变量以“变量名=值”的方式进行定义

class test :
    def count(self,money):   # 定义say实例方法
        sale = 0.8*money
        print("优惠后的价格为:",sale)
TEST= test()
TEST.count(100)
优惠后的价格为: 80.0

定义局部变量是为了所在类方法功能的实现,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁

二.函数装饰器

关于Python的函数装饰器的内容,见之前文章

Python学习笔记(十):函数装饰器

三.property()

property() 

在不破坏类封装原则的前提下,依旧使用“类对象.属性”的方式操作类中的属性

属性名=property(fget=None, fset=None, fdel=None, doc=None)

fget参数:指定获取该属性值的类方法
fset参数:指定设置该属性值的方法
fdel参数:指定删除该属性值的方法,最后的doc是文档字符串,说明此函数的作用

property() 函数中参数的指定并不是完全随意的,可以仅指定第1个、或前2个、或前3个,也可以全部指定
class test:
    def __init__(self,n):    #构造函数
        self.__name = n
    def setname(self,n):    #设置 name 属性值的函数
        self.__name = n
    def getname(self):     #访问nema属性值的函数
        return self.__name
    def delname(self):     #删除name属性值的函数
        self.__name="xxx"
    name = property(getname, setname, delname, '说明文档')    #为name 属性配置 property() 函数
    
#说明文档,print(test.name.__doc__)
help(test.name)
TEST = test("you")

print(TEST.name) #调用 getname() 方法

TEST.name="Python教程"#调用 setname() 方法
print(TEST.name)

del TEST.name #调用 delname() 方法
print(TEST.name)

getname()方法中需要返回 name 属性,如果使用 self.name 的话,其本身又被调用 getname(),会进入死循环

name属性必须设置为私有属性,即__name

name = property(getname, setname)

name是一个可读写的属性,但不能删除,因为函数中并没有为name配置用于函数该属性的方法

test类中设计有 delname()函数,这种情况下也不能用来删除 name 属性
有关类属性和类方法的属性设置(共有属性、保护属性、私有属性)

name = property(getname)    # name 属性可读,不可写,也不能删除
name = property(getname, setname,delname)    #name属性可读、可写、也可删除,没有说明文档

四.封装机制

封装(Encapsulation)

在设计类时,将一些属性和方法隐藏在类的内部,在使用此类时,无法直接以“类对象.属性名”(“类对象.方法名(参数)”)的形式调用

封装机制保证了类内部数据结构的完整性,避免了外部对内部数据的影响,提高了程序的可维护性
实现类的封装,Python采取下面的方法

默认情况下,Python类中的变量和方法都是公有(public)的,名称前没有下划线(_)

变量(函数)为私有变量类中的变量和函数,名称以双下划线“__”开头,属性等同于 private

可以定义以单下划线“_”开头的类属性或者类方法,这种类属性和类方法通常被视为私有属性和私有方法,也能通过类对象正常访问,但这是约定俗称的用法

以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),都是 Python内部定义的,用于Python内部调用,自己定义类属性或者类方法时,不要使用这种格式
class test :
    def setname(self, name):
        if len(name) < 3:
            raise ValueError('名称长度过短') #有关raise的具体用法,简单理解成如果用户输入不规范,程序会报错
        self.__name = name
    def getname(self):
        return self.__name

    name = property(getname, setname)    #为name配置setter和getter方法
    def setadd(self, add):
        if add.startswith("http://"):
            self.__add = add
        else:
            raise ValueError('以http://开头') 
    def getadd(self):
        return self.__add
   
    add = property(getadd, setadd)  #为add配置setter和getter方法

    def __display(self): 
        print(self.__name,self.__add)     #定义私有方法
TEST = test()
TEST.name = "you"
TEST.add = "http://youchanwill.com"
print(TEST.name)
print(TEST.add)
you
http://youchanwill.com

将name和add属性隐藏了起来,同时也提供了各自的setter和getter方法,这些方法都是公有(public)的

仅能通过暴露的setter()和getter()方法操作name和add属性,可以避免用户对类中属性的不合理操作,提高了类的可维护性和安全性

五.继承机制

继承机制

用于创建和现有类功能类似的新类,通过继承机制,可以轻松实现类的重复使用

class Shape:
    def draw(self,content):
        print("画",content)
class Form:
    def draw(self,content):
        print("画",content)
    def area(self):
        #....
        print("此图形的面积为...")

Shape类的draw()方法可以画出指定的形状,现在需要创建一个Form类,还可以计算出所画形状的面积

class Shape:
    def draw(self,content):
        print("画",content)
class Form(Shape):
    def area(self):
        #....
        print("此图形的面积为...")
        
class From(Shape) 就表示 From 继承 Shape

类的继承机制:让From类继承Shape类,当From类对象调用draw()方法时,解释器会先去From中找以draw为名的方法,如果找不到,还会自动去Shape 类中找
Python 中,实现继承的类称为子类,被继承的类称为父类(基类、超类)

子类继承父类,在定义子类时,将父类放在子类之后的圆括号里

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

如果该类没有显式指定继承自哪个类,则默认继承 object 类(object类是 Python中所有类的父类,直接父类、间接父类)

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

继承相对子类来说的,子类继承自父类;派生相对于父类来说的,父类派生出子类

子类拥有父类所有的属性和方法,就算属性或方法是私有(private)的

Python的多继承

多继承需要面临的问题是,多个父类中包含同名的类方法

处置措施:根据子类继承多个父类时这些父类的前后次序决定,在前面父类中的类方法会覆盖排在后面父类中的同名类方法
class People:
    def __init__(self):
        self.name = People
    def say(self):
        print("People类",self.name)
class Animal:
    def __init__(self):
        self.name = Animal
    def say(self):
        print("Animal类",self.name)

class Person(People, Animal): #People中的name属性和say()会遮蔽Animal 类中的name属性
    pass 
test = Person()
test.name = "you"
test.say()
People类 you

当Person同时继承People类和Animal类时,People类在前,如果拥有同名的类方法,实际调用的是People类中的

虽然Python在语法上支持多继承,但多继承容易让代码逻辑复杂、思路混乱