python 类


python的类变量和C++的静态变量不同,并不是由类的所有对象共享。

类本身拥有自己的类变量(保存在内存),当一个 TestClass 类的对象被构造时,会将当前类变量拷贝一份给这个对象,当前类变量的值是多少,这个对象拷贝得到的类变量的值就是多少;而且,通过对象来修改类变量,并不会影响其他对象的类变量的值,因为大家都有各自的副本,更不会影响类本身所拥有的那个类变量的值;只有类自己才能改变类本身拥有的类变量的值。

对于类数据属性实例数据属性,可以总结为:

  • 类数据属性属于类本身,可以通过类名进行访问/修改
  • 类数据属性也可以被类的所有实例访问/修改
  • 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
  • 实例数据属性只能通过实例访问
  • 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
class TestClass(object):
    # 类变量
    val1 = 100

    def __init__(self):
        # 成员变量
        self.val2 = 200

    def fcn(self, val=400):
        val3 = 300
        self.val4 = val
        self.val5 = 500
inst = TestClass()

print(TestClass.val1)  # 100
print(inst.val1)  # 100
print(inst.val2)  # 200

print(inst.val3)  # 'TestClass' object has no attribute 'val3'
print(inst.val4)  # 'TestClass' object has no attribute 'val4'
print(inst.val5)  # 'TestClass' object has no attribute 'val5'
inst1 = TestClass()
inst2 = TestClass()

print(inst1.val1)  # 100

inst1.val1 = 1000
print(inst1.val1)  # 1000

print(TestClass.val1)  # 100
print(inst2.val1)  # 100

TestClass.val1 = 2000
print(TestClass.val1)  # 2000
print(inst1.val1)  # 1000     被重新赋值后,就跟类变量没关系了
print(inst2.val1)  # 2000     没有重新赋值,跟类变量保存一致

inst3 = TestClass()
print(inst3.val1)  # 2000
不可变对象与可变对象

python内置的一些类型中

  • 可变对象:list dict set

  • 不可变对象:tuple string int float bool

Python中万物皆对象,每个对象包含3个属性,id,type,value

  • id就是对象地址,可以通过内置函数id()查看对象引用的地址。

  • type就是对象类型,可以通过内置函数type()查看对象的类型。

  • value就是对象的值

is 和==
  • is比较的是id是不是一样

  • ==比较的是值是不是一样。

a = 1
b = a
c = 1
d = 1.0
print(id(a))  # 140729241962160
print(id(b))  # 140729241962160
print(id(c))  # 140729241962160
print(id(d))  # 2070028299024
print(a is d)  # False
print(a == d)  # True
  • 判断a is d的时候,实际上比较的是id(d)==id(d),结果为False。

  • 判断a==d的时候,实际上比较的是id(a)这个地址指向的值是不是和id(d)这个地址指向值一样。结果为True。

python为了实现对内存的有效利用,对小整数[-5,256]内的整数会进行缓存,不在该范围内的则不会缓存。

注意在python交互式界面有内存池缓存机制,只适用于-5~256,在python脚本编程中则没有这个限制:

a = 255
b = 255
print(a is b)  # True

c = 257
d = 257
print(c is d)  # True

Python console

a = 255
b = 255
id(a)
Out[10]: 2922684620976
id(b)
Out[11]: 2922684620976
a is b
Out[4]: True
c = 257
d = 257
id(c)
Out[8]: 2920614082448
id(d)
Out[9]: 2920614081904    
c is d
Out[7]: False
类属性
class Person(object):
    tall = 180
    hobbies = []

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

    def inform(self):
        print('%s is %s weights %s' % (self.name, self.age, self.weight))

特殊的类属性:对于所有的类,都有一组特殊的属性

__name__:类的名字(字符串)

__doc__ :类的文档字符串

__bases__:类的所有父类组成的元组

__dict__:类的属性组成的字典

__module__:类所属的模块

__class__:类对象的类型

print(Person.__name__)  # Person
print(Person.__doc__)  # None
print(Person.__bases__)  # (<class 'object'>,)
print(Person.__dir__)  # <method '__dir__' of 'object' objects>
print(Person.__module__)  # __main__
print(Person.__class__)  # <class 'type'>

__dict__dir()区别

  • __dict__属性:
    • 类的__dict__存储所有实例共享的变量和函数(类属性,方法等),类的__dict__不包含其父类的属性
    • 实例的__dict__属性仅仅是那个实例的实例属性的集合,并不包含该实例的所有有效属性,正是因为实例的__dict__属性,每个实例的实例属性才会互不影响。
    • python一切皆对象,并不是所有对象都拥有__dict__属性。许多内建类型就没有__dict__属性,如list,此时就需要用dir()来列出对象的所有属性。
  • dir()函数:
    dir()是Python提供的一个API函数,dir()函数会自动寻找一个对象的所有属性(包括从父类中继承的属性)。
    所以如果想获取一个对象所有有效属性,应使用dir()

# 类数据属性属于类本身,可以通过类名进行访问/修改,此处添加"football"、"woman"两个
Person.hobbies.extend(["football", "woman"])
print(Person.hobbies)  # ['football', 'woman']

# 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
Person.hobbies2 = ["reading", "jogging", "swimming"]
print(Person.hobbies2)  # ['reading', 'jogging', 'swimming']

# 实例数据属性只能通过实例访问
Bruce = Person("Bruce", 25, 60)
print(f"{Bruce.name} is {Bruce.age} years old")  # Bruce is 25 years old

# 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
Bruce.gender = "male"
print("{Bruce.name} is {Bruce.gender}")  # Bruce is male

# class instance can access class attribute
Bruce.hobbies.append("C#")
print(Bruce.hobbies)  # ['football', 'woman', 'C#']
print(Bruce.hobbies2)  # ['reading', 'jogging', 'swimming']

类数据属性属于类本身,被所有该类的实例共享并且,通过实例可以去访问/修改类属性。但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现**属性"隐藏"**的情况

对于不可变类型的类属性,隐藏属性可以总结为:

  • 对于不可变类型的类属性 person.tall,可以通过实例Bruce进行访问,并且"person.tall is Bruce.tall"
  • 当通过实例赋值/修改tall属性的时候,将为实例Bruce新建一个tall实例属性,这时,“person.tall is not Bruce.tall
  • 当通过"del Bruce.tall"语句删除实例的tall属性后,再次成为"person.tall is Bruce.tall"
# 对于不可变类型的类属性person.tall,可以通过实例Bruce进行访问
print(Person.tall is Bruce.tall)  # True

# 重新赋值或者修改
Bruce.tall = 185
print(Bruce.tall)  # 185
print(Person.tall is Bruce.tall)  # False

# 再次删除实例的赋值
del Bruce.tall
print(Bruce.tall)  # 180
print(Person.tall is Bruce.tall)  # True

对于可变类型的类属性,隐藏属性可以总结为:

  • 同样对于可变类型的类属性person.hobbies,可以通过实例Bruce进行访问,并且"person.hobbies is Bruce hobbies"
  • 当通过实例赋值hobbies 属性的时候,都将为实例Bruce新建一个hobbies实例属性,这时,“person.hobbies is not Bruce hobbies
  • 当通过"del Bruce. hobbies"语句删除实例的hobbies属性后,再次成为"person. hobbies is Bruce hobbies"
  • 当通过实例修改hobbies属性的时候,将修改Bruce. hobbies指向的内存地址(即person.hobbies),此时,“person.hobbies is Bruce. hobbies
# 对于可变类型的类属性person.hobbies,可以通过实例Bruce进行访问
print(Person.hobbies is Bruce.hobbies)  # True

Bruce.hobbies.append("CSS")
print(Person.hobbies is Bruce.hobbies)  # True
print(Person.hobbies)  # ['football', 'woman', 'C#', 'CSS']

Will = Person("Will", 27, 60)
print(f"{Will.name} is {Will.age} years old") # Will is 27 years old

# Will shares the same class attribute with wilber
# Will don't have the "gender" attribute that belongs to wilber
print(Will.hobbies)  # ['football', 'woman', 'C#', 'CSS']
print(Will.gender)  # AttributeError: 'Person' object has no attribute 'gender'

注意,虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦

__del__
class Test:
    # 当内存不需要的时候调用这个删除方法,python解释器自动调用
    def __del__(self):
        print("Over")


t1 = Test()
t2 = t1
del t1
del t2
print("==========")

# Over
# ==========
class Test:
    # 当内存不需要的时候调用这个删除方法,python解释器自动调用
    def __del__(self):
        print("Over")

t1 = Test()
t2 = t1
del t1
print("==========")

# ==========
# Over

当删除了t1,内存空间还没有结束,还不会调用__del__方法,当调用完最后一条语句时,内存空间被释放,调用__del__方法

类class的访问控制

"_“和”__"的使用 更多的是一种规范/约定,并没有真正达到限制的目的

单下划线"_"

以单下划线开头的表示的是 protected 类型的变量,即只能允许其本身与子类进行访问;同时表示弱内部变量标示,如"from moduleName import * " 将不会引入以单下划线 "_"开头的函数

双下划线"__"

双下划线的表示的是private类型的变量。只能是允许这个类本身进行访问了,连子类也不可以,这类属性在运行时属性名会加上单下划线和类名。

  • 对于Python中的类属性,可以通过双下划线**"__来实现一定程度的私有化**,因为双下划线开头的属性在运行时会被"混淆”(mangling)

  • 双下划线的另一个重要的目地是,避免子类对父类同名属性的冲突

  • 单下划线,可被重写,调用子类方法

  • 双下划线,不能被重写,调用的还是父类方法

class A(object):
    def __init__(self):
        self.__private()
        self.public()

    def __private(self):
        print('A.__private()')

    def public(self):
        print('A.public()')


class B(A):
    def __private(self):
        print('B.__private()')

    def public(self):
        print('B.public()')


b = B()

# A.__private()
# B.public()
类class的继承

在Python中,同时支持单继承多继承

实现继承之后,子类将继承父类的属性(除了文档字符串),也可以使用内建函数insubclass()来判断一个类是不是另一个类的子孙类

类型比较
  • type()不会认为子类是一种父类类型
  • isinstance()会认为子类是一种父类类型。
class Foo(object):
  pass

class Bar(Foo):
  pass

print(type(Foo()) == Foo)  # True
print(type(Bar()) == Foo)  # False
print(isinstance(Foo(), Foo))  # True
print(isinstance(Bar(), Foo))  # True

**Example 1 **

class A:
    def __init__(self):
        self.__j = 1
        self.number = 5

class B(A):
    def __init__(self):
        self.__j = 2
        self.number = 7

    def show(self):
        print(self.__j, self.number)

b = B()
b.show() # 2 7

**Example 2 **

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

    def method(self):
        self.__method()

class B(A):
    def __method(self):
        print("I'm a method in B")


B().method()  # I'm a method in A
A().method()  # I'm a method in A

Example 3

class Parent(object):
    """
    parent class
    """
    pass


class Child(Parent):
    pass


# doc属性不会被继承
print(Parent.__doc__)  # parent class
print(Child.__doc__)  # None

super的使用

super主要显式调用父类,在子类中,一般会定义与父类相同的属性(数据属性,方法),从而来实现子类特有的行为。也就是说,子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的属性和方法

调用父类的方法:

  1. 将“self”显式的传递进去:
class Parent(object):
    Value = "Hi, Parent value"
    def fun(self):
        print("This is from Parent")

class Child(Parent):
    Value = "Hi, Child  value"
    def fun(self):
        print("This is from Child")
        # 调用父类Parent的fun函数方法
        Parent.fun(self)  

c = Child()
c.fun()

# This is from Child
# This is from Parent
  1. 使用Python中的super关键字
class Parent(object):
    Value = "Hi, Parent value"
    def fun(self):
        print("This is from Parent")

class Child(Parent):
    Value = "Hi, Child  value"
    def fun(self):
        print("This is from Child")
        # 相当于用super的方法与上一调用父类的语句置换
        super(Child, self).fun()

c = Child()
c.fun()

# This is from Child
# This is from Parent
super()

对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个MRO列表的构造是通过一个C3线性化算法来实现的。 实际上就是合并所有父类的MRO列表并遵循如下三条准则

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

虽然名义上来说super是用来调用父类中的方法,但是super实际上是在MRO表中找到下一个匹配的类。super原型如下:

def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]

两个参数 clsinst 分别做了两件事:

  1. inst 负责生成 MRO 的 list
  2. 通过 cls 定位当前 MRO 中的 index, 并返回mro[index + 1]

但是根据我们上面说的super本质知道 super 和父类其实没有实质关联,我们就不难理解为什么 enter B 下一句是 enter C 而不是 enter A了(如果认为 super 代表“调用父类的方法”,会想当然的认为下一句应该是enter A)。

可以用 self.__class__.__mro__ 方法来查询当前MRO

一个多继承中的MRO是固定的(只要每个类之间都有继承关系)

Example:

class A(object):
    def __init__(self):
        print("Enter A")

class B(A):
    def __init__(self):
        print('Enter B')
        super(B, self).__init__()
        print('Leave B')

class C(A):
    def __init__(self):
        print('Enter C')
        super(C, self).__init__()
        print('Leave C')

class D(B, C):
    def __init__(self):
        print('Enter D')
        super(D, self).__init__()
        print("Leave D")
        print(self.__class__.__mro__)
        
d = D()

# Enter D
# Enter B
# Enter C
# Enter A
# Leave C
# Leave B
# Leave D
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

上方例子的中流程:

  1. class D中,输出“Enter D" , 然后就会调用super方法,super()方法,第一个参数是D,在MRO列表中的下标(index)为0,那么调用的下一个类就是下标为(index+1)的类,即class B,

  2. 进入class B,输出"Enter B" ,再次调用super(),此时的index为1,那么调用的下一个类的index为2,即class C,输出“Enter C” .

  3. class C中,调用super(),进入class A,输出“Enter A”,

  4. 回到class C ,输出 “Leave C” , 再回到class B ,输出“Leave B”, 然后回到class D,输出“Leave D”。结束

当你使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super()并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。

__new__

__new__方法:类级别的方法

  1. 是在类准备将自身实例化时调用,并且至少需要传递一个参数**cls**,此参数在实例化时由python解释器自动提供
  2. 始终是类的类方法,即使没有被加上类方法装饰器;
  3. 必须要有返回值,返回实例化出来的实例;在自己实现__new__()时需要注意:可以return父类(通过super(当前类名,cls)).__new__出来的实例,或者直接是object的__new__出来的实例
class A(object):
    pass

# 默认调用父类object的__new__()方法来构造该类的实例
a = A()
print(a)  # <__main__.A object at 0x000001BE6F8CD520>

class A(object):
    def __new__(cls):
        "重写__new__方法"
        return "abc"

a = A()
print(a)  # 'abc'
print(type(a))  # <class 'str'>

通过__new__()方法实现单例

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance


a = Singleton()
b = Singleton()
c = Singleton()
print(a)  # <__main__.Singleton object at 0x000001F0B57D86D0>
print(b)  # <__main__.Singleton object at 0x000001F0B57D86D0>
print(c)  # <__main__.Singleton object at 0x000001F0B57D86D0>
__init__

__init__方法:实例级别的方法

  1. 有一个参数self, 该self参数就是__new__()返回的实例;

  2. __init__()__new()__的基础上完成初始化动作,不需要返回值

__new__()没有正确返回当前类cls的实例,那__init__()将不会被调用

创建的每个实例都有自己的属性,方便类中的实例方法调用;

class A:
    def __new__(cls, *args, **kwargs):
        print("A' __new__")
        # return super(A,cls).__new__(cls)
        return object.__new__(cls)

    def __init__(self):
        print("A' __init__")


class B(A):
    def __new__(cls, *args, **kwargs):
        print("B' __new__")

    def __init__(self):
        print("B' __init__")

a = A()
# B' __new__  没有创建真正的类B,所以类B的构造函数没有调用
b = B()


# A' __new__
# A' __init__
# B' __new__

当在Python中出现继承的情况时,一定要注意初始化函数__init__的行为:

  • 如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错

  • 如果子类定义了自己的初始化函数,而在子类中没有显式调用父类的初始化函数,则父类的属性不会被初始化

  • 如果子类定义了自己的初始化函数,在子类中显式调用父类,子类和父类的属性都被初始化

元类

类是能够创建出类实例的对象。类本身也是实例,它们是元类的实例

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了typetype实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。

其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

class A:
    pass

print(type(1))  # <class 'int'>
print(type(type(1)))  # <class 'type'>
print(type(int))  # <class 'type'>
print(type(A))  # <class 'type'>

元类是类的类,常可以用在类工厂中;

  • Python中所有的类都是对象,可以通过type()来创建元类
  • 在定义类时,可用过metaclass参数来指定此类的元类
  • Python类语句执行时,会先查找其类本身的metaclass属性,如果没找到,会继续在父类中找,还没找到,则到模块中找,最后再用内置的type来创建此类对象
  • 使用类、函数都可以当作元类,通常在**__new__**方法中通过type来自定义自己的元类
  • 从设计的复杂度来讲,尽量少用元类,多用普通类或函数
  • 33
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值