tengwang0318 / article

write some articles to share my experience

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Thoughts about class in python

tengwang0318 opened this issue · comments

Thoughts about class in python

1. Private attribute

在python用两个下划线可以用来表示private类型,但根据python的设计理念为"We are all consenting adluts",你依然可以在无需调用get 或 set方法对private类型的变量进行修改。

class Solution:
    __nb = "qwerdf"

    def get(self):
        return self.__nb


test = Solution()

# __nb is private attribute. You will get an error about you can't find the attribute about __nb
try:
    print(test.__nb)
except AttributeError as e:
    print(e)
# but you can use get method to attain __nb
# you can use public method to attain __nb
print(test.get())

# Python theory is "We are all consenting adults".
# You can use some tricks to realize to get and set private attribute without public method
# You can use classname._name__privateAttribute to get and set
print(test._Solution__nb)
test._Solution__nb = "🐂"
print(test._Solution__nb)

结果如下:

'Solution' object has no attribute '__nb'
qwerdf
qwerdf
🐂

2. Use super() or __init__() in muti-inheritance

多重继承中使用super()的两个原因:

1. 使用__init__()时,初始化的顺序是子类的实际调用顺序,而不是“导入”字累的顺序

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value


class TimesTwo(object):
    def __init__(self):
        self.value *= 2


class PlusFive(object):
    def __init__(self):
        self.value += 5


class OneWay(MyBaseClass, TimesTwo, PlusFive):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        TimesTwo.__init__(self)
        PlusFive.__init__(self)


class AnotherWay(MyBaseClass, PlusFive, TimesTwo):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        TimesTwo.__init__(self)
        PlusFive.__init__(self)


test1 = OneWay(5)
print("First order is 5 * 2 + 5 =", test1.value)
test2 = AnotherWay(5)
print("Another order is also 5 * 2  + 5 =", test2.value)

输出结果

First order is 5 * 2 + 5 = 15
Another order is also 5 * 2  + 5 = 15

以OneWay为例,他首先初始化MyBaseClass类,给OneWay的 self.value 设为5,然后执行TimesTwo的构造函数,即$$self.value = self.value * 2 = 5 * 2$$

AnotherWay“导入”超类的顺序为MyBaseClass, PlusFive , TimeTwo与OneWay“导入”超类的顺序不同,但AnotherWay构造函数执行的顺序与OneWay的构造函数执行顺序一致。

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value


class TimesTwo(MyBaseClass):
    def __init__(self, value):
        super().__init__(value)
        self.value *= 2


class PlusFive(MyBaseClass):
    def __init__(self, value):
        super().__init__(value)
        self.value += 5


class OneWay(PlusFive, TimesTwo):
    def __init__(self, value):
        super(OneWay, self).__init__(value)


class AnotherWay(TimesTwo, PlusFive):
    def __init__(self, value):
        super(AnotherWay, self).__init__(value)



test1 = OneWay(5)
print("First order is 5 * 2 + 5 = ", test1.value)
test2 = AnotherWay(5)
print("Another order is also (5 + 5) * 2 = ", test2.value)

输出结果:

First order is 5 * 2 + 5 =  15
Another order is also (5 + 5) * 2 =  20

可以看出,使用super()在多重继承中初始化顺序就是“导入”包的顺序,与__init__()不同。

可以调用mro函数,对其超类的倒入顺序进行查看,mro为method resolution order,即为方法执行顺序

运行代码

print(OneWay.mro())
print(AnotherWay.mro())
[<class '__main__.OneWay'>, <class '__main__.PlusFive'>, <class '__main__.TimesTwo'>, <class '__main__.MyBaseClass'>, <class 'object'>]
[<class '__main__.AnotherWay'>, <class '__main__.TimesTwo'>, <class '__main__.PlusFive'>, <class '__main__.MyBaseClass'>, <class 'object'>]

以OneWay为例,说明mro的意义:

从结果上来看,调用OneWay导入的反方向运行的,可以这样来理解,要想调用OneWay,就必须要先执行PlusFive,要想执行PlusFive就要执行MyBaseClass才能获取相应的值,要想执行MyBaseClass就要先执行object,就不难理解OneWay导入超类时,是反向的。(个人理解,勿喷)

2.多重继承问题。

例子为一个子类继承两个单独超类,两个超类都继承于同一个公共基类

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value


class TimesFive(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value *= 5


class PlusTwo(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value += 2


class ThisWay(TimesFive, PlusTwo):
    def __init__(self, value):
        TimesFive.__init__(self, value)
        PlusTwo.__init__(self, value)


test = ThisWay(5)
print("Should 5 * 5 + 2 = ", test.value)

输出结果:

Should 5 * 5 + 2 =  7

原因在于ThisWay的构造函数先调用TimesFive的构造函数,此时self.value = 5 * 5 = 25

ThisWay的构造函数会继续调用PlusTwo的构造函数,PlusTwo调用自身的构造函数时,会调用MyBaseClass的构造函数,就会将self.value重置为5,而不是25,这就是结果与预期相差的原因。

当然这种可以让调用TimeFive & PlusTwo构造函数调用时return一个值进行解决

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value


class TimesFive(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value *= 5
        return self.value


class PlusTwo(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value += 2
        return self.value


class ThisWay(TimesFive, PlusTwo):
    def __init__(self, value):
        value = TimesFive.__init__(self, value)
        PlusTwo.__init__(self, value)


test = ThisWay(5)
print("Should 5 * 5 + 2 = ", test.value)

输出结果为:

Should 5 * 5 + 2 =  27

使用super()来进行解决:

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value


class TimesFive(MyBaseClass):
    def __init__(self, value):
        super(TimesFive, self).__init__(value)
        self.value *= 5


class PlusTwo(MyBaseClass):
    def __init__(self, value):
        super(PlusTwo, self).__init__(value)
        self.value += 2


class ThisWay(PlusTwo, TimesFive):
    def __init__(self, value):
        super(ThisWay, self).__init__(value)


test = ThisWay(5)
print("Should 5 * 5 + 2 = ", test.value)

输出结果为:

Should 5 * 5 + 2 =  27

调用mro() ,查看超类导入顺序,

print(ThisWay.mro())

输出结果(从右往左为调用顺序?):先执行TimesFive的构造函数,才执行PlusTwo的构造函数。

[<class '__main__.ThisWay'>, <class '__main__.PlusTwo'>, <class '__main__.TimesFive'>, <class '__main__.MyBaseClass'>, <class 'object'>]

这样就没有使用__init__()出现钻石继承问题

此时,您可能会有个疑问,上述使用super()的情况都是只有一个属性值,如果两个超类有不互相同的属性,那么该如何操作呢?

就以课本上例子为例进行改造,

class People:
    name = ""
    age = 0
    __weight = 0

    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w


class Student(People):
    grade = ""

    def __init__(self, n, a, w, g):
        super(Student, self).__init__(n, a, w)
        self.grade = g


class Speaker(People):
    topic = ""

    def __init__(self, n, a, w, t):
        super(Speaker, self).__init__(n, t, a, w)
        self.topic = t

class Sample(Speaker, Student):
    def __init__(self):
    #不知道如何传参数,因为Speaker与Student的构造函数的参数不同
        super(Sample, self).__init__()

此时就不知道如何给Sample的构造函数传参数,因为Speaker与Student的构造函数的参数不同。

参考StackOverFlow

class People:
    name = ""
    age = 0
    __weight = 0

    def __init__(self, n="", a=0, w="", *args, **kwargs):
        self.name = n
        self.age = a
        self.__weight = w


class Student(People):
    grade = ""

    def __init__(self, n="", a=0, w="", g=0, *args, **kwargs):
        super(Student, self).__init__(n=n, a=a, w=w, *args, **kwargs)
        self.grade = g


class Speaker(People):
    topic = ""

    def __init__(self, n="", a=0, w=0, t="", *args, **kwargs):
        super(Speaker, self).__init__(n=n, t=t, a=a, w=w, *args, **kwargs)
        self.topic = t


class Sample(Speaker, Student):
    def __init__(self, n, a, w, g, t):
        super(Sample, self).__init__(n=n, a=a, w=w, g=g, t=t)


test = Sample(n="WangTeng", a=18, w=130, g=1000, t="Dont' be a 奋斗逼")

print("My name is {}, I'm {} years old. Today I want to tell u about {} which grade is {} ".format(test.name, test.age,
                                                                                                   test.topic,
                                                                                                   test.grade, ))
print(Sample.mro())

输出结果Bingo完美解决:

My name is WangTeng, I'm 18 years old. Today I want to tell u about Dont' be a 奋斗逼 which grade is 1000 
[<class '__main__.Sample'>, <class '__main__.Speaker'>, <class '__main__.Student'>, <class '__main__.People'>, <class 'object'>]

__init__() & super()都各有利弊,super()不太容易看出来初始化过程,此过程是由超类的导入顺序来决定的,__init__()对于编码来说,超类的构造函数运行顺序是由编码者决定的,给编码带来了更多的选择的空间,但可能会导致一些用户没想到的问题。It's up to u how to choose __init__() & super()