语言分类
面向机器
机器容易理解的语言,是一些机器指令,代表有汇编语言
面向过程
做一件事情,按步骤实现,第一步做什么,第二步做什么,如果情况A出现怎么处理,情况B处理怎么处理。适用问题规模小,可以按步骤实现,代表C语言
面向对象OOP
5*
是一种认识世界、分析世界的方法论。将万事万物抽象成为各种对象。
适用问题规模大,复杂的情况,代表C++、Java、Python
类class是一个抽象的概念
对象object,实例instance,都是一个实体,是个具体的概念
数据类,对应属性;动作类,操作类,行为,对应方法,类是对数据和操作的封装。
类中方法也是属性
- 生成器 迭代器 表达式
表达式又名解析式,用来构建list,set, dict, 比for循环效率高,使用列表表达式会立即生成新列表,占用大量的内存和cup;生成器的构建方式有生成器表达式,或者在函数中使用yield关键字,生成器是一个惰性对象,不会立即占用大量内存和cup,可以通过for循环和next方法获得里面的元素,生成器是一种迭代器。
面向对象3要素5*
1.封装
第一,将属性和方法封装, 也可以将对象封装到类当中
第二,将数据和操作适当暴露给客户,对外提供一些接口, 该隐藏的隐藏,该暴露的暴露,暴露的方法是公有方法,隐藏的方法有在属性和方法前面加一个下划线或者两个下划线(protected、私有)。
(暴露的方法有property中的getter,setter方法. 可以用装饰器实现,也可以自己写函数加到property实例中。)
2.继承
都是来自object(python3)类,object父类为None;分为单一继承和多继承,子可以直接调用或者重写的父类的属性和方法,尽量使用单一继承,少用多继承,多继承,OCP开闭原则(软件实现应该对扩展开放,对修改关闭)
(object无祖先,所有类的类型都是type)
单一继承
# 父类 超类 基类
# 子类 派生类
# 继承过程也叫派生过程
class Animal:
def __init__(self, name):
self._name = name
def shout(self):
print('{} , shouts'.format(self.__class__.__name__))
# 在父类中,通过实例取得,子类的名字
class Cat(Animal): pass
c = Cat('tutu')
c.shout()
print(c.__class__.__base__) #Animal
print(c.__class__.__bases__) #元组(Animal, )
print(c.__class__.__mro__) #通过属性获得mro,方法解析顺序,元组里面放的是类Cat Animal object
print(Cat.mro()) # 通过方法获得mro, 方法解析顺序,返回列表,同上
print(Animal.__subclasses__()) #Cat
print(int.__subclasses__()) #布尔型
# # object是根基类,没有父类,父类为None
# print(type(Cat)) type类型
# print(type(object)) type类型
多继承(少用)
python使用MRO(method resolution order 方法解析顺序),解决基类搜素顺序的问题,将非线性结构转化为线性搜索顺序。内部通过C3算法实现,采用深度优先原则。
多继承 :一个类继承自多个类,称为多继承
缺点:二义性
原则:OCP原则,多用继承,少修改
用途:复用,在子类上实现对基类的增强,实现多肽
3.多态
多肽就是继承加覆盖, B C两个类同时继承了A类,并重写了A类的一个方法,
同一套方法,在不同的子类上表现不同,这就是多肽。
(面向对象编程最灵活的地方,动态绑定,同一个方法在,不同的类型上
表现不同。例如继承自动物类的人类、猫类的操作'吃'方法不同)
python中的面向对象
定义:类名大驼峰(标识符),class关键字,定义后产生类对象,绑定到标识符上。
class ClassName:
语句块
#实例化的真实过程,先调用了类的 obj = MyClass.__new__()
#然后调用了obj.__init__()方法,但是__init__()的返回值只能是None,
#内部做了优化所以,实例化之后返回一个对象,每次实例化都走一次__init__
#实例化与初始化
#先实例化再初始化
#__init__称为初始化,出厂配置,构造器,构造方法,dunder方法,第一参数必须是实例
#类属性与类方法
#类属性与类方法本质上都是类属性
#类方法method,就是函数,可以通过类标识符.函数名,访问这个属性
#点号,就是成员访问符
普通方法,类方法,静态方法
例子:
class Path:
def __init__(self):
self.name = 'tom'
def method(self, m): #普通方法,需要2个参数,类调用时不会注入第一参数,实例调用时,
# 会将实例自身作为第一参数注入。
print('普通方法:', self, m)
@classmethod
def class_method(cls, b): #类方法,无论类调用还是实例调用,都会将类作为第一参数注入,第一参数默认是类,都不需要手动给值
print('class method :', cls, b)
@staticmethod #(用的少)静态方法, 无论类调用还是实例调用,都不会注入第一参数
def static_method(b, a):
print('static method :', b, a)
# 普通方法例子
print(Path.method) #
类对象与实例对象的_class_ __name__ __dict__
__class__表示类型
class Person:
height = 170
def __init__(self, name, age):
self.name = name
self.age = age
def getage(self):
print(self.name, __class__.__name__, self.__class__.__name__,type(self).__name__)
# 后3种都可以取到类的__name__
pclass = Person
pinstance = Person('Tom', 18)
# __name__
pclass.__name__ # Person, 只有类可以取到名字,实例如果不定义取不到name
# __class__
# 如果直接在类中使用__class__,不通过点的方式,则表示当前类
pclass.__class__ # 类型是type pclass.__base__父类为object
pinstance.__class__ # Person
# __dict___
pclass.__dict__ # {'height': 170 , '__init__': ,'getage':, '__doc__': }
pinstance.__dict__ # {'name': 'tom', 'age': 18}
print(*Person.__dict__.items(), sep='\n') #查看当前类的所有__dict__
函数与类中出现+=比较
class Person:
height = 170
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('tom', 12)
p.height += 10 # 不会报错,不会走__init__,只会到类中去找height属性
p.height
def f():
a = 10
def f1():
a += 1
print(a)
return f1
# f()() 报错
# 另一些例子
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def growup(self, i=1):
self.age += i
print(self.age)
# 因为已经传入了一个i,所以不会报错
def growup1(self, i=1):
i += 1
print('i', i)
a = 1
def growup2(self):
__class__.a += 1
print('a', __class__.a)
# 因为没有传入了i,在函数内部赋值即定义,所以会报错
b = 2
def growup3(self):
b += 1
p = Person('tom')
print(p.growup()) # 19
print(p.growup1()) # 2
print(p.growup2()) # 2
p.growup3() # 会报错
访问控制
私有,公有,protected
-
self.__age = 1 私有属性,只能类中使用_Person.__age, 当前类名加私有属性名,实例不可以访问, 想查看到_Person.__age,需要调用实例 .__dict__
# 当存在于实例的字典当中时 p.__dict__.get("_Person.__age") # 当存在于类的字典当中时 Person.__dict__.get("_Person.__age")
-
_self._age = 1 protected保护的属性,名字还是_age, 可以访问和修改
print(p._age) print(Person._height) # 当实例的字典中存在_height属性时可以访问
-
self.age = 1 公有属性
# 当age为类属性时 print(Person.age) print(p.age)
- 同样适用于类的方法,分为私用,保护,公有方法,因为
method也是一种属性
继承中的访问控制
# 一下例子用来测试,正常代码不要这么写
class Animal:
__COUNT = 100
HEIGHT = 0
def __init__(self, age, weight, height):
self.__COUNT += 1
self.age = age
self.__weight = weight
self.HEIGHT = height
def eat(self):
print('{} eat'.format(self.__class__.__name__))
def __getweight(self):
print(self.__weight)
@classmethod
def showcount(cls):
print(cls)
print(cls.__dict__)
print(cls.__COUNT)
@classmethod
def __showcount2(cls):
print(cls.__COUNT)
def showcount3(self):
print(self.__COUNT)
class Cat(Animal):
NAME = 'CAT'
__COUNT = 200
# 测试(只是为了测试)
c = Cat(3, 5, 15)
c.eat()
# print(c.__COUNT) #私有属性不可以通过实例直接访问访问
# CAT类的字典中_Cat__COUNT,访问方式_Cat__COUNT
# Animal类的字典中_Animal__COUNT ,访问方式_Animal__COUNT
# 实例的字典中没有这个属性,实例的字典中有_Animal__COUNT,访问方式_Animal__COUNT
print(Cat._Cat__COUNT) # 200
print(Animal._Animal__COUNT) # 100
print(c._Animal__COUNT) # 101
# c.__getweight() 父类的私有方法不可以通过,实例访问
c._Animal__getweight() #在实例字典中找到了_Animal__weight,值为5
实例属性的访问顺序
class C:
def __init__(self):
print('C init ~~~~~')
class A(C):
def __init__(self):
#super().__init__()
print('A init ~~~~')
class B(A):
def __init__(self):
#super().__init__()
print('B init ~~~~')
# 构造实例对象时,先找B中的__new__,再找
# A中的__new__, 都没找到,最后找的是object
# 中的__new__; 出厂配置时,初始化的顺序B,A,C
# 如果B中有__init__则只初始化B,
# 如果B中没有__init__, AC中都有,则只初始化A
# 如果B,A中都没有,只有C中有__init__,则只初始化C
# 如果A,B,C中都有__init__,想都初始化,则需要加2个super
b = B()
# 使用super()加载父类中的方法,然后在子类中增强
# 继承的正常使用方法1
class Animal:
def __init__(self, age):
self.age = age
def getage(self):
print(self.age)
class Cat(Animal):
def __init__(self, age, weight):
super().__init__(age)
self.weight = weight
c = Cat(1, 10)
c.getage() # 1
# 继承的正常使用方法2(getage函数的位置改变了)
class Animal:
def __init__(self, age):
self.age = age
class Cat(Animal):
def __init__(self, age, weight):
super().__init__(age)
self.weight = weight
def getage(self):
print(self.age)
c = Cat(1, 10)
c.getage() # 1
以下代码只是为了测试
class Animal:
__COUNT = 100
HEIGHT = 0
def __init__(self, age, weight, height):
self.__COUNT += 1
self.age = age
self.__weight = weight
self.HEIGHT = height
def eat(self):
print('{} eat'.format(self.__class__.__name__))
def __getweight(self):
print(self.__weight)
@classmethod
def showcount(cls):
print(cls)
print(cls.__dict__)
print(cls.__COUNT)
@classmethod
def __showcount2(cls):
print(cls.__COUNT)
def showcount3(self):
print(self.__COUNT)
class Cat(Animal):
NAME = 'CAT'
__COUNT = 200
#
c = Cat(3, 5, 15)
c.eat()
# print(c.__COUNT) #私有属性不可以通过实例直接访问访问
# CAT类的字典中_Cat__COUNT,访问方式_Cat__COUNT
# Animal类的字典中_Animal__COUNT ,访问方式_Animal__COUNT
# 实例的字典中没有这个属性,实例的字典中有_Animal__COUNT,访问方式_Animal__COUNT
print(Cat._Cat__COUNT) # 200
print(Animal._Animal__COUNT) # 100
print(c._Animal__COUNT) # 101
# c.__getweight() 父类的私有方法不可以通过,实例访问
c._Animal__getweight() #在实例字典中找到了_Animal__weight,值为5
super用法
# 继承的测试例子(正常代码不要这么写):
# getage在子类中实现
class Animal:
def __init__(self, age):
self.age = age
class Cat(Animal):
def __init__(self, age, weight):
super().__init__(age)
self.weight = weight
def getage(self):
print(self.age)
c = Cat(5, 10)
c.getage() # 5
# getage在父类中实现
class Animal:
def __init__(self, age):
self.__age = age
def getage(self):
print(self.__age)
class Cat(Animal):
def __init__(self, age, weight):
super().__init__(age) # 等价于super(Cat, self).__init__(age)
# 执行完super,在c实例的字典中多了一个_Animal__age : 3
self.__age = age + 1 # 执行完这一句,在c实例的字典中多了一个_Cat__age:4
self.weight = weight
c = Cat(3, 10)
c.getage() # Cat类中没有getage方法,所以到父类中找getage方法,找的是c实例字典中的_Animal__age方法,所以返回3
class Animal:
def __init__(self, age):
self.__age = age
class Cat(Animal):
def __init__(self, age, weight):
super().__init__(age)
self.__age = age + 1
self.weight = weight
def getage(self):
print(self.__age)
c = Cat(3, 10)
c.getage() # 4
class A:
def __init__(self, a, d=10):
self.a = a
self.__d = d
def showd(self):
return self.__d
class B(A):
def __init__(self, b, c):
self.b = b
self.c = c
A.__init__(self, b+c, b-c)
#super(C, f).__init__(2)
def printvalues(self):
print(self.b)
print(self.a)
return self.showd()
b = B(2, 3)
print(b.printvalues()) # b=2, a=5 _A__d=-1
print(b.showd()) # -1
class Animal:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name #不可以用return self.name, 会形成递归
def shout(self):
print('{} shouts'.format(self.__class__.__name__))
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
def shout(self): #override
print(super()) # 返回2个值,打印的是super()实例自己,包括当前类和当前类对象,super类实例化时,也是接受的这两个参数 。
# super().shout()
# 等价于super(self.__class__, self)
# 等价super(Cat, self)
# 等价于super(Cat, self).shout()
# 等价于Animal.shout(self) 不推荐
# 等价于self.__class__.__base__.shout(self) #坚决不用,认识就可以
super().shout()
print('miao miao')
c = Cat('tutu')
c.shout() # 'Cat shout'
print(c.name) # tutu
属性装饰器
# 注意事项,递归
class People:
def __init__(self, name):
self.name = name
#初始化时会先执行self.name = name,name现在为只读属性,在报递归错误之前(name(self)==self.name),会报cannot set attribute 错误, 因为只加一个@property,name会变成只读属性。
@property
def name(self):
return self.name
p = People('name: tutu')
print(p.name)
因为发生了递归,错误例子
class Person:
def __init__(self, name):
self.name = name
# 遇到self.name= name, 就会找到@name.setter,形成递归
@property
def name(self):
return self.name
@name.setter
def name(self, name):
self.name = name
p = Person('name: tutu')
print(p.name) #会形成递归,因为加上property后,name变成self的属性,name(self)等价于self.name
可读可写属性正确的例子1:
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
# 实例字典 {'_Person__name': 'name: tutu'}
# 类字典 {'name': }
p = Person('name: tutu')
print(p.__dict__)
print(Person.__dict__)
print(p.name)
只读属性正确的例子2:
class Person:
def __init__(self, name):
self.__name = name
@property
def new_name(self):
return self.__name
p = Person()
print(p.new_name)
可读可写属性例子3
class Person:
def __init__(self):
self.__eyecolor = 'blue'
@property
def eyecolor(self):
return self.__eyecolor
@eyecolor.setter
def eyecolor(self, value):
self.__eyecolor = value
@eyecolor.deleter # 用的不多
def eyecolor(self):
del self.__eyecolor
p = Person()
print(p.__dict__['_Person__eyecolor'])
#p.__eyecolor #访问不到
print(p.eyecolor) #eyecolor函数可以,当作属性来用
p.eyecolor = 'yellow' #修改属性,如果这个属性是只读模式,则不用写下面的setter和deleter方法
del p.eyecolor
可读可写属性例子4
# @property, 适合只有一个属性,这种方法只支持隐式调用不支持显示调用
# setter方法中一定要加判断,不然属性装饰器就没有意义了
class Person:
def __init__(self):
print('1')
self._first_name = "tutu"
print('3')
@property
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, first_name):
print('2')
if not isinstance(first_name, str):
raise TypeError('Expected a string')
self._first_name = first_name
# 只是实例化不会打印2,只会打印1, 3
p = Person()
p.first_name = 'guai'
print(p.first_name)
property 例子1:
# 使用函数property实现
class Person:
def __init__(self):
self.__eyecolor = 'blue'
def geteyecolor(self): return self.__eyecolor
def seteyecolor(self, value): self.__eyecolor = value
def deleyecolor(self): del self.__eyecolor
eyecolor = property(geteyecolor, seteyecolor, deleyecolor, "I'm the 'eyecolor' property.")
p = Person()
print(p.eyecolor)
p.eyecolor = "red"
print(p.eyecolor)
print(p.__dict__)
print(Person.__dict__)
print(p.geteyecolor()) #需要加括号
p.seteyecolor('yellow')
print(p.geteyecolor())
显示及隐式,显示调用就是使用property函数,隐式调用就是使用property装饰器
property 例子2:
# 下面的例子同时支持,显示调用和隐式调用
# 显式调用情景,python需要被集成到庞大的系统基础设施时,例如
# 分布式对象插入到一个大型的分布式系统中时,显示调用比隐式调用
# 更方便
class Person:
def __init__(self, first_name):
print('1')
self._first_name = first_name
print('3')
def get_first_name(self):
return self._first_name
def set_first_name(self, first_name):
print('2')
if not isinstance(first_name, str):
raise TypeError('Expected a string')
self._first_name = first_name
first_name = property(get_first_name, set_first_name)
p = Person('tom')
print(p.get_first_name()) # 'tom'
p.set_first_name('jerry')
print(p.get_first_name()) # 'jerry'
# 加了property函数后,也可以通过属性访问
print(p.first_name) # 'jerry'
p.first_name = 'tom'
print(p.first_name) # 'tom'
描述器
描述器在python中应用非常广泛,类如staticmethod()、classmethod()都实现了非数据描述器,property()函数实现为一个数据描述器。
Descriptors 定义 : 用到3个魔术方法的称为描述器
__get__、__set__、__delete__
实现描述器的2个条件:
- 2个类,属主类和描述器类,描述器类中有__get__, 或者其他2个魔术方法
- 属主的类属性,设置为描述器的类实例
分类:
-
非数据描述器(__get__)
实例可以重新定义和覆盖方法,准许单个实例获取与同一类其他实例不同的行为
数据描述器(__get__)(__set__)
添加非数据描述器和数据描述器实例属性的访问顺序:
实例__dict__ —> 非数据描述器get方法
数据描述器get方法 —> 实例_dict_ ->类字典
非数据描述器,找实例字典例子
class A:
def __init__(self):
print('A.init')
def __get__(self, instance, owner):
print('A.__get__')
return self
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x'
b = B()
# 实例的属性访问,不会调用A.__get__,会找实例自己的字典
# 在__init__中找了self.x,所以返回'b.x'
print(b.x)
非数据描述器,找非数据描述器get方法例子:
class A:
def __init__(self):
pass
def __get__(self, instance, owner):
return '9999'
class B:
x = A()
def __init__(self):
pass
b = B()
print(b.x) # 9999
数据描述器,先找get方法
class A:
def __init__(self):
pass
def __get__(self, instance, owner):
return self
def __set__(self, instance, value):
self.data = value
instance.__dict__['x'] = '2334'
class B:
x = A()
def __init__(self):
self.x = 'b.x'
b = B()
print('#########')
print(b.x) # 返回<__main__.A object at 0x10ed2d0b8>
print('#########')
print(b.__dict__) # {'x': '2343'}
print(B.__dict__) # {'x': <__main__.A object at 0x10a305048>}
print(B.__dict__['x'].__dict__) # {'data': 'b.x'}
数据描述器综合例子:
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
# print(self.__dict__)
print('A.__get__')
return self
def __set__(self, instance, value):
print('A.__set__')
print('first print')
self.data = value
instance.__dict__['x'] = '2334'
class B:
x = A()
def __init__(self):
print('B.init 1')
self.x = 'b.x'
print('B.init 2')
print('*********')
# 实例化时就会调用A.__set__
# 调用过程:先执行B类中的__init__方法,发现x属性是类描述器(在定义后执行时就存下来了),
# 所以执行装饰器中的__set__方法。打印顺序依次是print('B.init 1') -> print('A.__set__')
# print('first print') -> print('B.init 2')
b = B()
print('*********')
print(A().__dict__) # 打印A.init, {'a1': 'a1'}
print('#########')
# 实例的属性访问,会调用A.__get__,会先找描述器的字典,再找实例自己的字典,
# 然后找B类的字典,最后找到了。
# 数据描述器中,获得实例的属性
# print(a.__dict__)==print(b.x.__dict__) # {'a1': 'a1', 'data': 'b.x'}
print(b.__dict__) # {'x': '23'}
print(B.__dict__) # 含有{'x': <__main__.A object at 0x10a305048>}
print(b.x)
print('***********')
# 打印a1
print(b.x.a1) # 'a1'
print('##########')
# 打印b.x
print(b.x.data) # b.x
print(b.x.__dict__) # {'a1': 'a1', 'data': 'b.x'}
在外部增加属性赋值:
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
# print(self.__dict__)
print('A.__get__')
return self
def __set__(self, instance, value):
print('A.__set__')
print('first print')
self.data = value
class B:
x = A()
def __init__(self):
print('B.init 1')
self.x = 'b.x'
print('B.init 2')
b = B()
b.x = 500
print(b.x) # 会调用__set__方法, __main__.A object
B.x = 600
print(b.x) # 600
__get__
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print('A.__get__')
class B:
x = A()
def __init__(self):
print('B.init')
# 如果只是定义,虽然B中有x = A()
# 但是,没有获取x属性,则不执行__get__方法
print(B.x) # 打印A.__get__,因为__get__方法返回None,
# 所以打印None
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print('A.__get__')
return self
class B:
x = A()
def __init__(self):
print('B.init')
print(B.x.a1) # __get__方法返回self,即A实例,
# 这个A实例赋值给了x, 所以x有属性a1
属主中,只有通过类属性访问,才可以触发描述器的__get__,通过实例属性访问,则不可以
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print('A.__get__')
return self
class B:
x = A()
def __init__(self):
print('B.init')
self.b1 = A()
print('**********')
# B的实例没有x属性回去B类的字典中去找,所以触发
# __get__方法
b = B()
print(b.x)
print(b.x.a1)
# b1是实例属性,不会触发 __get__方法
print(b.b1)
实例不能覆盖属性的行为
class A:
@classmethod
def foo(cls): # 非数据描述器
pass
@staticmethod # 非数据描述器
def bar():
pass
@property # 数据描述器
def z(self):
return 5
def getfoo(self): # 非数据描述器
return self.foo
def __init__(self): # 非数据描述器
self.foo = 100
self.bar = 200
# self.z = 300
a = A()
# foo bar 都可以在实例中覆盖,但是z不可以
print(a.__dict__)
print(A.__dict__)
3.6新增方法__set_name__(不多)
可以知道属主类
class A:
def __init__(self):
print('A init')
def __get__(self, instance, owner):
print(1, self, instance, owner)
return self
def __set_name__(self, owner, name):
print(2, self, owner, name)
self.name = name
class B:
x = A() # 类属性创建时调用描述器的__set_name__方法
# 不需要创建B实例,直接运行就会打印__set_name__方法内部内容
# name为x
# 调用__get__方法,打印1和A实例
print(B().x)
简单例子:实现静态方法和类方法
静态方法
class StaticMethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
return self.fn
class A:
@StaticMethod
def f1(x, y): # 在解释器经过时,f1就被装饰过了,此时符f1指向StaticMethod类的实例
print(x + y)
a = A()
a.f1(4, 5) # a点f1会先调用类StaticMethod的__get__方法,然后调用函数f1
类方法:
from functools import partial
class ClassMethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner): # A的实例,与类A
return partial(self.fn, owner)
class A:
@ClassMethod
def f1(cls, x, y): # f1是ClassMethod实例
print(x + y)
a = A()
a.f1(4, 5) # a.f1 返回被固定了一个参数的fn
A.f1(4, 5)
参数检查(3种)
1、写函数 2、装饰器 3、描述器
# 1、写函数
class Person:
def __init__(self, name: str, age: int):
params = ((name, str), (age, int))
if not self.checkdata(params):
raise TypeError('params type error')
self.name = name
self.age = age
def checkdata(self, params):
for param, typ in params:
if not isinstance(param, typ):
return False
return True
# p = Person('tom', '10')
2、装饰器
import inspect
def check_params(cls):
def wrapper(*args, **kwargs):
params = inspect.signature(Person).parameters
values = list(params.values())
return cls
return wrapper
class Person:
def __init__(self, name, age: int = 10, *args, weight=90, **kwargs):
self.__name = name
self.__age = age
# p = Person('tom', 10)
3、描述器
# 版本1
class TypeCheck:
def __init__(self, attr, typ):
self.attr = attr
self.typ = typ
def __get__(self, instance, owner):
if instance: # ????????????
return instance.__dict__[self.attr]
return self
def __set__(self, instance, value):
self.value = value
if not isinstance(value, self.typ):
raise TypeError('{} is requirement {}'.format(self.attr, self.typ))
instance.__dict__[self.attr] = value
class Person:
name = TypeCheck('name', str) # 硬编码
age = TypeCheck('age', int) # 不优雅
def __init__(self, name: str, age: int):
self.name = name
self.age = age
self.__dict__['weight'] = 90
p = Person('tom', 18)
print(p.__dict__)
# print(Person.__dict__)
print(p.name)
print(p.age)
print(p.weight)
# 版本2
# 需求:要求Person中只存在__init__()方法,注入name, age类属性
# 且使用描述器,提起__init__()方法的形参名称和注解类型
class TypeCheck:
def __init__(self, attr, typ):
self.attr = attr
self.typ = typ
def __get__(self, instance, owner):
if instance: # ????????????
return instance.__dict__[self.attr]
return self
def __set__(self, instance, value):
self.value = value
if not isinstance(value, self.typ):
raise TypeError('{} is requirement {}'.format(self.attr, self.typ))
instance.__dict__[self.attr] = value
def propsinject(cls):
params = inspect.signature(cls).parameters
for name, param in params.items():
if param.annotation is not inspect._empty:
# 当参数有注解时,就将属性注入到类中
setattr(cls, name, TypeCheck(name, param.annotation))
return cls
@propsinject
class Person:
# 类属性,由装饰器注入,不需要手动把属性都写出来
# name = TypeCheck('name', str)
# age = TypeCheck('age', int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
self.__dict__['weight'] = 90
p = Person('tom', 18)
print(p.__dict__)
# print(Person.__dict__)
print(p.name)
print(p.age)
print(p.weight)
# 版本3
# 将版本2中的函数装饰器改装成类装饰器
class TypeCheck:
def __init__(self, attr, typ):
self.attr = attr
self.typ = typ
def __get__(self, instance, owner):
if instance: # ????????????
return instance.__dict__[self.attr]
return self
def __set__(self, instance, value):
self.value = value
if not isinstance(value, self.typ):
raise TypeError('{} is requirement {}'.format(self.attr, self.typ))
instance.__dict__[self.attr] = value
class PropsInject:
def __init__(self, cls):
self.cls = cls
params = inspect.signature(cls).parameters
for name, param in params.items():
if param.annotation != param.empty:
# 当参数有注解时,就将属性注入到类中
setattr(cls, name, TypeCheck(name, param.annotation))
def __call__(self, *args, **kwargs):
return self.cls(*args, **kwargs) # 构建一个新的Person对象
@PropsInject
class Person: # Person = PropsInject(Person)
# 在类装饰器中Person只能是PropsInject的实例,但是类PropsInject
# 中有__call__方法,所以Person('jerry', 10), 还可以调用
def __init__(self, name: str, age: int):
self.name = name
self.age = age
self.__dict__['weight'] = 90
p = Person('tom', 18)
print(p.__dict__)
# print(Person.__dict__)
print(p.name)
print(p.age)
print(p.weight)
产生递归的错误例子:
class TypeCheck:
def __init__(self, name, typ):
self.name = name
self.type = typ
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
print('A.__set__')
if not isinstance(value, self.type):
raise TypeError
setattr(instance, self.name, value)
class Person:
name = TypeCheck('name', str)
age = TypeCheck('age', int)
def __init__(self, name:str, age:int):
self.name = name
self.age =age
p = Person('tom', 10)
# 会产生递归, 实例化时走到self.name = name, 然后调用
# __set__方法,执行setattr,相当于执行给p的实例增加,'name'
# 属性,属性值为name, 又回到self.name = name,所以递归
魔术方法5*
-
enter与exist方法
当类中实现 __enter__和__exit__了方法,将 类的实例 放在with语法后面,则会执行__enter__ 方法,如果加了as,__enter__方法的返回值则是as后面的内容,然后执行with语句块,无论遇没遇到异常都会执行__exit__方法, __exit__方法的返回值默认是None,遇到异常就会抛出,如果将返回值改成等效为True的对象,则会压制异常。(具体例子参见上下文)
-
new与init方法
当实例化一个类时会先调用__new__方法,然后调用__init__,如果类中没实现,会到父类中去找__new__和__init__ class A: def __init__(self) pass a = A()
-
repr与str
当直接打印实例时会执行类的__str__方法,如果这个方法没实现会找__repr__方法,当打印类的实例,并且这个实例放在列表中时,会执行__repr__方法, 如果只实现了__str__,则会直接打印实例对象。
-
setattr getattr setitem getitem等
c = Cart() # __setattr__ # 相当于调用实例的__setattr__方法 c.name = 'tutu' print(c.name) # __getattr__ # 相当于调用实例的__getattr__方法 print(c.name) # __getitem__,key可以是索引或者键(可哈希) # 加[]相当与调用实例的__getitem__方法 print(c[0]) # __setitem__ # 相当于调用实例的__setitem__方法 c[0] = 'tutu' print(c)
-
len() 与__len__
# 当类中实现了__len__方法时 len(A()) # 返回__len__方法返回值 len(A) # 报错,好像魔术方法都是实例的 # 当类中没实现__len__方法 len(A()) # 报错 len(A) # 报错
iter
容器相关方法
# 将购物出改装成容器练习
# 也可以通过继承的方式将购物出改装成容器
class Item:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
return '名称:{},价格:{}'.format(self.name, self.price)
__repr__ = __str__
class Cart:
def __init__(self):
self.items = []
def additem(self, item):
self.items.append(item)
return self
def __len__(self):
return len(self.items)
def __str__(self):
return str(self.items)
def __iter__(self):
yield from self.items
# 等价于,return iter(self.items)
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, key, value):
self.items[key] = value
__add__ = additem
# __repr__ = __str__
c = Cart()
# 大小
print(len(c))
#__bool__
# 调用函数bool(), 返回布尔值,没有定义__bool__,就找__len__返回长度,非0为真
# 如果__len__也没有定义,那么所有实例都返回真值
class A: pass
a = A()
print(bool(a)) #True
print(bool(A)) #True
class A:
def __len__(self):
return 0
a = A()
print(bool(a)) # False
print(bool(A)) # True 只对实例起作用,对类A,不起作用,还是父类中的True
# add及可视化
c.additem(Item('car', 200)).additem(Item('earphone', 10))
print(c) # 如果类Cart中没有__str__,则返回一个object,如果类Item中没有__str__
# 则外层的object有包括几个object,类Cart中__str_显示最外层的列表,类Item中__str__
# 显示里层的名称和价格
c + Item('macbook', 300) + Item('phone', 400)
print(c)
# 遍历,返回一个新的迭代器对象
# 因为类中有__iter__方法,所以迭代实例c, 相当于迭代yield from形成的生成器对象
# 因为类Item中有__str__方法,所以可以看到具体的内容
for i in c:
print(i)
print('=' * 10)
#in 成员运算符
#__contains__方法如果没有实现
# 就调用__iter__方法,判断目标字符串是否在,迭代器中
print('tutu' in c)
#__missing__字典或子类使用__getitem__(),如果key, 不存在,会调用此方法
构造器与析构器
class Person:
def __init__(self, name): #构造器,构造函数,资源申请
self.name = name
def __del__(self): #析构器,构造函数,资源释放
print('del instance', self)
def test():
p = Person('tom')
del p #del x 并不直接调用 x.__del__() --- 前者会将 x 的引用计数减一,而后者仅会在 x 的引用计数变为零时被调用
# 即在函数程序执行过程中引用计数变成0,调用了__del__
print('*' * 10)
test() # 执行完函数,不会打印:del instance <__main__.Person object at 0x10236c588>
# 因为在函数内部类对象的引用计数已经被清0了,会在号上面打印,起作用的是del
def test():
p = Person('tom')
print('*' * 5)
test() # 执行完函数,才会执行__del__ 在*号之后打印:del instance <__main__.Person object at 0x10236c588>
# p = Person('tom')
# print(p.name) # 执行完最后一句print,才会执行__del__,打印:del instance <__main__.Person object at 0x10236c588>
#实例化 ?????????????????
#__new__是静态方法,所以第一参数必须手动给,这个方法很少用到
# 即使创建了该方法,也会调用一次,super().__new__(cls)
class A:
def __new__(cls, *args, **kwargs):
super().__new__(cls)
def __init__(self, name, age):
self.name = name
self.age = age
a = A('tom', age=20)
print(a.name)
__call__方法
# 在类中定义一个__call__方法,**实例**就可以像函数一样调用
class Adder:
def __call__(self, *args):
self.result = sum(args)
return self.result
# 以下两种调用方法等效
print(Adder()(*range(5)))
a = Adder()
print(a.__call__(*range(5)))
a = Adder()
print(callable(Adder)) #实例与类都是可调用的
print(callable(a))
# 函数调用的本质:
# 正常的函数调用,如bin() ,实际是调用了bin.__call__()
__hash__
# 判断一个对象是否可hash, isinstance(p1, collections.Hashable)
# list实例为什么不可以hash?
# 因为List类中有__hash__ = None,所以list实例不可以hash, 但是list类本身是可以hash的,print(hash(list)),
# 所有的类都继承自object, object类中有__hash__(),所以所有的类都是可hash的,
# 如果所有的类不可hash,需要在object中设置
class A:
pass
print(A)
print(A()) #什么都不写,类和实例也都可hash
print('=' * 10)
class A:
__hash__ = None
print(hash(A)) #可hash,
#print(hash(A())) #不可hash,因为None类型不可以这么使用None(A())
# print(A.__hash__()) # 不等价于 hash(A), 因为前面不可调用,但是后面
# 可hash
#集合去重的本质,需要hash值相同,并且内容相同
class A:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
return self.name == other.name
def __str__(self):
return str(self.name)
__repr__ = __str__
a = A('tom')
b = A('tom')
print(a is b) #False
print(a == b) #True
print(hash(a)) #5241461326091852353
print(hash(b)) #同上
print({a, b}) #去重了只剩下{tom},因为a b值相等,集合中的元素是repr的返回值
a = A('tom')
b = A('jerry')
print(a is b) #False 判断的是id(a) id(b)
print(a == b) #False
print(hash(a)) #5241461326091852353
print(hash(b)) #不同上
print({a, b}) #{tom, jerry} 没有去重,因为a b值不相等,集合中的元素是repr的返回值
# 将hash魔术方法的返回值修改了
class A:
def __init__(self, name):
self.name = name
def __hash__(self):
return 123
def __eq__(self, other):
return self.name == other.name
def __str__(self):
return str(self.name)
__repr__ = __str__
a = A('tom')
b = A('tom')
print(a is b) #False
print(a == b) #True
print(hash(a)) #123
print(hash(b)) #同上
print({a, b}) #去重了只剩下,tom
a = A('tom')
b = A('jerry')
print(a is b) #False
print(a == b) #False
print(hash(a)) #123
print(hash(b)) #同上
print({a, b}) #{tom, jerry} 没有去重
#去掉魔术方法eq
class A:
def __init__(self, name):
self.name = name
def __hash__(self):
return 123
def __str__(self):
return str(self.name)
__repr__ = __str__
a = A('tom')
b = A('tom')
print(a is b) #False
print(a == b) #False 如果类中没写__eq__那么这一行相当于判断is,地址是否相同
print(hash(a)) #123
print(hash(b)) #同上
print({a, b}) #{tom, tom},没有去重复,因为a b 值不想等
print(hash(1)) #居然等于1
可视化方法
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return ''.format(self.name)
def __bytes__(self):
return str(self).encode()
__repr__ = __str__
p = Person('tom')
print(p)
print(str(p))
print(repr(p))
print('{}'.format(p))
print(bytes(p)) # b''
运算符重载
# 当类中需要进行大量运算时使用,int类几乎实现了所有操作符,可以作为参考
# 自己是现实只需要实现3中运算符,其他3种可以推断出来
# __eq__ 由等于可以推出不等于
# __ge__ 由大于等于可以推断出小于等于
# __gt__ 由大于可以推断出小于
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __eq__(self, other): #等效于 ==
return self.age == other.age
def __gt__(self, other):
return self.age > other.age
def __ge__(self, other):
return self.age >= other.age
def __add__(self, other):
return self.age + other.age
def __iadd__(self, other):
self.age += other.age
return self.age
def __sub__(self, other):
pass
def __isub__(self, other):
pass
def __str__(self):
return self.name
__repr__ = __str__
a = A('tom')
b = A('jerry', 25)
# 可以直接对两个实例进行排序
# 因为实例中含有__str__方法,所以会显示名字[tom, jerry],默认是升序,实际比较
# 的是实例,名字只是显示
print(sorted([a, b]))
print(a == b)
print(a > b)
print(a <= b)
print(a != b)
# 在类上面加一个装饰器@total_ordering,但是这个装饰器效率不高,一般不用
# from functools import total_ordering
print('=' * 10)
print(a + b)
a += b
print(a)
# 另一个例子
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return self.x + other.x, self.y + other.y
# #方法1
# def __iadd__(self, other):
# self.x, self.y = self + other
# return self
# #方法2
# def __iadd__(self, other):
# return Point(*(self + other))
# #方法3
# def __iadd__(self, other):
# self.x += other.x
# self.y += other.y
# return self
#方法4
def __iadd__(self, other):
self.x = self.x + other.x
self.y = self.y + other.y
return self
def __str__(self):
return str((self.x, self.y))
p1 = Point(1, 2)
p2 = Point(2, 1)
p1 += p2 #为什么一定要有返回值呢???????????????
print(p1)
查看属性和方法__dir__
# 特殊属性
# __name__ 类、函数、方法等的名字,实例没有
# __module__ 类定义所在的模块名字
# __class__ 实例或者类所属的类
# __bases__ 类的基类,返回一个元组,顺序为它们在基类列表中出现的顺序
# __doc__ 类或者函数的文档字符串,如果没有定义则为None
# __mro__ 类的继承顺序,class.mro()返回的结果保存在,__mro__中
# __dict__ 类或者实例的属性
#查看属性的方法__dir__
#dir()函数操作实例就是调用__dir__()
# dir(obj)
# obj的类型:
# 模块名,返回的**列表**包含模块的属性名和变量名
# 例如:print(dir('OOP魔术方法练习.py'))
# 类或者类型,返回的列表包含类的属性名,及它祖先的属性名
# 类的实例,
# 有__dir__方法,返回可迭代对象的返回值
# 没有__dir__方法,返回列表包含,实例的属性名,类的属性名,祖先类的属性名
# dir() #里面什么都不写
# 在模块中,返回模块的属性和变量名
# 在方法或者函数中,返回本地作用域的变量名
# 内建函数:
print(locals()) # 返回当前作用域中,变量**字典**
print(globals()) # 返回当前模块全局变量的字典
方法重载(overload)
函数名字相同,但是参数的类型不同, 其他语言可以使用,但是python的重载,如下面的例子所示, 同一个add函数可以实现,整数相加,字符串相加,也可以接受多个参数, 即灵活的形参,python语法本身就实现了其他语言的重载
class Add:
def add(self, a, b):
return a + b
Add().add(1, 2)
Add().add('a', 'b')
override 重写,覆盖
猴子补丁(发现python的方法或类写的不好,用的不多)
from test2 import Person
from test3 import get_score
def monkeypatch4Person():
Person.get_score = get_score
monkeypatch4Person()
# 修改了Person,中的get_score方法
上下文管理
with open('test') as f:
pass
# open('test')调用open对象的__enter__方法,并且这个函数的返回值
# 赋值给f
# with语句块内容执行完成后,又调用open对象的__exit__()方法
- 上下文管理对象
一个类实现了__enter__() _exit_(),就属于上下文管理对象
__enter__()需要有返回值
# l
f = open('test.py', encoding='utf8')
with f as p:
print(f is p) #True
print(f == p) #False
class Point:
def __init__(self):
pass
def __enter__(self):
return self #添加返回值
def __exit__(self, exc_type, exc_val, exc_tb):
pass
# 2
p = Point()
with p as f: #等价 f = p.__enter__()
print(p is f) #True
print(p == f) #True
#因为__enter__有返回值
__exit__(self, exc_type, exc_val, exc_tb )
方法参数:
没有异常是都为None
有异常时
exc_type:异常类型
exc_val:异常值
exc_tb:异常追踪信息
_\_exit\_\_()方法默认返回None,等效False, 异常会正常抛出
如果返回值是等效于True的值,则压制异常值。具体例子见文档魔术方法上下文管理
例子1
# __exit__返回None,不会压制异常,会执行函数__exit__(),打印1
# 不会打印2
class Point:
def __init__(self):
pass
def __enter__(self):
return self # 添加返回值
def __exit__(self, exc_type, exc_val, exc_tb):
print('1')
p = Point()
with p as f:
raise Exception('Error')
print('2')
例子2
# __exit__()等效返回True, 异常会被压制,
# 与上面相同不会打印2
class Point:
def __init__(self):
pass
def __enter__(self):
return self # 添加返回值
def __exit__(self, exc_type, exc_val, exc_tb):
print('1')
return 123
p = Point()
with p as f:
raise Exception('Error')
print('2')
-
上下文管理的安全性
在with语句中如果抛出异常,或者调用sys.exit(1),上下文管理中的
__enter__() _exit_() 方法也都会执行,文件描述符会被关闭,很安全
具体例子见02魔术方法上下文管理
-
上下文管理例子
为函数增加计时功能
方法1,装饰器
from functools import wraps import time import datetime def timeit(fn): @wraps(fn) def wrapper(*args, **kwargs): start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print(delta) return ret return wrapper @timeit def add(x, y): time.sleep(2) return x + y
方法2,上下文(需要写一个类)
import time
import datetime
class TimeIt:
def __init__(self, fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
def __exit__(self, exc_type, exc_val, exc_tb):
print(self.fn(1, 2))
print((datetime.datetime.now() - self.start).total_seconds())
def add(x, y):
time.sleep(2)
return x + y
with TimeIt(add):
pass
#fn函数的调用方式也可放在with语句中
#另一种方法,通过可调用对象实现(口诀with后面需要返回可调用对象)
class TimeIt:
def __init__(self, fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print((datetime.datetime.now() - self.start).total_seconds())
def __call__(self, x, y):
return self.fn(x, y)
def add(x, y):
time.sleep(2)
return x + y
with TimeIt(add) as add: #当__enter__返回实例时,add指向实例,实例再调用call方法
print(add(1, 2)) # add.__call__()
方法3,通过类装饰器实现(只要用到装饰器,就要考虑更新wrapper)
from functools import update_wrapper, wraps
class TimeIt:
def __init__(self, fn):
self.fn = fn
# 更新文档,名字,字典等,方法1
#wraps(fn)(self)
#方法2
#update_wrapper(self, fn)
#方法3, WRAPPER_ASSIGNMENTS中才有__name__,这个方法只能更新__dict__
#self.__dict__.update(fn.__dict__)
#方法4, 只能更新指定的几个不能更新全部
self.__name__ = fn.__name__
self.__dict__.update(fn.__dict__)
self.__doc__ = fn.__doc__
def __call__(self, x, y):
start = datetime.datetime.now()
ret = self.fn(x, y)
print(self.fn.__name__, (datetime.datetime.now() - start).total_seconds())
return ret
@TimeIt # add = TimeIt(add)
def add(x, y):
time.sleep(2)
return x + y
print(add(1, 2))
# 实例没有名字,但是函数和类都有名字
print(add.__name__) #add
print(TimeIt.__name__) #TimeIt
上下文应用场景
-
增强功能
在代码执行前后增加代码,以增强其功能,类似装饰器
-
资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接
-
权限验证
在执行代码前,做权限的验证,在__enter__中处理
-
实现简单上下文管理的装饰器
context lib.contextmanager, 是一个装饰器实现上下文管理,但是没有with语句,也不用实现类, 和类中的_enter__() __exit_()等方法,而是通过关键yield实现,它装饰的是一个函数,这个函数需要是生成器,而且只能yield一个值。
# 不添加异常的例子 import contextlib @contextlib.contextmanager def foo(): print('enter') try: yield [1, 2] #yield后面只能有一个值,相当于__enter__方法的返回值 finally: print('exit') with foo() as f: print(f) #执行过程:with语句执行到yield,有一个返回值付给f,然后执行with语句内部的内容 #最后执行foo函数中的finally语句块的内容 #执行结果:打印enter,with语句中f的返回值[1, 2] # exit,最后finally中的打印值,相当于__exit__中执行的内容 # 添加异常的例子 @contextlib.contextmanager def foo(): print('enter') try: yield [1, 2] finally: print('exit') with foo() as f: raise Exception() print(f) # 添加异常 # 会打印'enter','exit',但是with语句块的内容不会被执行,即不会打印[1, 2]
反射5*
是指程序运行时可以动态的获得属性和方法,python中具有反射能力的函数type(), getattr(),setattr()
定义:是指程序运行时,能够通过对象找出type, attribute,(class,),或者动态添加方法的能力。(运行时是指,解释性语言的程序被加载到内存中执行时,不同于编译性语言。python中具有反射能力的函数type(), getattr(),setattr()(instance(), callable(), dir(), hasattr())等)
-
反射与装饰器,Mixin方式的差异:
这种动态增加属性的方式,是运行时改变类或者实例的方式,但是装饰器和Mixin都是定义时就决定了,因此反射的具有更大的灵活性。
-
内建函数hasattr(), setattr(), getattr()
第2个位置的参数,必须是字符串
getattr() setattr() 等价于取点的方式获得和设置属性
-
getattr() setattr() 与魔术方法__getattr__ __setattr__关系
setattr()函数,当类中实现了__setattr__方法时,会走类中的这个方法
getattr()函数,当类中实现了__getattr__方法时,并且实例的字典中没有要获取的属性时,才会走类中的这个方法
-
反射与内键函数的所有例子只需要看一个例子
# 其实不用实现这两个魔术方法,就可以满足开发需求 class Point: def __init__(self, x, y): self.x = x # 这时会调用__setattr__ self.y = y p = Point(1, 2) print(p.x) # 1 print(p.y) # 2 p.z = 9 print(p.z) # 9
getattr() # 等价于p.z setattr() # 等价于p.z = 10