• 面向对象--类和实例


    面向对象--类和实例

    一、类的基本概念

      类是用来创建数据结构和新类型对象的主要机制。一个类定义了一系列与其实例对象密切关联的属性.典型的属性包括变量(也被称为 类变量)和函数(又被称为方法)。

      1、class上下文

      class语句用来定义一个类.类的主体语句在类定义同时执行。

    复制代码
    class Account(object):
        """一个简单的类"""
        print("hello")
        account_type = "Basic"
        def __init__(self, name, balance):
            """初始化一个新的Account实例"""
            self.name = name
            self.balance = balance
        def deposit(self, amt):
            "存款"
            self.balance = self.balance + amt
        def withdraw(self, amt):
            """取款"""
            self.balance = self.balance - amt
        def inquiry(self):
            """返回当前余额"""
            return self.balance
    # 执行上述语句,直接打印hello
    复制代码

      在当前Account类中,凡是不含有self的属性和方法,都是直接用Account.属性和Account.方法来访问或执行的。它有些和匿名函数类似。再看下面代码:

    复制代码
    class Account(object):
        """一个简单的类"""
        print("hello")
        account_type = "Basic"
        def sayHello():
            return "hello"
        
    # 直接执行时,会打印hello
    print(Account.account_type)
    Account.sayHello()
    # 打印结果为
    # Basic
    # 'hello'
    复制代码

      结合两个Account类,可以看出:

      1.能够直接用对象.属性和对象.方法执行的,都是类属性和类方法;不能执行(含有self参数)的,都是实例对象的属性和方法,需要实例化对象(或者类.方法传入参数实例对象)才能执行。

      类方法有两个含义:一是给类定义的,属于类内存空间的方法,如Account.sayHello;二是该方法既然是类对象的方法,就能够被类对象和所有实例对象调用。

    复制代码
    class A:
        def __init__(self, *args, **kwargs):
            self.name, self.age, self.gender = args[:3]
        def sayHello(self):
            print("my name is %s, %s, %s." % (self.name, self.age, self.gender))
        
    a = A("Li", 27, "male")
    A.sayHello(a)
    # my name is Li, 27, male.
    复制代码

      2.在class中直接写func()(如print),会在代码解析时直接执行,这说明:类属性和类方法、实例方法(如上例account_type)是在类创建时就生成了。跟有没有实例化对象无关。在类中引用一个类的属性必须使用类的全名。
      3.代码在解析Account类时,就为类对象开辟了内存空间。而此时还没有实例化对象。

      4.类对象作为一个名字空间,存放在类定义语句运行时创建的对象。class语句并不创建类的实例,它用来定义所有实例都应该有的属性。类的名字空间并不是为类主体中的代码(而是实例)服务的。

      2、类装饰器--@staticmethod

      sayHello()这个函数加上前缀@staticmethod,用以标识它属于这个类(而不是普通函数)的方法,这被称为静态方法。

    复制代码
    class AClass(object):
        @staticmethod # 静态态方法修饰符,表示下面的方法是一个静态态方法 
        def astatic(): 
            print('a static method')
    anInstance = AClass()
    AClass.astatic()                    # prints: a static method
    anInstance.astatic()                # prints: a static method
    复制代码

      你完全可以将静态方法当成一个用属性引用方式调用的普通函数,静态方法可以直接被类或类实例调用。它没有常规方法那样的特殊行为(绑定、非绑定、默认的第一个参数规则等等)。任何时候定义静态方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现)。

      3、类装饰器--@classmethod

      @classmethod装饰器来装饰的通常称为类方法,并且第一个固定不变的参数是cls,也就是该类对象自身。

    复制代码
    class ABase(object):
        @classmethod #类方法修饰符
        def aclassmet(cls): 
            print('a class method for', cls.__name__)
    class ADeriv(ABase): 
        pass
    bInstance = ABase()
    dInstance = ADeriv()
    ABase.aclassmet()
    bInstance.aclassmet()
    ADeriv.aclassmet()
    dInstance.aclassmet()
    # 打印结果为
    # a class method for ABase
    # a class method for ABase
    # a class method for ADeriv
    # a class method for ADeriv
    复制代码

      任何时候定义类方法都不是必须的(类方法能实现的功能都可以通过定义一个普通函数来实现,只要这个函数接受一个类对象做为参数就可以了)。避免在类方法中使用了带有self参数的语句,以使cls和self产生混乱,这看起来不伦不类。

      4、类装饰器--@property

      @property用于将一个实例方法变为属性访问。即调用方式由实例.方法()调用变为实例.方法。

    复制代码
    class Goods:
        def __init__(self, price, discount):
            self.__price = price
            self.discount = discount
        
        @property
        def price(self):
            return self.__price * self.discount
        
        @price.setter
        def price(self, newprice):
            self.__price = newprice
    
        @price.deleter
        def price(self):
            del self.__price
            
    apple = Goods(20, 0.8)
    print(apple.price) # 16
    apple.price = 30   # 看起来像是对self.price重新赋值,但是调用了self.price方法来设置
    print(apple.price) # 24
    # del apple.price
    复制代码

      5、类的名称空间

      所有位于class语句中的代码都在特殊的命名空间中执行--类命名空间(class namespace)。这个命名空间可由类内所有成员访问。

    复制代码
    class MemberCounter(object):
        members = 0
        def init(self):
            MemberCounter.members += 1
            print(MemberCounter.members)
    
    m1 = m2 = m3 = MemberCounter()
    m1.init()
    m2.init()
    m3.init()
    # 打印结果为
    # 1
    # 2
    # 3
    复制代码

      6、类是如何产生的

      当解释器执行到class关键字时,会扫描class上下文的代码语句(类名,类所属类,内容),并交由解释器底层,来通过object实现类的创建。这一过程在class上下文结束时已经完成了。

    复制代码
    class_name = "Foo"    # 类名
    class_parents = (object, )   # 基类
    # 类主体
    class_body = """
    name = "Foo"
    def __init__(self, x):
        self.x = x
    def hello(self):
        print("Hello")
    """
    class_dict = {}
    # 在局部字典class_dict中执行类主体
    exec(class_body, globals(), class_dict)
    # 创建类对象Foo
    Foo = type(class_name, class_parents, class_dict)  # type可以指定
    Foo("X").hello()
    复制代码

      exec和Foo=type()两行模拟了解释器实现类的过程。当我们写class时,解释器会自动查找class_name,class_parents(默认是metaclass=type)和class_body,并在扫描到class上下文结束时,调用type类创建我们写的Foo类。

    二、实例的基本概念

      1、类和实例的关系

      

      1.类对象通过Class()来实例化对象(如上面的MemberCounter(),即类对象加括号,以此为例)。

      2.类对象内存空间和实例对象的内存空间是相互独立的,但实例对象保留了对类内存空间的引用和访问。也即类内存空间、类属性和方法能够被其实例化的对象访问。

    复制代码
    class MemberCounter:
        members = 0
        # MemberCount.__init__
        # 写__init__(self)只是对MemberCounter.__init__(类对象的特殊方法)的重写
        # 不写__init__(self),在实例化对象时直接调用MemberCounter.__init__
        # def __init__(self): 
        # pass
        def init(self):
            MemberCounter.members += 1
            print(MemberCounter.members)
    
    m1 = m2 = m3 = MemberCounter()  # MemberCount()直接调用了MemberCount.__init__方法来实例化对象
    m1.init()
    m2.init()
    m3.init()
    复制代码

      2、实例化的过程

    复制代码
    class A(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
            print("__init__ has called.")
        
        def __new__(cls, *args, **kwargs):
            print("__new__ has called.")
            return object.__new__(cls)

    a = A("Li", 27)
    # __new__ has called.
    # __init__ has called.
    复制代码

      实例化至少分两个步骤:

      1.调用父类的__new__方法来创建一个实例对象。__new__()始终是一个类方法,接受类对象作为第一个参数。尽管__new__()会创建一个实例,但它不会自动地调用__init__()。

        如果看到在类中定义了__new__(),通常表明这个类会做两件事之一。
        首先,该类可能继承自一个基类,该基类的实例是不变的。如果定义的对象继承自不变的内置类型(如整数、字符串、元组),常常会遇到这种情况,因为__new__()是唯一在创建实例之前执行的方法,也是唯一可以修改值得的地方法。__new__()的另一个主要用途是在定义元类时使用。

    复制代码
    class myStr(str):
        def __new__(cls, value=""):
            u1 = myStr.upper(value)
            print(u1)
            return str.__new__(cls, value.upper())
        @classmethod
        def upper(cls, value):
            return value.upper()
    u2 = myStr("hello")
    print(u2)
    
    """
    HELLO
    HELLO
    
    """
    复制代码

      2.调用自己(或父类)__init__方法来初始化一个实例对象。__init__方法主要用于初始化实例对象的属性,也就是往self.__dict__里添加键值对。在这一过程中,它会调用__setattr__方法。

    复制代码
    class MemberCounter:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        
        def __setattr__(self, old, new):
            print("__setattr__ has called.")
            self.__dict__[old] = new  # 当这一句被隐藏掉时,会发现打印的字典里没有存储任何值
    member = MemberCounter("An", 24)   
    member.gender = "female"
    print(member.__dict__)
    
    """
    __setattr__ has called.
    __setattr__ has called.
    __setattr__ has called.
    {'name': 'An', 'age': 24, 'gender': 'female'}
    """
    复制代码

      3、实例属性

      实例对象的主要作用是设置一些key:value,并调用类内存空间中定义好的实例方法来做一些事情。__dict__也是类的特殊方法,用以查看实例对象(或者类对象)的属性。实例对象通常以字典的形式保存属性。

    复制代码
    class MemberCounter:
        members = 0
        def __init__(self, name, age):
            self.name = name
            self.age = age
            
    member = MemberCounter("Li", 27)
    print(member.name, member.age) 
    
    member.gender = "female"  #print(member.gender)
    
    member.name = "An" #print(member.name)
    
    print(member.__dict__) # 查,字典操作
    
    del member.name  #print(member.__dict__)
    
    # Li 27
    # female
    # An
    # {'name': 'An', 'age': 27, 'gender': 'female'}
    # {'age': 27, 'gender': 'female'}
    复制代码

      4、实例方法

      实例方法的第一个参数必须为self。

    复制代码
    class MemberCounter:
        members = 0
        records = {}
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def sayHello(self):
            print("Hello, my name is %s, %s." % (self.name, self.age))
            
    member = MemberCounter("Li", 24)
    member.sayHello()
    复制代码

      5、类和实例的私有属性[数据隐藏]

      以__xx格式定义的实例属性和方法被称为私有属性或方法。这样系统会自动生成一个新的名字 _Classname__xx 并用于内部使用。所谓的私有(内部属性),实际上都是公开的。

    复制代码
    class Person:
        __country = "China"
        def __init__(self, name, age):
            self.name = name
            self.__age = age
            
    print(Person.__dict__)
    print(Person._Person__country)
    print(Person.__dict__["_Person__country"])
    
    Li = Person("Li", 27)
    print(Li.__dict__)
    print(Li.name)
    print(Li._Person__age)
    print(Li.__dict__["_Person__age"])
    复制代码

    三、3个面试题

      1、请说出下面代码打印结果并予以解释

    复制代码
    class Foo:
        def __init__(self):
            self.func()
        def func(self):
            print('in Foo')
    
    class Son(Foo):
        def func(self):
            print('in son')
    
    s = Son()
    # in son
    复制代码

      解释: 在实例化对象时,如果没有定义__init__方法,则会查找并调用父类中的__init__方法。此时实例对象已经由object.__new__创建,命名为self。于是self.func()会调用s中的func()方法。

      2、请说出下面代码打印结果并予以解释

    复制代码
    class Foo:
        def __init__(self):
            self.__func()    # self._Foo__func
        def __func(self):
            print('in Foo')

    class Son(Foo):
        def __func(self):    # _Son__func
            print('in son')

    s = Son()
    # in Foo
    复制代码

      解释: 私有方法和私有属性,在其被访问或执行时,会在当前的class上下文中被强制转化成带有当前classname的新属性。因此,self.__func()在被执行前已被强制转换成self._Foo__func。

    复制代码
    print(Foo.__dict__)
    """
    {
        '__module__': '__main__', 
        '__init__': <function Foo.__init__ at 0x110523510>, 
        '_Foo__func': <function Foo.__func at 0x10d022730>, 
        '__dict__': <attribute '__dict__' of 'Foo' objects>, 
        '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 
        '__doc__': None
    }
    """
    复制代码

      3、请用__new__方法实现单例模式

    复制代码
    class Person:
        __isinstance = None
        def __init__(self, name):
            self.name = name
        
        def __new__(cls, *args, **kargs):
            if not cls.__isinstance:
                obj = object.__new__(cls)
                cls.__isinstance = obj
            return cls.__isinstance
        
    alex = Person("alex")
    egon = Person("egon")
    print(id(egon))
    print(id(alex))
    print(alex.__dict__)
    print(egon.__dict__)
    """
    4514525024
    4514525024
    {'name': 'egon'}
    {'name': 'egon'}
    """
    复制代码

      说明,单例模式只会开辟一个实例内存,不管创建多少个实例,都会覆盖这个内存空间。

    四、python3的继承和组合

      1、多继承问题

      python3的多继承遵循广度优先算法。它会保证每个节点从左到右,从下到上都只访问一次,并找到最近的父类进行继承。所有的节点都必须访问并且都只访问一次。

        

    class A:
        def f(self):
            print('in A')
    
    class B(A):
        pass
        # def f(self):
        #     print('in B')
    
    class C(A):
        pass
        # def f(self):
        #     print('in C')
    
    
    class D(B,C):
        pass
        # def f(self):
        #     print('in D')
    
    class E(C):
        # pass
        def f(self):
            print('in E')
    
    class F(D,E):
        pass
        # def f(self):
        #     print('in F')
    
    d = D()
    d.f()
    print(F.mro())
    mro

      2、super继承

      self是子类实例化的对象,在对父类不初始化时,调用父类的实例方法只是"借壳生蛋"。当一个子类实例被创建时, 基类的__init__()方法并不会被自动调用。

      super继承:super用来解决python钻石多重继承出现的基类重复调用的问题。在Python3中,直接写super().__init__(*args, **kwargs)。

    复制代码
    class B:
        varB = 42
        def method1(self):
            print("Class B : method1")
        def method2(self):
            return B.varB * 2
        
    class A(B):
        varA = 3.3
        def method3(self):
            print("Class A : method3")
            B.method1(self)  # 注意,这里的self是A()初始化后的a,B类没有初始化,直接把a当做self传递进去了
            return B.method2(self)
    
    a = A()
    print(a.method3())
    
    """
    Class A : method3
    Class B : method1
    84
    """
    复制代码
    复制代码
    class B:
        def __init__(self, name, age, *args, **kwargs):
            self.name = name
            self.age = age 
            self.salary = 20000
        def method1(self):
            print("My name is {}, {}, salary {}.".format(self.name, self.age, self.salary))
        
    class A(B):
        def __init__(self, *args, **kwargs):
            self.name, self.age, self.gender = args
            super().__init__(*args, **kwargs)   # 第一种继承方法super().__init__(*args, **kwargs)
            # B.__init__(self, *args, **kwargs)   # 第二种写法
    
    a = A("Li", 27, "male")
    a.method1()
    
    """
    My name is Li, 27, salary 20000.
    """
    复制代码

      3、调查继承

      如果想要查看一个类是否是另一个类的子类,可以使用内建的issubcalss函数。如果想要知道已知类的基类,可以直接使用它的特殊属性__bases__。

    print(issubclass(A, B))  # True
    print(A.__bases__)  # (<class '__main__.B'>,)

      可以使用isinstance方法检查和一个实例对象是否是一个类的实例。使用__class__特性查找一个实例对象属于哪个类。

    print(isinstance(a, A))  # True
    print(isinstance(a, B))  # True

      实例被特殊属性__class__链接回它们的类,所属类名可以用__name.__访问类特殊属性__bases__中将类链接到它们的基类,该属性是一个基类元组。这种底层结构是获取、设置和删除对象属性的所有操作的基础。

    复制代码
    class A:
        pass
    class B(A):
        pass
    
    b = B()
    
    print(b.__class__)
    print(b.__class__.__name__)
    print(B.__bases__)
    
    """
    <class '__main__.B'>
    B
    (<class '__main__.A'>,)
    """
    复制代码

     

     
     
  • 相关阅读:
    分页插件PageHelper
    持久层的具体实现
    SSM框架搭建
    mysql库中建立存储过程
    安装python2.7
    Spark应用程序第三方jar文件依赖解决方案
    spark2.0.1源码编译
    Hadoop2.7.3源码编译
    Hadoop2.x伪分布式环境搭建(一)
    Linux基础环境的各项配置(三)
  • 原文地址:https://www.cnblogs.com/di2wu/p/9075953.html
Copyright © 2020-2023  润新知