28.6. 06.类和对象

28.6.1. 类和对象

定义类

class Person:
    """
    这是学习Python定义的一个Person类
    """
    hair = 'black'

    def __init__(self, name='Charlie', age=8):
        self.name = name
        self.age = age

    # 下面定义一个say方法
    def say(self, content):
        print(content)

对象的产生和使用

p = Person()
print(p.name, p.age)                    #Charlie 8
p.name = "hujianli"
p.say("Python语言很简单,学习很容易!") #Python语言很简单,学习很容易!
print(p.name, p.age)        # hujianli 8

对象动态的增加和删除

p = Person()
# 为p对象新增一个skills实例变量
p.skills = ['programming', 'swimming']
print(p.skills)

# 删除p对象的name实例变量
del p.name

# # 再次访问p的name实例变量
# print(p.name)       #AttributeError:

· 动态增加方法

p = Person()
def info(self):
    print("----------info函数----------", self)


# 动态增加方法,使用info对p的foo方法赋值
p.foo = info
# 手动将调用者绑定到第一个参数
p.foo(p)        # ----------info函数---------- <__main__.Person object at 0x0000020C22F7E4E0>


# 使用lambda表达式为p对象的bar方法赋值(动态增加方法)
p.bar = lambda self: print("----lambda表达式-----", self)
p.bar(p)       # ----lambda表达式----- <__main__.Person object at 0x0000020C22F7E4E0>

实例方法和自动绑定self

class Dog:

    # 定义一个jump()方法
    def jump(self):
        print("正在执行jump方法")

    def run(self):
        # 使用self参数引用调用run()方法对象
        self.jump()
        print("正在执行run方法")

把self参数当成实例方法的返回值。

class ReturnSelf:
    def grow(self):
        if hasattr(self, 'age'):
            self.age += 1
        else:
            self.age = 1
        # 返回调用该方法的对象
        return self


rs = ReturnSelf()
rs.grow().grow().grow()
print("rs的age属性值是:", rs.age)        #rs的age属性值是: 3

如果在某个方法中把self参数作为返回值,则可以多次连续调用同一个方法,从而使得代码更加简洁。

28.6.2. 方法

类也能调用实例方法

class User:
    def walk(self):
        print(self, "正在慢慢地走")


u = User()              # <__main__.User object at 0x000002355E7338D0> 正在慢慢地走
# 显示地位方法的第一个参数绑定参数值
User.walk(u)
User.walk("hujianli")   #hujianli 正在慢慢地走

类方法与静态方法

class Bird:
    # 类方法
    @classmethod
    def fly(cls):
        print("类方法fly:", cls)

    # 静态方法
    @staticmethod
    def info(p):
        print("静态方法info: ", p)


# 调用类方法,Bird类会自动绑定到第一个参数
Bird.fly()  # 类方法fly: <class '__main__.Bird'>

# 调用静态方法,不会自动绑定,要手动绑定一个参数
Bird.info("hujianli722")  # 静态方法info:  hujianli722

b = Bird()
b.fly()  # 类方法fly: <class '__main__.Bird'>
b.info("jianli722")  # 静态方法info:  jianli722

@函数装饰器

def funA(fn):
    print("A")
    fn()
    return "xiaojianaichirou"


@funA
def funB():
    print("B")


print(funB)

"""
A
B
xiaojianaichirou
"""

复杂一点的函数装饰器

def foo(fn):
    def bar(*args):
        print("========1==========", args)
        n = args[0]
        print("========2==========", n * (n - 1))
        print(fn.__name__)
        fn(n * (n - 1))
        print("*" * 15)
        return fn(n * (n - 1))

    return bar


@foo
def my_test(a):
    print("=====my_test函数=====", a)


my_test(10)
my_test(6, 5)

通过@符号来修饰函数是Python的宇哥非常实用的功能,可以在被修饰函数的前面添加一些额外的处理逻辑。 比如(权限检查),也可以在被修饰函数的后面添加一些额外的逻辑(比如记录日志。) 也可以在目标方法抛出异常时进行一些修复操作….. 这种改变不需要修改被修饰函数的代码,只要增加一个修饰即可。

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/24 13:35
# filename: sample01.py

def auth(fn):
    def auth_fn(*args):
        # 用一条语句模拟执行权限检查
        print("-------------模拟执行权限检查--------")
        # 回调被修饰的目标函数
        fn(*args)

    return auth_fn


@auth
def t0e0st(a, b):
    print("执行test函数,参数a:%s,参数b: %s" % (a, b))


t0e0st(20, 15)
'''
-------------模拟执行权限检查--------
执行test函数,参数a:20,参数b: 15
'''

在论命名空间

global_fn = lambda p: print("执行lambda表达式,p参数:", p)


class Category:
    cate_fn = lambda p, d: print("执行lambda表达式,%s 参数 %s:" % (p, d))


# 调用全局空间内的global_fn,为参数p传入参数值
global_fn("jianli")

c = Category()

# python为方法绑定了参数值 self
c.cate_fn("aa")

28.6.3. 成员变量

class Record:
    item = "鼠标"
    date = '2020-3-24'

    def info(self):
        print("info方法中: ", self.item)
        print("info方法中: ", self.date)


rc = Record()
print(rc.item)
print(rc.date)
rc.info()

rc.item = "键盘"
rc.date = "2020-03-20"
rc.info()
class Inverntory:
    item = "鼠标"
    quantity = 2000

    def change(self, item, quantity):
        self.item = item
        self.quantity = quantity


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

# 访问Inventory的item和quantity类变量
print(Inverntory.item)      # 鼠标
print(Inverntory.quantity)  # 2000


# 修改类变量,实例变量不受影响
Inverntory.item = "笔记本"
Inverntory.quantity = 5000

print(iv.item)      # 显示器
print(iv.quantity)  # 500

使用property函数定义属性

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

class Rectangle:
    # 定义构造方法
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setsize()函数
    def setsize (self , size):
        self.width, self.height = size
    # 定义getsize()函数
    def getsize (self):
        return self.width, self.height
     # 定义getsize()函数
    def delsize (self):
        self.width, self.height = 0, 0
    # 使用property定义属性
    size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')
# 访问size属性的说明文档
print(Rectangle.size.__doc__)
# 通过内置的help()函数查看Rectangle.size的说明文档
help(Rectangle.size)
rect = Rectangle(4, 3)
# 访问rect的size属性
print(rect.size) # (4, 3)
# 对rect的size属性赋值
rect.size = 9, 7
# 访问rect的width、height实例变量
print(rect.width) # 9
print(rect.height) # 7
# 删除rect的size属性
del rect.size
# 访问rect的width、height实例变量
print(rect.width) # 0
print(rect.height) # 0
print(dir(Rectangle))

还可以使用@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并没有提供类似于其他语言的private等修饰符,因此Python并不能真正的支持隐藏, 为了隐藏类中的成员,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)

28.6.4. 类的继承

继承的语法

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

关于多继承

Python 虽然在语法上明确支持多继承,但是通推荐:

如果不是很有必要,则尽量不要使用多继承,而是使用单继承,这样可以保证
编程思路更清晰,而且可以避免很多麻烦。
class Item:
    def info(self):
        print("Item中方法:", "这是一个商品")


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


class Mouse(Item, Product):
    pass


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

Python优先到Item父类中搜寻方法,一旦在Item父类中搜寻到目标方法,Python就不会继续向下搜索了。

重写父类的方法

class Bird:
    # Bird类的fly()方法
    def fly(self):
        print("我在天空里自由自在地飞翔...")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("我只能在地上奔跑...")

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

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

如果需要在子类中调用父类中被重写的实例方法,如何操作?

即使是实例方法,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方法
        self.foo()
        # 使用类名调用实例方法(未绑定方法)调用父类被重写的方法
        BaseClass.foo(self)


sc = SubClass()
sc.bar()

'''
执行bar方法
子类重写父类中的foo方法
父类中定义的foo方法
'''

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

28.6.5. Python多态的意义和灵活性

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

# 多态的典型应用
class Canvas:
    def draw_pic(self, shape):
        print('--开始绘图--')
        shape.draw(self)

class Rectangle:
    def draw(self, canvas):
        print('在%s上绘制矩形' % canvas)
class Triangle:
    def draw(self, canvas):
        print('在%s上绘制三角形' % canvas)
class Circle:
    def draw(self, canvas):
        print('在%s上绘制圆形' % canvas)
c = Canvas()
# 传入Rectangle参数,绘制矩形
c.draw_pic(Rectangle())
# 传入Triangle参数,绘制三角形
c.draw_pic(Triangle())
# 传入Circle参数,绘制圆形
c.draw_pic(Circle())
print(hasattr(c, 'draw_pic'))
print(hasattr(c.draw_pic, '__call__'))
print(Circle.__dict__)

28.6.6. 类型检查的相关函数和属性

# 定义一个字符串
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)))

28.6.7. 枚举类的用法

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)

# extend
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)

28.6.8. 枚举类及其构造器

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)