面向对象

面向对象3要素

1、封装

​ 组装:将数据和操作组装到一起。

​ 隐藏数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解其中的机动原理。

2、继承

​ 多复用,继承来的就不用自己写了

​ 多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性

3、多态

​ 面向对象编程最灵活的地方,动态绑定

封装

封装就是定义类,将属性和操作组织在类中

Python类定义

class ClassName:

语句块

1、必须使用class关键字

2、类名强烈建议使用大驼峰命名方式,即每个单词首字母大写。其本质上就是一个标识符

3、类定义完成后,就产生了一个类对象,绑定到了标识符ClassName上

class fn:
    """这是个类"""
    x = 'abc' #这是个类属性

    def foo(self):
        """这个是fn类中的foo方法"""
        return __name__


print(fn, type(fn))
print(fn.__name__)#查看类的名字
print(fn.__doc__, fn.foo.__doc__, sep='\n')#查看类的文档和方法的文档
print(fn.foo)
print(fn.foo.__qualname__)#查看类中方法的全名
print(fn.x)#查看类属性

#结果
<class '__main__.fn'> <class 'type'>
fn
这是个类
这个是fn类中的foo方法
<function fn.foo at 0x000001A8846ABD90>
fn.foo
abc

类及类属性

类对象:类也是对象,类的定义执行后会生成一个类对象

类属性:类定义中的变量和类中定义的方法都是类的属性。上例中类Person的x和showme

类变量:属性也是标识符,也是变量。上例中类Person的x和showme

fn中,x、foo都是类的属性,__name____doc__是类的特殊属性

self 指代当前实例本身

实例化

a = fn()

使用上面的语法,在类对象名称后面加上一个括号,就调用类的实例化方法,完成实例化。实例化就真正创建一个该类的对象(实例instance)

class fn:
    """这是个类"""
    x = 'abc'

    def __init__(self, name, age):
        print('init++++')
        self.name = name
        self.age = age

a = fn('Tom',12)
b = fn('dada')
print(a.name,a.age)

#结果
init++++
init++++   
Tom 12

创建的实例,就会执行一下__init__方法

__init__方法

fn()实例化后,要初始化,要调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用其父类的。

作用:对实例进行初始化

初始化函数可以多个参数,请注意第一个位置必须是self,例如__init__(self, name, age)

注意:__init__()方法不能有返回值,也就是只能是return None

实例对象instance

类fn实例化后获得一个该类的实例,就是实例对象。下例中的a、b就是fn类的实例

__init__方法的第一参数 self 就是指代某一个实例自身

执行a=fn('Tom', 12)时,调用__init__()方法。self.name就是a对象的name,name是保存在了a对象上,而不是fn类上。所以,称为实例变量

方法绑定

采用a.showage()的方式调用,实例对象会绑定到方法上。这个self就是a,指向当前调用该方法的实例本身

a.showage()调用时,会把方法的调用者tom实例作为第一参数self的实参传入__init__()方法

class fn:
    """这是个类"""
    x = 'abc'

    def __init__(self, name, age=18):
        print('init++++')
        self.name = name
        self.age = age

    def showage(self):
        print(f'{self.name} is {self.age} years old!')


a = fn('Tom', 12)
b = fn('dada')
print(a.name, a.age)
a.showage()
b.showage()
#结果
init++++
init++++
Tom 12
Tom is 12 years old!
dada is 18 years old!

实例变量和类变量

class fn:
    """这是个类"""
    age = 10

    def __init__(self, name):
        print('init++++')
        self.name = name


a = fn('Tom')
b = fn('dada')
print(a.name, a.age)
b.age=30
print(b.name, b.age)
#结果
init++++
init++++
Tom 10
dada 30

实例变量是每一个实例自己的变量,是自己独有的

类变量是类的变量,是类的所有实例共享的属性或方法

特殊属性

__name__ 对象名
__class__ 对象类型
__dict__ 对象的属性字典
__qualname__ 类的限定名

Python中每一种对象都拥有不同的属性。函数是对象,类是对象,类的实例也是对象

class fn:
    """这是个类"""
    age = 10

    def __init__(self, name):
        print('init++++')
        self.name = name


a = fn('Tom')
b = fn('dada')
print(a.name, a.age)
b.age=30
print(b.name, b.age)
print(fn.__dict__)
print(a.__dict__)
print(b.__dict__)
#结果
init++++
init++++
Tom 10
dada 30
{'__module__': '__main__', '__doc__': '这是个类', 'age': 10, '__init__': <function fn.__init__ at 0x00000211A306BE18>, '__dict__': <attribute '__dict__' of 'fn' objects>, '__weakref__': <attribute '__weakref__' of 'fn' objects>}
{'name': 'Tom'}
{'name': 'dada', 'age': 30}

属性本质

class fn:
    """这是个类"""
    height = 10

    def __init__(self, name, age=40):
        print('init++++')
        self.name = name
        self.age = age


a = fn('Tom')
b = fn('dada', 20)
print(fn.__dict__)
print(a.__dict__)
print(b.__dict__)
print(id(fn.height))
print(a.name, a.age, a.height,id(a.height))#40 10
print(b.name, b.age, b.height,id(b.height))#20 10
fn.age = 30
fn.height = 50
a.height=60
fn.w=30
print(fn.__dict__)
print(a.__dict__)
print(b.__dict__)
print(id(fn.height))
print(a.name, a.age, a.height,a.w,id(a.height))#30 50
print(b.name, b.age, b.height,b.w,id(b.height))#20 50
c=fn('yaya')
print(c.__dict__)
print(c.name, c.age, c.height,c.w)
#结果
init++++
init++++
{'__module__': '__main__', '__doc__': '这是个类', 'height': 10, '__init__': <function fn.__init__ at 0x0000020D8763BD90>, '__dict__': <attribute '__dict__' of 'fn' objects>, '__weakref__': <attribute '__weakref__' of 'fn' objects>}
{'name': 'Tom', 'age': 40}
{'name': 'dada', 'age': 20}
1621197200
Tom 40 10 1621197200
dada 20 10 1621197200
{'__module__': '__main__', '__doc__': '这是个类', 'height': 50, '__init__': <function fn.__init__ at 0x0000020D8763BD90>, '__dict__': <attribute '__dict__' of 'fn' objects>, '__weakref__': <attribute '__weakref__' of 'fn' objects>, 'age': 30, 'w': 30}
{'name': 'Tom', 'age': 40, 'height': 60}
{'name': 'dada', 'age': 20}
1621198480
Tom 40 60 30 1621198800
dada 20 50 30 1621198480
init++++
{'name': 'yaya', 'age': 40}
yaya 40 50 30

上例中,可以看到类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,也可以借助__class__找到所属的类,再通过类来访问类属性,例如a.__class__.age

总结

是类的,也是这个类所有实例的,其实例都可以访问到;

是实例的,就是这个实例自己的,通过类访问不到。

类变量是属于类的变量,这个类的所有实例可以共享这个变量。

对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)

实例.__dict__[变量名]实例.变量名都可以访问到实例自己的属性

对实例访问来说,实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量。但是注意类变量还在那里,并没有真正被覆盖

实例属性的查找顺序

指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找

注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找

类方法和静态方法

前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类实例化之后,由实例来调用这个方法

普通函数

class fn:
    def foo1():
        print('+++')

        
print(fn.foo1)
fn.foo1()

fn().foo1()#这种调用是不被允许的

#结果
<function fn.foo1 at 0x0000025B2368BE18>
+++

    fn().foo1()
TypeError: foo1() takes 0 positional arguments but 1 was given

fn.foo1()

可以放在类中定义,因为这个方法只是被fn这个类管理的一个普通的方法,foo1fn的一个属性而已

由于foo1在定义的时候没有指定形参self,但不能用fn().foo1()调用。原因是,fn()是实例,实例调用的时候,由于做了实例绑定,那么就需要foo1的第一个形参来接收绑定的实例

类方法-------->classmethod

内建函数,装饰器内部获取类型信息,并绑定这个类型

class fn:
    @classmethod
    def foo(cls):
        print('===')
        
        
print(fn.foo)
fn.foo()
print(fn().foo())
fn().foo()

#结果
<bound method fn.foo of <class '__main__.fn'>>
===
<bound method fn.foo of <class '__main__.fn'>>
===

类方法

​ 1、在类定义中,使用@classmethod装饰器修饰的方法

​ 2、必须至少有一个参数,且第一个参数留给了clscls指代调用者即类对象自身

​ 3、cls这个标识符可以是任意合法名称,但是为了易读,请不要修改

​ 4、通过cls可以直接操作类的属性

cls只能使用类本身的属性,而__init__是初始化属性,属于实例的属性,所以,cls无法调用__init__中的属性,如下例

class Animal:
    __count = 100
    _age=18
    def __init__(self, name,male="female"):
        self._name = name
        self.male=male
    @classmethod
    def showcount(cls):
        print(cls)
        print(cls.__dict__.keys())
        print(cls._name)		#这个_name就无法调用

通过类、实例都可以非常方便地调用类方法。classmethod装饰器内存将类或提取实例的类注入到类方法的第一个参数中

静态方法--------->staticmethod

装饰器,内建函数,调用函数,不会有绑定效果,不会注入第一个参数

class fn:
    @staticmethod
    def foo():
        print('===')
        
        
print(fn.foo)
fn.foo()
print(fn().foo)
fn().foo()

静态方法

​ 1、在类定义中,使用@staticmethod装饰器修饰的方法

​ 2、调用时,不会隐式的传入参数

通过类、实例都可以调用静态方法,不会像普通方法、类方法那样注入参数

方法的调用

class fn:
    def foo(self):
        print('+++')


# a = fn('Tom')
a = fn()
print(fn.foo)
fn.foo(a)
print(id(fn.foo(fn())))
fn.foo([1,2,3])
print(fn().foo)
print(id(fn().foo()))
#结果
<function fn.foo at 0x0000026D9AD6BD90>
+++
+++
1378516272
+++
<bound method fn.foo of <__main__.fn object at 0x0000026D9AD75D68>>
+++
1378516272

总结:

​ 类除了普通方法都可以调用

​ 普通方法需要对象的实例作为第一参数

​ 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法内部都要使用实例的类

访问控制

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

    def __age_up(self, age):
        self.__age += age

    def get_age(self):
        self.__age_up(20)
        return self.__age


a = Person('Tom')
print(a.get_age())
print(a.name)
print(a.__age)#前面带双下划线的属性是私有属性,外部是无法使用的
print(a.__age_up)

#结果
38
Tom
    print(a.__age)
AttributeError: 'Person' object has no attribute '__age'
    print(a.__age_up)
AttributeError: 'Person' object has no attribute '__age_up'

私有(Private)属性

使用双下划线开头的属性名,就是私有属性

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

    def __age_up(self, age):
        self.__age += age

    def get_age(self):
        print(self.name)
        return self.__age


a = Person('Tom')
print(a.get_age())
print(a.name)
print(a.__dict__)
a.name='berr'
a.__age=100
print(a.__age)
print(a.get_age())
print(a.__dict__)

#结果
Tom
18
Tom
{'name': 'Tom', '_Person__age': 18}
100
berr
18
{'name': 'berr', '_Person__age': 18, '__age': 100}

通过实验可以看出,外部已经访问不到__age了,age根本就没有定义,更是访问不到

希望通过a.__age=100来修改私有属性,是无法实现的,通过字典可以看到,私有属性__age是以下划线+类名+私有属性(例如_Person__age)的形式存在字典中的,a.__age=100只是为a这个实例添加了一个属性而已

私有成员

在Python中,在类变量或实例变量前使用两个下划线的变量,称为私有成员,包括私有属性、私有方法

上例中的__age就是私有成员,无法通过a.__age来调用,但是可以通过内部方法返回对应成员,达到调用的目的

def get_age(self):
return self.__age

私有变量的本质

秘密都在__dict__中,原来私有成员都被改了名。

私有变量的本质:

​ 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名__变量名的名称,所以用原来的名字访问不到了

{'name': 'berr', '_Person__age': 18, '__age': 100}

哪怕后来自己定义了a.__age=100,但是依然不是改变的私有变量,只是人为又添加了一个属性而已

保护成员

在类变量或实例变量前使用一个下划线的变量,称为保护成员

class Person:
    def __init__(self, name, age=18):
        self._name = name
        self.__age = age

    def _get_age(self):
        return self.__age
    def get_name(self):
        print(self._get_age())
        return self._name


a = Person('Tom')
print(a._get_age())
print(a._name)
print(a.__dict__)

#结果
18
Tom
{'_name': 'Tom', '_Person__age': 18}

可以看出,保护成员没有改变名称,解释器不做任何特殊处理。

这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用

补丁

可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了

猴子补丁(Monkey Patch):

在运行时,对属性、方法、函数等进行动态替换。其目的往往是为了通过替换、修改来增强、扩展原有代码的能力

主程序

from t1 import fn
from t2 import foo


def f():
    fn.chengji = foo


f()
if __name__ == '__main__':
    print(fn().chengji())

t1.py

class fn:
    def chengji(self):
        x = {'Chinese': 66, 'math': 77}
        return x

t2.py

def foo(cls):
    return {'Chinese':100,'math':100}

上例中,假设fnchengji方法是从数据库拿数据,但是测试的时候,不方便。

为了测试时方便,使用猴子补丁,替换了chengji方法,返回模拟的数据

属性装饰器

一般好的设计是:把实例的某些属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性

class Preson:
    def __init__(self, name, age=20):
        self.name = name
        self.__age = age

    @property
    def age(self):
        print('is getter——')
        return self.__age

    @age.setter
    def age(self, value):
        print('is setter***')
        self.__age += value

    @age.deleter
    def age(self):
        print('is deleter')
        del self.__age


a = Preson('Tom')
print(a.age)
a.age = 30
print(a.age)
print(a.__dict__)
del a.age
print(a.__dict__)

#结果
is getter——
20
is setter***
is getter——
50
{'name': 'Tom', '_Preson__age': 50}
is deleter
{'name': 'Tom'}

特别注意:使用property装饰器的时候这三个方法同名

property装饰器

​ 后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性

setter装饰器

​ 与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写

deleter装饰器

​ 可以控制是否删除属性。很少用

property装饰器必须在前,setter、deleter装饰器在后

​ property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

也可以使用property内建函数

效果是一样的

age = property(getage, setage, delage, 'is property')

第一个参数是getter

第二个参数是setter

第三个参数是deleter

第四个是__doc__

class Preson:
    def __init__(self, name, age=20):
        self.name = name
        self.__age = age

    def getage(self):
        print('is getter——')
        return self.__age

    def setage(self, value):
        print('is setter***')
        self.__age += value

    def delage(self):
        print('is deleter')
        del self.__age

    age = property(getage, setage, delage, 'is property')


a = Preson('Tom')
print(a.age)
a.age = 30
print(a.age)
print(a.__dict__)
del a.age
print(a.__dict__)

也可以把第一个函数写成匿名函数的形式

age = property(lambda self:self.__age, setage, delage, 'is property')

对象的销毁

类中可以定义__del__方法,称为析构函数(方法)。

作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。

注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。使用del语句删除实例,引用计数减1。当引用计数为0时,会自动调用__del__方法。

由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收

每次对象调用结束后,会调用一下__del__方法

class Preson:
    def __init__(self, name, age=20):
        self.name = name
        self.age = age
    def __del__(self):
        print('调用了__del__方法')

a = Preson('Tom')
print('='*20)
#结果
====================
调用了__del__方法

当引用计数并未清零的时候,会在程序结束时,执行__del__方法

class Preson:
    def __init__(self, name, age=20):
        self.name = name
        self.age = age
    def __del__(self):
        print('调用了__del__方法')

a = Preson('Tom')
a1=a
a2=a1
print('='*20)
del a2
del a1
print('-'*20)
#结果
====================
--------------------
调用了__del__方法

当引用计数被清零,也会执行一次__del__方法

class Preson:
    def __init__(self, name, age=20):
        self.name = name
        self.age = age
    def __del__(self):
        print('调用了__del__方法')

a = Preson('Tom')
a1=a
a2=a1
print('='*20)
del a2
del a1
del a
print('-'*20)
#结果
====================
调用了__del__方法
--------------------

由于垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用__del__方法,除非你明确知道自己的目的,建议不要手动调用这个方法

方法重载(overload)

其他面向对象的高级语言中,会有重载的概念。

所谓重载,就是同一个方法名,但是形式参数个数、类型不一样,就是同一个方法的重载

Python没有重载!

Python不需要重载!

Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。所以Python不需要方法的重载。

或者说Python语法本身就实现了其它语言的重载

类的继承

面向对象三要素之一,继承Inheritance

在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多复用。子类也可以定义自己的属性和方法

class Animal:
    def __init__(self, name):
        self._name = name

    def jiao(self):
        print('{}在叫!'.format(self.__class__.__name__))
        print(self.name)

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


a = Animal('animal')
a.jiao()


class Dog(Animal):
    pass


b = Dog('xiaohuang')
b.jiao()


class Cat(Animal):
    pass


c = Cat('xiaohua')
b.jiao()

#结果
Animal在叫!
animal
Dog在叫!
xiaohuang
Dog在叫!
xiaohuang

继承

class Cat(Animal) 这种形式就是从父类继承,括号中写上继承的类的列表。

继承可以让子类从父类获取特征(属性和方法)

父类

Animal就是Cat的父类,也称为基类、超类

子类

Cat就是Animal的子类,也称为派生类

定义

格式

class 子类名(基类1[,基类2,...]):

语句块

如果类定义时,没有基类列表,等同于继承自object

Python支持多继承,继承也可以多级。

查看继承的特殊属性和方法

特殊属性和方法 含义
__bases__ 类的基类元组
__base__ 类的基类元组的第一项
__mro__ 显示方法查找顺序,基类的元组
mro()方法 显示方法查找顺序,基类的列表
__subclasses__() 类的子类列表

上例的基础上

print(Animal.__bases__)
#结果
(<class 'object'>,)

print(Dog.__bases__)
#结果
(<class '__main__.Animal'>,)

print(Dog.__base__)
#结果
<class '__main__.Animal'>

print(Dog.__base__.__name__)
#结果
Animal

print(Animal.__subclasses__())
#结果
[<class '__main__.Dog'>, <class '__main__.Cat'>]

print(Dog.__base__.__subclasses__())
#结果
[<class '__main__.Dog'>, <class '__main__.Cat'>]

print(Dog.mro())
#结果
[<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]

print(Dog.__mro__)
#结果
(<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)

继承中的访问控制

class Animal:
    __count = 100
    _age=18
    def __init__(self, name):
        self._name = name

    @classmethod
    def showcount(cls):
        print(cls)
        print(cls.__dict__)
        print(cls._age)
        print(cls.__count)

class Cat(Animal):
    """is Cat doc"""
    _age=20
    __count = 200


a = Cat('mi')
a.showcount()
print('=='*20)
print(a.__dict__)


#结果
<class '__main__.Cat'>
{'__module__': '__main__', '__doc__': 'is Cat doc', '_age': 20, '_Cat__count': 200}
20
100
========================================
{'_name': 'mi'}

可以看出_age是可以被继承的,而私有属性__count无法被继承的!

从父类继承,自己没有的,就可以到父类中找

私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类或实例的__dict__

总结

继承时,公有成员,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。

Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制

实例属性查找顺序

实例的__dict__→类__dict__→如果有继承→父类 __dict__

如果搜索这些地方后没有找到就会抛异常,先找到就立即返回了

方法的重写、覆盖override

class Animal:
    def jiao(self):
        print('Animla 叫')
class Cat(Animal):
    def jiao(self):
        super().jiao()
        print('Cat 叫')
class Dog(Cat):
    def jiao(self):
        super(Cat, self).jiao()
        print('Dog 叫')
class Pig(Dog):
    def jiao(self):
        super(Pig, self).jiao()
        print('Pig叫')

b=Cat()
b.jiao()
print('=='*20)
a = Dog()
a.jiao()
print('=='*20)
c = Pig()
c.jiao()


#结果
Animla 叫
Cat 叫
========================================
Animla 叫
Dog 叫
========================================
Animla 叫
Dog 叫
Pig叫

class Dog(Cat):
def jiao(self):
super(Cat, self).jiao()
print('Dog 叫')

这个形式,就是对父类方法的重写或者加强

super(Cat, self).jiao()

是指,先调用Cat父类的jiao()方法,然后再在这个基础上执行自己的代码

如果是在自己父类的基础上加强,可以简写为

super().jiao()

类方法和静态方法

class A:
    @classmethod
    def clsdemo(cls):
        print('A classmethod')

    @staticmethod
    def stademo():
        print('A staticmethod')


class B(A):
    @classmethod
    def clsdemo(cls):
        super(B, cls).clsdemo()
        print('B classmethod')

    @staticmethod
    def stademo():
        A.stademo()
        print('B staticmethod')

b=B()
b.clsdemo()
b.stademo()


#结果
A classmethod
B classmethod
A staticmethod
B staticmethod

结果也是一样的,只是静态方法无法调用super(),因为没有第一参数

静态方法和类方法,是特殊的方法,也是类属性,这些方法都可以覆盖,原理都一样,属性字典的搜索顺序

继承时使用初始化

class Animal:
    def __init__(self, a, b):
        self.a = a
        self.__b = b

class Cat(Animal):
    def __init__(self, c, d):
        super(Cat, self).__init__(c + d, c - d)
        self.c = c
        self.d = d

    def get(self):
        print(self.a)
        print(self.__b)
        print(self.c)
        print(self.d)


b = Cat(20,5)
print(b.__dict__)
b.get()

#结果
{'a': 25, '_Animal__b': 15, 'c': 20, 'd': 5}
25
    print(self.__b)
AttributeError: 'Cat' object has no attribute '_Cat__b'

私有属性__b是无法调用的

class Animal:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    # def jiao(self):
    #     print('Animla 叫')


class Cat(Animal):
    def __init__(self, c, d):
        super(Cat, self).__init__(c + d, c - d)
        self.c = c
        self.d = d

    # def jiao(self):
    #     super().jiao()
    #     print('Cat 叫')
    def get(self):
        print(self.a)
        print(self.b)
        print(self.c)
        print(self.d)


b = Cat(20,5)
print(b.__dict__)
b.get()
#结果
{'a': 25, 'b': 15, 'c': 20, 'd': 5}
25
15
20
5
class Animal:
    def __init__(self, c):
        self.__c = c

    def get(self):
        print(self.__c)


class Cat(Animal):
    def __init__(self, c, d):
        super(Cat, self).__init__(c)
        self.__c = c + 1
        self.d = d


b = Cat(20, 5)
print(b.__dict__)
b.get()

#结果
{'_Animal__c': 20, '_Cat__c': 21, 'd': 5}
20

多继承

Mixin

1、在Document中提供print方法

基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。

基类中只定义,不实现的方法,称为“抽象方法”。

在Python中,如果采用这种方式定义的抽象方法,子类可以不实现,直到子类使用该方法的时候才报错

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

    def printable(self):
        raise NotImplementedError()

class Word(Document):
    def printable(self):
        print(self.__class__.__name__,'打印',self.name)

class Pda(Document):
    def printable(self):
        print(self.__class__.__name__,'打印',self.name)

a=Word('hello world!')
a.printable()
b=Pda('hello world!')
b.printable()
#结果
Word 打印 hello world!
Pda 打印 hello world!

2、类装饰器

用装饰器增强一个类,把功能给类附加上去,哪个类需要,就装饰它

def printdoc(cls):
    def print1(self):
        print('+++++', self.name)

    cls.print2 = print1
    return cls


@printdoc
class Document:
    def __init__(self, name):
        self.name = name


@printdoc
class Word(Document):
    pass


a = Word('hello world!')
print(a.__dict__)
print(Word.__dict__)
a.print2()
b = Document('123445566')
b.print2()

#结果
{'name': 'hello world!'}
{'__module__': '__main__', '__doc__': None, 'print2': <function printdoc.<locals>.print1 at 0x000002084319BF28>}
+++++ hello world!
+++++ 123445566

优点:

简单方便,在需要的地方动态增加,直接使用装饰器。

可以为类灵活的增加功能。

3、Mixin

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


class Word(Document): pass


class printMixin:
    def print1(self):
        print(self.name)


class printable(printMixin, Word): pass


a = printable('hello world!')
a.print1()
print(a.__dict__)
print(printable.__dict__)
print(printMixin.__dict__)
print(printable.mro())

#结果
hello world!
{'name': 'hello world!'}
{'__module__': '__main__', '__doc__': None}
{'__module__': '__main__', 'print1': <function printMixin.print1 at 0x000001B1EA0FBE18>, '__dict__': <attribute '__dict__' of 'printMixin' objects>, '__weakref__': <attribute '__weakref__' of 'printMixin' objects>, '__doc__': None}
[<class '__main__.printable'>, <class '__main__.printMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

Mixin就是其它类混合进来,同时带来了类的属性和方法

Mixin本质上就是多继承实现的。

Mixin体现的是一种组合的设计模式

在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,这就需要很多的类组合在一起。

从设计模式的角度来说,多组合,少继承

Mixin类的使用原则

Mixin类中不应该显式的出现__init__初始化方法

Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现

Mixin类的祖先类也应是Mixin

使用时,Mixin类通常在继承列表的第一个位置

例如class PrintableWord(PrintableMixin, Word): pass


通过把Mixin类插在继承类的前面,达到插队的效果

{'__module__': '__main__', 'print1': <function printMixin.print1 at 0x000001B1EA0FBE18>, '__dict__': <attribute '__dict__' of 'printMixin' objects>, '__weakref__': <attribute '__weakref__' of 'printMixin' objects>, '__doc__': None}

通过这个字典可以看出,通过Mixin插队,将方法print1插入到了类的字典中

查看属性

dir(obj)对于不同类型的对象obj具有不同的行为:

​ 如果对象是模块对象,返回的列表包含模块的属性名和变量名

​ 如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名

​ 如果是类的实例

​ 有__dir__方法,返回可迭代对象的返回值

​ 没有__dir__方法,则尽可能收集实例的属性名、类的属性和祖先类的属性名

​ 如果obj不写,返回列表包含内容不同

​ 在模块中,返回模块的属性和变量名

​ 在函数中,返回本地作用域的变量名

​ 在方法中,返回本地作用域的变量名

方法 意义
__dir__ 返回类或者对象的所有成员名称列表。
dir()函数操作实例就是调用__dir__()

如果dir([obj])参数obj包含方法__dir__(),该方法将被调用。如果参数obj不包含__dir__(),该方法将最大限度地收集属性信息

t1.py

print('-' * 40)
x = 10


class A:
    def __init__(self, name):
        self._name = name
        self.__age = 20
        self.gender = 'male'


print(__name__, dir())

#结果
----------------------------------------
__main__ ['A', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']

t2.py

import t1
from t1 import A

print('~'*40)
class B(A):
    pass

print(t1.__name__,dir(t1))
print(__name__,dir())
print('='*40)

Tom=B('tom')
print(Tom)
print('dir---',dir(Tom))
print('sorted---',sorted(Tom.__dict__.keys()
             | B.__dict__.keys()
             | A.__dict__.keys()
             | object.__dict__.keys()
             ))

#结果
----------------------------------------
t1 ['A', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t1 ['A', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
__main__ ['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 't1']
========================================
<__main__.B object at 0x000001712FA81080>
dir--- ['_A__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_name', 'gender']
sorted--- ['_A__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_name', 'gender']

上面的例子可以看出dir()内建函数返回的就是继承顺序的相关__dict__.keys()排序结果

from t1 import A


class C(A):
    def __dir__(self):
        return [123]

c=C('tom')
print(dir(c))
print(dir(C))

#结果
[123]
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

__dir__方法只影响对象的dir()函数,不影响类的dir()函数

内建函数

locals() 返回当前作用域中的变量字典

globals() 当前模块全局变量的字典

import inspect

print('-' * 40)
x = 10


class A:
    print(inspect.stack()[0][3])

    def show(self, name):
        self._name = name
        print(inspect.stack()[0][3])
        y = 20
        print(dir())
        print(sorted(locals().keys()))
        print(sorted(globals().keys()))
        print('=' * 40)


def B():
    print(inspect.stack()[0][3])
    z = [1, 2, 3]
    print(dir())
    print(sorted(locals().keys()))
    print(sorted(globals().keys()))
    print('*' * 40)


A().show('b')
B()
print(dir())
print(sorted(locals().keys()))
print(sorted(globals().keys()))

#结果
----------------------------------------
A
show
['name', 'self', 'y']
['name', 'self', 'y']
['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'inspect', 'x']
========================================
B
['z']
['z']
['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'inspect', 'x']
****************************************
['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'inspect', 'x']
['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'inspect', 'x']
['A', 'B', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'inspect', 'x']

globals()在哪里都是一样的

locals()返回的则是本地的,与dir()相同

魔术方法

分类:

​ 创建、初始化与销毁

__new__

__init____del__

​ 可视化

​ hash

​ bool

​ 运算符重载

​ 容器和大小

​ 可调用对象

​ 上下文管理

​ 反射

​ 描述器

​ 其他杂项

实例化

方法 意义
__new__ 实例化一个对象
该方法需要返回一个值,如果该值不是cls的实例,则不会调用__init__
该方法永远都是静态方法
class A:
    def __new__(cls, *args, **kwargs):
        print('A new', '-' * 30)
        print(args)
        print(kwargs)
        return super().__new__(cls)

    def __init__(self, name, age):
        print('A init', '#' * 30)
        self.name = name
        self.age = age


a = A('Tom', age=12)
print(a)

#结果
A new ------------------------------
('Tom',)
{'age': 12}
A init ##############################
<__main__.A object at 0x0000022028821240>

__new__方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)基类object的__new__方法来创建实例并返回。

return super().__new__(cls)很重要,将类本身传给父类的__new__方法处理,由父类来调用__init__

可视化

方法 意义
__str__ str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。
如果没有定义,就去调用__repr__方法返回字符串表达,如果__repr__没有定义,就直接返回对象的内存地址信息
__repr__ 内建函数repr()对一个对象获取字符串表达。调用__repr__方法返回字符串表达
如果__repr__也没有定义,就直接返回object的定义就是显示内存地址信息
__bytes__ bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
class A:

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

    def __str__(self):
        return '调用str'
    def __repr__(self):
        return '调用repr'

    def __bytes__(self):
        return str(self).encode()

a = A('Tom', age=12)
print(a)
print('='*30)
print(str(a))
print('~'*30)
print([a],(a,),{a})
print(a,[str(a)],[a])
print(bytes(a))

#结果
调用str
==============================
调用str
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[调用repr] (调用repr,) {调用repr}
调用str ['调用str'] [调用repr]
b'\xe8\xb0\x83\xe7\x94\xa8str'

上例也可以看出

​ 直接调用对象,如print(a)或者print(str(a))都是调用__str__方法

​ 间接调用对象,如print([a],(a,),{a})是调用的__repr__方法

​ 如果__str__没有定义,__repr__定义了,则无论直接调用还是间接调用,都是调用__repr__方法

​ 如果__repr____str__都没有定义,则返回对象的地址

hash

方法 意义
__hash__ 内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
__eq__ 对应==操作符,判断2个对象内容是否相等,返回bool值
定义了这个方法,如果不提供__hash__方法,那么实例将不可hash了
class A:

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

    def __repr__(self):
        return self.name

    def __hash__(self):
        return 100

    def __eq__(self, other):
        return True


a = A('Tom')
b = A('Tom')
c = A('rose')
print(a == b)
print(a == c)
print(1, hash(a), id(a))
print(2, hash(b), id(b))
print(3, (a, b, c))
print(4, [a, b, c])
print(5, {a, b, c})

#结果
True
True
1 100 2223860224464
2 100 2223860224184
3 (Tom, Tom, rose)
4 [Tom, Tom, rose]
5 {Tom}

因为上例的__hash____eq__返回设置的是固定值,所以,当出现比较的时候,所有的都是一样的,而集合先调用__hash__,结果是一样的,然后需要去重,这个时候就要调用__eq__方法,结果返回的都是Ture,所以所有的结果都是一样的

修改

class A:

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

    def __repr__(self):
        return self.name

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        return self.name==other.name


a = A('Tom')
b = A('Tom')
c = A('rose')
print(a == b)
print(a is b)
print(1, hash(a), id(a))
print(2, hash(b), id(b))
print(3, (a, b, c))
print(4, [a, b, c])
print(5, {a, b, c})

#结果
True
False
1 5640338519104148085 1394922099264
2 5640338519104148085 1394922099320
3 (Tom, Tom, rose)
4 [Tom, Tom, rose]
5 {Tom, rose}

可以看出来,hash()调用的是__hash__方法

去重的时候调用的是__eq__方法

__hash__方法只是返回一个hash值作为set的key,但是去重,还需要__eq__来判断2个对象是否相等。

hash值相等,只是hash冲突,不能说明两个对象是相等的。

因此,一般来说提供__hash__方法是为了作为set或者dict的key,如果去重要同时提供__eq__方法。

不可hash对象isinstance(p1, collections.Hashable)一定为False。

去重需要提供__eq__方法

字典,会先调用__hash__如果__hash__相同,再调用__eq__

bool

方法 意义
__bool__ 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。
没有定义__bool__(),就找__len__()返回长度,非0为真。
如果__len__()也没有定义,那么所有实例都返回真
class A:
    def __init__(self, name):
        self.name = name

    def __bool__(self):
        return True


a = A(1)
b = A(0)
print(bool(a))
print(bool(1))
print(bool(b))
print(bool(0))

#结果
True
True
True
False
class A(list):
    pass


a = A()
a.append(1)
b = A()
print(a)
print(bool(a))
print(b)
print(bool(b))

print('-' * 30)

#结果
[1]
True
[]
False
------------------------------


class B(list):
    def __len__(self):
        return 0


c = B()
c.append(1)
d = B()
print(c)
print(bool(c))
print(d)
print(bool(d))


#结果
[1]
False
[]
False
class A(list):
    def __len__(self):
        return 1


a = A()
a.append(1)
print(a)
if a:
    print('a不为空')
if A():
    print('A()不为空')
    
#结果
[1]
a不为空
A()不为空

上例可以看出,if 判断也是先调用__bool__,__bool__方法未定义就调用__len__方法,上例中__len__方法返回的是非0数,无论是否真为空,一直为真,所以if语句一直执行

运算符重载

operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作

运算符 特殊方法 含义
<, <=, ==, >,>=, != __lt__, __le__, __eq__, __gt__, __ge__,__ne__ 比较运算符
+, -, *, /, %,//, **,divmod(求商和余数) __add__, __sub__, __mul__, __truediv__,__mod__, __floordiv__, __pow__, __divmod__ 算数运算符,移位、位运算也有对应的方法
+=, -=, *=,/=, %=, //=,**= __iadd__, __isub__, __imul__, __itruediv__,__imod__, __ifloordiv__, __ipow__ 就地修改
class A:
    def __init__(self, name, age):
        self.name = name
        self.age = age


a = A('a', 20)
b = A('b', 10)
print(a - b)

#结果
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 9, in <module>
    print(a - b)
TypeError: unsupported operand type(s) for -: 'A' and 'A'

上例中,实例无法相减,提示没有相减的方法

修改——————》添加减方法

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

    def __sub__(self, other):
        print('sub——————')
        return self.age - other.age


a = A('a', 20)
b = A('b', 10)
print(a - b)

#结果
sub——————
10

如果是就地修改

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

    def __sub__(self, other):
        print('sub——————')
        return self.age - other.age


a = A('a', 20)
b = A('b', 10)
print(a - b)
a-=b
print(a,type(a))

#结果
sub——————
10
sub——————
10 <class 'int'>

也是调用的__sub__方法,但是因为就地修改,会出现直接把对象修改成int类型,所以需要重载__isub__方法

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

    def __sub__(self, other):
        print('sub——————')
        return self.age - other.age

    def __isub__(self, other):
        print('isub——————')
        self.age -= other.age
        return self

    def __repr__(self):
        return '< {}.{} {}>'.format(__class__.__name__, self.name, self.age)


a = A('a', 20)
b = A('b', 10)
print(a,b)
a -= b
print(a, type(a))

#结果
< A.a 20> < A.b 10>
isub——————
< A.a 10> <class '__main__.A'>

__isub__方法定义,一般会in-place就地来修改自身

如果没有定义__isub__方法,则会调用__sub__


练习

完成Point类设计,实现判断点相等的方法,并完成向量的加法在直角坐标系里面,定义原点为向量的起点。两个向量和与差的坐标分别等于这两个向量相应坐标的和

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __add__(self, other):
        return (self.x + other.x, self.y + other.y)

    def __iadd__(self, other):
        self.x += other.x
        self.y += other.y
        return self

    def __repr__(self):
        return '< {}->({},{})>'.format(__class__.__name__, self.x, self.y)


a = Point(2, 3)
c = Point(2, 3)
b = Point(3, 4)
print(a, b)
print(a == b)
print(a == c)
print(a + b)
print(a)
a += b
print(a)

#结果
< Point->(2,3)> < Point->(3,4)>
False
True
(5, 7)
< Point->(2,3)>
< Point->(5,7)>

__eq__等于可以推断不等于

__gt__大于可以推断小于

__ge__大于等于可以推断小于等于

也就是用3个方法,就可以把所有比较解决了

容器相关方法

方法 意义
__len__ 内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或者dict。
bool()函数调用的时候,如果没有__bool__()方法,则会看__len__()方法是否存在,存在返回非0为真
__iter__ 迭代容器时,调用,返回一个新的迭代器对象
__contains__ in 成员运算符,没有实现,就调用__iter__方法遍历
__getitem__ 实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为hashable。key不存在引发KeyError异常
__setitem__ __getitem__的访问类似,是设置值的方法
__missing__ 字典或其子类使用__getitem__()调用时,key不存在执行该方法
class A:
    def __init__(self):
        self.__iterm = []

    # 格式化打印
    def __repr__(self):
        return str(self.__iterm)

    # 实现添加元素
    def add_iter(self, key):
        return self + key

    # 对+号进行重载
    def __add__(self, other):
        self.__iterm.append(other)
        return self

    # 对-号进行重载
    def __sub__(self, other):
        self.__iterm.remove(other)

    # 通过索引获取元素
    def __getitem__(self, item):
        return self.__iterm[item]

    # 通过索引进行修改元素
    def __setitem__(self, key, value):
        self.__iterm[key] = value
        return self

    #重载生成迭代器
    def __iter__(self):
        print('iter----')
        # return iter(self.__iterm)
        yield from self.__iterm


a = A()
print(a)
a.add_iter(1).add_iter(2)
a + 3 + 4 + 'asd'
a - 4
print(a)
print(a[1])
a[2] = 'wwww'
print(a)
print('----------------')
for i in a:
    print(i)
    

#结果
[]
[1, 2, 3, 'asd']
2
[1, 2, 'wwww', 'asd']
----------------
iter----
1
2
wwww
asd

可调用对象

Python中一切皆对象,函数也不例外

def foo():
    print(foo.__module__, foo.__name__)


foo()  # 等价于
foo.__call__()

#结果
__main__ foo
__main__ foo

函数即对象,对象foo加上(),就是调用此函数对象的__call__()方法

方法 意义
__call__ 类中定义一个该方法,实例就可以像函数一样调用

可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。

class A:
    def __init__(self):
        self.ans = 0

    def __call__(self, *args):
        self.ans = sum(args)
        return self.ans


a = A()
print(a(1, 2, 3, 4))

#结果
10

练习

定义一个斐波那契数列的类,方便调用,计算第n项。

增加迭代数列的方法、返回数列长度、支持索引查找数列项的方法

class Fib:
    #初始化,准备一个容器存储计算的数据
    def __init__(self):
        self.item = [0, 1]

	#只计算容器中没有的数据,然后从容器中返回对应项
    def __getitem__(self, n):
        if n < 0:
            raise KeyError
        for i in range(len(self.item), n + 1):
            self.item.append(self.item[i - 1] + self.item[i - 2])
        return self.item[n]
	#因为__call__方法和__getitem__方法一致
    __call__=__getitem__
    def __len__(self):
        return len(self.item)

    def __iter__(self):
        yield from self.item


f = Fib()
print(f(10))
for i in f:
    print(i, '++')
print(f[5])
print(len(f))

#结果
55
0 ++
1 ++
1 ++
2 ++
3 ++
5 ++
8 ++
13 ++
21 ++
34 ++
55 ++
5
11

上下文管理

文件IO操作可以对文件对象使用上下文管理,使用with...as语法。

仿照上例写一个自己的类,实现上下文管理

class A:
    def __init__(self):
        print('1 init------------------')

    def __enter__(self):
        print('2 enter--------------')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('5 exit------------------')


with A() as f:
    print('3 开始--------------')
    print('4 结束--------------')

print('6 end---------------')


#结果
1 init------------------
2 enter--------------
3 开始--------------
4 结束--------------
5 exit------------------
6 end---------------

加入中断

class A:
    def __init__(self):
        print('1 init------------------')

    def __enter__(self):
        print('2 enter--------------')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('5 exit------------------')


with A() as f:
    print('3 开始--------------')
    exit(100)
    print('4 结束--------------')

print('6 end---------------')

#结果
1 init------------------
2 enter--------------
3 开始--------------
5 exit------------------

当一个对象同时实现了__enter__()__exit__()方法,它就属于上下文管理的对象

方法 意义
__enter__ 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象相关的上下文

实例化对象的时候,并不会调用enter,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。

with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。

注意,with并不开启一个新的作用域

with语句

class A:
    def __init__(self):
        print('1 init------------------')

    def __enter__(self):
        print('2 enter--------------')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('5 exit------------------')


a = A()
with a as f:
    print('a=',a)
    print('f=',f)
    print(a is f, '+++')
    print(a == f)

print('6 end---------------')

#结果
1 init------------------
2 enter--------------
a= <__main__.A object at 0x0000025801751208>
f= None
False +++
False
5 exit------------------
6 end---------------

上面可以看出a是对象的地址,但是f却是None

是因为,f是__enter__方法上的返回值

因为没有返回值,所以为None

__enter__中添加return self就可以了

with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋给as子句的变量。

上例,可以等价为f = a.__enter__()

方法的参数

__enter__方法没有其他参数。

__exit__方法有3个参数:

__exit__(self, exc_type, exc_value, exc_tb)

这三个参数都与异常有关。

如果该上下文退出时没有异常,这3个参数都为None

如果有异常,参数意义如下

exc_type,异常类型

exc_value,异常的值

exc_tb,异常的追踪信息

__exit__方法返回一个等效True的值,则压制异常;否则,继续抛出异常

class A:
    def __init__(self):
        print('1 init------------------')

    def __enter__(self):
        print('2 enter--------------')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('5 exit------------------')
        print('异常类型', exc_type)
        print('异常值', exc_val)
        print('异常的追踪', exc_tb)


a = A()
with a as f:
    print('#########')
    1 / 0

print('6 end---------------')

#结果
1 init------------------
2 enter--------------
#########
5 exit------------------
异常类型 <class 'ZeroDivisionError'>
异常值 division by zero
异常的追踪 <traceback object at 0x0000017BE50C52C8>
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 18, in <module>
    1/0
ZeroDivisionError: division by zero

练习

为加法函数计时方法

1、使用装饰器显示该函数的执行时长方法

2、使用上下文管理方法来显示该函数的执行时长

装饰器解决

import datetime, time
from functools import wraps

def get_time(fn):
    @wraps(fn)
    def wrapper(*args,**kwargs):
        star = datetime.datetime.now()
        c = fn(*args,**kwargs)
        t = datetime.datetime.now() - star
        print(t)
        return c

    return wrapper


@get_time
def add(a, b):
    time.sleep(2)
    return a + b


print(add(1, 2))

#结果
0:00:02.004639
3

用上下文管理来解决

import datetime, time


def add(a, b):
    time.sleep(2)
    return a + b


class Get_time:
    def __init__(self, fn):
        self.fn = fn

    def __enter__(self):
        self.star = datetime.datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        t = datetime.datetime.now() - self.star
        print('耗时', t)

    def __call__(self, *args):
        return self.fn(*args)


with Get_time(add) as f:
    print(f(1, 2))

用类作为装饰器

import datetime, time

class Get_time:
    """
    这里是Get_tim
    """
    def __init__(self, fn):
        self.fn = fn
        self.__doc__=self.fn.__doc__
        self.__name__=self.fn.__name__

    def __call__(self, *args):
        stat=datetime.datetime.now()
        ans=self.fn(*args)
        t=datetime.datetime.now()-stat
        print('耗时', t)
        return ans


@Get_time
def add(a, b):
    """
    这里是add
    """
    time.sleep(2)
    return a + b


print(add(1, 2))
print(add.__doc__)
print(add.__name__)

#结果
耗时 0:00:02.012385
3

    这里是add
    
add

使用functools中的update_wrapper,wraps

import datetime, time
from functools import update_wrapper, wraps


class Get_time:
    """
    这里是Get_tim
    """

    def __init__(self, fn):
        self.fn = fn
        # update_wrapper(self, fn)
        wraps(fn)(self)

    def __call__(self, *args):
        stat = datetime.datetime.now()
        ans = self.fn(*args)
        t = datetime.datetime.now() - stat
        print('耗时', t)
        return ans


@Get_time
def add(a, b):
    """
    这里是add
    """
    time.sleep(2)
    return a + b


print(add(1, 2))
print(add.__doc__)
print(add.__name__)

#结果
耗时 0:00:02.004670
3

    这里是add
    
add

update_wrapper(self, fn) 是先写装饰器,再写被装饰函数

wraps(fn)(self) 是先写被装饰函数,再写装饰器

上下文应用场景

  1. 增强功能

    ​ 在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。

  2. 资源管理

    ​ 打开了资源需要关闭,例如文件对象、网络连接、数据库连接等

  3. 权限验证

    ​ 在执行代码之前,做权限的验证,在__enter__中处理

contextlib.contextmanager

contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter____exit__方法。

对下面的函数有要求:必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。

也就是这个装饰器接收一个生成器对象作为参数

from contextlib import contextmanager


@contextmanager
def A():
    print('1 enter-------------')
    try:
        yield 111111
        print('4 +++++++++++++++')
    finally:
        print('5 exit------------------')


with A() as f:
    print('2 star----------------')
    print('3 f=',f)
    
#结果
1 enter-------------
2 star----------------
3 f= 111111
4 +++++++++++++++
5 exit------------------

f接收yield语句的返回值

如果下面with语句中有异常,是不会执行print('5 exit------------------')

所以,需要利用try............finally..............来实现__exit__的功能


再写对add语句计时

from contextlib import contextmanager
from datetime import datetime
from time import sleep


@contextmanager
def A():
    star = datetime.now()
    try:
        yield
        print('4 +++++++++++++++')
    finally:
        print(5, datetime.now() - star)


def add(x, y):
    print('2 add--------')
    sleep(2)
    return x + y


with A() as f:
    print('1 star----------------')
    print(3, add(1, 2))

    
#结果
1 star----------------
2 add--------
3 3
4 +++++++++++++++
5 0:00:02.002123

或者,通过传参的方式实现

from contextlib import contextmanager
from datetime import datetime
from time import sleep


@contextmanager
def A(fn):
    star = datetime.now()
    try:
        yield fn
        print('4 +++++++++++++++')
    finally:
        print(5, datetime.now() - star)


def add(x, y):
    print('2 add--------')
    sleep(2)
    return x + y


with A(add) as f:
    print('1 star----------------')
    print(3, f(1,2))
    
#结果
1 star----------------
2 add--------
3 3
4 +++++++++++++++
5 0:00:02.000568

当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。

​ 把yield之前的当做__enter__方法执行

​ 把yield之后的当做__exit__方法执行

​ 把yield的值作为__enter__的返回值

总结

如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter____exit__方法方便

反射

反射,reflection,指的是运行时获取类型定义信息。

一个对象能够在运行时,像照镜子一样,反射出其类型信息。

简单说,在Python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或者自省。

具有反射能力的函数有 type()isinstance()callable()dir()getattr()

反射相关的函数和方法

内建函数 意义
getattr(object,name[, default]) 通过name返回object的属性值。
当属性不存在,将使用default返回
如果没有default,则抛出AttributeError
name必须为字符串
setattr(object,name, value) object的属性存在,则覆盖,不存在,新增
hasattr(object,name) 判断对象是否有这个名字的属性,name必须为字符串
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print('<x={} y={}>'.format(self.x, self.y))

a=A(3,4)
print(getattr(a, 'x'))
print(getattr(a, 'z','没有z'))
print(getattr(a,'__dict__'))
setattr(a,'y',10)
setattr(a,'z',100)
print(getattr(a,'__dict__'))
print(hasattr(a, 'show'))
getattr(a,'show','没有show属性')()

#结果
3
没有z
{'x': 3, 'y': 4}
{'x': 3, 'y': 10, 'z': 100}
True
<x=3 y=10>

使用反射为对象添加方法

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return '< A-->({},{}) >'.format(self.x, self.y)


a = A(3, 4)
b = A(5, 6)

#对类添加方法
if not hasattr(A, 'add'):
    setattr(A, 'add', lambda self, other: A(self.x + other.x, self.y + other.y))

print(a.add(b))

#对实例添加方法
if not hasattr(a, 'sub'):
    setattr(a, 'sub', lambda m, n: A(m.x - n.x, m.y - n.y))
print(a.sub(a, b))

#结果
< A-->(8,10) >
< A-->(-2,-2) >
class C:
    def fn(self,a):
        if a:
            print('None是非空')
        else:
            print('None是空')

getattr(C(), "fn")(None)

反射相关的魔术方法

__getattr__()__setattr__()__delattr__()这三个魔术方法,分别测试这三个方法

__getattr__()

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        print('__getattr__++++++++++++', item)
        return item

    def __repr__(self):
        return '< A-->({},{}) >'.format(self.x, self.y)


a = A(3, 4)
#无论是用.获取__dict___中的属性,还是用getattr获取不存在的属性,都是在调用__getattr__方法
print(a.w)
print(getattr(a, 'z', 123))

#结果
__getattr__++++++++++++ w
w
__getattr__++++++++++++ z
z

无论是用.获取__dict__中的属性,还是用getattr获取不存在的属性,都是在调用__getattr__方法

查找属性顺序为:instance.__dict__ --> instance.__class__.__dict__ --> 继承的祖先类(直到object)的__dict__ ---找不到--> 调用__getattr__()

__setattr__()

class B:
    n=222

class A(B):
    m=111
    def __init__(self, x, y):
        print('init-------------star')
        self.x = x
        self.y = y
        print('init-------------end')

    def __setattr__(self, key, value):
        print(f'__setattr__----------{key}:{value}')

    def __getattr__(self, item):
        print('没有{}属性!'.format(item))

    def __repr__(self):
        return '< A-->({},{}) >'.format(self.x, self.y)


a = A(3, 4)
print('*'*30)
print(a.__dict__)
print(1,a.x)
print(2,a.y)
print(3,a.m)
print(4,a.n)

#结果
init-------------star
__setattr__----------x:3
__setattr__----------y:4
init-------------end
******************************
{}
没有x属性!
1 None
没有y属性!
2 None
3 111
4 222

上例可以看出,创建实例,调用初始化方法__init__时,会调用__setattr__方法,当__setattr__方法没有对属性进行操作,a.__dict__字典为空,也就无法查到实例的属性,只能查到继承自类的属性

修改
class B:
    n = 222


class A(B):
    m = 111

    def __init__(self, x, y):
        print('init-------------star')
        self.x = x
        self.y = y
        print('init-------------end')

    def __setattr__(self, key, value):
        print(f'__setattr__----------{key}:{value}')
        self.__dict__[key] = value
        # super().__setattr__(key,value)

    def __getattr__(self, item):
        print('没有{}属性!'.format(item))

    def __repr__(self):
        return '< A-->({},{}) >'.format(self.x, self.y)


a = A(3, 4)
print('*' * 30)
print(a.__dict__)
print(1, a.x)
print(2, a.y)
print(3, a.m)
print(4, a.n)
print('='*30)
setattr(a,'z',1000)
print(a.__dict__)


#结果
init-------------star
__setattr__----------x:3
__setattr__----------y:4
init-------------end
******************************
{'x': 3, 'y': 4}
1 3
2 4
3 111
4 222
==============================
__setattr__----------z:1000
{'x': 3, 'y': 4, 'z': 1000}

从上例可以看出
__setattr__方法可以写成
直接给__dict__注入kv对:self.__dict__[key] = value
或者,交给父类处理super().__setattr__(key,value)
setattr()内建函数也是调用的__setattr__方法

__delattr__()

class A:
    m = 111
    n = 222

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __delattr__(self, item):
        print('删除属性', item)


a = A(3, 4)
print('*' * 30)
del A.m
print('+' * 30)
del a.n
del a.x

#结果
******************************
++++++++++++++++++++++++++++++
删除属性 n
删除属性 x

可以看出,只有删除实例的属性时才会调用__delattr__方法
可以阻止通过实例来删除属性的操作。但是通过类依然可以删除类属性。

__getattribute__

class A:
    m = 111
    n = 222

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattribute__(self, item):
        print('__getattribute__-----------', item)
        return '__getattribute__******'


a = A(3, 4)
print(a.x)
print(a.__dict__)
print('=' * 30)
print(object.__getattribute__(a, '__dict__'))

#结果
__getattribute__----------- x
__getattribute__******
__getattribute__----------- __dict__
__getattribute__******
==============================
{'x': 3, 'y': 4}

可以看出,实例对象的字典时存在的,但是因为定义了__getattribute__方法,被__getattribute__完全拦截了,导致任何属性返回的都是__getattribute__方法返回的结果
实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常
它的return值将作为属性查找的结果。
如果抛出AttributeError异常,则会直接调用__getattr__方法,因为表示属性没有找到

可以改写成

class A:
    m = 111
    n = 222

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattribute__(self, item):
        print('__getattribute__-----------', item)
        # return object.__getattribute__(self,item)
        return super().__getattribute__(item)

a = A(3, 4)
print(a.x)
print(a.__dict__)
print('=' * 30)
print(object.__getattribute__(a, '__dict__'))
#结果
__getattribute__----------- x
3
__getattribute__----------- __dict__
{'x': 3, 'y': 4}
==============================
{'x': 3, 'y': 4}

__getattribute__方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.__getattribute__(self, name)
注意,除非你明确地知道__getattribute__方法用来做什么,否则不要使用它

总结

魔术方法 意义
__getattr__() 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法
__setattr__() 通过.访问实例属性,进行增加、修改都要调用它
__delattr__() 当通过实例来删除属性时调用此方法
__getattribute__ 实例所有的属性调用都从这个方法开始

属性查找顺序

实例调用__getattribute__()--->instance.__dict__---> instance.__class__.__dict__---> 继承的祖先类(直到object)的__dict__---> 调用__getattr__()

描述器 Descriptors

描述器的表现

用到3个魔术方法:__get__()__set__()__delete__()

方法签名如下:

object.__get__(self, instance, owner)

object.__set__(self, instance, value)

object.__delete__(self, instance)

self 指代当前实例,调用者

instanceowner的实例

owner 是属性的所属的类

class A:

    def __init__(self):
        print('A init----------------')
        self.x = 'x1'

    def __get__(self, instance, owner):
        print('get-------', self, instance, owner)
        return self

    def __repr__(self):
        return '< A 的实例 >'


class B:
    y = A()

    def __init__(self):
        print('B init ----------------')
        self.z = A()

    def __repr__(self):
        return '< B 的实例 >'


print('-' * 30)
print(B.y)
print(B.y.x)
print('=' * 30)
b = B()
print(b.y)
print(b.z)


#结果
A init----------------
------------------------------
get------- < A 的实例 > None <class '__main__.B'>
< A 的实例 >
get------- < A 的实例 > None <class '__main__.B'>
x1
==============================
B init ----------------
A init----------------
get------- < A 的实例 > < B 的实例 > <class '__main__.B'>
< A 的实例 >
< A 的实例 >

可以看出,只有作为类属性时,才会调用__get__方法,作为实例的属性,不会调用
__get__方法是当其他实例调用本实例时调用的

描述器定义

Python中,一个类实现了__get____set____delete__三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
仅实现了__get__,就是非数据描述符 non-data descriptor
实现了__get____set__就是数据描述符 data descriptor
如果一个类的类属性设置为描述器实例,那么它被称为owner属主。
当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法

仅实现__get__

class A:

    def __init__(self):
        print('A init----------------')
        self.x = 'x1'

    def __get__(self, instance, owner):
        print('get-------', self, instance, owner)
        return self

    def __repr__(self):
        return '< A 的实例 >'


class B:
    y = A()

    def __init__(self):
        print('B init ----------------')
        self.y = 'abc'
        print('+++')

    def __repr__(self):
        return '< B 的实例 >'


print('=' * 30)
b = B()
print(b.y)

#结果
A init----------------
==============================
B init ----------------
+++
abc

b.y访问的是初始化中的实例属性y

是非数据描述符

同时实现了__get____set__

class A:

    def __init__(self):
        print('A init----------------')
        self.x = 'x1'

    def __get__(self, instance, owner):
        print('get-------', self, instance, owner)
        return self

    def __set__(self, instance, value):
        print('set-------', self, instance, value)
        self.data=value

    def __repr__(self):
        return '< A 的实例 >'


class B:
    y = A()

    def __init__(self):
        print('B init ----------------')
        self.y = 'abc'
        print('+++++++++++++++++')

    def __repr__(self):
        return '< B 的实例 >'


print('=' * 30)
b = B()
print(b.y)
print('*'*30)
print(b.y.data)

#结果
A init----------------
==============================
B init ----------------
set------- < A 的实例 > < B 的实例 > abc
+++++++++++++++++
get------- < A 的实例 > < B 的实例 > <class '__main__.B'>
< A 的实例 >
******************************
get------- < A 的实例 > < B 的实例 > <class '__main__.B'>
abc

执行顺序是

因为B类属性中有y = A(),所以先执行A实例的初始化

再执行B实例对象的初始化,由于A类中同时实现了__get____set__,B实例对象的__init__中又出现了B类属性相同的属性y,所以,成为了数据描述符,需要要调用__set__

第三步执行A类中的 __set__

第四步,b.y,调用的是A类的__get__方法,返回值是A的实例对象

要想获取B实例属性的y,可以在A类中的 __set__中进行存储,然后通过b.y.data调用

属性查找的优先级

数据描述器>__dict__>非数据描述器

Python中的描述器

Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。

property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为

仿写类方法classmethod()和静态方法staticmethod()

from functools import partial
class Classmethod:
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, instance, owner):
        print('C-get-----',self, instance, owner)
        return partial(self.fn,owner)

    def __repr__(self):
        return ('<Classmethod------>')

class Statistics:
    def __init__(self,fn):
        self.fn=fn

    def __get__(self, instance, owner):
        print('S-get----------',self,instance,owner)
        return self.fn
class A:
    @Classmethod
    def test(cls,a):
        print('test------',cls,a)

    @Statistics
    def foo():
        print('foo---------')

A.test(1)
A().test(1)
A.foo()
A().foo()

#结果
C-get----- <Classmethod------> None <class '__main__.A'>
test------ <class '__main__.A'> 1
C-get----- <Classmethod------> <__main__.A object at 0x000001964D3612E8> <class '__main__.A'>
test------ <class '__main__.A'> 1
S-get---------- <__main__.Statistics object at 0x000001964D3612B0> None <class '__main__.A'>
foo---------
S-get---------- <__main__.Statistics object at 0x000001964D3612B0> <__main__.A object at 0x000001964D3616D8> <class '__main__.A'>
foo---------

练习

对数据类型进行校验

class Type_chack:
    def __init__(self, typ):
        self.typ = typ

    def __get__(self, instance, owner):
         if instance:
            return instance.__dict__[self.pname]
        else:
            raise TypeError('不可用类直接访问!')

    def __set__(self, instance, value):
        if instance and isinstance(value, self.typ):
            instance.__dict__[self.pname] = value    #符合条件,就把对应属性的对应值塞到对象的字典中
        else:
            raise TypeError

    def __set_name__(self, owner, name):   #获取属性名字
        self.pname = name


class A:
    name = Type_chack(str)
    age = Type_chack(int)

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


a = A('a', 12)
print(a.age)
print(a.__dict__)
print('=' * 30)
b = A('a', '12')

#结果
12
{'name': 'a', 'age': 12}
==============================
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 31, in <module>
    b = A('a', '12')
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 24, in __init__
    self.age = age
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 12, in __set__
    raise TypeError
TypeError

利用inspect做装饰器来生成前期的对象

import inspect


class Type_chack:
    def __init__(self, name, typ):
        self.typ = typ
        self.pname = name

    def __get__(self, instance, owner):
        return instance.__dict__[self.pname]

    def __set__(self, instance, value):
        if instance and isinstance(value, self.typ):
            instance.__dict__[self.pname] = value
        else:
            raise TypeError


def creat_object(cls):
    dict_list = inspect.signature(cls).parameters
    for k, v in dict_list.items():
        if v.annotation is not v.empty:
            setattr(cls, k, Type_chack(k, v.annotation))
    return cls


@creat_object
class A: #A=creat_object(A)
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


print('----------')
a = A('a', 15)
print(a.age)
print(a.__dict__)
print('=' * 30)
b = A('a', '16')
print(b, '~~~~~~~~~~~~')

#结果
----------
15
{'name': 'a', 'age': 15}
==============================
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 39, in <module>
    b = A('a', '16')
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 31, in __init__
    self.age = age
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 16, in __set__
    raise TypeError
TypeError

用类来做装饰器

import inspect


class Type_chack:
    def __init__(self, name, typ):
        self.typ = typ
        self.pname = name

    def __get__(self, instance, owner):
        return instance.__dict__[self.pname]

    def __set__(self, instance, value):
        if instance and isinstance(value, self.typ):
            instance.__dict__[self.pname] = value
        else:
            raise TypeError


# def creat_object(cls):
#     dict_list = inspect.signature(cls).parameters
#     for k, v in dict_list.items():
#         if v.annotation is not v.empty:
#             setattr(cls, k, Type_chack(k, v.annotation))
#     return cls
class creat_object:
    def __init__(self, fn):
        self.fn = fn
        dict_list = inspect.signature(self.fn).parameters
        for k, v in dict_list.items():
            if v.annotation is not v.empty:
                setattr(self.fn, k, Type_chack(k, v.annotation))

    def __call__(self, *args):
        return self.fn(*args)


@creat_object
class A:  # A=creat_object(A)
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


print('----------')
a = A('a', 15)
print(a)
print(a.age)
print(a.__dict__)
print('=' * 30)
b = A('a', '16')
print(b, '~~~~~~~~~~~~')

#结果
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 50, in <module>
    b = A('a', '16')
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 34, in __call__
    return self.fn(*args)
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 41, in __init__
    self.age = age
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 16, in __set__
    raise TypeError
TypeError
----------
<__main__.A object at 0x0000025F0F5612B0>
15
{'name': 'a', 'age': 15}
==============================

综合,写出双向链表

具有+、-、pop、insert、正反向打印、len、iter

class Data_lib:
    def __init__(self, item, prev=None, next=None):
        self.prev = prev
        self.item = item
        self.next = next

    def __repr__(self):
        return '{:<4}<=={:^4}==>{:>4}'.format(self.prev.item if self.prev else 'None', self.item,
                                              self.next.item if self.next else 'None')


class Link_list:

    def __init__(self):
        self.head = None
        self.tail = None
        self.up = None
        self.__size = 0

    def __add__(self, key):
        d = Data_lib(key)
        if self.head is None:
            self.head = d
        else:
            self.up = self.tail
            d.prev = self.up
            self.tail.next = d
        self.tail = d
        self.__size += 1
        return self

    def insert(self, index, value):
        if index < -len(self):
            index = 0
        if index > len(self):
            self + value
            return self
        elif index >= 0:
            v = self[index]
            d = Data_lib(value, v.prev, v)
            if v.prev == None:
                self.head = d
            else:
                v.prev.next = d
            v.prev = d
            self.__size += 0
            return self
        back = self[index]
        d = Data_lib(value, back, back.next)
        if back.next == None:
            self.tail = d
        else:
            back.next.prev = d
        back.next = d
        self.__size += 1
        return self

    def __sub__(self, other):
        for v in self:
            if other == v.item:
                if v.next == None:
                    if v.prev == None:
                        self.tail = None
                        self.head = None
                        return self
                    else:
                        self.tail = v.prev
                else:
                    v.next.prev = v.prev
                if v.prev == None:
                    self.head = v.next
                else:
                    v.prev.next = v.next
                self.__size -= 1
                return self
        else:
            raise ValueError('没有 {}'.format(other))

    def pop(self, index=-1):
        back=self[index]
        if back.next == None:
            self.tail = back.prev
        else:
            back.next.prev = back.prev
        if back.prev == None:
            self.head = back.next
        else:
            back.prev.next = back.next
        self.__size -= 1
        return back.item


    def __getitem__(self, item):
        if item >= 0 and self.head != None:
            for i, v in enumerate(self):
                if i == item:
                    return v
            else:
                raise IndexError('超界!')
        elif item < 0 and self.head != None:
            back = self.tail
            a = abs(item)
            i = 1
            while True:
                if i == a:
                    return back
                else:
                    i += 1
                    back = back.prev
                    if back == None:
                        raise IndexError('没有索引 {}'.format(item))
        else:
            raise IndexError('超界!')

    def __setitem__(self, key, value):
        self[key].item = value

    def __len__(self):
        return self.__size

    def iter_node(self, reverce=False):
        go = self.head if not reverce else self.tail
        while go:
            yield go
            go = go.next if not reverce else go.prev

    __iter__ = iter_node


l = Link_list()


def print_list(reverce=False):
    if l.head == None:
        print('head=', l.head)
        print('tail=', l.tail)
    else:
        print('head=', l.head.item)
        for i in l.iter_node(reverce):
            print(i)
        print('tail=', l.tail.item)
    print('#' * 30)


l + 5 + 6 + 7 + 8
print_list()
print(l[3])
l[-4] = 100
print_list()
print(len(l))
l.insert(5, 1000)
print_list()
print_list(True)
print(l.pop())
print_list()

#结果
head= 5
None<== 5  ==>   6
5   <== 6  ==>   7
6   <== 7  ==>   8
7   <== 8  ==>None
tail= 8
##############################
7   <== 8  ==>None
head= 100
None<==100 ==>   6
100 <== 6  ==>   7
6   <== 7  ==>   8
7   <== 8  ==>None
tail= 8
##############################
4
head= 100
None<==100 ==>   6
100 <== 6  ==>   7
6   <== 7  ==>   8
7   <== 8  ==>1000
8   <==1000==>None
tail= 1000
##############################
head= 100
8   <==1000==>None
7   <== 8  ==>1000
6   <== 7  ==>   8
100 <== 6  ==>   7
None<==100 ==>   6
tail= 1000
##############################
1000
head= 100
None<==100 ==>   6
100 <== 6  ==>   7
6   <== 7  ==>   8
7   <== 8  ==>None
tail= 8
##############################

仿写property装饰器

class Property:
    def __init__(self, fget, fset=None):
        # print('Property-----init')
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        # print('get--')
        if instance:
            return self.fget(instance)
        else:
            raise AttributeError('不能修改!')

    def __set__(self, instance, value):
        # print(instance, value, 'set----', self.fset)
        if instance:
            self.fset(instance, value)
        else:
            raise AttributeError('只能用对象访问!')

    def setter(self, fn):
        self.fset = fn
        return self


class A:
    def __init__(self, x):
        self.__x = x

    @Property
    def x(self):  # x=Property(x)
        return self.__x

    @x.setter
    def x(self, value):
        self.__x = value


a = A(12)
print(a.x)  # 12
a.x = 16
print(a.x)

#结果
12
16

未实现和未实现异常

NotImplemented是个值,单值,是NotImplementedType实例,必须通过return返回,类似return None,这个表示功能未实现(无能为力)!

NotImplementedError 是类型,是异常类,返回type,是通过raise来弹出

运算符重载中的反向方法

运算符重载的方法,例如__add____iadd__,这里还有个__radd__方法

例如1+'a',前面的1是int类型,当加上字符串类型时,无法解决,返回NotImplementedType,就会调用字符串的__radd__方法

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

    def __add__(self, other):
        print('A add------')

    def __radd__(self, other):
        print('a radd----')


class B:
    def __init__(self, x):
        self.x = x
    def __add__(self, other):
        print('b add ----')
        return NotImplemented

a = A(2)
b = B(2)
b + a

#结果
b add ----
a radd----

上例中

b+a执行顺序

先在b实例中找__add__方法,看能否执行b.__add__(a),然而,返回了NotImplementedType,b表示未实现功能

接着转而问a实例有没有__radd__方法,实例a有__radd__方法

转而调用a的__radd__,执行a.__radd__(b)

如果依然无法解决,就会报TypeError,没有相加的功能

实现能自由度很高的加法

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

    def __add__(self, other):
        if isinstance(other, type(self)):
            return self.x + other.x
        elif isinstance(other, type(self.x)):
            return self.x + other
        else:
            return NotImplemented

    def __iadd__(self, other):
        if isinstance(other, type(self)):
            self.x = self.x + other.x
            return self
        elif isinstance(other, type(self.x)):
            self.x = self.x + other
            return self
        else:
            return NotImplemented

    # __radd__ = __add__    #因为两个方法的重复度很高,可以直接写成相等
    def __radd__(self, other):
        if isinstance(other,type(self)):
            return self.x+other.x
        elif isinstance(other,type(self.x)):
            return self.x+other
        else:
            raise TypeError('unsupported operand type(s) for +: {} and {}'.format(type(self).__name__,type(other).__name__))


a = A(1)
print(a + 1)
print(1 + a)
print('b' + a)

#结果
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 36, in <module>
    print('b' + a)
  File "C:/Users/Administrator/Desktop/学习笔记/Python/代码/t1.py", line 30, in __radd__
    raise TypeError('unsupported operand type(s) for +: {} and {}'.format(type(self).__name__,type(other).__name__))
TypeError: unsupported operand type(s) for +: A and str
2
2

可迭代对象

可迭代对象必须有__iter__方法

class A:
    def __init__(self):
        self.x = [10, 20, 30, 40]

    def __iter__(self):
        yield from self.x

    def __reversed__(self):
        yield from reversed(self.x)


a = A()
print([i for i in a])
print([i for i in reversed(a)])
#结果
[10, 20, 30, 40]
[40, 30, 20, 10]

如果需要实现反向迭代,就需要实现__reversed__方法

迭代器

生成器对象必须实现__next__方法,也应该实现__iter__方法

class A:
    def __init__(self):
        self.x = -1

    def __iter__(self):
        return self

    def __next__(self):
        self.x += 1
        if self.x>10:
            raise StopIteration
        return self.x


a = A()
print(next(a))
print(next(a))
print(next(a))
print('='*30)
for i in a:
    print(i)
print('-'*30)
print(next(a,'end--'))


#结果
0
1
2
==============================
3
4
5
6
7
8
9
10
------------------------------
end--

def __iter__(self):
return self

之所以直接return self,是因为,下面已经实现了__next__方法,已经是迭代器了,所以可以直接返回self

posted @ 2023-01-17 22:20  厚礼蝎  阅读(17)  评论(0编辑  收藏  举报