目录
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修饰。
- 总结:
- 类方法无论是使用类调用还是对象调用,都会自动绑定第一个参数
- 静态方法无论是使用类调用还是对象调用,都不会自动绑定,都需要手动绑定
- 实例方法在使用对象调用时可以自动绑定,而在使用类调用时不能自动绑定
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的子类也会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先使用,例如以下代码:
- 子类的构造方法调用父类的构造方法有两种方式:
- 使用未绑定方法
- 使用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()函数有三个参数:
- 参数一:创建的类名
- 参数二:该类的父类集合
- 参数三:字典对象为该类绑定的类变量和方法,其中字典的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提供了如下两个函数来检查类型
- issubclass(cls,class_or_tuple):检查cls是否为后一个类或元组包含的多个类中任意类的子类
- isinstance(obj,class_or_tuple):检查obj是否为后一个类或元组包含的多个类中任意类的子类
16.枚举类
- 在某种情况下,一个类的对象是固定且有限的,比如季节类只有春、夏、秋、冬四个对象。
- 程序有两种方式来定义枚举类
- 直接使用Enum列出多个枚举值来创建枚举类
- 通过继承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)