Python中面向对象的概念

时间:2020-12-15 17:22:02

1、语言的分类

1)面向机器

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

2)面向过程

做一件事,排除步骤,第一步做什么,第二步做什么,如果出现A问题,做什么处理,出现b问题,做什么处理。问题规模小,步骤化,按部就班处理。  代表:c语言。

(按照步骤进行处理的。)

面向对象和面向过程的差异(一步一步的走,都有谁做or抽象成为不同的类,谁能做。)

编程是多范式的,面向对象只是一种范式。

3)面向对象ocp

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

三要素:封装,父类有的,子类直接继承,多继承少修改,

继承是为了多复用。单一继承、多继承。Mixin技术

 

2、面向对象

1)定义    (是一种认识世界,分析世界的方法论,用类来表现实实在在的对象。)

c语言是面向过程的,Python和Java是面向对象的。(大型项目的话利用类抽象)面向对象一种是抽象的,一种是具体的。

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

2)类class

类是抽象的概念,是万事万物的抽象,是一类实物的共同特征的集合。

用计算机语言来描述类,就是属性和方法的集合。

3)对象instance、object

对象是类的巨象,是一个实体。

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

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

(吃,是具体的动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法,如果说的是鱼吃人,吃就是鱼类的动作了)

(吃是个动作,许多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已)

(驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。)

4)属性

是对象状态的抽象,用数据结构来描述。

5)操作

他是对象行为的抽象,用操作名和实现该操作方法的描述。

(每个人都有名字,身高,体重等信息,这些信息都是个人的属性,但是,这些信息不能保存在人类中,因为他是抽象的概念,不能保留具体的值。)

(而人类的实例,就是具体的人,他可以存储这些具体的特性,而且可以不同的人有不同的属性)

6)哲学

一切皆对象

对象是数据和操作的封装

对象是独立的,但是对象之间可以相互作用。

目前oop是最接近人类认知的编程范式。

3、面向对象3要素****

1)封装

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

*对外只是暴露一些接口,通过接口访问对象。(可供操作的属性,暴漏的属性)

2)继承

*多复用,继承来的就不用自己写了。(用到父类已经做好的实现和操作,在自己内部无需实现了,相同的就不写了,写自己独有的特点。)

*多继承少修改,ocp(open-closed princile),使用继承来改变,来体现个性。(父类的基础上少修改)。

3)多态

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

人类就是封装:

人类继承自动物类,孩子继承父母的特征,分为单一继承、多继承;

多态,继承自动物类的人类,猫类的操作吃的不同。

 

(类对象)类的定义

(类的对象)类的实例。

4、Python的类

1)定义

Class  ClassName:

语句块                    (放属性和方法,实例的属性。实例化过程。)

(1)必须使用class关键字。

(2)类名必须是用大驼峰命名。

(3)类定义完成后,就产生了一个类对象,绑定了标示符ClassName上。

(4)举例

class MyClass:
    """A example class"""
   
x = 1  #  类属性

    def foo(self):   #类属性foo,也是方法
        return " My Class"

print(MyClass.x)
print(MyClass.foo)
print(MyClass.foo(1))
print(MyClass.__doc__)

 

No1 print:   1

No2 print:   <function MyClass.foo at 0x0000002E421852F0>  (说是个函数类在某个地址里面)

No3 print:  My Class

No4 print:  A example class

 

类属性

 

 

(标示符就是一个属性)

5、类对象及类属性

1)类对象

类的定义就会产生一个类对象。

2)类属性

类定义中的变量和类中定义的方法都是类的属性。

3)类变量

上类中的x是类MyClass的变量。

(加括号调用方法,不加是调用的属性。)

4)总结

MyClass中  x、foo都是类的属性。

foo方法都是类的属性,如同吃是人类的方法。但是每个具体的人才能吃东西,也就说吃人的实例才能调用的方法。

 

foo 是方法对象method,不是普通的函数对象function了,他一般要求至少有一个参数。第一个参数可以是self(self是标示符),这个参数位置就留给了self

6、类的实例

调用MyClass().Show()     (new函数和init函数都调用了)  带有self的都是方法。

调用 a = MyClass()

 

背后是要进行绑定的。

前后都有下划线的称为魔术方法。初始化方法。

 

 

7、实例化

    a = MyClass()

1)         定义方法:

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

tom = MyClass()

Jerry = MyClass()

tom /jerry都是MyClass的实例,通过实例化生成了2个实例。

 

每次实例化后获得实例,是不同的实例,即使是使用同样的参数实例化,也得到不同的对象。

Python类实例化后,会自动调用__init__,这个方法第一个参数都必须留给self,其它参数随意。

2)__init__

3)MyClass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。

作用:对实例进行初始化(self当前对象的本身)。实例带初始化。

 

初始化函数可以多个参数,第一个参数位置必须是self。

__init__()方法不能有返回值,也就是只能是None。

class MyClass:
    """A example class"""
   
x = 1  #  类属性

    def foo(self):   #类属性foo,也是方法
        return " My Class"

# print(MyClass)    #不会被调用
print(MyClass())    #会调用
a = MyClass()     #  会被调用。。结果和2一样。
print(a)

 

No1  print:不会被调用

No2 print :<__main__.MyClass object at 0x0000001101903908>

No2 print :<__main__.MyClass object at 0x0000001101903908>

 

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

    def showage(self):
        print('{} is {}'.format(self.name,self.age))

tom = Person('Tom',20)
cat = Person('cat',12)
tom.showage()                   #  Tom is 20
cat.showage()                   #   cat is 12
print(tom.name,cat.name)        #    Tom cat
cat.age += 1
print(cat.age)                   #   13
cat.showage()                   #   cat is 13

 

 

 

每次实例化后都会生成不同的对象。(id地址不同)

 

 

8、实例对象instance

1)类实例化后一定会获得一个对象,就是实例对象。

tom  cat 就是Person类的实例。

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

类实例化后,得到一个实例对象,实例对象都会绑定方法,调用时采用tom.showage()的方式。函数签名是showage(self)。这个self就是tom,python中会把方法的调用者作为第一参数self作为实参传入。

Self,name就是tom对象的name,name是保存在tom对象上面,而不是Person的类上面,所有称为实例变量。

2)         Self 实例化。

class MyClass:
    def __init__(self):
        print('self in init = {}'.format(id(self)))

c = MyClass()
print('c = {}'.format(id(c)))

 

self in init = 583263140104

c = 583263140104

Self就是调用者,就是c对应的实例对象。

Self这个名字只是一个惯例,可以修改,最好不要修改,影响可读性。

 

 

 

 

 

9、实例变量和类变量

 

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

tom = Person('tom')
cat = Person('cat')

print(tom.name,tom.age)
print(cat.name,cat.age)
print(Person.age)
Person.age = 30
print(Person.age,tom.age,cat.age)

 

tom 3

cat 3

3

30 30 30

 

 

实例变量是每一个实例自己的变量,是自己独有的。类变量是累的变量,是类的所有实例共享和使用的方法。

特殊属性

含义

__name__

对象名

__class__

对象的模型

__dict__

对象的属性的字典

__qualname__

类的限定名

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

 

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

print('class-----------')
print(Person.__class__)
print(sorted(Person.__dict__.items()),end='\n\n')

tom = Person('tom')
print('----instance tom ----')
print(tom.__class__)
print(sorted(tom.__dict__.items()),end='\n\n')

print('-----tom class------')
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end= '\n\n')

 

 

class-----------

<class 'type'>

[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

 

----instance tom ----

<class '__main__.Person'>

[('name', 'tom')]

 

-----tom class------

Person

[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

 

 

 

class Person:
    age = 3
    hight = 170

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

tom = Person('tom')
cat = Person('cat',20)

Person.age = 30
print(Person.age,tom.age,cat.age)

print(Person.hight,tom.hight,cat.hight)
cat.hight = 175
print(Person.hight,tom.hight,cat.hight)
tom.hight +=10
print(Person.hight,tom.hight,cat.hight)

Person.hight +=15
print(Person.hight,tom.hight,cat.hight)

Person.weight = 70
print(Person.weight,tom.weight,cat.weight)

print(tom.__dict__['hight'])
# print(tom.__dict__['weight'])  不可执行,因为后来定义的字典内没有

 

 

30 18 20

170 170 170

170 170 175

170 180 175

185 180 175

70 70 70

180

 

总结:

是类的,也就是这个类的所有实例,其实例都可以访问的到,是实例的,就是这个实例自己,通过类访问不到。

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

实例可以动态的给自己增加一个属性。实例.__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找。

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

一般来说,类变量使用全部大写来命名。

 

特殊属性

 

 

实例的字典里面放的是和self有关的信息,类字典里面放的是类的信息,类的字典的items。

属性访问的:的找自己的找不到自己的找类的。

字典访问的:指定的是自己的字典。

赋值即定义,定义属性。

 

 

 

 

 

Person().normal()实例调用。必须传参数。不用。

Person.normal()类调用。

10、装饰一个类

 

def add_name(cls):
    cls.NAME = name
    return cls

@add_name('tom')
class Person:
    AGE = 3

 

 

def add_name(name):
    def wrapper(cls):
        cls.NAME = name
        return cls
    return wrapper

@add_name('tom')
class Person:
    AGE = 3

 

 

11、类方法和静态方法。

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

1)普通函数:

class Person:
    def normal_method():
        print('normal')
# Person.normal_method()   #可以调用
# Person().normal_method()   #不可以调用

print(Person.__dict__)

 

Person.normal_method()

可以被调用,因为这个方法只是被Person这个名词空间管理的一个普通方法,normal只是一个属性而已。

由于normal_method在定义的时候没有指定self。所以不能完成实例对象的banding,不能用,Person().Normal_method()绑定。  虽然语法对的,但是禁止这么写。

2)类方法

class Person:
    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

Person.class_method()
print(Person.__dict__)

 

 

class=Person(<class '__main__.Person'>)

{'__module__': '__main__', 'class_method': <classmethod object at 0x000000C59D4A3908>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}

 

 

(1)在类定义中,使用@clasmethod装饰器修饰的方法,

(2)必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身。

(3)cls这个标示符可以是任意合法名称,但是为了易读,不建议修改。

(4)通过cls可以直接操作类的属性。

 

(3)静态方法。

class Person:
    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HIGHT)

Person.class_method()
Person.static_method()
print(Person.__dict__)

 

 

class=Person(<class '__main__.Person'>)

170

{'__module__': '__main__', 'class_method': <classmethod object at 0x000000E7C4993908>, 'static_method': <staticmethod object at 0x000000E7C49938D0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}

 

 

(1)staticmethod 静态方法

(2)不需要传参。

(3)在类定义中,使用@staticmethod装饰器修饰的方法。

(4)调用时不会隐身传入参数,静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便管理。

12、方法的调用

class Person:
    def normal_method():
        print('normal')

    def method(self):
        print('{} method'.format(self))

    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HIGHT)

print('---类访问---')
print(1,Person.normal_method())    #  可以调用,返回None。
# print(2,Person.method())    #不可以被调用,必须传入参数,一个self
print(3,Person.class_method())   ##返回None
print(4,Person.static_method())   #返回None
print(Person.__dict__)
print('实例访问')
print('tom')
tom = Person()
# print(1,tom.normal_method())   #不能调用,报错 需要参数
print(2,tom.method())      #可以
print(3,tom.class_method())  #可以
print(4,tom.static_method())   #可以

 

总结:

类几乎可以调用所有内部定义的方法,但是调用普通方法的时候会报错,原因是第一参数必须是类的实例。

实例几乎可以调用所有,普通的函数的调用一般不可能出现,因为不允许定义。

 

类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。

实例可以调用所有类中定义的方法,(静态,类方法),普通方法传入实例本身,静态方法和类方法需要找到实例的类。

 

 

 

 

 

 

 

13、访问控制

1)私有属性(private)

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

    def growup(self,i=1):   #控制逻辑
        if i>0 and i <150:
            self.age +=1
p1 = Person('tom')
p1.growup(20)
p1.age =160
print(p1.age)

 

想通过方法来控制属性,但是由于属性在外部可以访问,就直接绕过方法,直接修改属性。私有属性则解决了这个问题。

 

私有属性:

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

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

    def growup(self,i=1):   #控制逻辑
        if i>0 and i <150:
            self.__age +=1
p1 = Person('tom')
p1.growup(20)
# p1.age =160
print(p1.__age)

 

 print(p1.__age)

AttributeError: 'Person' object has no attribute '__age'

访问不到 __age ..

 

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

    def growup(self,i=1):   #控制逻辑
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
print(p1.getage())

 Print 19

2)私有属性的本质,

外部访问不到,能够动态增加一个__age?

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

    def growup(self,i=1):   #控制逻辑
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())

 

年龄没有被覆盖,全部存在__dict__的字典里面了。

 

私有变量本质:

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

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

    def growup(self,i=1):   #控制逻辑
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())

p1._Person__age =15
print(p1.getage())
print(p1.__dict__)

 

 

28

15

{'name': 'tom', '_Person__age': 15, '__age': 28}

##

3)保护变量

在变量名前面使用一个下划线,称为保护变量。

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

       
tom = Person('tom')
print(tom._age)
print(tom.__dict__)

 

18

{'name': 'tom', '_age': 18}

_age属性没有改变名称,和普通属性一样,解释器不做任何处理,只是开发者共同的约定,看见这样的变量,就如同私有变量,不直接使用。

4)私有方法

参展保护变量,私有变量,使用单下划线,双下划线命名方法。

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

    def _getname(self):
        return self.name

    def __getage(self):
        return self._age

tom = Person('tom')
print(tom._getname())    ##没改名
# print(tom.__getage())    ##m没有次属性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage())   #改名了

 

tom

{'name': 'tom', '_age': 18}

{'__module__': '__main__', '__init__': <function Person.__init__ at 0x000000F1ECDB52F0>, '_getname': <function Person._getname at 0x000000F1ECDB5620>, '_Person__getage': <function Person.__getage at 0x000000F1ECDB56A8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

18

 

5)私有方法的本质

当下划线的方法只是开发者之间的约定,解释器不做任何改变

双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__昂发名。

方法变量都在类的 __dict__ 里面可以找到。

6)私有成员总结;

在python中使用_单下划线或者_双下划线来标示一个成员被保护或者被私有化隐藏起来,但是不管使用什么样的访问控制,都不能真正的组织用户修改类的成员,python中没有决定的安全环保成员或者私有成员。

因此,前道的下划线只是一种警告或者提醒,要遵守这个约定,除非有必要,否则不能修好或者使用保护成员或者私有成员,更不能修改。

 

 

 

 

14、属性装饰器

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

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

    @property
    def age(self):
        return self.__age
    @age.setter
    def age(self,age):
        self.__age = age

tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)

 

 

使用property装饰器的时候,三个方法必须同名。

Property装饰器:

 

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

 

Setter装饰器

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

 

Deleter装饰器

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

 

Property装饰器必须在前,setter  和  delter装饰器在后面。

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

 

其它写法:

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

    def getage(self):
        return self.__age

    def setage(self,age):
        self.__age =age

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

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

tom = Person('tom')
print(tom.age)
tom.age = 25
del tom.age

 

 

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

    age = property(lambda self:self.__age)

tom = Person('tom')
print(tom.age)

 

 

可读、可写。

15、补丁。

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

 

猴子补丁:

在运行时,对属性,方法,函数等进行动态替换。

其目的是为了替换,修改来增强,扩展原有的代码的能力。

慎重使用。

 

 

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

使用猴子补丁的方法,替换了get_score方法,返回模拟的数据。

 

16、对象的销毁。

__del__ 方法,称为析构函数(方法)

 

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

这个方法不能让对象真正的小虎,只是对象销毁的时候会自动调用它。

使用del的语句删除实例,引用计数减1,当引用计数为0时候,会idong调用__del__方法。

import time

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

    def __del__(self):
        print('delete{}'.format(self.name))


def test():
    tom = Person('tom')
    tom.__del__()
    tom.__del__()
    tom.__del__()
    print('=======')
    tom1 = tom
    tom3 = tom1
    print(1,'del')
    del tom
    time.sleep(3)

    print(2,'del')
    del tom3
    time.sleep(3)
    print('-----')

    del tom3
    time.sleep(3)
    print('........')
test()

 

 

由于垃圾回收对象销毁时候,才会真正清理对象,还会再之间自动调用__del__ 方法。除非知道自己的目的,否则不会手动清理;不会手动调用这个方法。

 

17、方法重载。

Python没有重载,不需要重载

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

或者说python本身就是实现了其他语言的重载。

 

18、封装:

面向对象的三要素之一,封装。

封装:

将数据和操作组织到类中,即属性和方法。

将数据隐藏起来,给使用者提供操作(方法),使用者通过操作就可以获取或者修改数据。

Getter和setter。

通过控制访问,暴漏给适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或者私有成员。

 

19、习题:

 

1)随机整数生成类

##初步代码:

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length

    def array(self):
        lst=  [random.randint(self.start,self.end)for i in range(self.length)]
        print(lst)

j = Ran()
j.length=5
j.array()

 

 

###第二步改进控制打印长度的

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length

    def array(self,length=0):
        length = self.length if length<=0 else length
        lst=  [random.randint(self.start,self.end)for i in range(length)]
        print(lst)

j = Ran()
j.array(20)

####三,利用工具实现的方式

import random
class Ran:
    @classmethod
    def array(cls,start = 1,end =100,length =10):
        return [random.randint(start,end)for _ in range(length)]

a = Ran()
print(a.array())

###利用生成器

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length
        self._gen = self._array()

    def _array(self):
        while True:
            yield random.randint(self.start,self.end)

    def array(self,length=0):
        if length <= 0:
            return [next(self._gen)for _ in range(self.length)]
        else:
            return [next(self._gen)for i in range(length)]

j = Ran()
print(j.array(20))
print(j.array())
import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length
        self._gen = self._array()

    def _array(self):
        while True:
            yield [random.randint(self.start,self.end)for _ in range(self.length)]

    def array(self,length=0):
        if length > 0:
            self.length = length
        return next(self._gen)

j = Ran()
print(j.array(20))
print(j.array())

 

2)利用上次的随机数生成20次,两两组和,打印坐标。

import random


class Ran:
    def __init__(self,start=0,end=100,count=20):
        self.start = start
        self.end= end
        self.count = count


    def array(self,count=0):
        count = self.count if count<=0 else count
        lst = [random.randint(self.start,self.end)for i in range(count)]
        return lst

a = Ran()
a.array()

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

# points = zip(a.array(),a.array())
# for x in points:
#     print(x)
points = [Point(x,y)for x,y in zip(a.array(),a.array())]
for p in points:
    print('{}:{}'.format(p.x,p.y))

 

3)车辆信息

class Car:

    def __init__(self, mark, color, price, speed, **kwargs):

        # self.id = genid
        self.mark = mark
        self.color = color
        self.price = price
        self.speed = speed
        self.__dict__.update(kwargs)

class CarInfo:
    cars = []
    def addcar(self,car:Car):
        self.cars.append(car)

    def getall(self):
        return self.cars

c1 = CarInfo()
car = Car('audi','red',100,100)
c1.addcar(car)
print(c1.getall())

 

4、实现温度的处理。

class Temp:
    def __init__(self,t,unit='c'):
        self._c = None
        self._f = None
        self._k =None
        #都先转换为摄氏温度,然后以后访问调用计算其他的温度的值
        if unit == 'k':
            self._k = t
            self._c = self.k2c(t)
        elif unit == 'f':
            pass
        else:
            self._c = t

    @property
    def c(self):
        return self._c

    @property
    def k(self):
        pass

    @property
    def f(self):
        pass
    #温度转换
    @classmethod
    def c2f(cls,c):
        return 9*c/5 +32

    @classmethod
    def f2c(cls,f):
        return 5*(f-32)/9

    @classmethod
    def c2k(cls,c):
        return c +273.15

    @classmethod
    def k2c(cls,k):
        return k -273.15

    @classmethod
    def f2k(cls,f):
        return cls.c2k(cls.f2c(f))

    @classmethod
    def k2f(cls,k):
        return cls.c2f(cls.k2c(k))

print(Temp.c2f(40))
print(Temp.c2k(40))
print(Temp.f2c(40))
print(Temp.k2c(40))
print(Temp.k2f(40))
print(Temp.f2k(40))

t = Temp(37)
print(t.c,t.k,t.f)

5、模拟购物车购物

class Item:
    def __init__(self,**kwargs):
        self.__spec = kwargs

    def __repr__(self):
        return str(sorted(self.__spec.items()))

class Cart:
    def __init__(self):
        self.items = []

    def additem(self,item:Item):
        self.items.append(item)

    def getallitems(self):
        return self.items

mycart = Cart()
myphone = Item(mark='apple',menory='4G')
mycart.additem(myphone)
print(mycart.getallitems())

 

 

一、类的继承

1、概念;

1)面向对象的三要素之一,继承inheritance

 

人类和猫类都是继承动物类。

个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。

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

 

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat:
    def shout(self):
        print('Cat shouts')

class Dog:
    def shout(self):
        print('Dog shouts')

a = Animal()
a.shout()

c = Cat()
c.shout()

d = Dog()
d.shout()

 

Animal shouts

Cat shouts

Dog shouts

 

上面的例子的类虽然有关系,但是定义时候并没有建立这种关系,而是各自完成定义。。

 

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

    def shout(self):
        print('{} shouts'.format(self.__class__.__name__))

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

a = Animal('monster')
a.shout()

class Cat(Animal):
    pass

cat = Cat('garfield')
cat.shout()

class Dog(Animal):
    pass

dog = Dog('ahuang')
dog.shout()
print(dog.name)

 

 

上例可以看出,通过继承,猫类,狗类,直接继承了父类的属性和方法。

2)继承

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

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

 

父类Animal 就是cat的父类,也称为基类,超类。

 

子类

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

 

2、定义

格式:

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

语句块

如果类定义的时候,没有基类列表,等同于继承自object,在python3种,object类是所有对象的根基类。

Class  A:

Pass

   #等价于

Class  A(object):

Pass

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

 

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

特殊属性和方法

含义

举例

__base__

类的基类

 

__bases__

类的基类元组

 

__mro__

显示方法查找顺序,基类的元组

 

mro()方法

同上

Int.mro()

__subclasses__()

类的子类列表

Int.__subclasses__()

3、继承中的访问控制

class Animal:
    __COUNT = 100
    HEIGHT = 0

    def __init__(self,age,weight,height):
        self.__COUNT += 1
        self.age = age
        self.__weight = weight
        self.HEIGHT = height

    def eat(self):
        print('{}eat'.format(self.__class__.__name__))

    def __getweight(self):
        print(self.__weight)

    @classmethod
    def showcount1(cls):
        print(cls.__COUNT)

    @classmethod
    def __showcount2(cls):
        print(cls.__COUNT)

    def showcount3(self):
        print(self.__COUNT)

class Cat(Animal):
    NAME = 'CAT'
    __COUNT = 200

# c = Cat()     函数参数错误,传参错误。
c = Cat(3,5,15)
c.eat()    ##cat eat
print(c.HEIGHT)   ##15
# print(c.__COUNT) ##私有不可访问
# c.__getweight()   #私有不可访问
c.showcount1()    #100因为__是改名的,在实例的类找不到,所以利用继承的。
# c.__showcount2()  #不可以,私有的不能访问
c.showcount3()   #101
print(c.NAME)   # CAT

print('{}'.format(Animal.__dict__))
print('{}'.format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())

 

后四个print

 

1/{'_Animal__getweight': <function Animal.__getweight at 0x000000D34976B0D0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, 'HEIGHT': 0, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'showcount3': <function Animal.showcount3 at 0x000000D34976B268>, 'showcount1': <classmethod object at 0x000000D349769748>, 'eat': <function Animal.eat at 0x000000D349764BF8>, '_Animal__COUNT': 100, '__module__': '__main__', '__init__': <function Animal.__init__ at 0x000000D349764D08>, '_Animal__showcount2': <classmethod object at 0x000000D349769780>, '__doc__': None}

2/{'NAME': 'CAT', '__module__': '__main__', '_Cat__COUNT': 200, '__doc__': None}

3/{'_Animal__COUNT': 101, 'HEIGHT': 15, '_Animal__weight': 5, 'age': 3}

4/[<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

 

从父类继承,自己没有的,就到父类中去找。

私有的都是不可以访问的,但是本质上依然是改了名字放在这个属性的类的__dict__ 中,知道这个新名称就可以直接找到这个隐藏的变量。

 

(类外部是不会被访问的,私有属性,内部可以。)

继承顺序:实例,自己的类,父类。继承类;

 

总结:

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

 

属性查找顺序:

实例的__dict__>>    类__dict__如果有继承==〉父类__dict__.         

dir()来查看继承属性。

4、方法的重写、覆盖override

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat(Animal):
    #覆盖了父类的方法
    def shout(self):
        print('miao')
a = Animal()
a.shout()
c =Cat()
c.shout()

print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

 

 

{}

{}

{'__dict__': <attribute '__dict__' of 'Animal' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None, 'shout': <function Animal.shout at 0x000000E832DF4D08>}

{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x000000E832DF4BF8>}

 

 

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat(Animal):
    #覆盖了父类的方法
    def shout(self):
        print('miao')
    #覆盖本身的方法,调用父类的方法
    def shout(self):
        print(super())
        print(super(Cat,self))
        super().shout()
        super(Cat,self).shout()   #等价于super()
        # self.__class__.__bases__.shout(self)
a = Animal()
a.shout()
c =Cat()
c.shout()

print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

 

 

Animal shouts

<super: <class 'Cat'>, <Cat object>>

<super: <class 'Cat'>, <Cat object>>

Animal shouts

Animal shouts

{}

{}

{'__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'shout': <function Animal.shout at 0x0000001852BD4D08>, '__doc__': None, '__dict__': <attribute '__dict__' of 'Animal' objects>}

{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x0000001852BDB0D0>}

 

Super()可以访问到父类的属性。

 

class Animal:

    @classmethod
    def class_method(cls):
        print('class_method_animal')

    @staticmethod
    def static_method():
        print('statice_method_animal')

class Cat(Animal):
    @classmethod
    def class_method(cls):
        print('class_method_cat')

    @staticmethod
    def static_method():
        print('statice_method_animal')

c = Cat()
c.class_method()
c.static_method()

 

 

class_method_cat

statice_method_animal

 

这些都可以进行被覆盖,原因是字典的搜索顺序。

 

5、继承中的初始化

 

上图中类B定义时候生命继承自类A,则在类B中__base__中是可以看到A的

但是这是和调用类A的构造方法是两回事。

如果B中调用了A的构造方法,就可以拥有父类的属性了。

 

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

class B(A):
    def __init__(self,b,c):
        A.__init__(self,b + c,b -c )
        self.b = b
        self.c = c

    def printv(self):
        print(self.b)
        print(self.a)
f = B(4,5)
print(f.__dict__)
print(f.__class__.__bases__)
f.printv()

 

 

 

{'c': 5, 'a': 9, '_A__b': -1, 'b': 4}

(<class '__main__.A'>,)

4

9在B中调用了父类的__init__函数。  如果父类中定义了__init__,子类中就应该调用他。

 

如果利用自动调用父类的__init__方法呢。

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'

class B:
    pass
b =B()
print(b.__dict__)

 

 

子类没有自己的属性和方法,直接继承。。。会自动调用的。

 

此种类型不会自动调用,需要手动处理调用:

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('Ainit')
class B(A):
    def __init__(self):
        self.b1 ='b1'
        print('B init')
b =B()
print(b.__dict__)

 

B init

{'b1': 'b1'}

正确初始化。

class A:
    def __init__(self,age):
        print('Ainit')
        self.age = age

    def show(self):
        print(self.age)

class B(A):
    def __init__(self,age,weight):

        print('Binit')
        self.age = age +1
        self.weight = weight
        super().__init__(age)
b = B(10,5)
b.show()

 

调用父类的__init__方法,出现在不同的位置,导致出现的结果不同。

 

class A:
    def __init__(self,age):
        print('Ainit')
        self.__age = age

    def show(self):
        print(self.__age)

class B(A):
    def __init__(self,age,weight):

        print('Binit')
        self.__age = age +1
        self.__weight = weight
        super().__init__(age)
b = B(10,5)
b.show()

 

打印出10,原因在__dict__,父类A的show方法中__age h会被解释为_A__age因此显示的就是10,而不是11.

自己私有的属性,就该自己读取和修改,不要借助其他类的方法,即使是父类或者派生类。

       

 

 

继承中的初始化

(继承加覆盖就是多态。)

 

 

二、多继承

1、Python不同的版本的类

Python2.2之前是没有共同祖先的,之后,引入object的类,他是所有类的共同 的祖先object。

Python2中为了兼容,分为古典类(旧式类)和新式类。

Python3中全部都是新式类。

新式类都是继承自object的,新式类可以使用super。

 

2、Ocp原则

多用继承,少用修改。

继承的用途,增强基类,实现多态。

 

多态:

在面向对象中,父类,子类通过继承联系在一起,如果通过一套方法,就可以实现不同的表现,就是多态。

一个类继承自多个类就是多继承,他将会有多个类的特征。

 

3、多继承弊端

多继承很好的模拟了世界,因为事物很少是单一继承的,但是舍弃简单,必然引入复杂性,带来了冲突。

 

如同一个孩子继承了来自父母双方的特征,那么到底眼睛像爸爸还是妈妈呢,孩子究竟该谁多一点呢。

 

多继承的实现会导致编译器设计的复杂度增加,所以很多语言舍弃了类的多继承。

 

C++支持多继承,JAVA也舍弃了多继承。

Java中,一个类可以实现多个接口,一个接口也可以继承多个解耦,Java的接口很纯粹,只是方法的生命。继承者必须实现这些方法,就具有了这些能力,就能干什么。

 

多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个类多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢。

 

解决的方案。

实现多继承的语言,要解决二义性,深度优先或者广度优先。

 

4、Python多继承实现。

Class ClassName(基类列表)

类体

 

左图是多继承,右图是单一继承。

 

多继承带来路径选择问题,究竟继承哪个父类的特征呢。

 

Python使用MRO(method resolution order)解决基类搜索顺序问题。

 历史原因:MRO有三个搜索算法:

经典算法,按照定义从左到右,深度优先策略,2.2之前左图的MRO是MYclass dbaca

新式算法,经典算法的升级,重复的是只保留最后一个,2.2左图的mro是myclassd,b,c,a,object

C3算法,在类被创建出来的时候,就计算出一个MRO有序列表,2.3之后,Python3唯一支持的算法。

左图中MRO是myclass,d,b,c,a,object的列表。

C3算法解决了多继承的二义性。

 

5、多继承的缺点

当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。

Python语法是允许很多继承的,但Python代码是解释执行的,只有执行到的时候,才会发现错误。

 

团队协作开发,如果引入多继承,name代码将会不可控。

不管编程语言是否支持多继承,都应避免使用多继承。

 

6、Mixin类                  (继承加覆盖等于多态。)

类有下面的继承关系。

 

本质上是:多继承的来实现,体现的是组合的设计模式。

 

文档Document类是其他所有文档类的抽象基类;

Word PDF类是Document的子类。

 

为document子类提供打印能力:

方法:

1)在Document中提供print方法。

 

class Document:
    def __init__(self,content):
        self.content = content
   
    def print(self):
        raise NotImplementedError()
   
class Word(Document):pass
class Pdf(Document):pass

 

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

Print是一种能力 ——打印功能,不是所有的Document的子类都需要,所有,从这个角度出发,会有问题的。

 

2)需要打印的子类上增加。

如果在现有子类上直接增加,违反了OCP原则,所以应该继承后增加。

 

##3采用多继承形式。

class Printable:
    def print(self):
        print(self.content)

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

class Word(Document):pass
class Pdf(Document):pass

class PrintableWord(Printable,Word):pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())

pw = PrintableWord('test str')
pw.print()

 

{'__doc__': None, '__module__': '__main__'}

[<class '__main__.PrintableWord'>, <class '__main__.Printable'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

test str

 

应用于网络,文档应该具备序列化的能力,类上就应该实现序列化。

可序列化还分为使用pickle,json,messagepack等

发现,类可能太多了,继承的方式很好。

功能太多,A需要几样功能,B需要几样功能,很繁琐。

3)装饰器。

使用装饰器增强一个类,把功能给类附加上去,那个类需要他,就去装饰他。

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

class Word(Document):pass
class Pdf(Document):pass



def printable(cls):
    cls.print = lambda self: print(self.content)
    return lcs

@printable  #printableWord=printable(printableword)
class PrintableWord(Word):pass

a = PrintableWord()
a.print('ls')

 

 

 

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

class Word(Document):pass
class Pdf(Document):pass



def printable(cls):
    def _print(self):
        print(self.content)
    cls.print = _print
    return cls
 
@printable  #printableWord=printable(printableword)
class PrintableWord(Word):pass

a = PrintableWord()
a.print('ls')

 

 

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

4)Mixin

 

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

class Word(Document):pass
class Pdf(Document):pass

class PrintableMixin:
    def print(self):
        print(self.content,'Mixin')

class PrintableWord(PrintableMixin,Word):pass
print(PrintableWordMixin.__dict__)
print(PrintableWordMixin.mro())

def printable(cls):
    def _print(self):
        print(self.content)
    cls.print = _print
    return cls

@printable  #printableWord=printable(printableword)
class PrintableWord(Word):pass

pw = PrintableWord('test str')
pw.print()

 

6、Mixin类(混合)   实现的是组合。解决的是应该的是缺少什么样的功能。场景和使用范围是不一样的。不出现除了功能以外的属性方法等。(组合模式)

 

Mixin本质上就是多继承。

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

Mixin体现在mro中。

 

在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能来自不同的类提供,需要好多类组合在一起。设计角度说多组合,少继承。

 

Mixin类的使用原则。

不应该显示的出现__init__初始化方法。

通常不能单独工作,因为他是准备混入别的类中的功能的实现。

Mixin类的祖先类应该是Mixin类。

使用时候,Mixin类通常在继承列表的第一个位置,class PrintableWord(PrintableMixin,Word):pass

 

人是具体的实例,必须进行实例化。登录网站,先抽象用户类,每个用户是独立的个体,每个人操作都是不同的。利用面向过程是难以实现的。

Mixin类和装饰器

这两种方式都可以使用,看个人喜好。

如果还需要继承就使用Mixin类的方式。