Python函数和面向对象程序设计(课本总结)

目录

自定义类

属性和方法

三大特性


本章重点 type和class以及object
1. 了解 type
2. 结合 type 和 class
3. type class 和 object超类
1. 了解 type
首先 type函数通常用来判断参数的类型 展示代码:

print(type(1))      #输出 <class 'int'> 因为 1 是 整型 的实例
print(type("字符串"))    #输出 <class 'str'> 因为 "字符串" 是 str 的实例
print(type(10.1235))    #输出 <class 'float'> 因为 10.1235 是 float 的实例
print(type(True))    #输出 <class 'bool'>因为 True 是 bool 的实例
print(type([]))        #输出 <class 'list'> 因为 [] 是 列表 的实例
print(type(()))        #输出 <class 'tuple'> 因为 () 是 元组 的实例
print(type({}))        #输出 <class 'dict'> 因为 {} 是 字典 的实例

上面的代码对python的基本数据类型进行的判断,每一次赋值都是创建类型的实例,实例又称之为对象!!

2. 结合 type 和 class
使用type判断实例和class所返回的值 附上代码:

class Demo:
    pass
if __name__ == '__main__':
    a = Demo()
    print(type(a)) #输出 <class '__main__.Demo'> a 是 Demo对象的实例
    print(type(Demo))      # 输出 <class 'type'> 
    print(type(list))    # 输出 <class 'type'> 
    print(type(int))    # 输出 <class 'type'> 
    
    #代码只演示到这里
    # ...... 后续的一些类都是type的实例

先定一个结论,在Python中所有的类都是type类的对象,都是type的实例

3. type class 和 object超类
很多语言中object是所有类的父类,Python中也不例外,所有的类都继承object,上述代码所判断的类型全部都是对象(值是Python的类型的对象,类型是type的对象,大家都是对象),那么object是对象吗?type是对象吗?直接看代码:
class Demo:
    pass
if __name__ == '__main__':
    print(Demo.__bases__) #自定义的类  输出 (<class 'object'>,) 基类是object
    print(type(object)) #输出 <class 'type'> object 是type 的实例
    print(type.__bases__)#输出 (<class 'object'>,) type 的基类 object
    print(type(type)) #输出 <class 'type'> type的实例是type

总结:

1. 自定义类的基类是object(所有类的基类)包括特殊的type
2. object 居然是type的实例(对象)
3. type 的实例(对象)居然是自己(上述结论正确)
4. 结合上述诠释了Python一切皆对象
 

Object类
所有类的父类,默认所有的类都继承至Object类

规定了类的结构,加载方式,常用函数

以前的写法:

class 类名(Object):

        pass

现在的写法:

class 类名:

       pass

如果有父类才编写,如果没有父类可以省掉Object类,但是也是默认继承

内置函数:

__new__(cls, *args, **kwargs)

创建对象时自动调用的函数,主要作用是创建对象,给该对象分配空间,方便之后的的操作

该函数会返回创建出来的对象实体,一旦正常的返回实体后,调用初始化函数

__init__(self)

初始化函数(构造函数),作用:给当前对象创建各类变量,并给变量赋初值,一般用于对象的初始设置,该函数没有返回值

__str__(self)

对象描述函数,作用:返回当前对象的字符串类型的信息描述,一般用于对象的直接输出显示

__del__(self)

删除该对象时会自动调用,一般用于工具型对象的资源回收

例如:

class Student:
 
    def __new__(cls, *args, **kwargs):
        print("创建对象时有自动调用的函数,如果当前函数没有返回对象,则不会再执行初始化函数了")
        return super().__new__(cls)             # 调用父类的创建对象的函数,进行当前对象的创建
 
    def __init__(self, name, age, sex):
        print("初始化函数,对象创建成功后自动调用,一般用于对象属性的赋值")
        self.name = name
        self.age = age
        self.sex = sex
 
    # 返回当前对象的字符串描述
    def __str__(self):
        return "我是{},今年{}岁,是个{}的".format(self.name, self.age, self.sex)
 
    def __del__(self):
        print("删除该对象时会自动调用该函数,一般用于工具类释放资源")
 
if __name__ == '__main__':
    stu1 = Student('张三', 20, '男')
    print(stu1)                          # 当输出对象时,输出该对象的字符串描述,自动调用__str__函数
    del stu1
 

python中__new__和__init__的区别

__new__方法和__init__方法都是python中的构造方法,其中__new__方法使用较少,__init__方法使用较多。
首先来了解下这两种方法:

1.__new__是在实例创建之前被调用的,用于创建实例,然后返回该实例对象,是个静态方法。
2.__init__是当实例对象创建完成后被调用的,用于初始化一个类实例,是个实例方法。

由上可知,__new__先被调用,__new__的返回值将传递给__init__方法的第一个参数,然后__init__被调用,给这个实例设置一些参数。

实例:

class A():
    def __new__(cls, *args, **kwargs):
        print('this is A __new__')
        # return super(A, cls).__new__(cls)  # 或者下面这种形式,两种都可
        return object.__new__(cls)

    def __init__(self, title):
        print('this is A __init__')
        self.title = title


a = A('python book')
1
2
3
4
5
6
7
8
9
10
11
12
输出结果:

this is A __new__
this is A __init__
1
2
两种方法的区别:

1.__new__至少要有一个参数cls,且必须要有返回值,返回的是实例化出来的实例,有两种return方式:

return super(父类,cls).__new__(cls)
或者
return object.__new__(cls)
1
2
3
2.__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__基础上完成一些其他初始化的动作,__init__不需要有返回值;

注意事项:

1.如果__new__没有返回cls(即当前类)的实例,那么当前类的__init__方法是不会被调用的;

实例:

class A():
    def __new__(cls, *args, **kwargs):
        print('this is A __new__')
        # return super(A, cls).__new__(cls)  

    def __init__(self, title):
        print('this is A __init__')
        self.title = title


a = A('python book')
1
2
3
4
5
6
7
8
9
10
11
输出结果:

this is A __new__
1
2.在定义子类的时候,没有重新定义__new__方法,那么直接继承父类的__new__方法构造该类的实例;

实例:

class A():
    def __new__(cls, *args, **kwargs):
        print('this is A __new__')
        return super(A, cls).__new__(cls)

    def __init__(self, title):
        print('this is A __init__')
        self.title = title


class B(A):
    def __init__(self, title):
        print('this is B __init__')
        self.title = title


b = B('python book')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输出结果:

this is A __new__
this is B __init__
1
2
3.在定义子类的时候,重写__new__方法,就不会调用父类的__new__方法;

实例:

class A():
    def __new__(cls, *args, **kwargs):
        print('this is A __new__')
        return super(A, cls).__new__(cls)

    def __init__(self, title):
        print('this is A __init__')
        self.title = title


class B(A):
    def __new__(cls, *args, **kwargs):
        print('this is B __new__')
        return super(A, cls).__new__(cls)  # 或者下面return的方式
        # return object.__new__(cls)

    def __init__(self, title):
        print('this is B __init__')
        self.title = title


b = B('python book')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出结果:

this is B __new__
this is B __init__
1
2
4.如果子类重写__new__方法时,return super(子类,cls),那么会调用父类的__new__方法构造类的实例;

实例:

class A():
    def __new__(cls, *args, **kwargs):
        print('this is A __new__')
        return super(A, cls).__new__(cls)

    def __init__(self, title):
        print('this is A __init__')
        self.title = title


class B(A):
    def __new__(cls, *args, **kwargs):
        print('this is B __new__')
        return super(B, cls).__new__(cls)

    def __init__(self, title):
        print('this is B __init__')
        self.title = title


b = B('python book')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
输出结果:

this is B __new__
this is A __new__
this is B __init__


super() 是python 中调用父类(超类)的一种方法,在子类中可以通过super()方法来调用父类的方法。【超类: 是指 2层以上的继承关系,假如 C类继承B类,B类由继承A类,那么A类就是C类的超类】

作用:

在继承中,让代码维护更加简单
解决多继承带来的重复调用(菱形继承)、查找顺序(MRO)问题
语法:

super(type[, object-or-type])
1
参数:
type – 类。
object-or-type – 类,一般是 self

Python 3 和 Python 2 的另一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

super的使用
1.通过super() 来调用父类的__init__ 构造方法:

class Person():
      def __init__(self):
        print('我是Peson的__init__构造方法')

class Student(Person):
        def __init__(self):
         super().__init__()
          print('我是Student的__init__构造方法')

stu = Student()
-----------------------------------------
我是Peson的__init__构造方法
我是Student的__init__构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
2 通过supper() 来调用与子类同名的父类方法
2.1 单继承
在单继承中 super 就像大家所想的那样,主要是用来调用父类的方法的。

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m

class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3
b = B()
b.add(2)
print(b.n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们执行以上代码,得到的输出如下:

self is <__main__.B object at 0x106c49b38> @B.add
self is <__main__.B object at 0x106c49b38> @A.add
8
1
2
3
这个结果说明了两个问题:

1、super().add(m) 确实调用了父类 A 的 add 方法。
2、super().add(m) 调用父类方法 def add(self, m) 时, 此时父类中 self 并不是父类的实例而是子类的实例, 所以 b.add(2) 之后的结果是 5 而不是 4 。

补充说明:
MRO—方法搜索顺序

MRO是method resolution order,主要用于在对继承是判断方法、属性的调用路径【顺序】,其实也就是继承父类方法时的顺序表。
Python中针对类提供了一个内置属性__mro__可以查看方法的搜索顺序
class C(A,B):
    pass
print(C.__mro__)
out:
(<class '__main__.C'>,<class'__main__.A'>,<class'__main__B'>,<class 'object'>)
1
2
3
4
5
在搜索方法时,是按照__mro__的输出结果从左到右的顺序查找的

如果当前类中找到方法,就直接执行,不再搜索
如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
如果找到最后一个类,还是没有找到方法,程序报错
2.2 多继承
在多继承中,会涉及到一个MRO(继承父类方法时的顺序表) 的调用排序问题。即严格按照MRO 顺序执行super方法

class A:
    def __init__(self):
        self.n = 2

    def add(self, m):
        print('self is {0} @A.add'.format(self))
        self.n += m

class B(A):
    def __init__(self):
        self.n = 3

    def add(self, m):
        print('self is {0} @B.add'.format(self))
        super().add(m)
        self.n += 3

class C(A):
    def __init__(self):
        self.n = 4

    def add(self, m):
        print('self is {0} @C.add'.format(self))
        super().add(m)
        self.n += 4

class D(B, C):
    def __init__(self):
        self.n = 5

    def add(self, m):
        print('self is {0} @D.add'.format(self))
        super().add(m)
        self.n += 5

d = D()
d.add(2)
print(d.n)
out:
self is <__main__.D object at 0x10ce10e48> @D.add
self is <__main__.D object at 0x10ce10e48> @B.add
self is <__main__.D object at 0x10ce10e48> @C.add
self is <__main__.D object at 0x10ce10e48> @A.add
19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
同样,不管往上调用几次,调用父类方法中 self 并不是父类的实例而是子类的实例,在上例中都是D的实例化对象
D.mro() == [D,B, C, A, object] ,多继承的执行顺序会严格按照mro的顺序执行。
整体的调用流程图如下:

在这里插入图片描述
d = D()
d.n == 5
d.add(2)

 

class D(B, C):          class B(A):            class C(A):             class A:
    def add(self, m):       def add(self, m):      def add(self, m):       def add(self, m):
        super().add(m)  1.--->  super().add(m) 2.--->  super().add(m)  3.--->  self.n += m
        self.n += 5   <------6. self.n += 3    <----5. self.n += 4     <----4. <--|
        (14+5=19)               (11+3=14)              (7+4=11)                (5+2=7)
1
2
3
4
5
6
7
8
9

其它:
1.super().__init__相对于类名.init,在单继承上用法基本无差别
2.但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次。
3.多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
4.单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
 

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

class Student(object):
    pass
1
2
可以看到这个类是没有自己写一些属性。

stu1 = Student();
stu1.name = 'Tom' #动态绑定一个属性

#查看这个属性
print(stu1.name, stu1.__getattribute__('name'))

#打印结果
Tom Tom
1
2
3
4
5
6
7
8
不仅仅可以绑定属性,也可以绑定方法!

绑定方法
#定义一个普通函数
def set_age(self, age):
    self.age = age

#可以调用这个函数 设置属性
set_age(stu1, 20)
print(stu1.age, stu1.__getattribute__('age'))


from types import MethodType

#但是这个属性不是实例自己的方法 现在给它设置这个方法
stu1.set_age = MethodType(set_age, stu1)
stu1.set_age(33)
print(stu1.age, stu1.__getattribute__('age'))

#打印结果
20 20
33 33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
但是要注意的是这个方法只属于stu1的,对于其他实例不起作用:

stu2 = Student()
stu2.set_age(32)
#打印结果
AttributeError: 'Student' object has no attribute 'set_age'
1
2
3
4
为了给所有实例都绑定方法,可以选择给class绑定方法

类绑定方法
#先打印测试一下有没有这个属性
print(stu1.__getattribute__('set_name'))
#注意此时是会报错的
#AttributeError: 'Student' object has no attribute 'set_name'

#定义一个函数
def set_name(self, name):
    self.name = name

#给类绑定这个属性
Student.set_name = set_name

#现在再次测试
print(stu1.__getattribute__('set_name'))

stu1.set_name('Rose')
stu2.set_name('Jack')

print(stu1.name)
print(stu2.name)

#打印结果
#去除报错语句后
<bound method set_name of <__main__.Student object at 0x00000000028FEEB8>>
Rose
Jack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__slots__
我们通过以上实现了动态添加属性,但是假如想要限制实例属性怎么办呢?
比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

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

stu1 = Student()
stu1.name = 'John'
stu1.age = 21

print(stu1.name, stu1.age)
#打印结果
John 21
1
2
3
4
5
6
7
8
9
10
但是:

stu1.addr = 'Beijing'
#打印结果
AttributeError: 'Student' object has no attribute 'addr'
1
2
3
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

#中学生..
class MidStudent(Student):
    pass

stu2 = MidStudent()
stu2.addr = 'Beijing'
print(stu2.addr)

#打印结果
Beijing
1
2
3
4
5
6
7
8
9
10
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。

__slots__需要注意的两点
slots只能限制添加属性,不能限制通过添加方法来添加属性

from types import MethodType

class School(object):
    __slots__ = ('name')
    pass

def set_city(self, city):
    self.city = city

School.set_city = MethodType(set_city, School)

sch = School()
sch.name = 'BeiJing University'
sch.set_city('BeiJing')

print(sch.name)
print(sch.city)

#打印结果
BeiJing University
BeiJing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上段代码中,Student类限制属性name,但可以通过添加方法添加一个city属性。

属性分实例属性和类属性,多个实例同时更改类属性,值是最后更改的一个

class School(object):
    pass

def set_city(self, city):
    self.city = city

School.set_city = MethodType(set_city, School)

sch1 = School()
sch2 = School()

sch1.set_city('BeiJing')
sch2.set_city('ShangHai')

print(sch1.city, sch2.city)

#打印结果
ShangHai ShangHai
 

一、多态

多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等。(一个抽象类有多个子类,因而多态的概念依赖于继承)

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('say miaomiao')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

二、多态性

注意:多态与多态性是两种概念

多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

'''
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('say miaomiao')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

c = Cat()
d = Dog()
p = Pig()

def func(obj):
    obj.talk()

func(c)
func(d)
func(p)

------------------------------

>>> say miaomiao
>>> say wangwang
>>> say aoao
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

综上可以说,多态性是 : 一个接口,多种实现

多态性的好处:

  • 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(obj)
  • 增加了程序额可扩展性,通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(obj)去调用

三、鸭子类型

调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景。而在python中,因为鸭子类型(duck typing)使得其多态不是那么酷。

鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

Duck typing 这个概念来源于美国印第安纳州的诗人詹姆斯·惠特科姆·莱利(James Whitcomb Riley,1849- 1916)的诗句:”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”

先上代码,也是来源于网上很经典的案例:

class Duck():
    def walk(self):
         print('I walk like a duck')
    def swim(self):
         print('i swim like a duck')

 

Python中类的特殊属性与魔术方法

带着梦想飞翔

于 2019-05-26 11:59:04 发布

672
 收藏 6
分类专栏: python基本知识 文章标签: 类的特殊属性与魔术方法 Python中类的特殊属性与魔术方法
版权

python基本知识
专栏收录该内容
105 篇文章9 订阅
订阅专栏
文章目录
Python中类的特殊属性与魔术方法
特殊的属性
查看属性
特殊函数
魔术方法
实例化
可视化
可哈希(hash)与等等运算符(==)
bool
运算符重载
1.比较运算符重载
2.算术运算符重载
3.反向运算符
4.赋值运算符的重载
运算符重载的应用场景
functools.total_ordering装饰器
容器相关方法
可调用对象
Python中类的特殊属性与魔术方法
特殊的属性
属性    含义
__name__    类,函数,方法等效的名字。即名称
__module__    类定义所在的模块名称
__class__    对象或类所属的类
__bases__    类的基类(父类)的元组,顺序为他们在基类列表中出现的顺序
__doc__    类、函数的文档字符串,如果没有定义则为None
__mro__    类的mro,class.mro()返回的结果都保存在__mro__中。C3算法帮忙保证类的mro唯一性
__dict__    类或实例的属性,可写的字典
查看属性
方法    含义
__dir__(self)    返回类或者对象的所有成员名称列表
dir()函数操作实例就是调用__dir()__
如果dir([obj])参数obj包含方法 __dir__() ,该方法将被调用。如果参数obj不包含 __dir__() ,该方法将最大限 度地收集属性信息。

dir(obj) #如果没有参数,返回当前局部作用域中的名称列表。使用参数时,尝试返回该对象的有效属性列表。
dir(obj)相当于调用obj.__dir__()的方法
对于不同类型的对象obj具有不同的行为:
如果对象是模块对象,返回的列表包含模块的属性名和变量名
如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它祖先类的属性名。
如果是类的实例
有__dir__方法,返回可迭代对象的返回值
没有__dir__方法,尽可能收集实例的属性名、类的属性和祖先类的属性名
如果obj不写,返回列表包含内容不同
在模块中,返回模块的属性和变量名
在类中,返回类本地作用域的属性和变量名
在函数中,返回本地作用域的变量名
在方法中,返回本地作用域的变量名
示例1,不同作用域中的dir返回值 返回局部作用域中的名称列表

animal.py文件信息如下:
#animal.py文件信息如下:
class Animal:
x = 123
def __init__(self,name):
    self._name = name
    self.__age = 18
    self.weight = 20

print("animal.py names = {}".format(dir()))
1
2
3
4
5
6
7
8
9
cat.py文件信息如下:

#cat.py文件信息如下:
import animal
from animal import Animal

class Cat(Animal):
    x = "cat"
    y = "abcd"
    print("Cat class 类中 names = {}".format(dir()))

    z = "xyz"

    def show(self):
        a = self
        b = 10
        print("show方法中 names = {}".format(dir()))

print("cat.py names = {}".format(dir()))
cat = Cat("苗")
cat.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
执行cat.py文件输出结果如下:


示例2:类型中的__dir__

如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它祖先类的属性名。
dir(类) 等价于调用type.__dir__(类)方法。应为类的类型是type
class Animal:
    x = 123
    def __init__(self,name):
        self._name = name
        self.__age = 18
        self.weight = 20

class Cat(Animal):
    x = "cat"
    z = "xyz"

    def show(self):
        a = self
        b = 10
        print("show方法中 names = {}".format(dir()))

class Dog(Animal):
    def __dir__(self):
        return ["dog"]  #__dir__方法必須返回可迭代对象

#Cat类的dir 会调用type中的__dir__方法返回类Cat的dir,应为Cat是类
print(type(Cat))
print("Cat 类的 names = {}".format(dir(Cat)))
#相当于排序后的集合
print("Cat 类的 names = {}".format(sorted((Cat.__dict__.keys()| Animal.__dict__.keys() | object.__dict__.keys()))))
print("Dog 类的 names = {}".format(dir(Dog)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26


示例3:示例属性中的__dir__ (尽可能的最大收集实例属性的属性名)

dir(self) 等价于 self.__dir__ #self为实例对象
如果类中重写了__dir__方法,那么dir(self)调用的返回值是重写的__dir__方法的返回值
__dir__方法的返回值必须是一个可迭代对象
class Animal:
    x = 123
    def __init__(self,name):
        self._name = name
        self.__age = 18
        self.weight = 20

class Cat(Animal):
    x = "cat"
    z = "xyz"

    def show(self):
        a = self
        b = 10
        print("show方法中 names = {}".format(dir()))

class Dog(Animal):
    def __dir__(self):
        return ["dog"]  #__dir__方法必須返回可迭代对象

cat = Cat("苗")
dog = Dog("旺旺")

#注意:dir(cat) 等价于cat.__dir__()
print("cat 实例属性,dir() = {}".format(dir(cat)))
print("cat 的内建函数__dir__ = {}".format(sorted(cat.__dir__())))
print("dog 实例的实例属性 dir() = {}".format(dir(dog)))
print("dog 实例的实例属性 __dir__() = {}".format(dog.__dir__()))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


特殊函数
locals() 返回当前作用域中的变量字典
globals() 当前模块全局变量的字典
class Animal:
    x = 123
    def __init__(self,name):
        self._name = name
        self.__age = 18
        self.weight = 20

class Cat(Animal):
    x = "cat"
    z = "xyz"
    print("Cat 类中 globals = {}".format(sorted(globals().keys())))
    print("Cat 类中 dir = {}".format(dir()))
    print("Cat 类中 locals = {}".format(sorted(locals().keys())))
    def show(self):
        a = self
        b = 10
        print("show方法中 globals = {}".format(sorted(globals().keys())))
        print("show方法中 dir = {}".format(dir()))
        print("show方法中 locals = {}".format(sorted(locals().keys())))

class Dog(Animal):
    def __dir__(self):
        return ["dog"]  #__dir__方法必須返回可迭代对象
    print("Dog 类中 globals = {}".format(sorted(globals().keys())))
    print("Dog 类中 dir = {}".format(dir()))
    print("Dog 类中 locals = {}".format(sorted(locals().keys())))


print("cat.py 模块中 globals = {}".format(sorted(globals().keys())))
print("cat.py 模块中 locals = {}".format(sorted(locals().keys())))
print("cat.py 模块中 dir = {}".format(dir()))
cat = Cat("苗")
cat.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33


魔术方法
实例化
方法    意义
__new__(self,*args,**kwargs)    示例化一个对象
该方法需要返回一个值,如果该值不少cls的实例,则会调用__init__
该方法永远都是静态方法
调用new方法前还没有实例对象,正常调用完成后会生成实例对象。
__init__(self)    对实例进行初始化,通常用来设置实例属性,基本配置信息等。
调用init方法前已经存在实例对象
__del__(self)    实例的引用次数为0时调用。即删除实例时调用。
当系统自动调用del方法后实例已经没有对象记录,等着垃圾回收gc来清理。
new的简单示例(init和del方法在前面文章中已经讲到,这里不做演示)
class A:
    def __new__(cls,*args,**kwargs):
        print(cls)
        print(args)
        print(kwargs)
        # return super().__new__(cls)
        # return 1
        return None

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

a = A()
print(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

可视化
方法    等效的内建函数    含义
__str__(self)    str()    str()函数、format()函数、print()函数调用,需要返回对象的字符串表达式。如果没有定义,就去调用__repr__方法返回字符串的表达。如果__repr__没有定义,就直接返回对象的内存地址信息
__repr__(self)    repr()    内建函数repr()对一个对象获取字符串表达。
调用__repr__方法返回字符串表达,如果__repr__也没有定义,就直接返回object的定义,显示内存地址信息。
__bytes__(self)    bytes()    bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
简单示例:
class A:
    def __init__(self,name,age=18):
        self.name = name
        self.age = age
        
    def __repr__(self):
        return "repr:{},{}".format(self.name,self.age)
    
    def __str__(self):
        return "str:{},{}".format(self.name,self.age)
    
    def __bytes__(self)->bytes:
        import json
        return json.dumps(self.__dict__).encode()
    
a = A("tom")
print(a) #print函数会默认调用对象的__str__ 等价于a.__str__
print([a]) #等价于[a].__str__  但[]里面会默认调用a的__repr__即等价于[a.__repr__()].__str__()
print([a.__repr__()].__str__())
print(bytes(a)) #等价于str(bytes(a))  等价于 a.__bytes__().__str__()
print( a.__bytes__().__str__())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


可哈希(hash)与等等运算符(==)
方法    等效的内建函数    意义
__hash__(self)    hash()    内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。
__eq__(self)    ==    对等等操作符,判断2个对象是否相等,返回值bool值
定义了这个方法,如果不提供__hash__方法,那么实例对象将不可hash了。
__hash__ 方法只是返回一个hash值作为set的key,但是 去重 ,还需要 __eq__ 来判断2个对象是否相等。 hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供 __hash__ 方法是为了作为set或者dict的key,如果 去重 要同时提供 __eq__ 方法。
判断对象是否可哈希isinstance(obj, collections.Hashable),obj为需要判断的对象。
示例1:

对象的hash值取决于对象的__hash__函数的返回值
如果使用"=="运算符,其结果为__eq__函数的返回值
class A:
    def __init__(self,name,age=18):
        self.name = name
        self.age = age

    def __hash__(self):
        return hash("{},{}".format(self.name,self.age))

    def __repr__(self):
        return "repr {} {}".format(self.name,self.age)

    def __eq__(self,other):
        # return "abc"  #如果返回字符串,那么print(b==a)的结果就是返回的字符串
        return True

    __str__ = __repr__

a = A("旺旺")
b = A("哈哈")
print(a)
print(hash(a),a.__hash__())
print(hash(b),b.__hash__())
print(b==a) #等价于 b.__eq__(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


示例2:

set字典中唯一性先判断对象是否可hash,在判断hash值是否相等以及是否“==”来判断新增加的值是否已经存在
class stt:
    def __repr__(self):
        return "("+",".join("{}:{}".format(x,y) for x,y in self.__dict__.items())+")"

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

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

    def __hash__(self):
        return hash("{},{}".format(self.x,self.y))

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

print(set([Point(1,2),Point(1,2)]))
print(set([Point2(1,2),Point2(1,2)]))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


示例3:

扩展
hash值对int类型求值是用一个大整数取摸(%)计算得到的结果
hash值的整数为:【2305843009213693951】
下面示例求出hash值对应的大整数是多少
# %%timeit
def getHashbigInt():
    i = 0
    ii = 2 #增量

    while ii >1:
        i = i+ii
        if hash(i)==i:
            ii = ii*2 #增量加大
        else: #调整增量
            i = i-ii  #i值还原
            ii = ii//2 #增量缩小
    return i+1

hig = getHashbigInt()
print(hig)
print(hash(hig-1),hash(hig),hash(hig+1))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


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

class B:
    def __bool__(self)->bool:
        #注意:bool函数的返回值必须是bool类型,否则会报错
        return False

class C:
    def __len__(self):
        return 1

class D:
    def __len__(self):
        return 0

a = A()
b = B()
c = C()
d = D()
print(bool(a),bool(b),bool(c),bool(d))
if a: #if a等价于 if a.__bool__()
    print("this is a = true")
if b:
    print("this is b = true")
if c:
    print("this is c = true")
if d:
    print("this is d = true")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


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

1.比较运算符重载
特殊方法    运算符    含义
__lt__(self,other)    <    小于运算符等效符调用的对应方法
在obj1 < obj2时调用obj1.__lt__(obj2)
__le__(self,other)    <=    小于等于运算符等效符调用的对应方法
在obj1 <= obj2时调用obj1.__le__(obj2)
__eq__(self,other)    ==    等等运算符等效调用的对应方法
在obj1 == obj2时调用obj1.__eq__(obj2)
__gt__(self,other)    >    大于运算符等效调用的对应方法
在obj1 > obj2时调用obj1.__gt__(obj2)
__ge__(self,other)    >=    大于等于运算符等效调用的对应方法
在obj1 >= obj2时调用obj1.__ge__(obj2)
__ne__(self,other)    !=    不等运算符等效调用的对应方法
在obj1 != obj2时调用obj1.__ne__(obj2)
注意:

完成小于方法,对应的大于方法可以不用重新也能使用。反之也一样。
完成小于等于方法,对应的大于等于方法可以不用重写。反之也一样。
简单示例:
class A:
    def __init__(self,age = 18):
        self.age = age

    def __lt__(self,other): #完成小于方法,大于方法也可以使用
        return self.age < other.age

    def __ge__(self,other): #完成大于等于方法,小于等于方法也可以使用
        return self.age >= other.age

    def __eq__(self,other): #完成
        return self.age == other.age

a1 = A()
a2 = A(20)
a3 = A(10)
a4 = A()
print("a1 < a2 ,{}".format( a1 < a2 ))
print("a1 > a2 ,{}".format( a1 > a2 ))
print("a1 >= a2 ,{}".format( a1 >= a2 ))
print("a1 <= a2 ,{}".format( a1 <= a2 ))
print("a1 <= a4 ,{}".format( a1 <= a4 ))
print("a1 == a4 ,{}".format( a1 == a4 ))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


2.算术运算符重载
特殊方法    运算符    含义
__add__(self,other)    +    加法运算等效调用的对应方法
在obj1 + obj2时调用obj1.__add__(obj2)
__sub__(self,other)    -    减法运算等效调用的对应方法
在obj1 - obj2时调用obj1.__sub__(obj2)
__mul__(self,other)    *    乘法运算等效调用的对应方法
在obj1 * obj2时调用obj1.__mul__(obj2)
__truediv__(self,other)    /    除法运算等效调用的对应方法
在obj1 / obj2时调用obj1.__truediv__(obj2)
__mod__(self,other)    %    **取摸运算(取余数)**等效调用的对应方法
在obj1 % obj2时调用obj1.__mod__(obj2)
__floordiv__(self,other)    //    整除运算(向下取整)等效调用的对应方法
在obj1 // obj2时调用obj1.__floordiv__(obj2)
__pow__(self,other)    **
pow(x,n)    次幂运算等效调用的对应方法。也等价于pow(x,n)方法
在obj1 ** obj2时调用obj1.__pow__(obj2)
__divmod__(self,other)    divmod(obj1,obj2)    获取数的商和余数组成的元组等效的对应方法
在divmod(obj1,obj2)时调用obj1.__divmod__(obj2)
__matmul__(self,other)    @    矩阵运算符等效调用的对应方法
在obj1 @ obj2时调用obj1.__matmul__(obj2)
__and__(self,other)    &    与运算符等效的对应方法
在obj1 & obj2时调用obj1.__and__(obj2)
__or__(self,other)    |    或运算符等效的对应方法
在obj1 | obj2时调用obj1.__or__(obj2)
__xor__(self,other)    ^    异或运算符等效的对应方法
在obj1 ^ obj2时调用obj1.__xor__(obj2)
__lshift__(self,other)    <<    左移运算符等效的对应方法
在obj1 << obj2时调用obj1.__lshift__(obj2)
__rshift__(self,other)    >>    右移运算符等效的对应方法。
在obj1 >> obj2时调用obj1.__rshift__(obj2)
__invert__(self,other)    ~    按位取反运算符等效的对应方法
在~obj时调用obj.__invert__()
简单示例:
class A:
    def __init__(self,age=18):
        self.age = age

    def __repr__(self):
        return "(age = {})".format(self.age)

    def __add__(self,other):
        return A(self.age + other.age)

a1 = A(15)
a2 = A(18)
print(a1,a2,a1+a2)
1
2
3
4
5
6
7
8
9
10
11
12
13


3.反向运算符
在obj1 + obj2中如果obj1不支持+操作,那么obj1.__add__(self,other)的返回值应该是NotImplemented,此时系统会调用obj2中的__radd__(self,other)方法来实现运算。即执行obj2.__radd__(obj1)
特殊方法    运算符    含义
以下示例中。按照__iadd__方法来举例:
obj1中必须没有定义对应的方法__add__。或者返回值为NotImplemented。才会调用obj2.__radd__(obj1)来完成运算
__radd__(self,other)    +    加法运算等效调用的对应方法
在obj1 + obj2时调用obj2.__radd__(obj1)
__rsub__(self,other)    -    减法运算等效调用的对应方法
在obj1 - obj2时调用obj2.__rsub__(obj1)
__rmul__(self,other)    *    乘法运算等效调用的对应方法
在obj1 * obj2时调用obj2.__rmul__(obj1)
__rtruediv__(self,other)    /    除法运算等效调用的对应方法
在obj1 / obj2时调用obj2.__rtruediv__(obj1)
__rmod__(self,other)    %    **取摸运算(取余数)**等效调用的对应方法
在obj1 % obj2时调用obj2.__rmod__(obj1)
__rfloordiv__(self,other)    //    整除运算(向下取整)等效调用的对应方法
在obj1 // obj2时调用obj2.__rfloordiv__(obj1)
__rpow__(self,other)    **
pow(x,n)    次幂运算等效调用的对应方法。也等价于pow(x,n)方法
在obj1 ** obj2时调用obj2.__rpow__(obj1)
__rdivmod__(self,other)    divmod(obj1,obj2)    获取数的商和余数组成的元组等效的对应方法
如果obj1没有__divmod__方法,或者返回NotImplemented,则会调用obj2.__rdivmod__(obj1)
__rmatmul__(self,other)    @    矩阵运算符等效调用的对应方法
在obj1 @ obj2时调用obj2.__rmatmul__(obj1)
__rand__(self,other)    &    与运算符等效的对应方法
在obj1 & obj2时调用obj2.__rand__(obj1)
__ror__(self,other)    |    或运算符等效的对应方法
在obj1 | obj2时调用obj2.__ror__(obj1)
__rxor__(self,other)    ^    异或运算符等效的对应方法
在obj1 ^ obj2时调用obj2.__rxor__(obj1)
__rlshift__(self,other)    <<    左移运算符等效的对应方法
在obj1 << obj2时调用obj2.__rlshift__(obj1)
__rrshift__(self,other)    >>    右移运算符等效的对应方法。
在obj1 >> obj2时调用obj2.__rrshift__(obj1)
简单流程运行图:


简单示例:

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

    def __repr__(self):
        return "(age = {})".format(self.age)

    def __add__(self,other):
        return A(self.age + other.age)

    def __radd__(self,other):
        return A(self.age + other.age)

class B:
    def __init__(self,age= 18):
        self.age = age

    def __repr__(self):
        return "(age = {})".format(self.age)

a = A(10)
b = B(15)
print(a+b)
print(b+a)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


4.赋值运算符的重载
特殊方法    运算符    含义
__iadd__(self,other)    +=    加等赋值运算等效调用的对应方法
在obj1 += obj2时调用obj1 = obj1.__iadd__(obj2)
__isub__(self,other)    -=    减等赋值运算等效调用的对应方法
在obj1 -= obj2时调用obj1 = obj1.__isub__(obj2)
__imul__(self,other)    *=    乘等赋值运算等效调用的对应方法
在obj1 *= obj2时调用obj1 = obj1.__imul__(obj2)
__itruediv__(self,other)    /=    除等赋值运算等效调用的对应方法
在obj1 /= obj2时调用obj1 = obj1.__itruediv__(obj2)
__imod__(self,other)    %=    取模等赋值运算等效调用的对应方法
在obj1 %= obj2时调用obj1 = obj1.__imod__(obj2)
__ifloordiv__(self,other)    //=    整除等赋值运算符等效调用的对应方法
在obj1 //= obj2时调用obj1 = obj1.__ifloordiv__(obj2)
__ipow__(self,other)    **=    次幂等运算符等效调用的对应方法
在obj1 **= obj2时调用obj1 = obj1.__ipow__(obj2)
__imatmul__(self,other)    @=    矩阵等赋值运算等效调用的对应方法
在obj1 @= obj2时调用obj1 = obj1.__imatmul__(obj2)
__iand__(self,other)    &=    与等赋值运算等效的对应方法
在obj1 &= obj2时调用obj1 = obj1.__iand__(obj2)
__ior__(self,other)    |=    或等赋值运算等效的对应方法
在obj1 |= obj2时调用obj1 = obj1.__ior__(obj2)
__ixor__(self,other)    ^=    异或等赋值运算等效的对应方法
在obj1 ^= obj2时调用obj1 = obj1.__ixor__(obj2)
__ilshift__(self,other)    <<=    左移等赋值运算等效的对应方法
在obj1 <<= obj2时调用obj1 = obj1.__ilshift__(obj2)
__irshift__(self,other)    >>=    右移等赋值运算等效的对应方法
在obj1 >>= obj2时调用obj1 = obj1.__irshift__(obj2)
注意:例如:obj1 += obj2中,如果obj1没有对应的加法赋值运算__iadd__那么等式会转换成obj1 = obj1 + obj2即obj1 = obj1.__add__(obj2),其他赋值也同样适用。
简单示例01:
class A:
    def __init__(self,age=18):
        self.age = age

    def __repr__(self):
        return "(age = {})".format(self.age)

    def __add__(self,other):
        return A(self.age + other.age)

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

a1 = A(15)
a2 = A(18)
print(a1,a2,a1+a2)
a1 += a2
print(a1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


简单示例02:
class A:
    def __init__(self,age=18):
        self.age = age

    def __repr__(self):
        return "(age = {})".format(self.age)

    def __add__(self,other):
        return A(self.age + other.age)

a1 = A(15)
a2 = A(18)
print(a1,a2,a1+a2)
a1 += a2  #如果没有__iadd__方法,会转换成 a1 = a1+a2来调用
print(a1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


综合示例:完成Point类设计,实现判断点相等的方法,并完成向量的加法
在直角坐标系里面,定义原点为向量的起点。两个向量和与差的坐标分别等于这两个向量相应坐标的和与差若向量 的表示为(x,y)形式。
A(X1,Y1) B(X2,Y2),则A+B=(X1+X2,Y1+Y2),A-B=(X1-X2,Y1-Y2)
class Point:
    def __init__(self,x:int,y:int):
        self.x = x
        self.y = y

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

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

    def __sub__(self,other):
        return Point(self.x-other.x,self.y-other.y)

    # def __isub__(self,other):
    #     self.x -= other.x
    #     self.y -= other.y
    #     return self

    def __repr__(self):
        return "<Point {},{}>".format(self.x,self.y)

p1 = Point(5,6)
p2 = Point(1,2)
print(p1+p2,p1-p2)
p1-=p2
print(p1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


运算符重载的应用场景
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中 的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point。提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。
int类,几乎实现了所有操作符,可以作为参考。

functools.total_ordering装饰器
__lt__ , __le__ , __eq__ , __gt__ , __ge__ 是比较大小必须实现的方法,但是全部写完太麻烦,使用 @functools.total_ordering 装饰器就可以大大简化代码。
但是要求 __eq__ 必须实现,其它方法 __lt__ , __le__ , __gt__ , __ge__ 实现其一

from functools import total_ordering

@total_ordering
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def __eq__(self,other):
        return self.age == other.age
    
    def __gt__(self,other):
        return self.age > other.age
    
tom = Person("tom",20)
jerry = Person("jerry",16)
print(tom > jerry)
print(tom < jerry)
print(tom >= jerry)
print(tom <= jerry)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


上例中大大简化代码,但是一般来说比较实现等于或者小于方法也就够了,其它可以不实现,所以这个装饰器只是 看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。

class Person:
    def __init__(self,name,age):
        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
    
tom = Person("tom",20)
jerry = Person("jerry",16)
print(tom > jerry)
print(tom < jerry)
print(tom >= jerry)
print(tom <= jerry)
print(tom == jerry)
print(tom != jerry)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


__eq__等于可以推断不等于
__gt__大于可以推断小于
__ge__大于等于可以推断小于等于
也就是用3个方法,就可以把所有比较解决了。
容器相关方法
方法    对应的操作    意义
__len__(self)    len(obj)    内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或者dict。
bool()函数调用的时候,如果没有__bool__()方法,则会看__len__()方法是否存在,存在返回非0为真。
__iter__(self)    for i in obj    迭代容器时,调用,返回一个新的可迭代对象
__contains__(self,item)    x in obj    in 成员运算符,没有实现,就默认调用__iter__方法遍历
__getitem__(self,key)    obj[key]    实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为可以hashable(即可哈希)。key不存在引发KeyError异常
__setitem__(self,key)    obj[key] = value    和__getitem__的访问类似,是设置值的方法
__missing__(self,key)        字典或其子类使用__getitem__()调用时,key不存在执行该方法
__missing__(self,key)例子:
class A(dict):
    def __missing__(self,key):
        print("键值对不存在。。{}".format(key))
        return 0

a = A()
a["a"] = 10
print(a["a"])
print(a["b"])
1
2
3
4
5
6
7
8
9


简单综合示例:不使用继承,实现一个容器
class Goods:
    def __init__(self,name):
        self.name = name

    def __repr__(self):
        return "<Goods name = {}>".format(self.name)

class Cart:

    def __init__(self):
        self.__goods = list()

    def __len__(self):
        return len(self.__goods)

    def add(self,good:Goods):
        self.__goods.append(good)

    def __add__(self, other):
        self.add(other)
        return self

    def __iter__(self):
        # yield from self.__goods
        return iter(self.__goods)

    def __getitem__(self, item):
        return self.__goods[item]

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

    def __repr__(self):
        return str(self.__goods)

gd = Goods("棒棒糖")
gd2 = Goods("宫保鸡丁")
cat = Cart() + gd + gd2
cat.add(Goods("火爆鸡精"))
print(cat)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40


可调用对象
Python中一切皆对象,函数也不例外。

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

foo()
#等价于
foo.__call__()
1
2
3
4
5
6


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

方法    等效    意义
__call__(self)    obj()    类中定义一个该方法,类的实例就可以像函数一样调用
示例1:可调用对象,定义一个类,并实例化得到其实例,将实例像函数一样调用。
class Adder:
    def __call__(self,*args):
        self.sum = sum(args)
        return self.sum

adder = Adder()
print(adder(4,5,6,3,2))
print(adder.sum)
1
2
3
4
5
6
7
8


示例2:定义一个斐波拉契数列,方便调用,计算第n项。
增加迭代的方法、返回容器长度、支持索引的方法。
class Xdd_Fib:
    """
    斐波拉契数列类,获取第num个位置的斐波拉契数列。
    """
    def __init__(self):
        self.fib = [0,1,1]

    def __len__(self):
        return len(self.fib)

    #类的实例可调用
    def __call__(self,num:int):
        if not isinstance(num,int):
            raise TypeError("num必须是int类型")
        if num<0:
            raise KeyError("选项错误,必须大于0")
        elif num<3:
            return self.fib[num]
        else:
            for i in range(len(self),num + 1):
                self.fib.append(self.fib[i-1]+self.fib[i-2])
        return self.fib[num]

    #类可迭代
    def __iter__(self):
        return iter(self.fib)

    def __repr__(self):
        return ",".join(map(str,self.fib))

    __str__ = __repr__
    __getitem__ = __call__ #类可以像数组一样访问

fib = Xdd_Fib()
print(fib(5),fib(6),fib(8))
print(fib)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

————————————————
版权声明:本文为CSDN博主「带着梦想飞翔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013008795/article/details/90574509

自定义类

1、标准自定义函数:
-----形参列表是标准的tuple数据类型

 def __init__(self):(不能为空,但可以定义空函数)
     pass


2、没有形参的自定义函数:
该形式是标准自定义函数的特例。

def abvedu_print():
    print("Allow me to make it clear that my silence is not due to the terror this woman instills in me")
#明明就是怂了
 

    
3、使用默认值或关键字参数或者位置参数的自定义函数:

默认参数 (不调用则使用默认参数)

关键字参数 (突出形参=XXXXXXXXXXXXXX)

位置参数 (位置保持一致)

在定义函数指定参数时,有时候会有一些默认的值,可以利用“=”先指定在参数列表上,如果在调用的时候没有设置此参数,那么该参数就使用默认的值,或者在调用的时候,标明好形参=“XXX”(调用的时候加形参就说关键字参数,否则就算位置参数)

def abvedu_printSymbol(n,symbol = " %"):
    for i in range(1,n+1):
        print(symbol , end ="")
abvedu_printSymbol(6,'嘟嘟噜~~~~~~')
嘟嘟噜~~~~~~嘟嘟噜~~~~~~嘟嘟噜~~~~~~嘟嘟噜~~~~~~嘟嘟噜~~~~~~嘟嘟噜~~~~~~


4、参数个数不确定的自定义函数

不定长参数 (在声明时不会命名)
此函数可以接受没有预先设置的参数个数,定义方法是在参数的前面加上“*”。<class 'tuple'>

def abvedu_main(*args):
	print("参数分别是:")
	for arg in args:
		print(arg)
abvedu_main("Okabe","Mayuri","Itaru","Kurisu","Moeka","RukanFerris","Suzuha")
Okabe 
Mayuri 
Itaru 
Kurisu 
Moeka 
RukanFerris 
Suzuha


5、使用lambda隐函数的自定义函数
Python提供了一种非常有趣、精简好用的一行自定义函数的方法lambda,这是一种可以实现一行语句、用完即丢的自定义函数。

def  函数名称(参数列表):
return 语句内容
def c(参数)
    d=lambda 参数:公式
    return d
c(参数)

注意:return 用于设置该函数的返回值,当自定义函数没有return值时返回None,只调用不print,就不会出现None。(return和print最好只出现其一)

传递类型(主要看传递实参类型)

1.地址传递:适用于实参类型为可变类型(列表,字典)

主调函数在进行参数传递时将实参的指针传递给被调函数,即实参随着形参值改变而改变

2.值传递:适用于实参类型为不可变类型(字符串,数字,元组)

主调函数在进行参数传递时将实参临时副本传递给被调函数,被调函数只能修改临时副本的值,即实参不随着形参值改变而改变

变量作用范围类型

1.局部变量

指在函数体内被定义的变量

注意:global 关键字来将局部变量声明为全局变量 

(虽然可以在整个程序中使用,但是在模块文件运行的过程中会一直占用内存空间,所以少用。)

2.全局变量

既可以在函数体外使用,又可以在函数体内使用,其作用范围仅限单个模块文件内

注意:全局变量在函数体内使用时需要函数内部通过global关键字声明才可以使用,在进行声明不能同时赋值。

 (高级用法,在此记录)

# 轮胎
class Tire:
    def __init__(self, size, createDate):
        self.size = size  # 尺寸
        self.createDate = createDate  # 出厂日期


class BenzCar:
    brand = '奔驰'
    country = '德国'

    def __init__(self, color, engineSN, tires):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号
        self.tires = tires


# 创建4个轮胎实例对象
tires = [Tire(20, '20160808') for i in range(4)]
car = BenzCar('red', '234342342342566', tires)
print(car.tires)   # BenzCar实例的tires属性,得到一个列表,里面是四个 Tire 实例对象

print(car.tires[0])   # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象

print(car.tires[0].size)  # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象的size属性
[<__main__.Tire object at 0x000001FCD736A4C0>, <__main__.Tire object at 0x000001FCD7485820>, <__main__.Tire object at 0x000001FCD74857F0>, <__main__.Tire object at 0x000001FCD7485EE0>]
<__main__.Tire object at 0x000001FCD736A4C0>
20

属性和方法

类属性和类方法

  • 定义在类内,并且在各函数成员之外的属性称为类属性,实例之间共享
  • 类属性的读写访问都是通过“,类名.”来实现

定义命运石之门类,在该类中定义2个类属性laboratorycountry,用于记录特征

class SteinsGate:
    laboratory = '秋叶原'  # 地方属性  公共特征
    country = '日本'  # 国家属性
print(SteinsGate.country)
日本
秋叶原

实例属性属性和方法不一样和实例方法

  • 在类内的函数中定义的属性称为实例属性,作用范围为当前实例。
  • 实例属性是每个对象各自持有的独立空间,内存空间独立,互不影响,对象单方面修改不影响其它对象。
  • 实例属性在类的内部通过“self.”访问,在外部通过对象实例(就是赋值一个东西)访问。
  • 实例化之后实例属性只能通过实例名修改

在这里插入图片描述

静态方法

  • 静态(static)可以修饰属性和方法。
  • 称为静态属性(类属性)、静态方法(类方法)。
  • 静态成员是全类所有对象共享的成员。
  • 在全类中只有一份,不因创建多个对象而产生多份,任何对象修改,都会影响其它对象。

在这里插入图片描述

@staticmethod

Mayuri方法是类的静态方法, (静态方法是不能访问实例属性的)

要调用执行该类的静态方法,像这样就可以了

    @staticmethod
    def Mayuri():
        print('嘟嘟噜~~~~~~')
SteinsGate.Mayuri()
嘟嘟噜~~~~~~

注意: 如果你的实例属性名称 和 静态属性重复了 ,通过类实例访问该属性,访问的是实例属性。

 __int__()

创建好实例后 立即就要 执行 的方法,所以称之为初始化方法。

    def _init_(self,labnum,mantra):     
        self.labnum =labnum        # 成员编号    (self参数变量指向的就是实例对象本身)
        self.mantra = mantra        # 口头禅

Mayuri = SteinsGate('No.002','嘟嘟噜~~~~~~')
print(Mayuri.labnum)
print(Mayuri.mantra)

__del__()

del 用于删除对象。在 Python,一切都是对象,因此 del 关键字可用于删除变量、列表或列表片段等。

class Timemachine:
  def __init__(self):         #=0
    print("调用 __init__() 方法构造对象")
  def __del__(self):
    print("调用__del__() 销毁对象,释放其空间")
organ = Timemachine()           #+1
# 变量就对应了一个 Timemachine 类型 的实例对象,具有Timemachine类的一切属性和方法。
cl = organ                      #+1
del organ                        #-1
print("***********")
调用 __init__() 方法构造对象
***********
调用__del__() 销毁对象,释放其空间
原理:

每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,

如果有变量引用该实例对象,其计数器的值会加 1,依次类推;

反之,每当一个变量取消对该实例对象的引用,计数器会减 1。

如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,

此时 Python 就会自动调用 __del__() 方法将其回收。

根据访问限制的不同,类中的属性分为公有属性,私有属性和受保护属性

受保护属性 

以1个下划线为开头是受保护属性

  • 只有类及其子类可以访问。此变量不能通过from XXX import xxx 导入
class Okabe:
    _intelligence_quotient=250
    def __init__(self):
        print("初始化对象智商:",Okabe._intelligence_quotient)
Okabe = Okabe()
print("查询智商:",Okabe._intelligence_quotient)
初始化对象智商: 250
查询智商: 250

受保护属性可以通过实例名查询

特殊属性

以两个下划线为开头 以两个下划线为结束 

def __init__(self):

(过于高深,课本没讲)

                                         

私有属性

以两个下划线为开头 不以两个下划线为结束 

  • 只允许类本身访问,连子类都不可以访问
class Kurisu:
    __old = "18岁"
    print("初始化对象年龄:", Kurisu.__old)
Kurisu = Kurisu()
print("查询年龄:", Kurisu.__old)
Traceback (most recent call last):
  File "C:/Users/26025/pythonProject/自定义类.py", line 23, in <module>
    class Kurisu:
  File "C:/Users/26025/pythonProject/自定义类.py", line 25, in Kurisu
    print("初始化对象年龄:", Kurisu.__old)
NameError: name 'Kurisu' is not defined

注意:在类外,不允许直接访问私有数据,但可以通过“实例名._类名__私有属性名”访问

class Kurisu:
    __old = "18岁"
    print("初始化对象年龄:", Kurisu.__old)
Kurisu = Kurisu()
print("查询年龄:", Kurisu._Kurisu__old)
初始化对象年龄: 18岁
查询年龄: 18岁

进程已结束,退出代码0

但可以使用“类的实例名._类名__私有属性名”访问

公有属性

其他符合命题规则的标识符

三大特性

1.单继承性

括号参数用于制定要继承的基类,默认基类为object(所有对象的根基类,定义了公用方法的默认实现)

  • 子类会自动拥有父类的一切属性和方法
  • 子类默认无__init__(),则会直接继承父类的
class SteinsGate:
    laboratory = '秋叶原'  # 地方属性  公共特征
    country = '日本'  # 国家属性
    def __init__(self,labnum,mantra):     #__int__创建好实例后 立即就要 执行 的方法,所以称之为初始化方法。)
        self.labnum  =labnum        # 成员编号    ( self 参数变量 指向的 就是 实例对象 本身 )
        self.mantra = mantra        # 口头禅
        print(self.labnum)
        print(self.mantra)

class Okabe(SteinsGate):
    pass
Okabe = Okabe(labnum="No.001",mantra="El psy congroo")
No.001
El psy congroo

 子类调用基类的__init__()初始化(覆盖)数据时。需要使用super()才能调用回父类的__init__函数

class SteinsGate:
    laboratory = '秋叶原'  # 地方属性  公共特征
    __country = '日本'  # 国家属性
    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')
    def __init__(self,labnum,mantra):     #__int__创建好实例后 立即就要 执行 的方法,所以称之为初始化方法。)
        self.labnum  =labnum        # 成员编号    ( self 参数变量 指向的 就是实例对象本身 )
        self.mantra = mantra        # 口头禅
        print(self.labnum)
        print(self.mantra)

class Okabe(SteinsGate):
    __age="18"
Okabe1 = Okabe(labnum="No.001",mantra="El psy congroo")
print(Okabe1.laboratory)
print(Okabe1._Okabe__age)
# print(Okabe._Okabe__country) #不能调用,证明是其假私有属性
class Kakali(Okabe):
    def __init__(self,labnum,mantra):
        super(Kakali, self).__init__(labnum,mantra)
        self.labnum = labnum
        self.mantra = mantra

一个子类在继承父类的一切特性的基础上,可以有自己的属性和方法。

父类的私有属性子类是无法被继承的。(假私有属性

No.001
El psy congroo
秋叶原
18
地点报错
None
El psy congroo

进程已结束,退出代码0

子类的初始化方法调用父类的初始化方法 __init__,要传入相应的参数, 像上面那样,然后可以加上自己的特有的初始化代码。如下所示

    def __init__(self,labnum,mantra):
        super(Kakali, self).__init__(labnum,mantra)
        self.labnum = labnum
        self.mantra = mantra
   


    def __init__(self,labnum,mantra):    ## 先调用父类的初始化方法 
        Okabe.__init__(self,labnum,mantra)
        self.labnum = labnum
        self.mantra = mantra

注意:如果子类 没有 自己的初始化方法,实例化子类对象时,解释器会自动调用父类初始化方法

(重写覆盖)

    def __init__(self):
        pass
        

2.多重继承

注意:如果继承的多个父类中有相同的方法名,但在使用中未指明父类,将会以从左到到右的方法搜索使用

class Okabe(SteinsGate):
    __age="18"
    dad_featrue="笨蛋中二病傲娇"
# Okabe1 = Okabe(labnum="No.001",mantra="El psy congroo")

class Kurisu(SteinsGate):
    __old = "18岁"            #父类私有属性不继承子类
    mom_featrue = "天才傲娇"
# Kurisu = Kurisu(labnum="No.004",mantra="才不是助手")

class Kakali(Okabe,Kurisu):
    def __init__(self):      #重写覆盖,不继承父类部分属性
        pass
Kakali=Kakali()
#print(Kakali._Kurisu__age)
print(Kakali.mom_featrue)
print(Kakali.dad_featrue)
天才傲娇
笨蛋中二病傲娇

进程已结束,退出代码0

注意部分继承

1.未继承部分设为父类的私有属性

2.将未继承部分放入__init__()中,再重写覆盖

如果子类 有自己 的初始化方法,实例化子类对象时,解释器就不会自动化调用父类的初始化方法

3.封装性

        举例:外部访问私有属性通过在函数内设定访问私有属性(套娃)

4.多态性

        基类的同一个成员函数或方法在不同的子类中具有不同的表现和行为

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸢一折纸520

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值