10 . Python之面向对象

时间:2022-07-25
本文章向大家介绍10 . Python之面向对象,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向对象初识别

面向过程vs函数式编程

面向机器

抽象成机器指令,机器容易理解 代表: 汇编语言

面向过程

做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了B,做什么处理. 问题规模小,可以步骤化,按部就班处理 代表: C语言.

# 面向过程编程 测量对象的元素个个数。
s1 = 'fjdsklafsjda'
count = 0
for i in s1:
    count += 1

l1 = [1,2,3,4]
count = 0
for i in l1:
    count += 1

函数编程相比面向过程编程最明显两个特点

# 1. 减少代码的重用性
# 2. 增强代码的可读性
函数编程vs面向对象编程
# 函数式编程

# auth 认证相关
def login():
    pass

def regisgter():
    pass

# account 账户相关
def func1():
    pass

def func2():
    pass


# 购物车相关
def shopping(username,money):
    pass
def check_paidgoods(username,money):
    pass
def check_unpaidgoods(username,money):
    pass
def save(username,money):
    pass
# 面向对象编程
class LoginHandler:
    def login(self):
        pass

    def regisgter(self):
        pass

class Account:
    def func1(self):
        pass

    def func2(self):
        pass

class ShoppingCar:
    def shopping(username,money):
        pass
    def check_paidgoods(username,money):
        pass
    def check_unpaidgoods(username,money):
        pass
    def save(username,money):
        pass

通过对比可以看出面向对象第一个优点

# 面向对象编程:是一类相似功能函数的集合,使你的代码更清晰化,更合理化。

# 说第二个优点之前,先看看什么是面向对象。

# 面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。

# 那什么是类?什么是对象?

# 类:就是具有相同属性和功能的一类事物。

# 对象:就是类的具体表现。

# 具体一些:先解释解释什么是⻋? 有轱辘, 有⽅向盘, 有发动机, 会跑的是⻋. 好. 在解释⼀个. 什么是⼈. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是⼈.那么广义上 车,人就是类:但是具体的我的车,你这个人这是一个对象。

# 猫,是一类,你们家养的 大橘。

# 狗,是一类,隔壁家养的那只二哈就是对象。

# ⾯向对象思维, 要⾃⼰建立对象. ⾃⼰建立场景. 你是就是⾯向对象世界中的上帝. 你想让⻋⼲嘛就⼲嘛. 你想让⼈⼲嘛⼈就能⼲嘛。

再说第二个优点:面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来(慢慢体会)。

函数式编程

def func(s):
    count = 0
    for i in s:
        count += 1
    return count
func('fdsafdsa')
func([1,2,3,4])

面向对象(Object Oriented Programming)

随着计算机需要解决的问题的规模扩大,情况越来越复杂,需要很多人,很多部门协调,面向过程编程太不适合了. 代表: C++,JAVA,Python等.

面向对象

什么是面向对象?

一种认知世界、分析世界的方法论,将万事万物抽象为类.

类class

类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合. 用计算机语言描述类,就是属性和方法的集合.

对象instance、object

对象是类的具象,是一个实体. 对于我们每个人这个个体,都是抽象概念人类的不同的实体。

对象与类

# 类:对象的类型 => 数字
#    具有相同特征与行为集合的

# 对象:类的具体表现 => 数字10
#    类的实例化,就是具有特征与行为实际存在的个体(每一个对象都是唯一的)

# Example1
# 你吃鱼
# 你,就是对象; 鱼,也是对象;吃就是动作
# 你是具体的人,是具体的对象。你属于人类,人类是一个抽象的概念,
# 是无数具体的个体的抽象.与,也是具体的对象,就是你吃的这一条具体的鱼。
# 这条鱼属于鱼类,是无数的鱼抽象出来的概念.

# 吃,是动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法,
# 如果反过来,鱼吃人,吃就是鱼类的动作了.
# 吃,这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,
# 是动物都有吃的动作,但是吃法不同而已.
# 你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法.

# 属性,他是对象状态的抽象,用数据结构来描述.
# 操作,他是对象行为的抽象,用操作名和实现该操作的方法来描述.

# 每个人都有名字,身高,体重等信息,这些信息是个人的属性,但是,这些信息不能保存在人类中,
# 因为它是抽象的概念,不能保留具体的值.
# 而人类的实例,是具体的人,也可以存储这些具体的属性,而且可以不同人有不同的属性.

# 哲学
# 一切皆对象
# 对象是数据和操作的封装
# 对象是独立的,但是对象之间可以相互作用.
# 目前OOP是最接近人类认知的编程范式.
为什么需要面向对象编程?

面向过程:开发成本高,解决问题局限性小 面向对象:开发成本低,解决问题局限于对象

问题:’abc’ => {‘a’, ‘b’, ‘c’} 面向过程: 手撸 面向对象:str => list => set

开发:优选面向对象(找解决问题的对象), 再考虑找多个对象(面向对象与面向过程的结合), 最后自己去封装一个可以解决问题的对象(对外是面向对象的体现,内部解决问题的核心是面向过程)

类的相关知识

类与对象的声明
class ClassName:
	语句块
# 1. 必须使用class关键字
# 2. 类名必须使用驼峰命名
# 3. 类定义完成后,就产生了一个类对象,绑定到了ClassName上.

class Student:
    """YouMen"""
    age = 21 # 第一部分: 静态属性  属性  静态变量 静态字段
    def foo(self):
        print(self.age)
        return self.age

print(Student)              # 打印类
print(Student.__name__)     # 打印类名
print(Student.foo)          # 打印对象里面的函数
print(Student.age)          # 打印类里面的静态属性
print(Student.__doc__)      # 打印类里面的注释
print(type(Student))


# 将类对象化
mycls = Student()           # 实例化,初始化
print(type(Student))
mycls.foo()
print(mycls.foo())

<class '__main__.Student'>
Student
<function Student.foo at 0x000001C1E9FE1EA0>
21
YouMen
<class 'type'>
<class 'type'>
21
21
21

# 类对象及类属性
# 类对象,类的定义就会生成一个类对象
# 类的属性, 类定义中的变量和类中定义的方法都是类的属性.
# 类变量, x是Student的属性,__doc__也是类的属性
# foo方法是类的属性,如果吃是人类的方法,但是每一个具体的人才能吃东西,
# 就也就是说人的实例才能调用的方法.
# foo是method方法对象,不是普通的函数对象function了,他必须至少有一个参数
# 且第一个参数必须是self(self可以换个名字),这个参数位置就留给了self。
# 此处的self指代当前实例本身.
什么是对象?

对象是从类中出来的,只要类名加上(),就是一个实例化过程,这个就会实例化一个对象. 当我们写 demo1 = ClassName() 实例化对象时,他会自动执行__init__方法

实例化一个对象发生了什么?

# 1. 在内存中开辟了一个对象空间.
# 2. 自动执行类中的__init__方法,并将这个对象空间(内存地址)传给了__init__方法的第一个位置参数self.
# 3. 在__init__方法中通过self给对象空间添加属性
对象操作对象空间属性
# __init__方法
# MyClass()实际上调用的是__init(self)方法,可以不定义,如果没有定义会在实例化后隐式调用.
# 作用: 对实例进行初始化.

class Student:
    """this is a class"""
    mind = '有思想'
    language = '使用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

obj = Student('youmen','男','18','运动')
print(obj.__dict__)
{'n': 'youmen', 's': '男', 'a': '18', 'h': '运动'}

对象操作对象的单个属性,万能的点

obj = Student('youmen','男','18','运动')
obj.job = 'python'      # 增加属性job
del obj.n               # 删除Obj的name属性
obj.s = '女'            # 改
print(obj.s)
print(obj.__dict__)     # 查

# 女
# {'s': '女', 'a': '18', 'h': '运动', 'job': 'python'}

对象查看类中的属性

obj = Student('youmen','男','18','运动')
print(obj.mind)
print(obj.language)
obj.a = 88
print(obj.a)

# 有思想
# 使用语言
# 88

对象操作类中的方法

class Student:
    """this is a class"""
    mind = '有思想'
    language = '使用语言'
    def __init__(self,name,sex,age,hobby):
        # self 和obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性
        self.n = name
        self.s = sex
        self.a = age
        self.h = hobby

    def work(self):
        print(self)
        print('人类会工作')

    def tools(self):
        print('人类会使用工具')
obj = Student('youmen','男','18','深度思考')
obj.work()
obj.tools()

# <__main__.Student object at 0x0000021E46230588>
# 人类会工作
# 人类会使用工具

类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self

self是什么?
# self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动调用这个函数的对象传给self,所以咱们把类中的方法第一个参数约定俗称设置为self,代表这个就是对象.

# 一个类可以实例化成多个对象

类的空间问题以及类之间的关系

类的空间问题

何处可以添加对象属性和静态属性

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

    def func(self,sex):
        self.sex = sex

    def func1(self):
        A.bbb = 'ccc'

# 类外面添加对象属性
obj = A('youmen')
obj.age = 18
print(obj.__dict__)

# 类外面添加静态属性
A.aaa = 'zhou'
print(A.__dict__)

# 类里面添加对象属性
obj = A('flying')   # __inif__方法可以
obj.func('男')
print(obj.__dict__)

# 类的内部也可以添加
A.func1(123)
print(A.__dict__)


# {'name': 'youmen', 'age': 18}
# {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000019395441EA0>, 'func': <function A.func at 0x0000019395441D90>, 'func1': <function A.func1 at 0x0000019395441F28>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'aaa': 'zhou'}

# {'name': 'flying', 'sex': '男'}
# {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000019395441EA0>, 'func': <function A.func at 0x0000019395441D90>, 'func1': <function A.func1 at 0x0000019395441F28>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'aaa': 'zhou', 'bbb': 'ccc'}

对象如何找到类的属性

对象之所以可以找到类,是因为对象空间中有类对象指针这个东西

# 对象查找属性的顺序: 先从对象空间找  --->  类空间找  --->  父类空间找
# 类名查找属性的顺序: 先从本类空间找  --->  父类空间找 

类与类之间的关系

# 1. 依赖关系
# 2. 关联关系
# 3. 组合关系
# 4. 聚合关系
# 5. 实现关系
# 6. 继承关系

面向对象的继承

什么是继承?

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。 字面意思就是:子承父业,合法继承家产,就是如果你是独生子,而且你也很孝顺,不出意外,你会继承你父母所有家产,他们的所有财产都会由你使用(败家子儿除外)

class Person:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex

class Cat:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex

class Dog:
    def __init__(self,name,sex,age):
        self.name = name
        self.age = age
        self.sex = sex

# 继承的用法:
class Aniaml(object):
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex


class Person(Aniaml):
    pass

class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

继承优点:

1. 增加了类的耦合性(耦合性不宜多,宜精)

2. 减少了重复代码

3. 使得代码更加规范化,合理化

继承分类
# 继承可以分为单继承,多继承

# 在Python2x版本中存在两种类
# 		一个叫经典类,在Python2.2之前,一直使用的是经典类,经典类在基类的根如果什么都不写.
# 		一个叫新式类,在Python2.2之后出现了新式类,特点是基类的根是Object类.

# Python3x版本中只有一种类
# Python3中使用的都是新式类,如果基类谁都不继承,那么这个类会继承Object
单继承

类名,对象执行父类方法

class Aniaml(object):
    type_name = '动物类'

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

    def eat(self):
        print(self)
        print('吃东西')


class Person(Aniaml):
    pass


class Cat(Aniaml):
    pass


class Dog(Aniaml):
    pass

# 类名:
print(Person.type_name)  # 可以调用父类的属性,方法。
Person.eat(111)
print(Person.type_name)

# 对象:
# 实例化对象
p1 = Person('春哥','男',18)
print(p1.__dict__)
# # 对象执行类的父类的属性,方法。
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()

# 类名,对象分别调用父类方法

执行顺序

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex

    def eat(self):
        print(self)
        print('吃东西')

class Person(Aniaml):
    
    def eat(self):
        print('%s 吃饭'%self.name)
        
class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

p1 = Person('barry','男',18)
# 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。
p1.eat()
# 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。

# 执行顺序

同时执行类以及父类方法

方法一

如果想执行父类的func方法,这个方法并且子类中也用,那么在子类的方法中写上:

父类.func(对象,其他参数)

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex

    def eat(self):
        print('吃东西')

class Person(Aniaml):
    def __init__(self,name,sex,age,mind):
        '''
        self = p1
        name = '春哥'
        sex = 'laddboy'
        age = 18
        mind = '有思想'
        '''
        # Aniaml.__init__(self,name,sex,age)  # 方法一
        self.mind = mind

    def eat(self):
        super().eat()
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

# 方法一: Aniaml.__init__(self,name,sex,age)
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)

# 对于方法一如果不理解:
# def func(self):
#     print(self)
# self = 3
# func(self)
多继承
class ShenXian: # 神仙
    def fei(self):
        print("神仙都会⻜")
class Monkey: # 猴
    def chitao(self):
        print("猴⼦喜欢吃桃⼦")
class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是⼀只猴
    pass
sxz = SunWukong() # 孙悟空
sxz.chitao() # 会吃桃⼦
sxz.fei() # 会⻜

此时, 孙悟空是⼀只猴⼦, 同时也是⼀个神仙. 那孙悟空继承了这两个类. 孙悟空⾃然就可以执⾏这两个类中的⽅法. 多继承⽤起来简单. 也很好理解. 但是多继承中, 存在着这样⼀个问题. 当两个⽗类中出现了重名⽅法的时候. 这时该怎么办呢? 这时就涉及到如何查找⽗类⽅法的这么⼀个问题.即MRO(method resolution order) 问题. 在python中这是⼀个很复杂的问题. 因为在不同的python版本中使⽤的是不同的算法来完成MRO的.

在python3中已经不存在经典类了,但是经典类的MRO可以学一下,有可能笔试有

# Example3
# 多参
class Person:
    x = 'flying'
    def __init__(self,name,age=19):
        self.name = name
        self.y = age
    """this is a Person class"""

    def show(self,x,y):
        print(self.name,self.x,x,y)
        self.y = x
        
a = Person('tom')
b = Person('alice',18)
print(a.name,a.y,b.name,b.y)
print(a.x,b.x)
a.show(a.y,b.y)
print(a.y)
#  __init__()方法不能有返回值,只会返回Nove
# 类实例化后一定会获得一个对象,就是实例对象.
# 上例中的tom,alice就是Person的实例.
# __init__方法的第一参数self就是指某一个实例.
# Example4
class Student:
    def __init__(self):
        print('self in init = {}'.format(id(self)))

c = Student()   # 会调用__init__
print('c = {}'.format(id(c)))

# 输出结果为:
self in init = 46710672
c = 46710672

# 上例说明,self就是调用者,就是c对应的实例对象.
# self这个名字只是一个惯例,他可以修改,但是请不要修改,否则影响代码可读性.
# Example6
class People:
    name = '人'

p1 = People()
p2 = People()

# 结论1: 类与每一个对象的名称空间都是独立的
print(p1.__dict__)
print(p2.__dict__)
print(People.__dict__)

# 结论2: 类与每一个对象都可以使用类中的名字
print(People.name)
print(p1.name)
print(p2.name)

# 结论3: 对象访问名字,优先访问自己的,自己没有再访问类的
p1.name = '张三'
p2.user = '李四'
print(People.name)
print(p1.name)
print(p2.user)
print(p2.name)

# 重点
# 对象操作名字,操作的是对象的,类操作名字是类的,之间相互不干预
# 类只能访问类的名字
# 对象访问名字,优先访问自身的,但是没有再访问类的

{}
{}
{'__module__': '__main__', 'name': '人', '__dict__':  '__weakref__'of 'People' objects>, '__doc__': None}
人
人
人
人
张三
李四
人

类的初始化方法

# 可以快速为类实例化每一个对象,产生的对象名称空间的多个名字

class NewTeacher:
    def __init__(self,name,sex,age):
        # print(id(self))    # self就是实例化产生的对象(nt1)
        # print('init被调用了')
        self.name = name
        self.sex = sex
        self.age = age
    pass

# 类()就是在调用类的__init__方法
nt1 = NewTeacher('YouMen','Man',30)
print(nt1.name,nt1.sex,nt1.age)

nt2 = NewTeacher('flying','Man',40)
print(nt2.name,nt2.sex,nt1.age)

类的方法分类

对象方法: 直接定义的方法,建议由对象调用,类中内部需要使用对象的数据时的方法要定义对象方法

  1. 对象方法对象调用,默认传入对象给第一个形参
class 类名
	def fn(self,*args,**kwargs):pass

类方法: 被classmethod修饰的方法,建议由类调用,类中内部需要使用类的数据时的方法要定义为类方法

  1. 类方法由类调用,默认传入类给第一个形参.
class 类名:
    @classmethod
    def fn(cls,*args,**kwargs): pass
  1. 静态方法建议由类调用,默认不传入调用者
@staticmethod
def fn(*args,**kwargs): pass

Example1:

class Book:
    name = '书'
    def __init__(self,name,price):
        self.name = name
        self.price = price
    # 书的详细信息 => 一定需要知道那本书
    # @classmethod 类调用cls就是类,对象调用就是处理成对象.__class__
    def detail(self):
        # print(cls.name)
        print('%s的价格为:%s元' % (self.name,self.price))

book1 = Book('西游记',29.8)
book2 = Book('水浒传',98.9)
book1.detail()
book2.detail()
# print(book1.__class__)

# 静态方法: 方法的内部不需要对象及类的参与,所以定义为静态方法,但是方法必须由调用者,建议用类就可以了.
class NumTool:  # 工具类 => 模块
    def max_two(self,n1,n2):
        max_num = n1 if n1 > n2 else n2
        print('大数是%s' % max_num)
    @staticmethod
    def new_max_two(n1,n2):
        max_num = n1 if n1 > n2 else n2
        print('大数是%s' % max_num)

n1 = NumTool()
n2 = NumTool()
n1.max_two(10,20)
n2.max_two(10,20)

NumTool.new_max_two(10,20)
n1.new_max_two(10,20)

Example2:

# 类方法: 方法的内部需要类的参与,所以定义为类方法,第一个参数默认传参
class NewNumTool:
    PI = 3.14
    @classmethod
    def new_max_two(cls,n1,n2):
        max_num = n1 if n1 > n2 else n2
        return max_num
    
    @classmethod
    def new_max_three(cls, n1, n2, n3):
        # max_num = "想去复用new_max_two"
        max_num = cls.new_max_two(n1, n2)
        max_num = cls.new_max_two(max_num, n3)
        return max_num

    @classmethod
    def is_PI(cls,num):
        if num == cls.PI:
            return True
        return False

reslt = NewNumTool.new_max_three(1,5,3)
print('大数是%s' % reslt)
print(NewNumTool.is_PI(3.14))

大数是5
True

面向对象三大特性

封装

什么是封装?

# “装”的意思就是往一个容器放入一系列属性
# "封"的意思就是藏起来,在内部可以看到,但对外部是隐藏的.

# 封装: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.

为什么要用封装?

# 将复杂的操作封装成简单的接口,并严格控制接口的调用过程

如何用封装

# 但凡是双下划线开头(不能是双下划线结尾)的属性会被隐藏起来,类内部可以直接使用
# 而类外部无法直接使用,即封装对外不对内的

# 这种隐藏的特点:
# 1. 只是一种语法上的变形,会将开头的属性变形为: 自己的类名属性名(n=1 # __Foon=1)
# 2. 该变形只在类定义阶段发生一次,在类定义阶段之后新增的__开头的属性并不会变形.
# 3. 隐藏是对外不对内的
# 4. 在继承中,父类如果不想让子类覆盖自己的同名方法,可以将方法定为私有的.

Example1

class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)

    def tell_info(self):
        print('姓名:%s,年龄:%s' %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须是整型')
        self.__name=name
        self.__age=age

t=Teacher('tom',18)
t.tell_info()

t.set_info('flying',19)
t.tell_info()

Example2

class Student(object):
    def __init__(self,name,score):
        self.name=name
        self.score=score
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score>= 60:
            return 'B'
        else:
            return 'C'

Tom = Student('Tom',99)
Alice = Student('Alice',57)
print(Tom.name,Tom.get_grade())
print(Alice.name,Alice.get_grade())

对外封装的方式

# 什么是封装: 把类的一下属性和方法对外隐藏,对内可见,变成了私有变量。
# 为什么封装: 为属性和方法的操作添加权限,具体权限都是通过自定义逻辑来处理

# 封装的手段: 在类属性方法,对象属性方法,静态方法名字前添加__
# 只要是通过__名字,这种命名规范,就是对外隐藏
# 本质: __名字 封装隐藏白能量的本质是 将名字修饰成_类名__名字

# 对外解决封装的方式
# 1. 如果真的不想让外界访问,就不对外提供访问数据的方法.
# 2. 如果想让外界访问,可以对外提供访问数据的方法,方法具有逻辑,使用可以添加操作权限.
class Test:
    def __init__(self,name):
        # __name只是对外隐藏,对内可见
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self,name):
        if 'sh' not in name:    # 对数据的修改可能会产生数据的安全性问题,可以添加限制条件
            self.__name = name

# 重点: 封装的对外访问语法的优化
class User:
    def __init__(self,name):
        self.__name = name

    @property # 将方法伪装成属性
    def name(self):
        return self.__name

    @name.setter # 能为有伪装get方法的(方法)属性,再伪装set方法
    def name(self, value):
        self.__name = value
    
    @name.deleter
    def name(self):
        del self.__name

# 总结
# 1. 对象没了,对象的属性也就没了,所以不需要属性 @名字.deleter
# 2. 对外提供get方法是基础. @property,如果没有,外界不可读不可写
# 3. 如果有@property,则可以@名字.setter,有set,为可读可写,无set为只读.

@property # 伪装的属性方法,不需要一定有__开头的名字预支对应
def pwd(self):
    return '123456'

u1 = User('Ower')
print(u1.name)
# del u1.name
print(u1.name)

Owen
继承
# 1. 什么是继承?
# 继承就是一种新建类的方式,新建的类称为子类或者派生类,被继承的类称之为父类或者基类或者超类
# 子类会继承所有父类的属性和方法,既可以直接使用这些属性和方法
# ⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容. 说⽩了, ⼉⼦可以随便⽤爹的东⻄. 但是朋友们, ⼀定要认清楚⼀个事情. 必须先有爹, 后有⼉⼦. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系. 那么什么情况可以使⽤继承呢? 单纯的从代码层⾯上来看. 两个类具有相同的功能或者特征的时候. 可以采⽤继承的形式. 提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类. 比如. 猫是⼀种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的"动"这个属性. 再比如, ⽩骨精是⼀个妖怪. 妖怪天⽣就有⼀个比较不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此时 ⽩骨精继承妖精.

# 2. 为什么要用继承
# 减少代码冗余
# 让子类获得父类的全部功能.

假设我们要实现以下四种动物

# Dog - 狗狗
# Bat - 蝙蝠
# Parrot - 鹦鹉
# Ostrich - 鸵鸟

如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次:

如果按照"能跑"和"能飞"来归类,我们就应该设计出这样的类的层次

如果要把上面的两种分类都包含进来,我们就得设计更多层次

* 哺乳类:  能跑的哺乳类,能飞的哺乳类
* 鸟类:  能跑的鸟类,能飞的鸟类.
# 这样一来,类的层次就复杂了.

如果要在增加"宠物类"和"非宠物类",这么搞下去,类的数量会呈指数增长,很明显这样设计并不行 正确的做法是多重继承,首先,主要的类层次仍按照哺乳类和鸟类设计

class Animal(object):
    pass

# 大类:
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

# 各种动物:
class Dog(Mammal):
    pass

class Bat(Mammal):
    pass

class Parrot(Bird):
    pass

class Ostrich(Bird):
    pass

现在我们给动物加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类

class Runnable(object):
    def run(self):
        print('Running...')

class Flyable(object):
    def fly(self):
        print('Flying...')

对于需要Runable功能的动物,就多继承一个Runnable,例如Dog.

class Dog(Mammal, Runnable):
    pass

对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:

class Bat(Mammal, Flyable):
    pass
# 通过多重继承,一个子类就可以同时获得多个父类的所有功能.
# 3.如何使用
**Example1**

class Parent1:
    pass
class Parent2:
    pass
class Son1(Parent1):
    pass
# Python支持多继承,一个类可以有多个父类,但java只有一个
class Son2(Parent2):
    pass
print(Son1.__bases__)
print(Son2.__bases__)

(<class '__main__.Parent1'>,)
(<class '__main__.Parent2'>,)

# 切换python解释器3.x >>> 2.x得出一个结论
"""
在python3中,如果没有显示地继承任何类,那默认继承object类
在python2中,如果没有显示地继承任何类,也不会继承object类


在python中类分为两种:
        新式类:
            但凡继承object的类,或者该类的子类都是新式类
            >>>:在python3中所有的类都是新式类
        经典类
            没有继承object类,以及该类的子类都是经典类
            也就意味着经典类和新式类这对儿概念只在python2中有
            python3中只有新式类
"""

基于继承减少代码冗余的案例+派生/衍生

对象与对象之间总结相似的特征与技能得到类 类与类之间总结相似的属性和特征得到父类

Example2

import pickle

class OldboyStudent:
    school = 'oldboy'

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

    def choice_course(sel):
        print('%s is choosing course' %self.name)

    def save(self):
        with open(self.name,'wb') as f:
            pickle.dump(self,f)

class OldboyTeacher:
    school = 'oldboy'

    def __init__(self,name,age,sex,level):
        self.name = name
        self.age = age
        self.sex = sex
        self.level = level

    def score(self):
        print('%s is score' %self.name)

    def save(self):
        with open(self.name,'wb') as f:
            pickle.dump(self,f)
stu = OldboyStudent('alice',22,'male')
stu.save()

tea = OldboyTeacher('Tom',32,'male',10)
tea.save()

# 我们回过头看,上面代码是否存在相似的部分,我们刚好学过解决类之间解决代码冗余的方式
class OldboyPeople:
    school = 'oldboy'

    def save(self):
        with open(self.name, 'wb') as f:
            pickle.dump(self, f)
# 初步继承类抽取,思考继承之后对象查找属性和方法的顺序
stu.save()
stu.school

# 刚刚只是讲属性和方法抽成了父类,但是init里面也有重复的代码,应该也可以抽取
class OldboyPeople:
    school = 'oldboy'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

    def save(self):
        with open(self.name, 'wb') as f:
            pickle.dump(self, f)
# 先不考虑老师和学生init中不同的,先全部继承这个父类统一的init,发现也可以使用到父类的init

# 派生概念
# 在子类能够使用父类所有的属性和方法的同时,自身还有一些额外的属性和方法
# 小思考:如果派生的属性和方法恰巧和父类的一样,那在查找属性和方法的时候先找到谁呢? >>> 还是按查找顺序来

# 再回过头来看老师的init中有额外的level参数,不应该在父类中添加默认参数,
# 只能自己重写init方法,但是又有重复的代码出现
    def __init__(self,name,age,sex,level):
        OldboyPeople.__init__(self,name,age,sex)
        self.level = level
"""
在子类派生出的新方法中重用父类的方法
方式1:指名道姓访问,与继承没有任何关系
OldboyPeople.__init__(self,name,age,sex)
言外之意还有一种跟继承有关系的能够重用父类的方法的方式,先不着急说

继承原理

  • 刚刚我们讨论的是单继承下属性查找的顺序,那如果是多继承情况尼? 单继承测试
class Foo:
    def f1(self):
        print('Foo.f1')
    
    def f2(self):
        print('Foo.f2')
        self.f1() # obj.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()

Foo.f2
Bar.f1

多继承

# F(A,B,C) 无论是新式类还是经典类,都是从左往右挨个走到底 画图 切换python版本演示,
# 记得加文件头coding:utf-8
# 2、多继承的属性查找“:对象自己-》对象的类-》从左往右一个一个的分支找下去

class D:
    # def test(self):
    #   print('D')
    pass

class E:
    def test(self):
        print('E')

class F:
    def test(self):
        print('F')

class A(D):
    # def test(self):
    #   print('A')
    pass

class B(E):
    def test(self):
        print('B')

class C(F):
    def test(self):
        print('C')

class G(A,B,C):
    # def test(self):
    #   print('G')
    pass

obj=G()
obj.test()

B

回过头再来看通过继承关系调用父类的方法

# 方式二:super(自己的类名,self).父类中的方法名()
# 调用super会得到一个特殊的对象,该对象是专门用来引用父类中的方法的,
# 具体的:该对象会严格按照当前类的MRO列表从当前类的父类中依次查找属性,即这种方式是严格依赖于继承的
# ps:在python3中可以简写为super()

class OldboyPeople:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class OldboyTeacher(OldboyPeople):
    def __init__(self,name,age,sex,level):
        # OLdboyPeople.__init__(self,name,age,sex)
        super(OldboyTeacher,self).__init__(name,age,sex)
        self.level=level
tea1=OldboyTeacher('alice',18,'male',10)
print(tea1.name,tea1.level)

class A:
    def f1(self):
        print('A')
        super().f2()    # super () 会基于当前和所在的查找位置继续往后查找

    def f2(self):
        print('A')

class B:
    def f2(self):
        print('B')

class C(A,B):
    def f2(self):
        print('C')
obj=C()
print(C.mro())
obj.f1()

alice 10
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
A
B
# 实际工作中要谨慎使用多继承,编程是解耦合,而继承是强耦合.

Mixin

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird,但是,如果需要混入额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable,这种设计称之为Mixin。

为了更好地看出继承关系,我们把RunnableFlyable改为RunnableMixInFlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。 Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServerUDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixInThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。

比如编写一个多进程模式的TCP服务,定义如下:

class MyTCPServer(TCPServer, ForkingMixIn):
    pass

编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn

class MyTCPServer(TCPServer, CoroutineMixIn):
    pass

这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的类. 小结

由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。 只允许单一继承的语言(如Java)不能使用MixIn的设计。

多态

什么是多态

# 同一种事物的多种形态(动物: 人,猫,狗)
# 同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "alex", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。

为什么要使用多态

# 多态性: 指的是可以在不用考虑对象具体类型的前提下,直接调用对象的方法

如何使用多态

class Animal:
    def talk(self):
        pass

class People(Animal):
    def talk(self):
        print('say hello')

class Dog(Animal):
    def talk(self):
        print('汪汪汪')

class cat(Animal):
    def talk(self):
        print('喵喵喵')
peo1=People()
dog1=Dog()
cat1=cat()

# 不用考虑具体类型的前提下,直接调用对象的方法
peo1.talk()
dog1.talk()
cat1.talk()

"""
再来想 车是不是有很多牌子,你去学车需要说专门学那个牌子的车的驾驶方式吗?
"""

# 之前一直在用多态性,不用考虑对象具体类型前提下,直接调用对象的方法
l = list([1,2,3])
s = str('hello')
t = tuple((4,5,6))

l.__len__()
s.__len__()
t.__len__() # 我不需要考虑这三个具体什么类型,只要是容器类型就能调用len这个方法
class Animal:
    def talk(self):
        pass

class People(Animal)
    def jian(self):
        print('say hello')
    
class Dog(Animal):
    def han(self):
        print('汪汪汪')

class Cat(Animal):
    def hou(self):
        print('喵喵喵')
# 多态性能实现的条件就是父类给子类定义了一个标准,动物都必须会叫,并且叫的方法都必须是talk
# 但是你现在能约束我说子类必须叫这个方法吗?

# 那有没有一种情况能够做到说子类必须按照父类定义的标准
import abc
class Animal(metaclass=abc.ABCMeta): # 父类存在的意义就是用来定义规范
    @abc.abstractmethod
    def talk(self):
        pass
# Aoimal() 抽象基类不能被实例化()
class People(Animal):
    def jian(self):
        print('say hello')

class Dog(Animal):
    def han(self):
        print('汪汪汪')

class Cat(Animal);
    def hou(self):
        print('喵喵喵')
# 上卖弄三个类,一实例化就会报错
# 但是Python推崇的是自由,简约并不希望限制程序员开发
class People:
    def talk(self):
        print('say hello')
class Dog:
    def talk(self):
        print('汪汪汪')
class Cat:
    def talk(self):
        print('喵喵喵')
# 再来看Linux中,一切皆文件!
class Disk:
    def read(self):
        print('disk read')
    def write(self):
        print('disk write')

class Process:
    def read(self):
        print('processes')
    def write(self):
        print('processes write')

class Memory:
    def read(self):
        print('memory read')
    def write(self):
        print('memory write')
获取对象信息

首先,我们来判断对象类型,使用type()函数:

# 基本类型都可以用`type()`判断:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

# 如果一个变量指向函数或者类,也可以用`type()`判断:
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

使用isinstance()

对于class的继承关系来说,使用type()就很不方便,我们要判断class的类型,可以使用isinstance()函数. 我们假设继承关系是:

# Object --> Animal  --> Dog  --> Husky
# 那么,isinstance()就可以告诉我们,一个对象是否具有某种类型,先创建三种类型的对象:
# isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上.
# type()判断的基本类型也可以用isinstance()判断.

dir()

# 如果要获得一个对象的所有属性和方法,可以使用dir()函数,他返回一个包含字符串的list,比如,
# 获得一个str对象的所有属性和方法.
str = 'ABC'
print(type(str))
print(dir(str))

len

# 类似`__xxx__`的属性和方法在Python中都是有特殊用途的,比如`__len__`方法返回长度。
# 在Python中,如果你调用`len()`函数试图获取一个对象的长度,实际上,在`len()`函数内部,
# 它自动去调用该对象的`__len__()`方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
# 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法.
class MyDog(object):
    def __len__(self):
        return 100

dog = MyDog()
print(len(dog))

lower()

# 还有一些普通属性或方法,比如lower()返回小写的字符串.
>>> 'ABC'.lower()
'abc'

getattr,setattr,hasattr

# 仅仅把属性和方法列出来是不够的的,配合getattr(), setattr()以及hasattr(),我们可以操作一个对象的状态.
class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
        return self.x * self.x

obj = MyObject()
print(obj.power())
# 紧接着我们可以测试对象的属性
print(hasattr(obj,'x'))
print(obj.x)

setattr(obj,'y',19)
print(hasattr(obj,'y'))
print(obj.y)
# 如果获取不存在的属性,会抛出AttributeError的错误.
# 可以传入一个default的参数,如果属性不存在,就返回默认值
print(getattr(obj,'z',404))

# 也可以获得对象的方法
print(hasattr(obj,'power')) # 有属性'power'吗
print(getattr(obj,'power')) # 获取属性'power'
fn = getattr(obj,'power')   # 获取属性'power'并赋值到变量fn
print(fn())
print(fn)

小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据,要注意的是,只有在不知道对象信息的时候,我们才会 去获取对象信息,如果可以直接写:

sum = obj.x + obj.y
# 就不要写
sum = getattr(obj,'x') + getattr(obj,'y')
# 一个正确的例子如下
def readImage(fp):
    if hasattr(fp,'read'):
        return  readData(fp)
    return  None
# 假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,
# 如果不存在,则无法读取。hasattr()就派上了用场。

# 请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,
# 它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能
实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性. 给实例绑定属性的方法是通过实例变量,或者通过self变量.

class Student(object):
    def __init__(self,name):
        self.name = name

s = Student('Alice')
s.score = 90
> 但是,如果`Student`类本身需要绑定一个属性呢?可以直接在class中定义属性,
# 这种属性是类属性,归`Student`类所有:
class Student(object):
    name = 'Student'

> 当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。来测试一下:

class Student(object):
    name = 'Student'

s = Student()
print(s.name)
print(Student.name)

s.name = 'YouMen'
print(s.name)   # 由于实例属性优先级比类属性搞,因此他会屏蔽类的name属性

del s.name  # 如果删除实例的name属性
print(s.name)   # 再次调用s.name,由于实例的Name属性没有找到,类的属性就显示出来了

从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

小结

# 实例属性属于各个实例所有,互不干扰
# 类属性属于类所有,所有实例共享一个属性
# 不要对实例属性和类属性使用相同的名字,否则产生难以发现的错误.

类中的装饰器

property

# 人体的BMI指数
"""
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)/ 身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
"""

class People:
    def __init__(self,name,height,weight):
        self.name=name
        self.height=height
        self.weight=weight
    @property
    def bmi(self):
        return self.weight / (self.height ** 2)

youmen=People('YouMen',1.78,55)
youmen.height=1.78
print(youmen.bmi)

了解

class People:
    def __init__(self,name):
        self.__name=name

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,va1):
        # print('====> 准备修改名字的值:',val)
        if type(va1) is not str:
            raise TypeError('名字的值必须为str类型')
        self.__name=val
    
    @name.deleter
    def name(self):
        # def self.__name
        print('不让删啊,兄呆')
# classmethod
# staticmethod

反射

什么是反射?

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

python面向对象的反射: 通过字符串形式操作对象相关的属性,python中的一切事物都是对象(都可以使用反射) 通过字符串来获取类或对象的属性或方法

反射: 指的是通过字符串来操作类或者对象的属性

class People:
    country='China'
    def __init__(self,name):
        self.name=name
obj=People('YouMen')

# 涉及四个内置函数
# hasattr
print('country' in People.__dict__)
print(hasattr(People,'country'))

# getattr
print(People.__dict__['country'])
print(getattr(People,'country'))
print(getattr(People,'country1111',None))

# delattr
delattr(People,'country')
print(People.__dict__)

应用

class ftp:
    def get(self):
        print('get...')

    def put(self):
        print('put...')

    def auth(self):
        print('auth...')
    
    def run(self):
        while True:
            cmd=input('>>>:').strip() # cmd='get'
            if hasattr(self,cmd):
                method = getattr(self,cmd)
                method()
            else:
                print('输出的方法不存在')
obj=ftp()
obj.run()

面向对象高级编程

使用__slots__

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.

class Student(object):
    pass

# 然后,尝试给实例绑定一个属性
s = Student()
s.name = 'Michael'  # 动态给实例绑定一个属性
print(s.name)

# 还可以尝试给实例绑定一个方法:
from types import MethodType

def set_age(self,age):
    self.age = age

s.set_age = MethodType(set_age,s)   # 给实例绑定一个方法
s.set_age(25)
print(s.age)

# 但是给一个实例绑定的方法,对另一个实例是不起作用的.
>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

# 为了给所有实例都绑定方法,可以给class绑定方法.
from types import MethodType
def set_score(self,score):
    self.score=score
    return score

Student.set_score = set_score
# 给class绑定方法后,所有实例均可调用:
print(s.set_score(34))

通常情况下,上面的set_score方法可直接定义在class中,但动态绑定允许我们在程序运行过程中动态给class加上功能,这在静态语言中难以实现 使用__sllots__

但是,如果我们想要限制实例的属性怎么办? 比如,只允许对Student实例添加name和age属性. 为了达到限制目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制class实例能添加的属性.

class Student(object):
    __slots__ = ('name','age')

# 然后,我们试试
s = Student()   # 创建新的实例
s.name = 'Michael'  # 绑定属性'name'
s.age = 25      # 绑定属性age
# s.score = 99    # 绑定属性score

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。 使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

使用@property

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来简单,但是没办法检查参数,导致我们可以把成绩随便改.

s = Student()
s.score = 9999

这显然不和逻辑,为了限制score的范围,可以通过一个set_score()方法来设置成绩,在通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数.

class Student(object):

    def get_score(self):
        return self.__score

    def set_score(self,value):
        if not isinstance(value,int):
            raise ValueError('score must be an integer')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100')
        self.__score = value
# 现在,对任意的Student实例进行操作,就不能随心所欲设置score了。
s = Student()
s.set_score(60)
print(s.get_score())
# s.set_score(1000)
# print(s.get_score())
定制类

看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。 __slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()函数。 除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。

str 我们先定义一个Student类,打印一个实例:

class Student(object):
    def __init__(self,name):
        self.name = name

print(Student('YouMen'))
<__main__.Student object at 0x018EAEF0>

# 我们觉得打印这种地址值不是很好看,怎样打印好看尼,只需要定义好__str__()方法,返回一个好看的字符串就可以了.
class Student(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return 'Student object (name: %s)' % self.name
print(Student('YouMen'))
Student object (name: YouMen)
# 这样打印的实例,不但好看,而且容易看出实例内部重要的数据.
# 但是会发现不用print,打印实例还是不好看.
# 这是因为直接显示变量调用的不是__str__(),而是__repr__()返回用户看到的字符串,
# 而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的

# 解决方法是再定义一个__repr__(),但是通常__str__()和__repr__()代码是一样的,所以,有个偷懒的写法。
class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

iter

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):
    def __init__(self):
        self.a,self.b = 0,1 # 初始化两个对象

    def __iter__(self):
        return self         # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a,self.b = self.b,self.a + self.b # 计算下一个值
        if self.a > 10000: # 退出循环的条件
            raise StopAsyncIteration()
        return self.a       # 返回下一个值.

for i in Fib():
    print(i)

getitem

Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

class Fib(object):
    def __getitem__(self, i):
        a,b=1,1
        for x in range(i):
            a,b = b,a+b
        return x

a=Fib()
print(a.__getitem__(324))
# 现在,就可以按下标访问数列的任意一项了.

# 但是要想和list一样能切片,但是这个Fib却报错
class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L
f = Fib()
print(f[0:5])

getattr

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义Student类:

class Student(object):
    
    def __init__(self):
        self.name = 'Michael'

# 调用`name`属性,没问题,但是,调用不存在的`score`属性,就有问题了:
# 错误信息很清楚地告诉我们,没有找到`score`这个attribute。
# 要避免这个错误,除了可以加上一个`score`属性外,Python还有另一个机制,
# 那就是写一个`__getattr__()`方法,动态返回一个属性。修改如下
class Student(object):
    def __init__(self):
        self.name = 'YouMen'

    def __getattr__(self, attr):
        if attr == 'score':
            return 404

s = Student()
print(s.name)
print(s.score)

# 返回函数也是完全可以的.
class Student(object):
    def __init__(self):
        self.name = 'YouMen'

    def __getattr__(self, sttr):
        if sttr == 'age':
            return lambda: 25

s = Student()
print(s.name)
print(s.score)

# 注意,只有在没有找到属性的情况下,才调用`__getattr__`,已有的属性,比如`name`,不会在`__getattr__`中查找
# 此外,注意到任意调用如`s.abc`都会返回`None`,这是因为我们定义的`__getattr__`默认返回就是`None`。
# 要让class只响应特定的几个属性,我们就要按照约定,抛出`AttributeError`的错误:
class Student(object):
    def __init__(self):
        self.name = 'YouMen'

    def __getattr__(self, sttr):
        if sttr == 'age':
            return lambda: 25
        raise AttributeError(''Student' object bas no attribute '%s'' % sttr)

a = Student()
print(a.sttr)

这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。 这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。

举个例子: 现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似: 如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。 利用完全动态的__getattr__,我们可以写出一个链式调用:

class Chain(object):
    def __init__(self,path=''):
        self.__path = path

    def __getattr__(self,path):
        return Chain('%s/%s' %(self.__path,path ))

    def __str__(self):
        return self.__path
    __repr__ = __str__

a = Chain()
print(a.status.user.timeline.list)
/status/user/timeline/list

# 这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
# 还有些REST API会把参数放到URL中,比如GitHub的API:
`GET /users/:user/repos`
# 调用时,需要把`:user`替换为实际用户名。如果我们能写出这样的链式调用:
`Chain().users('michael').repos`
# 就可以非常方便地调用API了。有兴趣的童鞋可以试试写出来。

call

一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?在Python中,答案是肯定的。 任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。请看示例:

class Student(object):
    def __init__(self,name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

s = Student('YouMen')
s()		# self 参数不要传入

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例:

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

通过callable()函数,我们可以判断一个对象是否是"可调用"对象.

使用枚举类

当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:

JAN = 1  
FEB = 2  
MAR = 3  
  
NOV = 11  
DEC = 12  

好处是简单,缺点是类型是 int,并且仍然是变量. 更好的方法是为这样的枚举类型定义一个 class 类型,然后,每个常量都是 class 的一个唯一实例.Python 提供了 Enum 类来实现这个功能:

from enum import Enum  
Month = Enum('Month',('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug'))  
  
# 这样我们就获得了`Month`类型的枚举类,可以直接使用`Month.Jan`来引用一个常量,  
# 或者枚举它的所有成员:  
  
for name, member in Month.__members__.items():  
 print(name, '=>', member, ',', member.value)  
  
# value属性则是自动赋给成员的int常量,默认从1开始计数。  
# 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类.  
from enum import Enum,unique  
  
@unique  
class Weekday(Enum):  
 Sum = 0 # Sum的value值被设定为0  
 Mon = 1  
 Tue = 2  
 Wed = 3  
 Thu = 4  
 Fri = 5  
 Sat = 6  
# @unique装饰器可以帮助我们检查保证没有重复值  
# 访问这些枚举类型可以有若干种方法:  
day1 = Weekday.Mon  
print(day1)  
print(Weekday.Sat)  
print(Weekday(1))  
  
for name, member in Weekday.__members__.items():  
 print(name, '=>', member)  
# 课件,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量.  

Example1

from enum import Enum,unique  
class Gender(Enum):  
 Male = 0  
 Female = 1  
  
class Student(object):  
 def __init__(self,name,gender):  
 self.name = name  
 self.gender = gender  
bart = Student('Bart',Gender.Male)  
if bart.gender == Gender.Male:  
 print("测试通过")  
else:  
 print('测试失败')  

小结

Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较.

使用元类

type

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

比方说我们要定义一个Hello的class,就写一个hello.py的模块.

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)

当Python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello的class对象,测试如下:

cat hello.py 
#!/usr/bin/python3
class Hello(object):
	def hello(self,name='world'):
		print('Hello,%s' % name)

cat type_demo.py 
#!/usr/bin/python3
from hello import Hello
h = Hello()
h.hello()
print(type(h))

python3 type_demo.py 
Hello,world
<class 'hello.Hello'>
# tyep()函数可以查看一个类型或变量的类型,`Hello`是一个class,它的类型就是`type`,
# 而`h`是一个实例,它的类型就是class `Hello`。

# 我们说class的定义是运行时动态创建的,而创建class的方法就是使用`type()`函数。

# `type()`函数既可以返回一个对象的类型,又可以创建出新的类型,比如,
# 我们可以通过`type()`函数创建出`Hello`类,而无需通过`class Hello(object)...`的定义:

要创建一个class对象,type()函数依次传入3个参数:

1. class的名称;
2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
3. class的方法名称与函数绑定,这里我们把函数`fn`绑定到方法名`hello`上。

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时, 仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

metaclass

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。 但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。 连接起来就是:先定义metaclass,就可以创建类,最后创建实例。 所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。 metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。 我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:

class MyList(list, metaclass=ListMetaclass):
    pass

当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。 __new__()方法接收到的参数依次是:

  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合。

测试一下MyList是否可以调用add()方法:

>>> L = MyList()
>>> L.add(1)
>> L
[1]

而普通的list没有add()方法:

>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'

动态修改有什么意义?直接在MyList定义中写上add()方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。 但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子。

ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。 要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

让我们来尝试编写一个ORM框架. 编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

其中,父类Model和属性类型StringFieldIntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。 现在,我们就按上面的接口实现该ORM 首先来定义Field类,他负责保存数据库表的字段名和字段类型.

class Field(object):

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

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

在Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerField等等.

class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

下一步,就是编写最复杂的ModelMetaclass了。

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

以及基类Model

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclassModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

ModelMetaclass中,一共做了几件事情:

  1. 排除掉对Model类的修改;
  2. 在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);
  3. 把表名保存到__table__中,这里简化为表名默认为类名。

Model类中,就可以定义各种操作数据库的方法,比如save()delete()find()update等等。 我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

编写代码试试:

u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()

输出如下:

Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345]

可以看到,save()方法已经打印出了可执行的SQL语句,以及参数列表,只需要真正连接到数据库,执行该SQL语句,就可以完成真正的功能。 不到100行代码,我们就通过metaclass实现了一个精简的ORM框架

小结

metaclass是Python中非常具有魔术性的对象,它可以改变类创建时的行为。这种强大的功能使用起来务必小心。

随机文章