【Python】类和对象

目录

 

1.定义类

2.对象的产生和使用

3.实例方法和自动绑定self

4.类与未绑定的方法

5.成员变量

6.property修饰器

7.封装和隐藏

8.类的继承

9.重写父类的方法

10.在子类中调用未被改写的父类方法

11.使用super函数调用父类的构造方法

12.__slots__与动态属性

13.使用type()函数定义类

14.使用metaclass

15.检查类型

16.枚举类


1.定义类

  • Python定义类的简单语法如下:

class 类名:
    执行语句...
    零到多个类变量...
    零到多个方法...
  • Python对象的实例变量也可以动态增加或删除,程序可以通过del语句删除已有对象的实例变量
  • 在类中定义的方法默认是实例方法,定义实例方法的方法与定义函数的方法基本相同,只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例)——因此实例方法至少应该定义一个参数,该参数通常被命名为self。
  • 在实例方法中有一个特殊的方法:__init__,这个方法被称为构造方法。构造方法用于构造该类的对象,Python通过调用构造方法返回该类的对象。

     


2.对象的产生和使用

  • 创建对象的根本途径是构造方法,调用某个类的构造方法即可创建这个类的对象,Python无须使用new调用构造方法。
p = Person()
  • Python对象大致有如下作用:
操作对象的实例变量(包括访问实例变量的值、添加实例变量、删除实例变量)
调用对象的方法
  • Python是动态语言,因此程序完全可以为对象动态增加实例变量——只要为它的新变量赋值即可,也可以动态删除实例变量——使用del语句即可删除。
#为p对象增加一个skills实例变量
p.skills = ["programing","swimming"]
print(p.skills)

#删除p对象的name实例变量
del p.name
print(p.name)
  • Python允许为对象动态增加方法,需要说明的是,为对象动态增加的方法,Python不会自动将调用者自动绑定到第一个参数,需要手动将调用者绑定到第一个参数
#定义一个函数
def info(self):
    print("---info函数---",self)

p.foo = info
p.foo(p)
#为对象动态添加的方法,需要手动将调用者绑定到第一个参数

p.bar = lambda self:print("---lambda表达式---",self)
p.bar(p)
  • 如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于types模块下的MethodType进行包装。
def intro_func(self,content):
    print("我是一个人,信息为:%s" %(content))

from types import MethodType

p.intro = MethodType(intro_func,p)
p.intro("生活在别处")

3.实例方法和自动绑定self

  • 对于在类体中定义的实例方法,Python会自动绑定方法的第一个参数,第一个参数总是指向调用该方法的对象,程序在调用普通实例方法、狗仔方法时不需要为第一个参数传值。
  • self参数的最大作用就是引用当前方法的调用者。例如在构造方法中通过self为该对象增加实例变量,也可以在一个实例方法中访问该类的另一个实例方法或变量。
class Dog:
    #定义一个jump()方法
    def jump(self):
        print("正在执行jump方法")
    #定义一个run()方法
    def run(self):
        #使用self参数引用调用run()方法的对象
        self.jump()
        print("正在执行run方法")
  • Python对象的一个方法调用另一个方法,不可以省略self。
  • 在构造方法中,self参数代表该构造方法正在初始化的对象。
class InConstructor:
    def __init__(self):
        #在构造方法中定义一个foo变量(局部变量)
        foo = 0
        #正在初始化的对象的foo实例变量设为6
        self.foo =6
#所有使用InConstructor创建的对象的foo实例变量将被设为6
print(InConstructor().foo)

4.类与未绑定的方法

  • 类也能调用实例方法,但实例方法的第一个参数(self)需要手动绑定
class User:
    def walk(self):
        print(self,"正在慢慢地走")
#通过类调用实例方法
User.walk()

运行上述代码会发生错误,错误原因是没有为类调用的实例方法手动绑定第一个参数,有以下两种方式解决:

#第一种方法-新建实例对象作为参数
u = User()
User.walk(u)

#第二种方法-直接传入参数
User.walk("fkit")
  • 类中的方法一般默认为实例方法,声明类方法的方式为在函数前添加@classmethod声明为静态方法的方式是添加@staticmethod修饰
  • 总结:
  1. 类方法无论是使用类调用还是对象调用,都会自动绑定第一个参数
  2. 静态方法无论是使用类调用还是对象调用,都不会自动绑定,都需要手动绑定
  3. 实例方法在使用对象调用时可以自动绑定,而在使用类调用时不能自动绑定
class Bird:
    #使用@classmethod修饰的方法就是类方法
    @classmethod
    def fly(cls):
        print("类方法fly: ",cla)
    
    #使用@staticmethod修饰的方法就是静态方法
    def info(p):
        print("静态方法info: ",p)

#调用类方法
Bird.fly()

#调用静态方法,不会自动绑定,因此程序必须手动绑定第一个参数
Bird,info("crazyit")

#创建Bird对象
b = Bird()

#使用对象调用fly()类方法
b.fly()

#使用对象调用info()静态方法
b.info("fkit")

5.成员变量

  • 在类体中定义的变量,默认属于类变量。
  • Python允许通过对象访问类变量,但如果程序通过对象尝试对类变量赋值,此时性质就变了——Python是动态语言,赋值语句往往意味着定义新变量。
class Inventory:
    #定义两个新变量
    item = "鼠标"
    quantity = 2000
    
    #定义实例方法
    def changek(self,item,quantity):
        #下面赋值语句不是对类变量赋值,而是定义新的实例变量
        self.item = item
        self.quantity = quantity

#创建Inventory对象
iv = Inventory()
iv.change("显示器",500)

#访问iv的item和quantity实例变量
print(iv.item)    #显示器
print(iv.quantity)   #500

#访问Inventory的item和quantity类变量
print(Inventory.item)  #鼠标
print(Inventory.quantity)   #2000
  • 从上述代码中可以看出Inventory类中的实例方法中,看似完成了对类变量item和quantity的赋值,但其实是定义了新的实例变量。

6.property修饰器

  • 使用getter和setter等访问器方法,可以将实例方法变为实例属性方式访问。property一般有两种使用方法。

1.直接使用property()函数,四个参数分别代表getter方法、setter方法、del方法和doc,其中doc为说明文档。

property(fget=None,fset=None,fdel=None,doc=None)

2.还可以使用@property装饰器来修饰方法,使之成为属性。

class Cell:
    #使用@property修饰方法,相当于为该属性设置getter方法
    @property
    def state(self):
        return self._state
    
    #为state属性设置setter方法
    @state.setter
    def state(self,value):
        if "alive" in value.lower():
            self._state = "alive"
        else:
            self._state = "dead"

    #为is_dead属性设置getter方法
    @property
    def is_dead(self):
        return not self._state.lower() == "alive"

c = Cell()
#修改state属性
c.state = "Alive"
#访问state属性
print("c.state")
#访问is_dead属性
print(c.is_dead)

7.封装和隐藏

  • Python可以通过在变量前加双下划线来隐藏变量避免直接访问,但Python并不支持真正的隐藏,添加双下划线前缀的变量和方法,Python会在这些方法名前添加单下划线和类名,因此可以通过如下的类似方法来调用:
u._User__hide()

u._User__name

8.类的继承

  • 继承是面对对象的三大特征之一,也是实现代码复用的重要手段。Python的继承是多继承机制,即一个子类可以同时有多个直接父类。
  • Python子类继承父类的语法是在定义子类时,将多个父类放在子类后的圆括号里。
class SubClass(SuperClass1,SuperClass2, ...):
    #类定义部分
  • 下面代码实例了子类继承父类的特点,下面是Fruit类的代码
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 Item:
    def info(self):
        print("Item中方法:", "这是一个商品")

class product:
    def info(self):
        print("Product中方法:", "这是一个工业产品")

class Mouse(Item,Product):
    pass

m= Mouse()
m.info()
#Item中方法:这是一个商品

9.重写父类的方法

  • 子类扩展了父类,大部分时候,子类总是以父类为基础,额外增加新的方法。但有时候子类需要重写父类的方法,以下代码示范了子类如何重写父类的方法。
class Bird:
    #Bird类的fly()方法
    def fly(self):
        print("我在天空里自由自在地飞翔...")

class Ostrich(Bird):
    #重写Bird类的fly()方法
    def fly(self):
        print("我只能在地上奔跑...")

#创建Ostrich对象
os = Ostrich()

#执行Ostrich对象的fly()方法,将输出“我只能在地上奔跑...”
os.fly()

10.在子类中调用未被改写的父类方法

  • 在子类中调用重写之后的方法,Python总会执行子类重写的方法,不会执行父类中被重写的方法。
  • 当在子类中调用父类中未被重写的实例方法时,Python允许通过类名实现调用,但需要手动绑定第一个参数(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()

11.使用super函数调用父类的构造方法

  • Python的子类也会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先使用,例如以下代码:
  • 子类的构造方法调用父类的构造方法有两种方式:
  1. 使用未绑定方法
  2. 使用super()函数调用父类的构造方法,值得注意的是,super()在一个构造函数中只能只用一次,后面需要用未绑定方法来调用父类构造方法。
#Manager继承了Employee、Customer
class Manager(Employee,Customer):
    #重写父类的构造方法
    def __init__(self,salary,favorite,address):
        print("--Manager--的构造方法--")
        
        #通过super()函数调用父类的构造方法
        super().__init__(salary)

        #使用未绑定方法调用父类的构造方法
        Customer.__init__(self,favorite,address)

#创建Manager对象
m = Manager(25000,"IT产品","广州")
m.work()
m.info()

12.__slots__与动态属性

  • Python是动态语言,即类、对象的属性、方法都可以动态增加和修改。
  • Python的动态性存在一定隐患:程序定义好的类,完全有可能在后面被其他程序修改,这就带来了一些不确定性。我们可以通过__slots__属性来限制为某个类添加属性和方法。
class Dog:
    #只允许实例动态添加walk、age、name这三个属性或方法
    __slots__ = ("walk","age","name")

    def __init__(self,name):
        self.name = name

    def test():
        print("预先定义的test方法")

d = Dog()
from types import MethodType

d.walk = Methodtype(lambda self: print("%s正在慢慢地走" %self.name),d)
d.age = 5
d.walk()

d.foo = 30 #AttributeError
  • 需要说明的是,__slots__并不限制为类动态添加属性或方法。同样,__slots__只对当前类的实例起作用,对该类派生出的子类是不起作用的。

13.使用type()函数定义类

  • 从Python解释器的角度看,当程序使用class定义类时,也可以理解为定义了一个特殊的对象(type类的对象),并将该对象赋值给类变量。因此,程序使用class定义的所有类都是type类的实例。
  • Python完全支持使用type()函数来创建type对象,又由于type类的实例就是类,因此Python可以使用type()函数来动态创建类。
def fn(self):
    print("fn函数")
#使用type()定义类
Dog =type("Dog" , (object), dict(walk=fn,age =6))

#创建Dog对象
d = Dog()

#分别查看d、Dog的类型
print(type(d))
print(type(Dog))
d.walk()
print(Dog.age)
  • 在上面的代码中,type()函数有三个参数:
  1. 参数一:创建的类名
  2. 参数二:该类的父类集合
  3. 参数三:字典对象为该类绑定的类变量和方法,其中字典的key就是类变量或方法名,如果字典的value是普通值,那就代表类变量,如果字典的value是函数,则代表方法。

14.使用metaclass

  • 如果希望创建某一批类全部具有某种特征,则可以通过metaclass来实现。
  • 为了使metaclass动态修改类定义,程序需要先定义metaclass,metaclass应该继承type类,并重写__new__()方法。
#定义ItemMetaClass,继承type
class ItemMetaClass(type):
    #cls代表被动态修改的类
    #name代表被动态修改的类名
    #bases代表被动态修改的类的所有父类
    #attr代表被动态修改的类的所有属性、方法组成的字典
    
    def __new__(cls,name,bases,attrs):
        #为该类动态添加一个cal_price方法
        attrs["cal_price"] = lambda self:self.price * self.discount
        return type.__new__(cls,name,bases,attrs)

上述代码为目标类动态添加了一个cal_price方法

如下程序使用metaclass定义了两个类

#定义Book类
class Book(metaclass = ItemMetaClass):
    __slots__ = ("name","price","_discount")
    def __init__(self,name,price):
        self.name = name
        self.price = price
     
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self,discount):
        self._discount =discount
#定义Cellphone类
class Cellphone(metaclass = ItemMetaClass):
    __slots__ = ("price","_discount")
    def __init__(self,price):
        self.price = price
    
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self,discount):
        self._discount=discount

虽然在定义Book、Cellphone类时没有定义cal_price()方法,但这两个类依然有cal_price()方法。

b = Book("疯狂Python讲义",89)
b.discount = 0.76

#创建Book对象的cal_price()方法
print(b.cal_price())

cp = Cellphone(2399)
cp.discount = 0.85

#创建Cellphone对象的cal_price()方法
print(cp.cal_price())

从上面的代码中看一看出,通过使用metaclass可以动态修改程序中的一批类,对它们集中进行某种修改。


15.检查类型

  • Python提供了如下两个函数来检查类型
  1. issubclass(cls,class_or_tuple):检查cls是否为后一个类或元组包含的多个类中任意类的子类
  2. isinstance(obj,class_or_tuple):检查obj是否为后一个类或元组包含的多个类中任意类的子类

16.枚举类

  • 在某种情况下,一个类的对象是固定且有限的,比如季节类只有春、夏、秋、冬四个对象。
  • 程序有两种方式来定义枚举类
  1. 直接使用Enum列出多个枚举值来创建枚举类
  2. 通过继承Enum基类来派生枚举类
  • 如下程序示范了直接使用Enum列出多个枚举值来创建枚举类
import enum

#定义Season枚举类
Season = enum.Enum(Season,("SPRING","SUMMER","FALL","WINTER"))
  • 在定义了上面的枚举类之后,程序可以直接通过枚举值进行访问,这些枚举值都是该枚举的成员,每个成员都有name、value两个属性,其中name属性值为该枚举值的变量名,value代表该枚举值的序号(序号通常从1开始)。
#直接访问指定枚举
print(Season.SPRING)

#访问枚举成员的变量名
print(Season.SPRING.name)

#访问枚举成员的值
print(Season.SPRING.value)
  • 还可以通过枚举变量名或枚举值来访问指定枚举对象
#根据枚举变量名访问枚举对象
print(Season["SUMMER"])

#根据枚举值访问枚举对象
print(Season(3))
  • Python还为枚举提供了一个__members__属性,该属性返回一个dict字典,其中包含了该枚举的所有枚举实例。
#遍历Season枚举的所有成员
for name,member in Season.__members__.items():
    print(name,"=>",member,"=>",member.value)

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值