手记

深入理解python面向对象-类特殊成员

类成员的修饰符

类的所有成员在上一篇已经做了详细的介绍,对于每一个类的成员都有两种形式:公有成员私有成员。成员定义是以双下划线开头,就是私有成员。除了一些特殊成员除外,例如:__init__、__call__、__dict__、__del__等,剩下的都是公有成员。

class  Base:
    def  __init__(self):
        self.name = '公有字段'
        self.__foo = "私有字段"

私有成员和公有成员的访问级别不同:

普通字段

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

私有字段其实不是不能访问,只是Python解释器对私有成员命名做了更改,对象._类名__私有字段名,例如:obj._Base__foo不建议强制访问私有成员。

普通公有字段例子

class Base:
    def __init__(self):
        self.foo = '公有字段'

    def func(self):
        print(self.foo) # 类内部访问

class Device(Base):
    def show(self):
        print(self.foo) # 派生类中访问

obj = Base()
print(obj.foo)      # 对象访问
obj.func()          # 类内部访问
obj_son = Device()
obj_son.show()      # 派生类访问

普通私有字段例子

class Base:
    def __init__(self):
        self.__foo = '私有字段'

    def func(self):
        print(self.__foo) # 类内部访问

class Device(Base):
    def show(self):
        print(self.__foo) # 派生类中访问

obj = Base()
print(obj.__foo)    # 对象访问   报错:AttributeError: 'Base' object has no attribute '__foo'
obj.func()          # 类内部访问
obj_son = Device()
obj_son.show()      # 派生类访问  报错:AttributeError: 'Device' object has no attribute '_Device__foo'

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

静态字段

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

静态公有字段例子

class Base:
    name = "公有静态字段"
    def func(self):
        print(Base.name)

class Device(Base):
    def show(self):
        print(Base.name)

Base.name # 类访问
obj = Base()
obj.func() # 类内部可以访问
obj_son = Device()
obj_son.show() # 派生类中可以访问

静态私有字段例子

class Base:
    __name = "公有静态字段"
    def func(self):
        print(Base.__name)

class Device(Base):
    def show(self):
        print(Base.__name)

Base.__name # 类访问  报错:AttributeError: type object 'Base' has no attribute '__name'
obj = Base()
obj.func() # 类内部访问
obj_son = Device()
obj_son.show() # 派生类访问  报错:AttributeError: type object 'Base' has no attribute '_Device__name'

通过上面的例子,你应该发现了一个问题,在普通公有字段中,子类可以通过self.foo访问父类定义的变量;在静态公有字段中,使用的是Base.name访问。那为什么不能使用self访问呢?上一篇我们其实讲过的,静态字段属于类所有,在类中只保存一份,所以它与继承无关,不管经过多少重继承,静态字段只有一份,只能通过类自身来访问。而普通字段是属于对象的,所以继承以后,每一个继承类的对象都会保存一份。

类的特殊成员

上面我们讲了类成员以及成员修饰符,知道了类中有字段、方法和属性,并且有公有和私有两种访问限制。但是还是存在着一些具有特殊含义的成员,详情如下:

  1. __doc__
    表示类的描述信息

库函数:range

class range(object):
    """
    range(stop) -> range object
    range(start, stop[, step]) -> range object
    
    Return an object that produces a sequence of integers from start (inclusive)
    to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
    start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
    These are exactly the valid indices for a list of 4 elements.
    When step is given, it specifies the increment (or decrement).
    """
    def count(self, value): # real signature unknown; restored from __doc__
        """ rangeobject.count(value) -> integer -- return number of occurrences of value """
        return 0

print(range.__doc__)

#输出结果:
range(stop) -> range object
range(start, stop[, step]) -> range object

Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).

当我们在类上面加上一些说明,通过此方法可以进行查看,同样也可以查看库函数的说明

  1. __module__ 和 class
      __module__ 表示当前操作的对象在那个模块
      __class__ 表示当前操作的对象的类是什么
class Foo:
    def func(self):
        pass

f = Foo()
print(f.__module__)    #输出:__main__
print(f.__class__)       #输出:<class '__main__.Foo'>

from multiprocessing import Process
p = Process()
print(p.__module__)    #输出:multiprocessing.context
print(p.__class__)       #输出:<class 'multiprocessing.context.Process'>
  1. __init__
      构造方法,通过类创建对象时,自动触发执行。
class  Foo:
     def  __init__(self, name):
        self.name = name
        self.age = 18

obj = Foo('wupeiqi') # 自动执行类中的 __init__ 方法

注意:Python这里的构造方法与C++不一样

class Foo:
    instance = None
    def __init__(self):
        print("__init__")

    @classmethod
    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            cls.instance = object.__new__(cls)
            print("__new__")
        return cls.instance

f1 = Foo()
f2 = Foo()
print(id(f1), id(f2))

#输出:
__new__
__init__
__init__
4343767560 4343767560

可以看到,f1和f2是同一个对象,占据同一块内存,也就是说在内存中只创建了一个对象,但是构造函数调用了两次。所以这里的创建对象时,自动触发并不是特别准确,使用的时候要多注意。

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

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

    def  __del__(self):
        print("del")

还是上面那个例子,加上__del__函数的定义
输出:

__new__
__init__: 
__init__: 
4424543312 4424543312
del

可以看到del也是调用了一次,再次证实了,对象只创建了一次

  1. __call__
    对象后面加括号,触发执行,相当于函数调用。

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

class  Foo:
    def  __init__(self):
        pass

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

obj = Foo()  # 执行 __init__
obj()        # 执行 __call__
  1. __dict__
    类或对象中的所有成员

上文中我们知道:类的普通字段属于对象;类中的静态字段和方法等属于类,即:

class Foo:
    instance = None
    def __init__(self, name):
        print("__init__: ", name)
        self.name = name

    def func(self):
        pass

f1 = Foo("name1")

# 获取对象的成员,即:普通字段
print(f1.__dict__)  # 输出:{'name': 'name1', 'age': 123}

# 获取类的成员,即:静态字段、方法、
print(Foo.__dict__) # 输出:{'__module__': '__main__', 'instance': None, '__init__': <function Foo.__init__ at 0x10df509d8>, 'func': <function Foo.func at 0x1151b79d8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
  1. __str__
    如果一个类中定义了__str__方法,那么在打印对象或者str转换时,默认输出该方法的返回值。
class  Foo:
    def  __str__(self):
        return  'foo_name'

obj = Foo()
print(obj)
# 输出:foo_name

print(str(obj)) 
# 输出:foo_name
  1. __getitem__、__setitem__、__delitem__
    用于索引操作,如字典、列表。以上分别表示获取、设置、删除数据
    字典
class Foo:
    def __getitem__(self, item):
        print("__getitem__: ", item)

    def __setitem__(self, key, value):
        print("__setitem__: ", key, value)

    def __delitem__(self, key):
        print("__delitem__: ", key)

obj = Foo()
result = obj["k1"]    # 自动触发__getitem__
obj["k2"] = "name"    # 自动触发__setitem__
del obj["k1"]         # 自动触发__delitem__

列表

class Foo:
    def __getitem__(self, item):
        print("__getitem__.start: ", item.start)
        print("__getitem__.stop: ", item.stop)
        print("__getitem__.step: ", item.step)

    def __setitem__(self, key, value):
        print("__setitem__.index: ", key)
        print("__setitem__.value: ", value)
        return value

    def __delitem__(self, key):
        print("__delitem__: ", key)

f = Foo()
f[1:5:2]    #自动触发__getitem__
f[0] = 1   #自动触发__setitem__
del f[0]    #自动触发__delitem__

#输出
__getitem__.start:  1
__getitem__.stop:  5
__getitem__.step:  2
__setitem__.index:  0
__setitem__.value:  1
__delitem__:  0
  1. __iter__
    用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 iter
class Foo:
    def __init__(self, sq):
        self.sq = sq

    def __iter__(self):
        return iter(self.sq)

obj = Foo([11,22,33,44])
for i in obj:
    print(i)

for循环迭代的其实是 iter([11,22,33,44]) ,所以相当于如下:

obj = iter([11,22,33,44])
for i in obj:
    print(i)

For循环迭代器输出

obj = iter([11,22,33,44])
while True:
    try:
        val = next(obj)
        print(val)
    except StopIteration as e:
        break

还有__metaclass__也是非常重要的一个,这个牵扯到反射机制,我们放在下一篇进行详细说明,今天的文章就到这里了,你有没有Get到新技能呢?欢迎留言一起探讨

0人推荐
随时随地看视频
慕课网APP