day6-面向对象进阶篇

在面向对象基础篇中,我们讲述了面向对象的很多基础知识,但也有很多限于篇幅并没有涉及到,这里通过进阶篇来完善补充。本篇将详细介绍Python 类的成员、成员修饰符。

一. python类的成员

以下内容转自http://www.cnblogs.com/wupeiqi/p/4766801.html
类的成员可以分为三大类:变量(属性)、方法和属性方法。

image

有的地方也称变量为字段,成员变量就是普通字段,类变量就是静态字段。

注意:所有成员中,只有成员变量(普通字段)的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个成员变量(普通字段)。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。

1.1 变量

成员变量属于对象(对象实例化的时候就创建出来,一对一的关系),为每个对象私有,不共用。类变量属于类,在类中直接定义(并非在构造函数中定义),也称为类的公有属性,只要类被访问,就已经生成,为类为模板所产生的对象object所共有,是提供给这个类所属的所有对象都可以访问的属性全局仅一份拷贝。成员变量和类变量的定义和用法如下:

  1 class Province(object):
  2 
  3     # 静态字段
  4     country = '中国'
  5 
  6     def __init__(self, name):
  7 
  8         # 普通字段
  9         self.name = name
 10 
 11 
 12 # 直接访问普通字段
 13 obj = Province('河北省')
 14 print(obj.name)
 15 
 16 # 直接访问静态字段
 17 print(Province.country)
 18 # 通过对象来访问类变量
 19 print(obj.country)
 20 
 21 结果输出:
 22 河北省
 23 中国
 24 中国
View Code

由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问,也可以通过类的实例化对象来访问,因为属于类中可共享访问的属性】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:

image

由上图可知:

  • 类变量在内存中只保存一份,所以称之为类的公有属性
  • 成员变量与实例化的对象一一绑定,每个对象都有自己专属私有的成员变量(不同的对象,在实例化时传递的成员变量值可能相同,也可能不同,但对于对象来说是唯一的)

应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用类变量(公有属性)

  • 类变量公有属性的修改

类变量作为一种直接在类中定义的公有属性,定义后能否修改呢?答案是肯定的,而且看起来可以通过类和对象两个维度去修改,实际过程如下:

  1 class dog(object):
  2     "dog class"
  3 
  4     nationality = "JP"
  5 
  6     def __init__(self,name):
  7         self.name = name
  8 
  9 d1 = dog("alex")
 10 d2 = dog("sanjiang")
 11 print(d1.nationality,d2.nationality)
 12 print("before change ...")
 13 d1.nationality = "CN"    #对象的d1修改公共属性得值
 14 print(d1.nationality,d2.nationality)
 15 print("after change ....")
 16 dog.nationality = "US"    #dog类修改公共属性的值
 17 print(d1.nationality,d2.nationality)
 18 
 19 #输出
 20 JP JP
 21 brfore change ...
 22 CN JP          #d1对象的公共属性被修改了
 23 after change ....
 24 CN US    #d1对象的公共属性值没有随着类本身的公共属性值修改而修改
View Code

以上代码看起来同时通过类和对象对类的公有属性进行了修改, 但实际上类变量只能通过类来进行修改, 通过对象来修改只是表象, 本质上是对象内部又重新创建了一个局部变量, 它与类变量重名而已, 实际与类变量没有任何关系.
上图把(以下图片和文字转自https://www.cnblogs.com/zhangqigao/articles/6885530.html):

image

对上面的图做一下总结:

  1. 对象d1去访问nationality属性时,如果在成员属性中找不到,就会找公共属性,也就是说自己的属性找不到就去找父亲的属性
  2. d1.nationality="CN" 相当于在自己对象内部又重新创建了一个新的局部变量,这个局部变量已经脱离了class本身,跟这个类已经没有半毛钱关系了,只是名字一样而已,如果不改,还是找全局的。

1.2 方法

python类中的方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同:
  • 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self
  • 类方法:可以通过类和实例调用; 至少一个cls参数;执行类方法时,自动将调用该方法的复制给cls,即cls代表类本身(类似于self代表实例化对象一样);也可以根据需要自定义其他参数,但至少得有一个参数,默认会自动设置为cls
  • 静态方法:可以通过类和实例调用;无默认参数;
  1. 普通方法
    除静态方法与类方法外,类的其他方法都属于实例方法。实例方法需要将类实例化后调用,如果使用类直接调用实例方法,需要显式地将实例作为参数传入。最左侧传入的参数self,是实例本身。
      1 # !/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 __author__ = 'Maxwell'
      4 
      5 class Dog(object):
      6     name = "AGou"
      7 
      8     def __init__(self, name):
      9         self.name = name
     10 
     11 
     12     def eat(self, food):
     13         print('%s is eatting %s' % (self.name, food))
     14 
     15 dog1 = Dog('XiaoHuang')
     16 dog1.eat('hotdog')
     17 
     18 输出:
     19 XiaoHuang is eatting hotdog
    View Code
  2. 类方法
    类方法使用 @classmethod 装饰器,可以使用类(也可使用实例)来调用方法。类只能访问类变量(又叫静态属性),不能访问实例变量。 实际可用于访问限定死的变量,比如朝鲜不允许修改国籍。以下示例一下类只能访问类变量,不能访问示例变量的情况:
    访问实例变量直接报错
      1 # !/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 __author__ = 'Maxwell'
      4 
      5 class Dog(object):
      6     #name = "AGou"
      7 
      8     def __init__(self, name):
      9         self.name = name
     10 
     11     @classmethod
     12     def eat(self, food):
     13         print('%s is eatting %s' % (self.name, food))
     14 
     15 dog1 = Dog('XiaoHuang')
     16 dog1.eat('hotdog')
     17 
     18 输出:
     19 Traceback (most recent call last):
     20   File "D:/python/S13/Day6/c3.py", line 16, in <module>
     21     dog1.eat('hotdog')
     22   File "D:/python/S13/Day6/c3.py", line 13, in eat
     23     print('%s is eatting %s' % (self.name, food))
     24 AttributeError: type object 'Dog' has no attribute 'name'
    View Code
    访问类变量:
      1 # !/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 __author__ = 'Maxwell'
      4 
      5 class Dog(object):
      6     name = "AGou"
      7 
      8     def __init__(self, name):
      9         self.name = name
     10 
     11     @classmethod
     12     def eat(self, food):
     13         print('%s is eatting %s' % (self.name, food))
     14 
     15 dog1 = Dog('XiaoHuang')
     16 dog1.eat('hotdog')
     17 
     18 输出:
     19 AGou is eatting hotdog
    View Code
    前面说过类方法至少得有一个cls参数,可以通过类方法的cls来访问类的属性或方法:
      1 # !/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 __author__ = 'Maxwell'
      4 
      5 class Dog(object):
      6     name = "AGou"
      7 
      8     def __init__(self, name):
      9         self.name = name
     10 
     11     def walk(self):
     12         print('%s is walking' % self.name )
     13 
     14     @classmethod
     15     def class_method(cls, food):
     16         print('%s is eatting %s' % (cls.name, food))
     17         cls.walk(cls)
     18 
     19 dog1 = Dog('XiaoHuang')
     20 dog1.class_method('hotdog')
     21 
     22 输出:
     23 AGou is eatting hotdog
     24 AGou is walking
    View Code

  3. 静态方法
    在类中往往有一些方法跟类有关系,但是又不会改变类和实例状态的方法,这种方法是静态方法。静态方法只是名义上归类管理,实际上在静态方法里访问不了类或实例中的任何属性。静态方法没有默认参数要求,本质上它与类没有什么直接关系。可以通过类或者实例化对象来调用静态方法,如果静态方法的参数中定义了self,无论是通过类还是通过对象来调用,self这个位置参数都不能省略,必须显式传入它自己进去(对象名或者类名)。因此有这样一句话:静态方法与类的唯一关系是,调用它必须通过类或者实例来调用,如果有定义self参数,那么调用的时候self参数不能省略,必须显式传进去
      1 # -*- coding: utf-8 -*-
      2 __author__ = 'Maxwell'
      3 
      4 class Dog(object):
      5     name = "AGou"
      6 
      7     def __init__(self, name):
      8         self.name = name
      9 
     10 
     11     @staticmethod
     12     def eat(self, food):
     13         print('%s is eatting %s' % (self.name, food))
     14 
     15 dog1 = Dog('XiaoHuang')
     16 dog1.eat(dog1, 'hotdog')
     17 Dog.eat(Dog, 'hotdog')
     18 
     19 输出:
     20 XiaoHuang is eatting hotdog
     21 AGou is eatting hotdog
    View Code
    这里如果在调用时不显式传入self参数,运行时会报错TypeError: eat() missing 1 required positional argument: 'food'
    如果静态方法没有定义self参数,也可以正常调用,我们只需要像调用普通函数一样确保参数ok即可:
      1 # !/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 __author__ = 'Maxwell'
      4 
      5 class Dog(object):
      6     name = "AGou"
      7 
      8     def __init__(self, name):
      9         self.name = name
     10 
     11 
     12     @staticmethod
     13     def eat(food):
     14         print('eating %s' % (food))
     15 
     16 dog1 = Dog('XiaoHuang')
     17 dog1.eat('hotdog')
     18 print('-----')
     19 Dog.eat('hotdog')
     20 
     21 输出:
     22 eating hotdog
     23 -----
     24 eating hotdog
    View Code
    可以看到,静态方法没有默认的self和cls参数,可以把它看成是一个普通的函数,我们当然可以把它写到类外面,但这是不推荐的,因为这不利于代码的组织和命名空间的整洁。

1.3 属性方法

以下内容还是继续转载,源自http://www.cnblogs.com/wupeiqi/p/4766801.html

  • 属性方法简介

如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。在方法名前加上@property装饰器,表示此方法为属性方法。属性方法的基本使用如下:

  1 # ############### 定义 ###############
  2 class Foo:
  3 
  4     def func(self):
  5         pass
  6 
  7     # 定义属性
  8     @property
  9     def prop(self):
 10         pass
 11 # ############### 调用 ###############
 12 foo_obj = Foo()
 13 
 14 foo_obj.func()
 15 foo_obj.prop   #调用属性
View Code

image

由属性的定义和调用要注意一下几点:

(1)定义时,在普通方法的基础上添加 @property 装饰器;

(2)定义时,属性仅有一个self参数

(3)调用时,无需括号
        方法:foo_obj.func()
        属性:foo_obj.prop

注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象,因为实际上是把一个方法变成可调用的属性,如果不采用特殊方式处理,它还是一个只读属性

        (下文会详述)

        属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能

  • 属性方法的应用场景

通过上面的介绍可以看出属性方法本质上是一个方法,但返回一个属性值,方法内部可以封装我们的处理逻辑,但对外仅仅返回一个简单的结果即可,这也是属性方法的功能所在。因此可以用于在类中封装较为复杂的处理逻辑。

例子1:
      以下内容部分转自https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820062641f3bcc60a4b164f8d91df476445697b9e000

      设想一个场景,我们对类的对象绑定一个属性时,实现起来很简单,但不能对属性值进行合法性检查,导致属性值可以任意修改:

  1 __author__ = 'Maxwell'
  2 
  3 class Student():
  4     def set_score(self, score):
  5         self.score = score
  6 
  7     def get_score(self):
  8         return self.score
  9 
 10 stdu1 = Student()
 11 stdu1.set_score(9999)
 12 stdu1.get_score()
View Code

这显然有违逻辑,如果要对分数的合法性进行检查要怎么办呢?

  1 class Student():
  2     def set_score(self, score):
  3         if isinstance(int, score) and int(score) > 0 and int(score) <= 100:
  4             self.score = score
  5         else:
  6             raise ValueError('score must be an integer!')
  7 
  8     def get_score(self):
  9         return self.score
 10 
 11 stdu1 = Student()
 12 stdu1.set_score(9999)
 13 stdu1.get_score()
View Code

为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数。但是这里的过程略显复杂,没有像调用属性一样那么简单。

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?属性方法就可以实现:

  1 class Student():
  2     @property
  3     def score(self):
  4         return self.__score
  5 
  6     @score.setter
  7     def score(self, score):
  8         if isinstance(score, int) and int(score) > 0 and int(score) <= 100:
  9             self.__score = score
 10         else:
 11             raise ValueError('score must be an integer!')
 12 
 13 stdu1 = Student()
 14 stdu1.score = 60
 15 print(stdu1.score)
 16 
 17 输出:
 18 60
View Code

从这里可以看出属性方法还有一个神技就是能把类的私有变量变成可通过对象直接访问。

我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

  1 class Student():
  2     @property
  3     def birth_year(self):
  4         return self.__birth_year
  5 
  6 
  7     @birth_year.setter
  8     def birth_year(self, birth_year):
  9         self.__birth_year = birth_year
 10 
 11 
 12     @property
 13     def age(self):
 14         return 2018 - self.__birth_year
 15 
 16 Stud1 = Student()
 17 Stud1.birth_year = 1990
 18 print(Stud1.age)
 19 
 20 输出:
 21 28
View Code

上述程序中的__birth_year通过setter设置值,但age是通过__birth_year计算出来的,不可认为设置,它是一个只读属性。

例子2:
以下例子还是转自大师级别的博客http://www.cnblogs.com/wupeiqi/p/4766801.html

  1 __author__ = 'Maxwell'
  2 
  3 class Goods(object):
  4 
  5     def __init__(self):
  6         # 原价
  7         self.original_price = 100
  8         # 折扣
  9         self.discount = 0.8
 10 
 11     @property
 12     def price(self):
 13         # 实际价格 = 原价 * 折扣
 14         new_price = self.original_price * self.discount
 15         return new_price
 16 
 17     @price.setter
 18     def price(self, value):
 19         self.original_price = value
 20 
 21     @price.deleter
 22     def price(self):
 23         del self.original_price
 24 
 25 obj = Goods()
 26 print(obj.price)         # 获取商品价格
 27 obj.price = 200   # 修改商品原价
 28 print(obj.price)
 29 del obj.price     # 删除商品原价
 30 print(obj.price)
 31 
 32 输出:
 33 Traceback (most recent call last):
 34   File "D:/python/S13/Day6/testclass.py", line 32, in <module>
 35     print(obj.price)
 36   File "D:/python/S13/Day6/testclass.py", line 16, in price
 37     new_price = self.original_price * self.discount
 38 AttributeError: 'Goods' object has no attribute 'original_price'
 39 80.0
 40 160.0
View Code

上述程序调用deleter之后,会发现报错提示'Goods' object has no attribute 'original_price', 表明'original_price'已经被成功清理。

  • 属性方法的getter、setter和deleter

getter、setter和deleter是property的具体方法上外加装饰器的用法,分别表示获取属性、设置属性和删除属性,实际上getter并没有显示应用,直接应用property装饰器就是应用了getter。对于deleter,需要注意的是:

(1)setter定义方法后,实际调用时是直接用赋值的方式来进行的,Obj_name.method = xyz

(2)deleter不仅要在属性方法中定义delete的属性,在调用时还要显式地写明del Obj_name.Property

由于上面两个例子已经展示了具体用法,这里不再赘述。


二、 python类的成员修饰符

类的成员修饰符,前面并没有深入讲述,这里补充一下。没办法,大师讲的好,还是转载一下吧http://www.cnblogs.com/wupeiqi/p/4766801.html

对于每一个类的成员而言都有两种形式:

  • 公有成员,在任何地方都能访问
  • 私有成员,只有在类的内部才能方法

私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)

  1 class C:
  2 
  3     def __init__(self):
  4         self.name = '公有字段'
  5         self.__foo = "私有字段"

私有成员和公有成员的访问限制不同

静态字段

  • 公有静态字段:类可以访问;类内部(实例化对象)可以访问;派生类中可以访问
  • 私有静态字段:仅类内部(对象)可以访问
  1 class C:
  2 
  3     name = "公有静态字段"
  4 
  5     def func(self):
  6         print(C.name)
  7 
  8 class D(C):
  9 
 10     def show(self):
 11         print(C.name)
 12 
 13 
 14 C.name         # 类访问
 15 
 16 obj = C()
 17 obj.func()     # 类内部可以访问
 18 
 19 obj_son = D()
 20 obj_son.show() # 派生类中可以访问
View Code
  1 class C:
  2 
  3     __name = "公有静态字段"
  4 
  5     def func(self):
  6         print(C.__name)
  7 
  8 class D(C):
  9 
 10     def show(self):
 11         print(C.__name)
 12 
 13 
 14 print(C.__name)       # 类访问            ==> 错误,会提示没有该属性
 15 
 16 obj = C()
 17 obj.func()     # 类内部可以访问     ==> 正确
 18 
 19 obj_son = D()
 20 obj_son.show() # 派生类中访问   ==> 错误,会提示没有该属性
View Code

普通字段

  • 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
  • 私有普通字段:仅类内部可以访问;

ps:如果想要强制访问私有字段,可以通过 【对象._类名__私有字段明 】访问(如:obj._C__foo),不建议强制访问私有成员。

  1 class C:
  2 
  3     def __init__(self):
  4         self.foo = "公有普通字段"
  5 
  6     def func(self):
  7         print(self.foo)  # 类内部访问
  8 
  9 class D(C):
 10 
 11     def show(self):
 12         print(self.foo) # 派生类中访问
 13 
 14 obj = C()
 15 
 16 obj.foo     # 通过对象访问
 17 obj.func()  # 类内部访问
 18 
 19 obj_son = D();
 20 obj_son.show()  # 派生类中访问
View Code
  1 class C:
  2 
  3     def __init__(self):
  4         self.__foo = "私有普通字段"
  5 
  6     def func(self):
  7         print self.foo  # 类内部访问
  8 
  9 class D(C):
 10 
 11     def show(self):
 12         print self.foo # 派生类中访问
 13 
 14 obj = C()
 15 
 16 obj.__foo     # 通过对象访问    ==> 错误
 17 obj.func()  # 类内部访问        ==> 正确
 18 
 19 obj_son = D();
 20 obj_son.show()  # 派生类中访问  ==> 错误
View Code

方法、属性的访问于上述方式相似,即:私有成员只能在类内部使用

ps:非要访问私有属性的话,可以通过 对象._类__属性名


posted @ 2018-08-13 06:29  里纳斯-派森  阅读(845)  评论(0编辑  收藏  举报