• 面向对象


    对象的相关定义:

      对象:对象指的是类的实例。一个对象有自己的状态(属性)行为(方法)和唯一的标识(内存地址);所有相同类型的对象所具有的结构和行为在它们共同的类中被定义。

      状态:包括这个对象已有的属性(通常是类里面已经定义好的),再加上对象具有的当前属性值(往往是动态的)

      行为:指一个对象如何影响外界和被外界影响,表现为对象自身状态的改变和信息的传递。

      标识:值一个对象所具有的区别于所有其他对象的属性(本质上指内存中所创建的对象的地址)

    简言之,对象应具有属性(即状态)、方法(即行为)和标识。标识是在内存中自动完成的,不用去管它,平时主要用到属性和方法。

      类:类描述了所创建的对象共同的属性和方法(数据属性和方法属性)

      实例:类是对象的定义,实例才是真实的物件,是具体的个体。实例只有数据属性,没有方法属性。实例调用的方法是类中的方法,由作用域决定。

    属性:

      类属性:在类中定义的变量,即类的属性。

    >>> class A:
    ...     x = 7    # 类中定义变量x
    ...
    >>> A.x    # 通过类名调用类中变量x
    7

      类的属性本质上就是类中的变量,它的值不依赖于任何实例,只是由类中所写的变量赋值语句确定。所以这个类属性又叫静态变量或静态数据。类属性只会在创建类的时候定义一次,不会因为对象的创建而重新创建。

      类属性可以增加、删除或者修改。

    >>> A.y = 9    # 直接向类A中增加新的属性
    >>> A.y
    9
    >>> del A.x    # 删除类中的已有属性
    >>> A.x
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'A' has no attribute 'x'
    >>> A.y = 99    # 修改类中的属性值
    >>> A.y
    99

      查看类中的内容:

    >>> dir(A)
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y']
    >>> A.__doc__    # 显示类的文档
    >>> A.__name__    # 以字符串的形式返回类的名字
    'A'
    >>> A.__base__    # 类的父类
    <class 'object'>
    >>> A.__dict__    # 以字典的形式显示类的所有属性
    mappingproxy({'__doc__': None, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'y': 99})
    >>> A.__module__    # 类所在的模块
    '__main__'

      类属性的总结:

        类属性跟类绑定,可以自定义、删除、修改值,也可以随时增加类属性;

            每个类都有一些特殊属性。

      创建实例:创建实例,就是调用类。

        当类被调用之后:

        创建实例对象;

        检查是否实现__init__()方法。如果没有实现,返回实例对象;如果实现了__init__(),则调用该方法,并且将实例对象作为第一个参数self传递进去。

      __init__()是一个特殊方法,它一般规定一些属性或者初始化,让类具有一些基本的特征。__init__()方法没有return语句。

      实例属性:与类属性类似,实例所具有的属性叫做实例属性。每个实例的属性定义在该实例的命名空间(内存地址)中,通过实例可以在不同的方法里直接共享实例属性。不同于函数中的局部变量,只能在函数自己内部使用,不能在其他函数中直接调用。

    >>> class A:
    ...     x = 7
    ...
    >>> a = A()
    >>> A.x    # 类属性
    7
    >>> a.x    # 实例属性
    7
    >>> a.x += 1    # 修改实例属性
    >>> a.x
    8
    >>> A.x    # 类属性不变
    7

      类属性和实例属性的区别在于,类属性是直接定义在类中的属性,而实例属性是通过实例化的对象去操作与类中同名的引用。相当于实例新建了一个新的属性,这个属性与类中的属性同名。所以修改实例属性不会影响类属性。

      类属性能够影响实例属性,这是因为实例就是通过调用类来建立的。即实例属性跟着类属性的改变而改变。以上都是类中变量引用不可变对象的结果,比如字符串和数。

      类中变量引用可变数据时,则情形会不同,因为可变数据能够进行原地修改。

    >>> class B:
    ...     lst = [1, 2, 3]    # 类中属性是可变类型
    ...
    >>> b = B()    # 类的实例
    >>> B.lst
    [1, 2, 3]
    >>> b.lst
    [1, 2, 3]
    >>> b.lst.append(9)    # 修改实例的属性
    >>> b.lst
    [1, 2, 3, 9]
    >>> B.lst    # 类属性也变化了
    [1, 2, 3, 9]
    >>> B.lst.append('python')    # 修改类属性
    >>> B.lst
    [1, 2, 3, 9, 'python']
    >>> b.lst    # 实例属性也变化了
    [1, 2, 3, 9, 'python']

      当类中变量引用的是可变对象时,类属性和实例属性都能直接修改这个对象,从而影响另一方的值。

      如果增加一个类属性,相应的也增加了一个实例属性。增加实例属性,类属性不会跟着增加。

    >>> B.y = 'java'    # 直接增加了一个类属性
    >>> b.y    # 实例属性相应的增加
    'java'
    >>> b.z = 99    # 增加一个实例属性
    >>> b.z
    99
    >>> B.z    # 类中没有此属性
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'B' has no attribute 'z'

      以上的实例属性或类属性,都是源自类中的变量所引用的值,是静态数据。还有一种实例属性的生存方法,是在实例创建的时候,通过__init__()初始化方法建立,是动态数据。

      self的作用:类中的任何方法,第一个参数都是self

      看下面的例子:

    >>> class Stu:
    ...     def __init__(self):
    ...         self.name = 'Jack'
    ...         self.age = 18
    ...         print(self)    # 打印self的内存地址
    ...         print(type(self))    # 打印self的类型
    ...
    >>> stu = Stu()    # 实例化时自动执行类中__init__()方法
    <__main__.Stu object at 0x7fc4dd792780>    # self的地址
    <class '__main__.Stu'>    # self的类型
    >>> print(stu)    # 与self对应的实例的地址
    <__main__.Stu object at 0x7fc4dd792780>
    >>> type(stu)     # 实例的类型
    <class '__main__.Stu'>

      上面可以看到,self的内存地址和类型与实例stu完全一致。这说明self就是Stu类的实例,即selfstu引用了同一个实例对象。当创建实例的时候,实例变量作为第一个参数,被python解释器传给了self,所以初始化函数中的self.name就是初始化实例的属性。self和实例可以理解为一个主内、一个主外,实例对象的引用会被传给selfself就是实例在类的内部的形式。

      这样,在类的内部,通过self实例,类中所有方法都能承接self实例对象,它的属性也被带到类中的每个方法之中,实现了数据在类内部的流转。

    方法:

      绑定方法和非绑定方法:

    >>> class Foo:
    ...     def bar(self):
    ...         print('This is a normal method of class')
    ...
    >>> foo = Foo()    # 实例化类对象
    >>> foo.bar()    # 实例调用类中方法
    This is a normal method of class
    >>> Foo.bar(foo)    # 类名调用类中方法,必须传入实例对象
    This is a normal method of class
    >>> Foo.bar()    # 类名调用类中方法,不传入实例对象时,报错
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: bar() missing 1 required positional argument: 'self'

      报错信息告诉我们,bar()方法缺少一个参数self,它是一个实例,所以要传入一个实例。

    >>> foo.bar    # 实例调用方法对象
    <bound method Foo.bar of <__main__.Foo object at 0x7fc4dd792940>>
    >>> Foo.bar    # 类名调用方法对象
    <function Foo.bar at 0x7fc4dd77c9d8>

      通过类来获取方法的时候,得到的是非绑定方法对象;

      过实例来获取方法的时候,得到的是绑定方法对象。

      类方法:

        看一个例子:

    >>> class Foo:
    ...     lang = 'Java'    # 类属性
    ...     def __init__(self):
    ...         self.lang = 'Python'    # 实例属性
    ...
    >>> def get_class_attr(cls):
    ...     return cls.lang
    ...
    >>> if __name__ == '__main__':
    ...     print('Foo.lang:', Foo.lang)    # 打印类属性
    ...     r = get_class_attr(Foo)
    ...     print('get class attribute:', r)    # 打印类属性
    ...     f = Foo()
    ...     print('instance attribute:', f.lang)    # 打印实例属性
    ...
    Foo.lang: Java
    get class attribute: Java
    instance attribute: Python

      上面的例子中,在类Foo中,定义了一个属性lang = 'Java',这是类属性;在初始化方法中,又定义了self.lang = 'Python',这是实例属性。

      在这个程序中,较特殊的函数是get_class_attr(cls),它写在类的外面,而这个函数又只能调用前面的那个类对象,这使得类和函数的耦合性太强,应将类和函数融为一体,写成类方法。

    >>> class Foo:
    ...     lang = 'Java'
    ...     def __init__(self):
    ...         self.lang = 'Python'
    ...     @classmethod    # 定义类方法
    ...     def get_class_attr(cls):
    ...         return cls.lang
    ...
    >>> if __name__ == '__main__':
    ...     print('Foo.lang:', Foo.lang)
    ...     r = Foo.get_class_attr()    # 直接使用类名调用类方法
    ...     print('get class attribute:', r)
    ...     f = Foo()
    ...     print('instance attribute:', f.lang)
    ...     print('instance get_class_attr:', f.get_class_attr())   # 也可使用实例调用类方法
    ...
    Foo.lang: Java
    get class attribute: Java
    instance attribute: Python
    instance get_class_attr: Java

      在上面的程序中,装饰器@classmethod所装饰的方法get_class_attr(cls)和类中的其他方法不同,它的第一个参数的cls(习惯用这个)表示类对象,即参数cls引用的对象类对象Foo

      所谓类方法,就是在类里面定义的方法。该方法由装饰器@classmethod装饰,其第一个参数cls引用这个类对象,即将类本身作为对象传入到此方法中。类方法可以直接用类名调用,也可用实例进行调用。

      静态方法:

    >>> import random
    >>> class Foo:
    ...     def __init__(self):
    ...         self.name = 'Jack'
    ...     def get_name(self, age):
    ...         if self.select(age):    # 通过实例self调用静态方法
    ...             return self.name
    ...         else:
    ...             return 'the name is secret.'
    ...     @staticmethod    # 定义静态方法
    ...     def select(n):
    ...         a = random.randint(1, 100)
    ...         return a - n > 0
    ...
    >>> if __name__ == '__main__':
    ...     f = Foo()
    ...     name = f.get_name(18)
    ...     print(name)
    ...
    the name is secret.

      上面程序中有一个函数select(n),这个函数虽然定义在类中,但它却是一个独立的方法,跟类没有关系。这个方法前要加装饰器@staticmethod,我们称之为静态方法。静态方法不以self作为第一个参数,当调用它的时候,可以同过实例调用,也可以通过类名调用。它存在的意义在于将要在类中调用的函数定于在类的命名空间中,便于维护。

      当类中要定义的某个方法和实例有关系,则定义为实例方法;当类中要定义的某个方法和类属性有关系,则定义为类方法;当类中要定义的某个既和类有关系又和实例有关系,则定义为静态方法。

    继承:

      继承可以使得子类别获得父类别的所有属性和方法,而不需要再次编写相同的代码。子类别可以重新定义新的属性,重写父类中的方法,使得子类别获得与父类别不同的功能。另外,也可为子类别追加新的属性和方法。

    >>> class Person:
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def height(self, m):
    ...         h = dict((['height', m],))
    ...         return h
    ...
    >>> class Girl(Person):
    ...     def get_name(self):
    ...         return self.name
    ...
    >>> if __name__ == '__main__':
    ...     p = Girl('yang')
    ...     print(p.get_name())
    ...     print(p.height(166))
    ...
    yang
    {'height': 166}

      上面的Girl类继承了类Person,那么Girl类就拥有了Person类中的所有方法和属性。当子类创建实例或调用父类中的方法时,也必须按照父类中方法的要求传入参数。

      子类中重写了父类中的方法或属性时,父类中的相应方法或属性将不再被继承到父类中,如果要在子类中继承父类中被重写的方法或属性,怎么办?

    >>> class Girl(Person):
    ...     def __init__(self, name):
    ...         Person.__init__(self, name)    # 子类继承父类中的同名方法
    ...            # super(Girl, self).__init__(name)    # 使用super继承父类中的同名方法
    ...         self.real_name = 'zhangsan'
    ...     def get_name(self):
    ...         return self.name
    ...
    >>> if __name__ == '__main__':
    ...     g = Girl('yang')
    ...     print(g.real_name)
    ...     print(g.get_name())
    ...     print(g.height(166))
    ...
    zhangsan
    yang
    {'height': 166}

      要继承父类中的同名方法或属性时,可以以类方法的方式调用Person.__init__(self, name),也可以以super的方式调用super(Girl, self).__init__(name)。

      多继承父类调用顺序:使用类名. __mro__查看父类的调用顺序。新式类采用广度优先的顺序;旧式类采用深度优先的顺序。

    >>> class Person:
    ...     pass
    ...
    >>> class Stu(Person):
    ...     pass
    ...
    >>> print(Stu.__mro__)    
    (<class '__main__.Stu'>, <class '__main__.Person'>, <class 'object'>)

      接口的作用是定义子类必须实现的方法,python没有直接实现接口,需要依靠abc模块来实现接口的定义与继承。

    多态和封装:

      Python不检查传入对象的类型,这种方式被称为“隐式类型”或“结构式类型”,也被称为“鸭子类型”。“鸭子类型”意味着可以向任何对象发送任何消息,语言只关心该对象能否接收该消息,不强求该对象是否为某一种特定的类型。

      Python中的封装私有化是将准备私有化的属性(方法)的名字前加双下划线。python中的私有化本质上是将以__开头的私有属性重命名为_类名__属性名。也可以以_开头定义私有属性和方法,以这种方式定义,只是一种规范的约定。

    >>> class PrivateMe:
    ...     def __init__(self):
    ...         self.__name = 'Hello'    # 私有化属性
    ...     def get_name(self):    # 类中显式方法
    ...         return self.__name
    ...     @property    # 装饰器property
    ...     def name(self):
    ...         return self.__name
    ...
    >>> if __name__ == '__main__':
    ...     p = PrivateMe()
    ...     print(p.get_name())    # 得到类中私有属性
    ...     print(p.name)
    ...
    Hello
    Hello

      私有化的方法或属性,按约定在类的外面无法调用。如果要调用类中私有属性,可以用property装饰器。用了@property之后,在调用那个方法的时候,用的是p.name的形式,就好像在调用一个属性一样。除此之外,也可在类中定义显式方法,在显式方法中返回或者输出私有属性,再通过调用类中的显式方法得到私有属性。

      私有方法和属性不能被继承:

    >>> class Person:
    ...     def __init__(self):
    ...         self.name = 'zhangsan'
    ...         self.__age = 22    # 私有属性
    ...     def eat(self):
    ...         print('eating...')
    ...     def __sleep(self):    # 私有方法
    ...         print('sleeping...')
    ...
    >>> class Stu(Person):
    ...     pass
    ...
    >>> stu = Stu()
    >>> stu.name
    'zhangsan'
    >>> stu.__age    # 私有属性不能被继承在类中,私有属性被重命名为_Person__age
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Stu' object has no attribute '__age'
    >>> stu.eat()
    eating...
    >>> stu.__sleep()    # 私有方法不能被继承在类中,私有方法被重命名为_Person__sleep
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Stu' object has no attribute '__sleep'

    类的魔法方法:

      __init__()方法:初始化对象的属性。

      __new__()方法:创建对象,只用于创建对象。
      对象在实例化时,分三步完成:
        1.先调用__new__方法创建对象;
        2.创建对象后,对象引用空间存在,此时会在对象引用空间内再调用__init__方法初始化对象,也就是说,只要对象引用空间存在了,有另一个变量也指向此对象空间时,__init__方法也会调用。__init__方法的调用不是通过__new__方法来实现的,而是只要有了对象空间,__init__方法便会调用;
        3.最后返回对象。
      只不过对于__new__方法,它只是用来创建对象,只有python内部才能够创建对象,我们几乎不会去重写它。

    >>> class A:
    ...     def __init__(self):
    ...         print('执行初始化')
    ...     def __new__(cls):    # 重写了父类的__new__方法,没有创建对象的操作,对象不会被创建
    ...         print('执行创建对象')
    ... 
    >>> a = A()    # 先调用重写的__new__方法,方法中没有调用__init__方法,对象也不会初始化
    执行创建对象
    改进代码:
    >>> class A:
    ...     def __init__(self):
    ...         print('执行初始化')
    ...     def __new__(cls):    # __new__方法通过类创建对象,所以参数传入cls
    ...         print('执行创建对象')
    ...         return super().__new__(cls)    # 调用父类__new__方法完成对象的创建
    ... 
    >>> a = A()
    执行创建对象
    执行初始化

      __new__方法通常用来创建单例对象:
        我们知道,每实例化一个对象时,都会创建其自己的引用空间。现在我们要求创建单例对象,即不论实例化多少个对象,它们的引用空间都是同一个,也即所有的对象名都指向同一个对象空间的引用。

    >>> class A:
    ...     __instance = None    # 定义一个类属性,用于存放实例化的对象
    ...     def __new__(cls):
    ...         if cls.__instance == None:    # 如果类属性为空,则执行第一次创建
    ...             cls.__instance = super().__new__(cls)
    ...             return cls.__instance    # 返回创建的对象
    ...         else:    # 如果类属性不为空,则不再执行对象创建,直接返回第一次创建的对象
    ...             return cls.__instance
    ... 
    >>> a = A()
    >>> print(id(a))    # 两个对象名指向了同一个对象的引用空间
    4341814776
    >>> b = A()
    >>> print(id(b))
    4341814776

      只初始化一次对象:
        当创建了单例对象后,每个实例化的对象名都指向了同一个对象空间,但是当在实例化对象并初始化对象时,初始化方法__init__都会被调用,从而执行多次初始化。

    >>> class A:
    ...     __instance = None
    ...     def __new__(cls, name):
    ...         if cls.__instance == None:
    ...             cls.__instance = super().__new__(cls)
    ...             return cls.__instance
    ...         else:
    ...             return cls.__instance
    ...     def __init__(self, name):
    ...         self.name = name
    ... 
    >>> a = A('zhangsan')    # 执行初始化方法__init__
    >>> print(a.name)
    zhangsan
    >>> b = A('lisi')    # 再次执行初始化方法__init__
    >>> print(b.name)
    lisi

      由上面代码可以看到,在实例化对象时,虽然__new__方法只被执行了一次,只创建了一个对象空间,但是由于对象空间已经创建,在实例化不同对象时,都会调用初始化方法,即初始化方法__init__被执行了两次。
      修改代码,使单例对象只初始化一次:

    >>> class A:    
    ...      __instance = None
    ...      __flag = False    # 定义类属性作为判断的条件
    ...      def __new__(cls, name):
    ...          if cls.__instance == None:
    ...              cls.__instance = super().__new__(cls)
    ...              return cls.__instance
    ...          else:
    ...              return cls.__instance
    ...      def __init__(self, name):
    ...          if A.__flag == False:    # 满足条件则执行赋值,不满足就不执行
    ...              self.name = name
    ...              A.__flag = True
    ... 
    >>> a = A('zhangsan')
    >>> print(a.name)
    zhangsan
    >>> b = A('lisi')    # 依然会执行初始化方法__init__,但不执行赋值操作
    >>> print(b.name)
    zhangsan    # 对象的name属性没有被修改

      __str__()方法:打印对象名时自动调用此方法。

        没有定义__str__()方法:

    >>> class Person:
    ...     def __init__(self, name, age):
    ...         self.name = name
    ...         self.age = age
    ...
    >>> p = Person('zhangsan', 22)
    >>> print(p)    # 类中没有定义__str__()方法,打印对象的地址
    <__main__.Person object at 0x7f94513b26d8>

        定义了__str__()方法:

    >>> class Person:
    ...     def __init__(self, name, age):
    ...         self.name = name
    ...         self.age = age
    ...     def __str__(self):
    ...         return '%s的年龄是%d岁。'%(self.name, self.age)
    ...
    >>> p = Person('zhangsan', 22)
    >>> print(p)    # 定义了__str__()方法,打印对象名时,自动输出__str__()方法的返回值
    zhangsan的年龄是22岁。

     

      __repr__()方法:在解析器中应用。

    >>> class A:
    ...     def __repr__(self):
    ...         return '在解析器中应用'
    ... 
    >>> a = A()
    >>> a
    在解析器中应用

      当类中同时定义了__str__()和__repr__(),打印对象名时,会调用__str__()方法,而当类中只定义了__repr__()方法时,打印对象名时,会调用__repr__()作为替代。

    >>> class A:
    ...     def __str__(self):
    ...         return '打印对象名时调用__str__'
    ...     def __repr__(self):
    ...         return '调用__repr__'
    ... 
    >>> a = A()
    >>> print(a)
    打印对象名时调用__str__
    >>> class B:
    ...     def __repr__(self):
    ...         return '调用__repr__作为替代'
    ... 
    >>> b = B()
    >>> print(b)
    调用__repr__作为替代

      __del__()方法:析构方法,在对象结束前会被自动调用,可用来作对象结束前的善后工作。一般很少使用,因为我们很难明确对象什么时候结束。

       

      __call__()方法:在类中定义了此方法,则类的实例就可调用。函数和方法中都会有此方法,表示函数和方法都是可调用的。

    >>> class A:
    ...     def __call__(self):
    ...         print('我执行啦!')
    ... 
    >>> a = A()
    >>> a()
    我执行啦!
  • 相关阅读:
    [Bzoj2120]数颜色
    [Bzoj2049][Sdoi2008]Cave 洞穴勘测
    [2019上海网络赛F题]Rhyme scheme
    [2019上海网络赛J题]Stone game
    Codeforces Round #688 (Div. 2) C
    Educational Codeforces Round 99 (Rated for Div. 2) D
    Educational Codeforces Round 99 (Rated for Div. 2) B
    Codeforces Round #685 (Div. 2) D
    Codeforces Round #685 (Div. 2) C
    Codeforces Round #685 (Div. 2) B
  • 原文地址:https://www.cnblogs.com/wgbo/p/9800983.html
Copyright © 2020-2023  润新知