24-2 面向对象(类的成员、类成员修饰符、类的特殊成员)

类的成员

类的成员可以分为三大类:字段、方法和属性。

 我个人理解:“字段”称为属性,“属性”称为属性样的方法更有利于理解。

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

 

一、字段

字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,

  • 普通字段     保存在对象中,只能通过对象访问(叫 “实例变量”更好一些)
  • 静态字段     保存在类中,可以通过类访问,也可以通过对象访问
class Province:
    # 静态字段,属于类
    country = '中国'

    def __init__(self, name):
        # 普通字段,属于对象
        self.name = name

#直接访问普通字段
henan = Province('河南')
print(henan.name)                # 河南
henan.name = "河南南"             # 将对象的name修改为‘河南南’

#直接访问静态字段
print(Province.country)          # 中国   通过类访问
print(henan.country)             # 中国   通过对象访问

由上图可知:

  • 静态字段在内存中只保存一份
  • 普通字段在每个对象中都要保存一份

应用场景: 通过类创建对象时,如果每个对象都具有相同的字段值,那么就使用静态字段。

 

二、方法

在类中定义的函数,称为方法。

方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。

  • 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self
  • 静态方法:由调用;无默认参数;
  • 类方法:由调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类赋值cls

1. 静态方法

通过@staticmethod 装饰器即可把其装饰的方法变为一个静态方法。

普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了。它可以通过类名或实例名被调用。

class Dog(object):

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

    @staticmethod   # 变为静态方法后,再通过实例调用时就不会自动把实例本身当作一个参数传给self了
    def eat(self):  # 这里的self已经不是自动传入的实例了,仅仅是一个名叫self的形参而已
        print("%s is eating" % self.name)



d = Dog("dahuang")
d.eat()             # 报错:TypeError: eat() missing 1 required positional argument: 'self'

想让上面的代码可以正常工作有两种办法:

  • 调用时主动传递实例本身给eat方法,即d.eat(d) 
  • 在eat方法中去掉self参数,但这也意味着,在eat中不能通过self.调用实例中的其它变量了

2. 类方法

类方法通过@classmethod装饰器实现,类方法和普通方法的区别是, 类方法只能访问类变量,不能访问实例变量。

class Dog(object):

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

    @classmethod   # 变为类方法后,再通过实例调用时会自动把类本身当作一个参数传给cls
    def eat(cls):  # cls只是一个变量名,可以写成其他
        print("%s is eating" % cls.name)



d = Dog("dahuang")
d.eat()            # 报错:type object 'Dog' has no attribute 'name'    因为name是实例变量,类方法是不能访问实例变量的

让上面的代码可以正常工作:此时可以定义一个类变量,也叫name

class Dog(object):
    name = "我是类变量"
    def __init__(self, name):
        self.name = name

    @classmethod   # 变为类方法后,再通过实例调用时会自动把类本身当作一个参数传给cls
    def eat(cls):  # cls只是一个变量名,可以写成其他
        print("%s is eating" % cls.name)



d = Dog("dahuang")
d.eat()            # 输出:我是类变量 is eating
View Code

  

三、属性方法

属性方法的作用就是通过@property把一个方法变成一个静态属性。调用时不需要加(),直接 object.属性方法名 即可,即像调用字段一样调用方法

1. 属性方法的定义

class Dog(object):

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

    @property
    def eat(self):     # 参数仅有一个self
        print(" %s is eating" %self.name)


d = Dog("dahuang")
# d.eat()              # 报错:'NoneType' object is not callable

d.eat                  # 输出:dahuang is eating

2. 属性存在的意义

把一个方法变成静态属性有什么卵用呢?既然想要静态字段,那直接定义成一个静态字段不就得了么?

因为有些值是一系列动作后才得到的结果,每次调用时,它都要经过一系列的动作才得到结果,但这些动作过程不需要用户关心, 用户只需要调用这个属性就可以了。

 比如 ,你想知道一个航班当前的状态,是到达了、延迟了、取消了、还是已经飞走了, 想知道这种状态:

class Flight(object):
    def __init__(self,name):
        self.flight_name = name

    def checking_status(self):
        print("checking flight %s status " % self.flight_name)
        return 1

    @property
    def flight_status(self):
        status = self.checking_status()
        if status == 0 :
            print("flight got canceled...")
        elif status == 1 :
            print("flight is arrived...")
        elif status == 2:
            print("flight has departured already...")
        else:
            print("cannot confirm the flight status...,please check later")


f = Flight("CA980")
f.flight_status

3. 属性的修改

以上例子我只能查询航班状态, 既然这个flight_status已经是个属性了, 那我能否给它赋值呢?

f.flight_status = 2      # 报错:can't set attribute

  当然可以改, 不过需要通过@flight_status.setter、@flight_status.deleter装饰器再装饰一下,此时 你需要写新方法, 对这个flight_status进行更改。

class Flight(object):
    def __init__(self,name):
        self.flight_name = name

    def checking_status(self):
        print("checking flight %s status " % self.flight_name)
        return 1

    @property
    def flight_status(self):
        status = self.checking_status()
        if status == 0:
            print("flight got canceled...")
        elif status == 1:
            print("flight is arrived...")
        elif status == 2:
            print("flight has departured already...")
        else:
            print("cannot confirm the flight status...,please check later")

    @flight_status.setter  # 修改
    def flight_status(self,status):
        status_dic = {
            0: "canceled",
            1:"arrived",
            2: "departured"
        }
        print("\033[31;1mHas changed the flight status to \033[0m",status_dic.get(status) )

    @flight_status.deleter  # 删除
    def flight_status(self):
        print("status got removed...")

f = Flight("CA980")
f.flight_status
f.flight_status = 2  # 触发 @flight_status.setter
del f.flight_status  # 触发 @flight_status.deleter
修改示例

4. 属性的两种定义方式

属性的定义有两种方式:

    • 装饰器 即:在方法上应用装饰器
    • 静态字段 即:在类中定义值为property对象的静态字段

(1)装饰器方式

class Goods(object):

    @property
    def price(self):
        print ('@property')

    @price.setter
    def price(self, value):
        print ('@price.setter')

    @price.deleter
    def price(self):
        print ('@price.deleter')



obj = Goods()
obj.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数
del obj.price      # 自动执行 @price.deleter 修饰的 price 方法

我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deltter
    def price(self, value):
        del self.original_price

obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

实例
示例

(2)静态字段方式

创建值为property对象的静态字段。有个四个参数:

    • 第一个参数是方法名,调用 “对象.属性” 时自动触发执行方法
    • 第二个参数是方法名,调用 “对象.属性=XXX ”时自动触发执行方法
    • 第三个参数是方法名,调用 "del 对象.属性" 时自动触发执行方法
    • 第四个参数是字符串,调用 "对象.属性.__doc__" ,此参数是该属性的描述信息 
class Foo:
    def get_bar(self):
        print('wupeiqi')

    def set_bar(self, value):          # 必须两个参数
        print('set value' + value)

    def del_bar(self):
        print('del wupeiqi')

    BAR = property(get_bar, set_bar, del_bar, 'description...')


obj = Foo()
obj.BAR              # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex"     # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
del obj.BAR          # 自动调用第三个参数中定义的方法:del_bar方法
obj.BAR.__doc__      # 自动获取第四个参数中设置的值:description...

由于静态字段方式创建属性具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    def get_price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self, value):
        del self.original_price

    PRICE = property(get_price, set_price, del_price, '价格属性描述...')

obj = Goods()
obj.PRICE         # 获取商品价格
obj.PRICE = 200   # 修改商品原价
del obj.PRICE     # 删除商品原价

实例
示例
类成员修饰符--双下滑线

类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式:

  • 公有成员,在任何地方都能访问
  • 私有成员,只有在类的内部才能方法,无法继承和被继承。私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)。

1. 修饰字段

(1) 普通字段

  • 公有普通字段:类可以访问;类内部可以访问;派生类中可以访问
  • 私有普通字段:仅类内部可以访问;
class foo:
    def __init__(self, name, age):
        self.name = name
        self.__age= age         # 私有,外部无法直接访问
    def show(self):
        return self.__age

obj = foo('alex', 19)
print(obj.name)
# print(obj.__age)              # 报错
ret = obj.show()                # 通过类内部的方法可以使用__age
print(ret)

(2)静态字段

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

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

class Foo:   
    __v = '123'               #私有静态字段
    def __init__(self):
        pass

    def show(self):
        return Foo.__v

obj = Foo()
# print(obj.__v)               # 报错
# Foo.__v                      # 报错
print(obj.show())              # 通过类内部的方法可以使用Foo.v

2. 修饰方法

class Foo:
    __v = '123'
    def __init__(self):
        pass

    def __show(self):
        print('show')

    def show2(self):
        self.__show()


obj = Foo()
obj.show2()

静态方法、类方法、属性方法同样适用,不再赘述。

 

类的特殊成员

1. __doc__

表示类的描述信息

class Foo:
    """ 描述类信息,这是用于看片的神奇 """

    def func(self):
        pass

print(Foo.__doc__)    # 输出:类的描述信息
View Code

1. __module__ 和 __class__

  __module__ 表示当前操作的对象在那个模块

  __class__     表示当前操作的对象的类是什么

1. __init__()

构造方法,通过类创建对象时,自动触发执行。

2. __del__()

析构方法,当对象在内存中被释放时,自动触发执行。

此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

3. __call__()

  对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo()                                # 执行 __init__
obj()                                      # 执行 __call__
View Code

4. __int__()

int 函数触发执行。

class Foo:
    def __init__(self):
        pass
    def __int__(self):
        return 111

obj = Foo()
print(obj, type(obj))                      # <__main__.Foo object at 0x000000000287B160> <class '__main__.Foo'>
r = int(obj)                               # int(对象)  自动执行对象的__int__方法,并将返回值赋值给r
print(r)                                   # 111
View Code

5. __str__()

str 函数触发执行。如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。 

class Foo:
    def __str__(self):
        return 'alex'

obj = Foo()
r =str(obj)                                # str(对象)  自动执行对象的__str__方法,并将返回值赋值给r
print(r)                                   # alex
print(obj)                                 # print内部会调用obj的__str__方法。 实际分为两步:第一步,print(str(obj));第二部str调用__str__
View Code

6. __add__()

两个对象相加时,自动执行第一个对象的__add__方法,并且将第二个对象当做参数传递给入给other。

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __add__(self,other):               # 系统自动赋值 self=obj1,other=obj2
        # return self.age + other.age      # 返回值为:39
        return Foo(self.name, other.age)

obj1 = Foo('alex', 19)
obj2 = Foo('eric', 20)
r = obj1 + obj2
print(r.name, r.age)                       # alex 20
View Code

7. __dict__

查看类或对象中的所有成员。返回存储对象属性的一个字典,其键为属性名,值为属性的值。

class Province:

    country = 'China'

    def __init__(self, name, count):
        self.name = name
        self.count = count

    def func(self, *args, **kwargs):
        print('func')

# 获取类的成员,即:静态字段、方法、
print(Province.__dict__)
# 输出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None}

obj1 = Province('HeBei',10000)
print(obj1.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 10000, 'name': 'HeBei'}

obj2 = Province('HeNan', 3888)
print(obj2.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 3888, 'name': 'HeNan'}
View Code

8. __getitem__()、__setitem__()、__delitem__()

用于索引操作,如列表(根据索引角标)、字典(根据键)。以上分别表示获取、设置、删除数据

class Foo(object):
    def __getitem__(self, key):
        print('__getitem__', key)
    def __setitem__(self, key, value):
        print('__setitem__', key, value)
    def __delitem__(self, key):
        print('__delitem__', key)

obj = Foo()
result = obj['k1']                         # 自动触发执行 __getitem__
obj['k2'] = 'wupeiqi'                      # 自动触发执行 __setitem__
del obj['k1']                              # 自动触发执行 __delitem__
View Code

也用于切片操作(在2.7里使用__getslice__、__setslice__、__delslice__进行切片操作)

class Foo(object):
    def __getitem__(self, key):
        print('__getitem__', key, type(key))
        if type(key) == slice:
            print("调用者希望做切片处理")
            print(key.start, key.stop, key.step)
        else:
            print("调用者希望做索引处理")
    def __setitem__(self, key, value):
        print('__setitem__', key, value)
    def __delitem__(self, key):
        print('__delitem__', key)

obj = Foo()
obj[0]          # 传入的key是<class 'int'>类型
obj[0:5:2]      # 传入的keyi是<class 'slice'>类型 
View Code

9. __iter__() 和__next__()

 for循环的本质为:通过其内部的__next__()方法,从迭代器中一个一个往外取值,如下:

obj = iter([11,22,33,44])

while True:
    val = obj.__next__()
    print(val)
View Code

所有的可迭代对象内部,都有一个__iter__方法(反之,内部有__iter__的对象都是可迭代对象)。当for循环时,可迭代对象先执行自己的__iter__返回一个迭代器。

class Foo():
    def __iter__(self):
        return iter([11, 22, 33, 44, 55])

obj = Foo()
for i in obj:
    print(i)
View Code

10. __new__()

见下文

metaclass “类的祖宗”

1、一切都是对象

class Foo(object): 
    def __init__(self):
        pass
 
obj = Foo() 

上述代码中,obj 是通过 Foo 类实例化的对象。其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。

那么Foo类对象是由哪个类的 构造方法 创建的呢?

print type(obj)   # 输出:<class '__main__.Foo'>     表示,obj对象由Foo类创建。obj是Foo类的一个实例。
print type(Foo)   # 输出:<type 'type'>              表示,Foo类对象由type类创建。Foo类对象是type类的一个实例。

所以:Foo类对象 是通过type类的构造方法创建。

 

2、“Foo类”创建的内部过程

当创建下列类时:

class Foo:         # 可写为class Foo(object)    object 可写可不写,所有的类都继承object类
    def f1(self)
	    print(123)

 内部实际是如下执行的:

def f1(self):
    print 'hello wupeiqi'
 
Foo = type('Foo',(object,), {'func': f1})     # 创建一个type对象,对象名为Foo。即Foo类由type类实例化产生
#type第一个参数:类名
#type第二个参数:当前类的基类(或称父类)。此参数用于解决继承问题
#type第三个参数:类的成员

 

3、验证

type类的源码是C写的,我们之间看不到。可以自定义type类的一个派生类MyType进行研究。

类中有一个隐含的属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,可以将设置 __metaclass__ =MyType,(这是python2的写法)从而查看 类 创建的过程。

 

"""
执行下列代码,输出为:123
说明:解释器从上往下解释代码,解释到Foo类的定义时,去执行了MyType类的构造方法
"""

class MyType(type):
    def __init__(self, *args, **kwargs):
        print(123)

class Foo(object, metaclass=MyType):      # python3中的写法,设定Foo对象是由MyType实例化而来
    def __init__(self):
        print(789)
    def func(self):
        print('hello world')
对“第一阶段”的验证
"""
执行下列代码,输出为:
123
456

说明:
1、解释器从上往下解释代码,解释到Foo类的定义时,去执行了MyType类的构造方法,所以输出123
2、创建obj对象时,Foo()是Foo类对象后加括号,会去执行MyType类的__call__方法,所以输出456
3、Foo类的构造方法,其实是在MyType类的__call__方法中去调用的,这列没有调用,所以789没有输出
"""

class MyType(type):
    def __init__(self, *args, **kwargs):
        print(123)
    def __call__(self):
        print(456)

class Foo(object, metaclass=MyType):
    def __init__(self):
        print(789)
    def func(self):
        print('hello world')

obj = Foo()
对“第二阶段-1”的验证
"""
执行下列代码,输出为:
123
456
789

说明:
所以,在创建对象obj时,不是直接去执行了Foo的构造方法,而是经历了几步别的代码
"""

class MyType(type):

    def __init__(self, what, bases=None, dict=None):
        print(123)
        super(MyType, self).__init__(what, bases, dict)
    def __call__(self, *args, **kwargs):
        print(456)
        # self = Foo
        obj = self.__new__(self, *args, **kwargs)            # 调用Foo中的__new__方法
        self.__init__(obj)                                   # 执行Foo的构造方法__init__

class Foo(object,  metaclass=MyType):
    # __metaclass__ = MyType                                  # python2中定义metaclass的写法
    def __init__(self):
        print(789)
    def __new__(cls, *args, **kwargs):
        print("aaa")
        return object.__new__(cls, *args, **kwargs)           # obj 其实是在__new__里面创建的

obj = Foo()
对“第二阶段-2-3”的验证

 

 

参考: 

http://www.cnblogs.com/wupeiqi/p/4766801.html

http://www.cnblogs.com/alex3714/articles/5213184.html

 

posted @ 2017-07-02 22:20  seaidler  阅读(173)  评论(0)    收藏  举报