类和对象
类和对象,其中类似某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。Python定义类的简单语法如下:
class 类名:
执行语句...
0到多个类变量...
0到多个方法...
在实例方法中有一个特别的方法:init,这个方法被称为构造方法,构造方法用于构造该类的对象,Python通过调用构造方法返回该类的对象。
对象的产生和作用
创建对象的根本途径是构造方法,调用某个类的构造方法即可创建这个类的对象。创建对象之后,即可使用该对象。
对象访问方法或变量的语法是:对象.变量|方法(参数)
对象的产生和使用
Python是动态语言,因此程序完全可以为对象动态增加实例变量,只要为它的新变量赋值即可;也可以动态删除实例变量,使用del语句即可删除。
实例方法和自动绑定self
self参数最大的作用就是引用当前方法的调用者。
方法的第一个参数所代表的的对象时不确定的,但它的类型是确定的—它所代表的只能是当前类的实例;只有当这个方法被调用时,它所代表的的对象才被确定下来----谁在调用这个方法,方法的第一个参数就代表谁。
自动绑定的self参数并不依赖具体的调用方式,不管是以方法调用还是以函数调用的方法执行它,self参数一样可以自动绑定。
方法
方法是类或对象的行为特征的抽象,但Python的方法其实也是函数,其定义方式、调用方式和函数都非常相似。
类方法与静态方法
Python的类方法和静态方法很相似,它们都推荐使用类来调用。区别在于:Python会自动绑定类方法的第一个参数,类方法的第一个参数会自动绑定到类本身;但对于静态方法则不会自动绑定。
@函数装饰器
当程序使用“@函数”(比如函数A)装饰另一个函数(比如函数B)时,实际上完成如下两步:
- 将被修饰的函数B作为参数传给@符号引用的函数A
- 将函数B替换成上一步的返回值
def funA(fn):
print('A')
fn() # 执行传入的fn参数
return 'fkit'
'''
下面装饰效果相当于:funA(funB),
funB将会替换(装饰)成该语句的返回值;
由于funA()函数返回fkit,因此funB就是fkit
'''
@funA
def funB():
print('B')
print(funB) # fkit
成员变量
类变量和实例变量
在类命名空间内定义的变量就属于类变量,Python可以使用类来读取或修改类变量。
Python允许通过对象访问变量,但如果程序通过对象尝试对类变量赋值,此时性质就变了----Python是动态语言,赋值语句往往意味着定义新变量。
使用property函数定义属性
如果为Python类定义了getter、setter等访问器方法,可使用property()函数将它们定义成属性
property()函数的语法格式如下:
property(self, fget=None, fset=None, fdel=None, doc=None)
还可使用@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方法
# 只有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)
隐藏和封装
封装是面向对象的三大特征之一(继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
Python类定义的所有成员默认都是公开的,如果希望将Python类中的某些成员隐藏,只要让该成员的名字以双下划线开头即可。
类的继承
继承的语法
Python子类继承父类的语法是在定义子类时,将多个父类放在子类之后的圆括号里,语法格式如下:
class SubClass (SuperClassl , SuperClass2 , .. . )
# 类定义部分
如果定义一个Python类时并未显示指定这个类的直接父类,则这个类默认继承object类。
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()
关于多继承
当一个子类有多个直接父类时,该子类会继承得到所有父类的方法(但是通常不推荐)。
重写父类的方法
子类包含于父类同名的方法的现象被称为方法重写(override),也被称为方法覆盖
class Bird:
# Bird类的fly()方法
def fly(self):
print("我在天空里自由自在地飞翔...")
class Ostrich(Bird):
# 重写Bird类的fly()方法
def fly(self):
print("我只能在地上奔跑...")
# 创建Ostrich对象
os = Ostrich()
# 执行Ostrich对象的fly()方法,将输出"我只能在地上奔跑..."
os.fly()
使用未绑定方法调用被重写的方法
通过使用未绑定方法即可在子类中再次调用父类中被重写的方法。
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()
使用super函数调用父类的构造方法
Python的子类会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先使用。
如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。子类的构造方法调用父类的构造方法有2种形式:
- 使用未绑定方式,因为构造方法也是实例方法,可以通过这种方式来调用;
- 使用super()函数调用父类的构造方法
class Employee :
def __init__ (self, salary):
self.salary = salary
def work (self):
print('普通员工正在写代码,工资是:', self.salary)
class Customer:
def __init__ (self, favorite, address):
self.favorite = favorite
self.address = address
def info (self):
print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))
# Manager继承了Employee、Customer
class Manager(Employee, Customer):
# 重写父类的构造方法
def __init__(self, salary, favorite, address):
print('--Manager的构造方法--')
# 通过super()函数调用父类的构造方法
super().__init__(salary)
# 与上一行代码的效果相同
# super(Manager, self).__init__(salary)
# 使用未绑定方法调用父类的构造方法
Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work() #①
m.info() #②
Python的动态性
动态属性与__slots__
程序要限制为某个类动态添加属性和方法,可通过__slots__属性来指定。__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性和方法名。
class Dog:
__slots__ = ('walk', 'age', 'name')
def __init__(self, name):
self.name = name
def test():
print('预先定义的test方法')
d = Dog('Snoopy')
from types import MethodType
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)
d.age = 5
d.walk()
#d.foo = 30 # AttributeError
上面程序中定义了__slots__ = (‘walk’, ‘age’, ‘name’),意味着只允许为Dog实例鼎泰添加walk、age、name三个属性或方法,添加其他额外属性,程序就会引发AttributeError错误。
使用type()函数定义类
Python允许使用type()函数来创建type对象,由于type类的实例就是类,因此Python可以使用type()函数来动态创建类
def fn(self):
print('fn函数')
# 使用type()定义Dog类
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()定义类时可指定三个参数:
- 参数一:创建的类名
- 参数二:该类继承的父类集合
- 参数三:该字典对象为该类绑定的类变量和方法
使用metaclass
如果希望创建某一批类全部具有某种特征,则可通过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)
多态
多态性
对于弱类型的语言来说,变量并没有声明类型,因此同一个变量完全可以在不同的时间引用不同的对象。当同一个变量在调用同一个方法时,完全可能呈现出多种行为,这就是所谓的多态。
class Bird:
def move(self, field):
print('鸟在%s上自由地飞翔' % field)
class Dog:
def move(self, field):
print('狗在%s里飞快的奔跑' % field)
# x变量被赋值为Bird对象
x = Bird()
# 调用x变量的move()方法
x.move('天空')
# x变量被赋值为Dog对象
x = Dog()
# 调用x变量的move()方法
x.move('草地')
检查类型
Python提供了如下2个函数来检查类型:
- issubclass(cls,class_or_tuple):检查cls是否为后一个类或元组包含的多个类中任意类的子类;
- isinstance(obj,class_or_tuple):检查obj是否为后一个类或元组包含的多个类中任意类的对象。
issubclass()的第一个参数是类名,而isinstance()的第一个参数是变量;issubclass用于判断是否为子类,而isinstance用于判断是否为该类或子类的实例。
枚举类
枚举入门
程序有两种方式来定义枚举类:
- 直接使用Enum列出多个枚举值来创建枚举类;
- 通过继承Enum基类来派生枚举类
枚举的构造器
枚举也是类,因此枚举也可以定义构造器,为枚举定义构造器之后,在定义枚举实例时必须为构造器参数设置值。
import enum
class Gender(enum.Enum):
MALE = '男', '阳刚之力'
FEMALE = '女', '柔顺之美'
def __init__(self, cn_name, desc):
self._cn_name = cn_name
self._desc = desc
@property
def desc(self):
return self._desc
@property
def cn_name(self):
return self._cn_name
# 访问FEMALE的name
print('FEMALE的name:', Gender.FEMALE.name)
# 访问FEMALE的value
print('FEMALE的value:', Gender.FEMALE.value)
# 访问自定义的cn_name属性
print('FEMALE的cn_name:', Gender.FEMALE.cn_name)
# 访问自定义的desc属性
print('FEMALE的desc:', Gender.FEMALE.desc)