欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python的_xxx, __xxx, __xxx__区别

程序员文章站 2022-06-02 20:15:31
...

_xxx

弱“内部使用”标识
如:”from M import *”,将不导入所有以下划线开头的对象,包括包、模块、成员。
表明该方法或属性不应该去调用
Python中不存在真正的私有方法。为了实现类似于c++中私有方法,可以在类的方法或属性前加一个“_”单下划线,意味着该方法或属性不应该去调用,它并不属于API。

xxx_

只是为了避免与python关键字的命名冲突

__xxx

伪私有方法

class A(object):
    def __init__(self):
        self.name = 'A'
    def __method(self):
        print("I'm a method in A")

a = A()
a.__method()

输出:

AttributeError: 'A' object has no attribute '__method'

虽然看上去在像是私有函数,在外部无法访问,但是其实python只是简单地给__method()改了下名字,仍然可以这样访问:

a._A__method()

输出:

I'm a method in A

了解了这么一个改名的机制,再看下面这个例子:

class BaseClass(object):
    def __init__(self):
        self.name = 'Base'
    def __method(self):
        print("I'm Base")
    def method(self):    
        self.__method()

class SubClass(BaseClass):
    def __init__(self):
        super(SubClass, self).__init__()
        self.name = 'Sub'
    def __method(self):  # 覆写父类__method()
        print("I'm Sub")
    def method(self):    # 覆写父类method()
        self.__method()


sub = SubClass()
sub.method()

此时的输出为:

I'm Sub

没问题,子类覆写了父类的method()方法,因此输出是”Sub”。如果不覆写:

class SubClass(BaseClass):
    def __init__(self):
        super(SubClass, self).__init__()
        self.name = 'Sub'
    def __method(self):  # 覆写父类__method()
        print("I'm Sub")
    #def method(self):     没有覆写父类method()
    #    self.__method()

调用sub.method()时,输出:

I'm Base

可以发现,子类调用的是从父类继承下来的method()方法,从而调用的自然是父类的__method__()

其实如果从改名的角度来思考,很容易理解。
在写每个类代码的时候,虽然我们写的都是:

def method(self):    
    self.__method()

但是BaseClass直接把代码改成了:

def method(self):    
    self._BaseClass__method()

对应着SubClass:

def method(self):    
    self._SubClass__method()

而继承可以理解成,如果子类没有覆写,则复制父类的代码下来。因而就出现了,调用sub.method()时,其实是调用了父类的_BaseClass__method(),输出BaseClass的情况。

为了加强继承的复制代码与改名机制的理解,我又写了如下代码

class BaseClass(object):
    def __init__(self):
        self.name = 'Base'
    def __method(self):
        print("I'm Base")
    def method(self):
        self._BaseClass__method()
        self._SubClass__method()

base = BaseClass()
base.method()

输出为:

I'm Base
AttributeError: 'BaseClass' object has no attribute '_SubClass__method'

对于父类来说,它的类方法只有_BaseClass__method(),因此调用_SubClass__method()时报错。
接着,运行如下代码:

class BaseClass(object):
    def __init__(self):
        self.name = 'Base'
    def __method(self):
        print("I'm Base")
    def method(self):
        self._BaseClass__method()
        self._SubClass__method()

class SubClass(BaseClass):
    def __init__(self):
        super(SubClass, self).__init__()
        self.name = 'Sub'
    def __method(self):
        print("I'm Sub")

sub = SubClass()
sub.method()

输出:

I'm Base
I'm Sub

对子类来说,继承的时候首先复制了父类的代码,因此父类的method()方法自然就被复制下来。然而,虽然父类的__method()方法和子类的__method()方法名字一样,但是由于改名机制的存在,其实两个函数的名字并不同,因此并不存在覆写的情况,而是SubClass不仅自己定义了一个_SubClass__method(),同时复制了父类的_BaseClass__method()。所以这里调用时输出是上面这样而不会报错。

__xxx__

“魔术”对象或属性
许多特殊的代码会调用对应的魔术方法。下面是总结的部分我遇到过的魔术方法:

__init__()

这个很常见,就是创建一个类的实例的时候会调用,相当于构造函数。

__call__()

如果一个类实现了这个函数,那么这个类就是“可调用对象”,可以通过如下方式调用:

class C:
    def __call__(self, name):
        print(name)

c = C()
c('Hello!')  # 会调用__call__()方法

输出:

Hello!

__getitem__()

class C:
    def __init__(self):
        self.list = [1,2,3,4,5]

    def __getitem__(self, item):
        return self.list[item]

c = C()

for i in range(5):
    print(c[i]) # 会调用__getitem__()方法

__iter__()__next__()

之所以把这两个放在一起,是因为我见到的这两个是在迭代器操作的时候同时出现的。直接看代码:

class IterDemo:
    def __iter__(self):
        print('__iter__() is called')
        return NextDemo()   # 需要返回一个实现了__next__()方法的类的对象

class NextDemo:
    def __next__(self):
        print('__next__() is called')

iterDemo = IterDemo()

nextDemo = iter(iterDemo)   # 调用iterDemo.__iter__()方法
next(nextDemo)              # 调用nextDemo.__next__()方法

__setattr__()

用在类内给成员变量赋值。

class C:
    def __init__(self):
        self.a = 1        #调用一次__setattr__()

    def __setattr__(self, key, value):
        dict = self.__dict__
        dict[key] = value
        print("__setattr__ !")

    def modify(self,new_a):
        self.a = new_a    #调用一次__setattr__()

c = C()
c.modify(2)

输出:

__setattr__ !
__setattr__ !

参考:

Python下划线与命名规范
python_____xx__的区别
pytorch学习笔记(四):输入流水线(input pipeline)
Python3关与迭代器next()使用为__next__()的一点注意事项

相关标签: python 继承