第六章 python 类和对象

python 类和对象

  • 一、类和对象
      • 1. 实例方法
        • (1) __ init __ 方法
  • 二、方法
      • 1. 类方法与静态方法
      • 2. @函数装饰器
  • 三、成员变量
      • 1. ★★使用property函数定义属性(getter、setter、del、doc)
  • 四、隐藏和封装
      • 1. 封装(Encapsulation)
  • 五、类的继承
      • 1. 继承的语法
      • 2. 关于多继承
      • 3. 重写父类的方法
      • 4. 使用未绑定方法调用被重写的方法
      • 5. 使用super函数调用父类的构造方法
  • 六、python的动态性
      • 1. ★★动态属性与 __ slots __
      • 2. 使用type()函数定义类
      • 3. ★★★ 使用metaclass
  • 七、多态
      • 1. 多态性
      • 2. 检查类型
  • 八、枚举类
      • 1. 枚举
      • 2. 枚举的构造器

Python支持面向对象的三大特征:封装、继承和多态,子类继承父类同样可以继承到父类的变量和方法。

一、类和对象

类(class)
对象(object,也被称为实例instance)

定义类的语法:
class 类名:
执行语句…
零到多个类变量…
零到多个方法…

1. 实例方法

(1) __ init __ 方法

称为构造方法,用于构造该类的对象,python通过调用构造方法返回该类的对象(无须使用new)

二、方法

1. 类方法与静态方法

类方法:自动绑定到第一个参数
静态方法:不会自动绑定,因此程序必须手动绑定第一个参数

代码如下:

class Bird:
    # classmethod修饰的方法是类方法
    @classmethod
    def fly (cls):
        print('类方法fly: ', cls)
    # staticmethod修饰的方法是静态方法
    @staticmethod
    def info (p):
        print('静态方法info: ', p)
# 调用类方法,Bird类会自动绑定到第一个参数
Bird.fly()  #①
# 调用静态方法,不会自动绑定,因此程序必须手动绑定第一个参数
Bird.info('crazyit')
# 创建Bird对象
b = Bird()
# 使用对象调用fly()类方法,其实依然还是使用类调用,
# 因此第一个参数依然被自动绑定到Bird类
b.fly()  #②
# 使用对象调用info()静态方法,其实依然还是使用类调用,
# 因此程序必须为第一个参数执行绑定
b.info('fkit')

2. @函数装饰器

三、成员变量

类变量
实例变量

1. ★★使用property函数定义属性(getter、setter、del、doc)

为python类定义getter、setter等访问器方法,则可使用property()函数将它们定义成属性(相当于实例变量)

语法格式:
property(fget=None, fset=None, fdel=None, doc=None)

四、隐藏和封装

1. 封装(Encapsulation)

指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

封装是面向对象编程语言对客观世界的模拟,在客观世界里,对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。

对一个类或对象实现良好的封装,可以达到以下目的。

  • 隐藏类的实现细节。
  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。
  • 可进行数据检査,从而有利于保证对象信息的完整性。
  • 便于修改,提高代码的可维护性。

为了实现良好的封装,需要从两个方面来考虑。

  • 将对象的属性和实现细节隐藏起来,不允许外部直接访问。
  • 把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。

因此,实际上封装有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。

Python并没有提供类似于其他语言的private等修饰符,因此Python并不能真正支持隐藏。为了隐藏类中的成员,Python玩了一个小技巧:只要将Python类的成员命名为以 双下画线开头 的,Python就会把它们隐藏起来。

代码如下:

class User :
    def __hide(self):
        print('示范隐藏的hide方法')
    def getname(self):
        return self.__name
    def setname(self, name):
        if len(name) < 3 or len(name) > 8:
            raise ValueError('用户名长度必须在3~8之间')
        self.__name = name
    name = property(getname, setname)
    def setage(self, age):
        if age < 18 or age > 70:
            raise ValueError('用户名年龄必须在18在70之间')
        self.__age = age
    def getage(self):
        return self.__age
    age = property(getage, setage)
# 创建User对象
u = User()
# 对name属性赋值,实际上调用setname()方法
# u.name = 'fk' # 引发 ValueError: 用户名长度必须在3~8之间
u.name = 'fkit'
u.age = 25
print(u.name) # fkit
print(u.age) # 25

# 尝试调用隐藏的__hide()方法
#u.__hide()

# 调用隐藏的__hide()方法
u._User__hide()
# 对隐藏的__name属性赋值
u._User__name = 'fk'
# 访问User对象的name属性(实际上访问__name实例变量)
print(u.name)

上面程序将User的两个实例变量分别命名为_name和_age,这两个实例变量就会被隐藏起
来,这样程序就无法直接访问_name、 _age变量,只能通过setname()、 getname()、setage()、getage()这些访问器方法进行访问,而setname()、setage()会对用户设置的name、age进行控制,只有符合条件的name、age才允许设置。

五、类的继承

1. 继承的语法

语法格式:
class SubClass(SuperClass1, SuperClass2, …):
# 类定义部分

如果在定义一个Python类时并未显式指定这个类的直接父类,则这个类默认继承object类。因此,object类是所有类的父类,要么是其直接父类,要么是其间接父类。

2. 关于多继承

多个父类中包含了同名的方法,排在前面的父类中的方法会“遮蔽”排在后面的父类中的同名方法。

代码如下:

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()

3. 重写父类的方法

子类覆盖父类的方法

代码如下:

class Bird: 
    # Bird类的fly()方法
    def fly(self):
        print("我在天空里自由自在地飞翔...")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("我只能在地上奔跑...")
  
# 创建Ostrich对象
os = Ostrich()
# 执行Ostrich对象的fly()方法,将输出"我只能在地上奔跑..."
os.fly()

4. 使用未绑定方法调用被重写的方法

子类中调用父类中被重写的实例方法

Python类相当于类空间,Python类中的方法本质上相当于类空间内的函数。即使是实例方法,Python也允许通过类名调用。

区别在于:在通过类名调用实例方法时,Python不会为实例方法的第一个参数self自动绑定参数值,而是需要程序显式绑定第一个参数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()

5. 使用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()  #②

上面程序中super()、Customer代码分别示范了两种方式调用父类的构造方法。
通过这种方式,Manager类重写了父类的构造方法,并在构造方法中显式调用了父类的两个构造方法执行初始化,这样两个父类中的实例变量都能被初始化。

六、python的动态性

1. ★★动态属性与 __ slots __

程序隐患:程序定义好的类,完全有可能在后面被其他程序修改,这就带来了一些不确定性。
如果程序要限制为某个类动态添加属性和方法,则可通过_slots_属性来指定。
_slots_属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名(对于Python而言,方法相当于属性值为函数的属性)。

代码如下:

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__属性并不限制通过类来动态添加方法
Dog.bar = lambda self: print('abc')
d.bar()

class GunDog(Dog):
    def __init__(self, name):
        super().__init__(name)
    pass
gd = GunDog('Puppy')
# 完全可以为Gundog实例动态添加属性
gd.speed = 99
print(gd.speed)

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

type()函数相当于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)

>> <class '__main__.Dog'>
>> <class 'type'>
>> fn函数
>> 6

Dog代码使用type。定义了一个Dog类。在使用type。定义类时可指定三个参数。

  • 参数一:创建的类名。
  • 参数二:该类继承的父类集合。由于Python支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。
  • 参数三:该字典对象为该类绑定的类变量和方法。其中字典的key就是类变量或方法名,如果字典的value是普通值,那就代表类变量;如果字典的value是函数,则代表方法。

由此可见,上面粗体字代码定义了一个Dog类,该类继承了 object类,还为该类定义了一个walk()方法和一个age类变量。

3. ★★★ 使用metaclass

如果希望创建某一批类全部具有某种特征,则可通过metaclass来实现。使用metaclass可以在创建类时动态修改类定义。

为了使用metaclass动态修改类定义,程序需要先定义metaclass, metaclass应该继承type类,并重写—new__()方法。

代码如下:

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)
# 定义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
# 定义Book类
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

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())

>> 67.64
>> 2039.1499999999999

程序定义了一个ItemMetaClass类,该类继承了 type类,并重写了_new_方法,在重写该方法时为目标类动态添加了一个cal_price方法。

metaclass类的_new_方法的作用是:
当程序使用class定义新类时,如果指定了 metaclass,那么metaclass的_new_ 方法就会被自动执行。

程序定义了 Book和CellPhone两个类,在定义这两个类时都指定了 metaclass信息,因此当Python解释器在创建这两个类时,ItemMetaClass的_new_方法就会被调用,用于修改这两个类。

ItemMetaClass类的_new_方法会为目标类动态添加cal_price方法,因此,虽然在定义Book、CellPhone类时没有定义cal_price()方法,但这两个类依然有cal_price()方法。

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

这个功能在开发一些基础性框架时非常有用,程序可以通过使用metaclass为某一批需要具有通用功能的类添加方法。

七、多态

1. 多态性

代码如下:

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('草地')

2. 检查类型

Python提供了如下两个函数来检查类型。

  • issubclass(cls, class or tuple):检查cis是否为后一个类或元组包含的多个类中任意类的子类。
  • isinstance(obj, class_or_tuple):检査o可是否为后一个类或元组包含的多个类中任意类的对象。

代码如下:

# 定义一个字符串
hello = "Hello";
# "Hello"是str类的实例,输出True
print('"Hello"是否是str类的实例: ', isinstance(hello, str))
# "Hello"是object类的子类的实例,输出True
print('"Hello"是否是object类的实例: ', isinstance(hello, object))
# str是object类的子类,输出True
print('str是否是object类的子类: ', issubclass(str, object))
# "Hello"不是tuple类及其子类的实例,输出False
print('"Hello"是否是tuple类的实例: ', isinstance(hello, tuple))
# str不是tuple类的子类,输出False
print('str是否是tuple类的子类: ', issubclass(str, tuple))
# 定义一个列表
my_list = [2, 4]
# [2, 4]是list类的实例,输出True
print('[2, 4]是否是list类的实例: ', isinstance(my_list, list))
# [2, 4]是object类的子类的实例,输出True
print('[2, 4]是否是object类及其子类的实例: ', isinstance(my_list, object))
# list是object类的子类,输出True
print('list是否是object类的子类: ', issubclass(list, object))
# [2, 4]不是tuple类及其子类的实例,输出False
print('[2, 4]是否是tuple类及其子类的实例: ', isinstance([2, 4], tuple))
# list不是tuple类的子类,输出False
print('list是否是tuple类的子类: ', issubclass(list, tuple))

data = (20, 'fkit')
print('data是否为列表或元组: ', isinstance(data, (list, tuple))) # True
# str不是list或者tuple的子类,输出False
print('str是否为list或tuple的子类: ', issubclass(str, (list, tuple)))
# str是list或tuple或object的子类,输出True
print('str是否为list或tuple或object的子类 ', issubclass(str, (list, tuple, object)))

八、枚举类

1. 枚举

程序有两种方式来定义枚举类。

  • 直接使用Enum列出多个枚举值来创建枚举类。
  • 通过继承Enum基类来派生枚举类。
import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)

# 根据枚举变量名访问枚举对象
print(Season['SUMMER']) # Season.SUMMER
# 根据枚举值访问枚举对象
print(Season(3)) # Season.FALL

# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)

代码如下:

import enum
class Orientation(enum.Enum):
    # 为序列值指定value值
    EAST = '东'
    SOUTH = '南'
    WEST = '西'
    NORTH = '北'
    def info(self):
        print('这是一个代表方向【%s】的枚举' % self.value)
print(Orientation.SOUTH)
print(Orientation.SOUTH.value)
# 通过枚举变量名访问枚举
print(Orientation['WEST'])
# 通过枚举值来访问枚举
print(Orientation('南'))
# 调用枚举的info()方法
Orientation.EAST.info()
# 遍历Orientation枚举的所有成员
for name, member in Orientation.__members__.items():
    print(name, '=>', member, ',', member.value)

2. 枚举的构造器

代码如下:

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)

你可能感兴趣的:(python)