Python基础语法——对象与类

1.面向对象相关概念简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
  • 和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
  • Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
  • 对象可以包含任意数量和类型的数据。

2.类的定义

(1)关键字:class

(2)语法格式:class <ClassName>:

                               “类的帮助信息” #类文档字符串

                               class_suite  #类体

  • ClassName:类的名称,遵循大驼峰命名规则,类的帮助信息通过ClassName__doc__查看。
  •    class_suite:由类成员变量(),成员方法方法,实例变量组成。
  • eg:

            

      

(3)设计类:

       设计一个卖车的4S店,该怎样做呢?

      

     

   设计一个卖北京现代车的4S店(有很多北京现代品牌的车,比如伊兰特、索纳塔等),该怎样做呢?

这样做,不太好,因为当北京现代又生产一种新类型的车时,又得在CarStore类中修改,有没有好的解决办法呢?

解决办法1.工厂模式:把生产环节重新创建了一个类,这确实比较像是一种编程习惯,但是此种解决方式确实被称作工厂模式简单工厂模式

当买车时,有很多种品牌可以选择,比如北京现代、别克、凯迪拉克、特斯拉等,那么此时该怎样进行设计呢?

工厂方法的定义

定义了一个创建对象的接口(可以理解为函数),但由子类决定要实例化的类是哪一个,工厂方法让类的实例化推迟到子类,抽象的CarStore提供了一个创建对象的方法createCar,也叫作工厂方法

子类真正实现这个createCar方法创建出具体产品。 创建者类不需要知到实际创建的产品是哪一个,选择了使用了哪个子类,自然也就决定了实际创建的产品是什么。

(4)_new__方法

class A(object):
    def __init__(self):
        print("这是 init 方法")

    def __new__(cls):
        print("这是 new 方法")
        return object.__new__(cls)

A()

总结

  • __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供

  • __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例

  • __init__有一个参数self,就是这个__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值

  • 我们可以将类比作制造商,__new__方法就是前期的原材料购买环节,__init__方法就是在有原材料的基础上,加工,初始化商品环节。

(5)单例模式:

如这段代码(上述例子中截取)

  • 当创建一个对象时就调用这个初始化方法即创建一个车场,不符合现实情况。不管创建多少个对象,只有一个车场,称为单例模式。单例模式主要目的是确保某一个类只有一个实例存在。
  • 单例模式要点

           A.某个类只有一个实例、;

           B.必须自行创建这个实例;

           C.必须自行向整个系统提供这个实例

  •   实现单例的方法

          A.Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成pyc文件,当第二次导入时,就会直接加载.pyc文               件,而不会再次执行模块代码,因此我们只需要把相关的函数和数据定义在一个模块中就可以获得一个单例模块了。

             

       将上面的代码保存在文件test1.py中,然后这样使用。

            

     B.使用__new__

        为了类只能出现一个实例,可以使用__new__来控制实例的创建过程

        

       将该类的实例和一个变量_instance关联起来,如果cls._instance为创建实例,否则直接返回cls._instance.

        

c.使用装饰器

类装饰器可以动态的修改一个类或函数的功能,我们可以使用装饰器来装饰某个类,使其只能生成一个实例。

3.类的构造方法和内置属性

(1)构造方法

构造方法值得是创建对象时其本身所运行的函数,,Python使用__int__()方法作为对象的构造方法,用户要在对象内指向对象本身时,可以使用self关键字,即def __int__(self).self代表对象本身。

(2)类的内置属性

  • Classname.__dict__:类内的属性是以字典的形式存储的,__dict__属性为该字典对象的值。
  • Classname.__name__:   __name__属性返回此类的名称
  • Classname.__moudle__:  返回包含此类的模块名称
  • Classname.__base__:返货此类的基类名称,以元组tuple的形式展现

4.类的内置方法

类本身有许多的内置方法,这些的方法的开头与结尾都带__(双下划线) 

  • __init__(self):当创建一个类的实例时,就会自动调用该方法。
  • __str__(self):用来设置对象以字符串类型出现时该如何显示,该函数的返回值是一个字符对象。
  • __repr(self):让对象以可读的形式出现。该方法被rep()内置函数调用。

    

   

  • __getattr__(self,name):该方法用在读取或修改不存在的成员属性的时候。在 Python 中,当访问一个对象的会根据不同的情况作不同的处理,是比较复杂的。一般象a.b这样的形式,python可能会先查找a.__dict__中是否存在,如果不存在会在类的__dict__中去查找,再没找到可能会去按这种方法去父类中进行查找。实在是找不到,会调用__getattr__,如果不存在则返回一个异常。那么__getattr__只有当找不到某个属性的时候才会被调用。因此,你可能会想实现一种机制,当访问一个不存的属性或方法时,自动提供一个缺省值。

         

         

        本例中c对象并没有sas属性,所以执行c.sas时,就会调用__getattr__\()函数,也叫.兜底函数

  • getattr(self,name[,default]):获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,可以在后面添加一对括号。

        

  • __setatrr__(self,name,value):该方法用在设置类属性的值。在对一个属性设置值的时候,会自动调用到这个函数,每个设置值的方式都会进入这个方法。

       

        在对a.value重新设置值100的时候,会再次进入__setattr__方法。

        需要注意的地方是,在重写__setattr__方法的时候千万不要重复调用造成死循环。

        如:

       

  这是个死循环。当我们实例化这个类的时候,会进入__init__,然后对value进行设置值,设置值会进入__setattr__方法,而  __setattr__方法里面又有一个self.name=value设置值的操作,会再次调用自身__setattr__,造成死循环。

      除了上面调用object类的__setattr__避开死循环,还可以如下重写__setattr__避开循环。

          

         

         

  •   __delattr__(self,name)是个删除属性的方法。

         

  • 同样需要注意的是避免赋值时的无限递归问题。因为在__delattr__()中的del语句可能会继续调用该方法,最终导致无限递归。所以在__delattr__()方法中,必须使用__dict__来获取属性并进行赋值,或者访问父类同名属性。所以,有下面几种方式避免无限递归调用。

       

       例如:

      

       http://www.imooc.com/article/270469(关于getattr等函数的区别以及用法详情)

  • 总结:内置函数XXXattr()管理属性

    (A)通过内置函数getattr()、setattr()、delattr()能简单访问、设置、删除对象上的属性。

    先看看它们的帮助文档:

    getattr(...)
        getattr(object, name[, default]) -> value
        Get a named attribute from an object;
        getattr(x, 'y') is equivalent to x.y.
    
    setattr(obj, name, value, /)
        Sets the named attribute on the given object to the specified value.
        setattr(x, 'y', v) is equivalent to ``x.y = v''
    
    delattr(obj, name, /)
        Deletes the named attribute from the given object.
        delattr(x, 'y') is equivalent to ``del x.y''

    (B)给定要操作的对象obj以及要操作的属性名称name。对于getattr()来说,如果要操作的属性不存在默认会报错,可以给定一个default参数表示属性不存在时返回该给定属性值。

    例如,:

    class Person():
        def __init__(self, name):
            self.name = name
    
    p = Person("malongshuai")

    使用getattr()获取name属性和不存在的age属性:

    print(getattr(p, "name"))
    print(getattr(p, "age", 23))

    上面访问age属性时,如果把第三个参数"23"去掉,将抛出异常。

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

    使用setattr()和delattr()设置和删除属性:

    setattr(p, "age", 25)
    print(p.__dict__)
    delattr(p, "age")
    print(p.__dict__)
    
  • __del__(self):该方法用在删除类对象,创建对象后,Python解释器默认调用__init__()方法。当删除一个对象时,Python解释器也会默认调用一个方法,这个方法为__del__()方法。在Python中,对于开发者来说很少会直接销毁对象(如果需要,应该使用del关键字销毁)。Python的内存管理机制能够很好的胜任这份工作。也就是说,不管是手动调用del还是由Python自动回收都会触发__del__方法执行

      

     

如图所示:

          

删除对象的意思就是这个对象所对应的内存空间被释放了

当cat1被删除了,cat2还在,引用计数减掉1而已,内存还不会被释放

因为两个对象的引用都删除了,则内存空间被释放,会调用__del__方

当删除了cat1,内存空间还没有结束,还不会调用__del__方法,当调用完最后一条语句时,内存空间被释放,调用__del__方法

 A.创建多个对象的时候触发__del__方法

    Python解释器释放实例对象的时候,调用该对象的__del__方法

   各个对象被被Python解释器释放对象的时候调用__del__方法

class Person(object):
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("实例对象:%s"%self.name,id(self))
        print("python解释器开始回收%s对象了" % self.name)

print("类对象",id(Person))
zhangsan  = Person("张三")
print("实例对象张三:",id(zhangsan))
print("------------")
lisi  = Person("李四")
print("实例对象李四:",id(lisi))

 B.使用del删除引用时的调用情况

当使用d.el 把内存的所有引用删除立刻调用__del__方法

执行效果

el  对象的引用

C,总结

创建对象后,Python解释器默认调用__init__()方法;

当删除一个对象时,python解释器也会默认调用一个方法,这个方法为__del__()方法

当有1个变量保存了对象的引用时,此对象的引用计数就会加1。

当使用del删除变量指向的对象时,如果对象的引用计数不会加1,比如3,那么此时只会让这个引用计数减1,即变为2,当再次调用del时,变为1,如果再调用1次del,此时会真的把对象进行删除

  • __call__()方法:

  关于 __call__ 方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数、内置函数和类都属于可调用 对象,但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable

如果在类中实现了 __call__ 方法,那么实例对象也将成为一个可调用对象,在Python中,方法也是一种高等的对象。这意味着他们也可以被传递到方法中就像其他对象一样。这是一个非常惊人的特性。 在Python中,一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。 __call__(self, [args...]),允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。注意 __call__ 参数可变。这意味着你可以定义 __call__ 为其他你想要的函数,无论有多少个参数。

__call__ 在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了:

  • __getitem__(self,index):该方法支持列表对象的索引,返回self[index]值。如果在类中定义了__getitem__()方法,那么他的实例对象(假设为P)就可以这样P[key]取值。当实例对象做P[key]运算时,就会调用类中的__getitem__()方法。

     

 

  • __len__():该方法用在len()内置函数中,显示类实例变量的长度。
  • __add__(self,other):该方法计算self+other的值。其中 self 指实例化对象本身,other 指另一个实例化对象本身.

    

    

  • __sub__(self,other):该方法计算sel-other的值
  • __isub__(self,other):该方法计算self-=other的值

      

      

    将类的两个实例X与y相减的结果赋值给实例x

  • __mul__(self,other):该方法计算self*other的值
  • __imul__(self,other):该方法计算self*=other的值(同isub),
  • __mod__(self,other):该方法计算self*other的值
  • __imod__(self,other):该方法计算self*=other的值
  • __neg__(self):该方法计算-self的结果。返回类实例x前加一个符号(-)的结果。
  • __pos__(self):该方法计算+self的结果。返回类实例x前加一个符号(+)的结果。

5.类实例(对象)

类实例是一个Python 对象,它是所有类可以创建的对象,每一个Python对象,都包含识别码,对象类型,属性(也叫作数据成员通过对象名.属性名可以指向某个对象的属性。)方法、数值等属性.

(1)创建类实例:

定义一个Car类;就好比有车一个张图纸,那么接下来就应该把图纸交给生成工人们去生成了

python中,可以根据已经定义的类去创建出一个个对象

创建对象的格式为:

    对象名 = 类名()

创建对象demo:


# 定义类
class Car:

    # 移动
    def move(self):
        print('车在奔跑...')

    # 鸣笛
    def toot(self):
        print("车在鸣笛...嘟嘟..")


# 创建一个对象,并用变量BMW来保存它的引用
BMW = Car()
BMW.color = '黑色'
BMW.wheelNum = 4 #轮子数量
BMW.move()
BMW.toot()
print(BMW.color)
print(BMW.wheelNum)

 

  
 

(2)总结:

  • BMW = Car(),这样就产生了一个Car的实例对象,此时也可以通过实例对象BMW来访问属性或者方法
  • 第一次使用BMW.color = '黑色'表示给BMW这个对象添加属性,如果后面再次出现BMW.color = xxx表示对属性进行修改
  • BMW是一个对象,它拥有属性(数据)和方法(函数)

 (3)一些特殊功能

     使用id()内置函数,可以返回类的识别码(地址)。

     使用type()内置函数,可以返回类的对象类型。

     isinstance(instance_object,class_object):用来测试一个类实例instance_object是否是class_object的子类。

6.类属性与实例属性

(1)类属性类属性就是类对象所拥有的属性,它被所有类对象实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象实例对象访问。

(2)实例属性(对象属性):实例属性是实例对象特有的,类对象不能拥有的。实例属性,不能通过类对象调用  类属性和实例属性混合。

class People(object):
    address = '山东' #类属性
    def __init__(self):
        self.name = 'xiaowang' #实例属性
        self.age = 20 #实例属性

p = People()
p.age =12 #实例属性
print(p.address) #正确
print(p.name)    #正确
print(p.age)     #正确

print(People.address) #正确
print(People.name)    #错误
print(People.age)     #错误

(3)通过实例(对象)去修改类属性

class People(object):
    country = 'china' #类属性


print(People.country)
p = People()
print(p.country)
p.country = 'japan' 
print(p.country)      #实例属性会屏蔽掉同名的类属性
print(People.country)
del p.country    #删除实例属性
print(p.country)

 

   

(4)总结

  • 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性
  • 千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
  • 引用类属性的时候要使用类名。属性名的方式(类内函数的全域名称空间是定义在该函数所在的模块
  • 若类变量的名称与实例变量的名称相同,通过实例对象调用,则优先调用实例变量,若通过类对象调用则优先调用类变量

       

       

    (5)修改属性

  • 修改类属性:类名.属性名=新值
  • 修改实例属性:对象名.属性名=新值
  • 通过类方法修改类属性:

        类方法是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对                  象 ,一般以cls作为类对象参数,能够通过实例对象和类对象去访问。

       

        

  • 通过实例方法修改实例属性:

         

7.保护对象的属性以及私有属性和私有方法的访问方法

(1)如果有⼀个对象,当需要对其进⾏修改属性时,有2种⽅法

  • 对象名.属性名    =    数据    ---->直接修改
  • 对象名.⽅法名()    ---->间接修改

2)为了更好的保存属性安全,即不能随意修改,⼀般的处理⽅式将属性定义为私有属性 添           加⼀个可以调⽤的⽅法,供调⽤

  •  私有属性:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。 在类内部的方法中使用时 类名.私有属 性名调用。
  •  私有方法:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。 在类的内部调用私有方法:类名.私有属 方法
  • 无法访问私有属性的原因是:python对私有属性的名字进行了修改 ,而实际上只是进行了重写, 这样做的好处是:防止子类修改基类的属性或者方法

  eg1:类内部普通方法可以访问私有属性和私有方法,然后在调用普通方法来访问私有属性或方法

         

     eg2:

        

        

eg3:类的外部访问私有属性或方法:

  • 对象名._类名__属性名/方法名(不建议这样做)

      

     

  •  

(3)总结: 

  • 默认情况下,Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量。
  • 在python中定义私有变量只需要在变量名或函数名前加上 ”__“两个下划线,那么这个函数或变量就是私有的了。
  • 在内部,python使用一种 name mangling 技术,将 __membername替换成 _classname__membername,也就是说,类的内部定义中,所有以双下划线开始的名字都被"翻译"成前面加上单下划线和类名的形式。
  • 例如:为了保证不能在class之外访问私有变量,Python会在类的内部自动的把我们定义的__spam私有变量的名字替换成为_classname__spam(注意,classname前面是一个下划线,spam前是两个下划线),因此,用户在外部访问__spam的时候就会提示找不到相应的变量。   python中的私有变量和私有方法仍然是可以访问的;
    • 访问方法如下:
    • 私有变量:实例._类名__变量名
    • 私有方法:实例._类名__方法名()
  • 其实,Python并没有真正的私有化支持,但可用下划线得到伪私有。   尽量避免定义以下划线开头的变量!
    • (1)_xxx      "单下划线 " 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量,需通过类提供的接口进行访问;不能用'from module import *'导入
    • (2)__xxx    类中的私有变量/方法名 (Python的函数也是对象,所以成员方法称为成员变量也行得通。)," 双下划线 " 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
    • (3)__xxx__ 系统定义名字,前后均有一个“双下划线” 代表python里特殊方法专用的标识,如 __init__()代表类的构造函数。
  • 私有的属性,不能通过对象直接访问,但是可以通过⽅法访问
  • 私有的⽅法,不能通过对象直接访问
  • 私有的属性、⽅法,不会被⼦类继承,也不能被访问
  • ⼀般情况下,私有的属性、⽅法都是不对外公布的,往往⽤来做内部的 事情,起到安全的作⽤
     

8.类的继承以及调用父类方法

  • 所谓类的继承,就是新类继承旧类的属性方法,这种行为称为派生子类,继承类称为派生子类。继承的新类称为派生类,被继承的旧类称为基类,当用户创建派生类后,就可以在派生类内新增或改写基类的任何方法。
  • 派生类的构造方法必须调用基础类的构造方法,并使用完整的基类名称。
  • 名称空间的搜索顺序:类的实例——类——基类。

       

       

    上例代码分析:

       A("王晓琳").printName()调用A类的printName()函数

      B(“张一飞”).printName()会先调用B类的printName()函数,因为已经找到了一个printName()函数,所以不会继续往A类找。

      C(“刘天佑”).printName()会先调用C类的printName()函数,因为已经找到了一个printName()函数,所以不会继续往B与A类找。

  • 在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程 序中便可以描述为猫和狗继承⾃动物;同理,波斯猫和巴厘猫都继承⾃猫, ⽽沙⽪狗和斑点狗都继承⾜够,如下如所示:

  (1)单继承及调用父类方法

       class  <派生类名>  (基类名)

       

     

  • 虽然⼦类没有定义    __init__    ⽅法,但是⽗类有,所以在⼦类继承⽗类的 时候这个⽅法就被继承了,所以只要创建Bosi的对象,就默认执⾏了那 个继承过来的    __init__    ⽅法
  • ⼦类在继承的时候,在定义类时,⼩括号()中为⽗类的名字 ⽗类的属性、⽅法,会被继承给⼦类

   单继承使用super调用父类方法:super().方法名=父类名.方法名

    

(2)多继承及调用父类方法

   多继承,即⼦类有多个⽗类,并且具有它们的特征.

  语法格式:

  class <类名称>  [(基类1,基类2,基类3)]

          [ “文件字符串"]

         <语句>

  • 多继承的使用注意事项    

  

子类从多个父类派生,而子类又没有自己的构造函数时,

  • 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
  • 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。
  • 如果不同的父类中存在 同名的方法子类对象在调用方法时,会调用哪一个父类中的方法呢?
  • Python 中的 MRO —— 方法搜索顺序

    • Python 中针对  提供了一个内置属性 __mro__ 可以查看方法搜索顺序
    • MRO 是 method resolution order,主要用于在多继承时判断 方法、属性 的调用 路径
    • 输出结果
    • (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
      
      在搜索方法时,是按照 __mro__ 的输出结果 从左至右 的顺序查找的
    • 如果在当前类中 找到方法,就直接执行,不再搜索
    • 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
    • 如果找到最后一个类,还没有找到方法,程序报错
  •  不使用super调用父类方法,使用父类名.方法名的形式。

          

          

  • .使用super调用父类中的方法(在python多继承中,利用super().父类方法,可以调用所有父类

 super() 实际上做了啥呢?简单来说就是:提供一个 MRO 以及一个 MRO 中的类 C , super() 将返回一个从 MRO 中 C 之后的类中查找方法的对象。

9.重写父类方法

所谓重写,就是⼦类中,有⼀个和⽗类相同名字的⽅法,在⼦类中的⽅法会 覆盖掉⽗类中同名的⽅法
继承父类的功能,不是想要的则重写父类方法。重写时与要重写的类的名称要相同。

重写父类的方法的目的是为了给他扩展功能

基类中定义的harvest()方法,无论派生类是什么水果都显示"水果…",如果想要针对不同水果给出不同的提示,可以在派生类中重写harvest()方法。例如,在创建派生类Orange()时,重写harvest()方法如下:

10.类的多态

  • 所谓类的多态就是指,有类可以有多个名称相同参书类型却不同的函数,Python并没有明显的多态性,因为Python函数的参数声不必声明数据类型,但是Python利用因为使用动态数据类型仍然可以处理对象的多态。为Python使用动态因数据类型所以所以Python必须等到运行函数时才能知道该函数的类型,这种特性称为运行期绑定。Python不允许类内有多个名称相同,参数不同的的函数存在,在Python的类内声明多个名称相同,参数却不同的函数,Python会使用类内最后一个声明的函数。
  • Python是一种弱类型的语言,对于弱类型的语言,变量并没与声明类型,因此同一个变量,完全可以在不同的时间引用不同的变量。当一个对象在调用不同的方法时完全可能呈现多种行为()具体呈现那种行为由该变量引用的对象决定。

上例中,当调用myClassL类中的handle 函数时,Python会使用有三个参数的函数handle(self,x,y,z).因此只提供一个参数时,会出现错误。

解决这个问题可以根据函数的参数数目决定调用哪一个函数。

    

11.类的封装

(1)所谓类的封装,就是指类将其属性(变量与方法)封装在该类内,只有该类中的成员,才可以使用该类中的其他成员。这种被封装的变量与方法,称为该类的私有变量与私有方法。Python类中的所有变量与方法都是公用的。只要知道该类的名称与该变量或方法的名称,任何外部对象都可以直接存取类中的属性与方法。

  (2)类中所有的属性都存储在该类的名称空间内。如果在类中存储了一个全域变量的值,此值就会被放置在该类的名称空间内。即使以后次全域名的值被改变,类内的值仍然不变。

12.静态方法、类方法和实例方法

(1)静态方法: 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用。 静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,主要是一些逻辑属于类,但是和类本身没有交互,是在类中封装的一个额外的功能,相当于类中一个独立的函数。即在静态方法中,不会涉及到类中的方法和属性的操作。可以理解为将静态方法存在此类的名称空间中。

   

   

如上,使用静态函数,既可以将获得时间的函数功能与实例解绑,我想获得当前时间的字符串时,并不一定需要实例化对象,此时更像是一种名称空间。

我们可以在类外面写一个简单的方法来做这些,但是这样做就扩散了类代码的关系到类定义的外面,这样写就会导致以后代码维护的困难。

静态方法不需要使用实例对象的属性和方法,也不需要类的属性和方法,所以无论往静态方法中传一个self还是一个cls都会加载对应的资源,而静态方法有不使用,可以节约资源。

对比代码

 静态函数可以通过类名以及实例两种方法调用! 

(2)类方法: 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmeothd 装饰器。类方法是将类本身作为对象进行操作的方法。类对象和实例对象都有单独的内存空间存储当调用一个类方法时会先加载这个类的内存空间。他和静态方法的区别在于:不管这个方式是从实例调用还是从类调用,它都用第一个参数把类传递过来

(3)普通方法(实例方法): 由对象调用,至少有一个self参数,self代表对象的引用,self指向当前的实例对象,所以只要该方法有self参数,在调用此方法的时候,会先把这个歌对象的内存空间加载进来,以便在该方法中使用该实例对象的属性或方法。

类对象不能调用实例方法。

总结:

(1)

实例方法(普通方法):随着实例属性的改变而改变

类方法(无论是类调用还是实例调用):都是类属性的值,不随实例属性的变化而变化

静态方法:不可以访问类属性,故直接输出传入方法的值

(2)从类方法和实例方法的定义形式可以看出,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法,而实例方法的第一个参数是self,那么通过self引用的可能是类属性,也有可能是实例属性,不过在相同的类属性名和实例属性名相同的情况下,,实例属性优先级高,静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类队形来引用。

(3)不允许类名访问实例属性的原因是:当类里有两个或两个以上的实例对象时,该实例对象指向类,但该类如果想访问实例属性时不知道该访问哪一个实例的属性,即找不到,相当于一对多,不知道是哪一个对象访问,对象访问类属性的时相当于多对一,可以明确的找到一个。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值