python 类的详细学习记录(包括特殊属性和特殊方法)

参考

python类的成员可以分为三个方面:字段、方法和属性。注意python都是引用,不同变量的数据值假如是相同的,就会去引用同一数据,指向同一块内存(也因此实现了弱类型,因为创建变量只是创建引用,即创建指向某一块内存,修改变量,给变量赋值其他类型,也只是修改变量的引用,指向其他的内存。)

在定义一个类以后,方法、属性和静态字段都是属于类的,在内存中只保存一份,只有普通字段是每个对象独有的,该类的每个对象都会创建并保存自己的一份。

 

我们可以将存储区分为几种:程序存储区、全局存储区、静态存储区、栈存储区。

  • 程序存储区: 我们所有的普通函数、类函数、类等程序所存储的位置。

  • 全局存储区:所有全局变量的存储区。

  • 静态存储区:类的静态变量存储区。

  • 栈存储区:栈存储区就是局部变量存储,所有的局部变量、类对象等都存储在这里。

 字段

字段包括:普通字段和静态字段,使用和定义都是不一样的,其最本质的区别就是内存中保存的位置不同。

  • 普通字段:每个对象的专属

  • 静态字段:属于类,该类的所有对象共享。通过类名.字段名去修改,所有对象的字段值都改变;通过对象名.字段名去修改,只有该对象的字段值被修改。

class Demo:
    aa = "我是静态字段" # 静态字段
    def __init__(self, name):
        self.bb = name # 普通字段

    def custom_func(self, name):
        self.cc = name
        
>>> obj1 = Demo("obj1")
>>> print("obj1.aa = ", obj1.aa) 
obj1.aa =  我是静态字段              # 调用得到静态字段的初始值
>>> Demo.aa = 111111                # 修改类的静态字段的值,注意调用方式是【类名.字段】
>>> print("obj1.aa = ", obj1.aa)
obj1.aa =  111111
>>> obj2 = Demo("obj2")
>>> print("obj1.aa = ", obj1.aa)    # 创建新对象
obj1.aa =  111111
>>> print("obj2.aa = ", obj2.aa)
obj2.aa =  111111                   # 所有该类对象的静态字段的值都被更改


>>> obj1.aa = 1                     # 修改对象的静态字段的值,只是该对象的静态字段被修改
>>> print("obj1.aa = ", obj1.aa)
obj1.aa =  1
>>> print("obj2.aa = ", obj2.aa)
obj2.aa =  111111
>>> print("Demo.aa = ", Demo.aa)
Demo.aa =  111111
>>> id(obj1.aa)                      # 修改后obj1.aa变量的引用从111111变为1
140735426205456
>>> id(obj2.aa)                        
2039924347312
>>> id(Demo.aa)
2039924347312
>>> obj2.aa =1
>>> id(obj2.aa)
140735426205456               # python为变量引用数据,加上其GC原理,值相同的变量指向同一块内存

通常情况下我们都使用普通字段,当一个变量在类的所有对象中共同使用,而且数据共享的时候,我们就可以使用静态字段。

方法

方法包括:普通方法、静态方法和类方法。他们的区别在于调用方式不同。

  • 普通方法:由对象调用包含一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;

  • 类方法:由类调用包含一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;

  • 静态方法由类调用;没有默认参数

>>> class Foo:
    FooName = "静态字段"
    def custom_func(self, name):
        # 普通方法,至少包含一个self参数
        self.name = name # 调用后,为该对象创建一个普通字段,并赋值
        print("普通方法")

    @classmethod
    def class_func(cls, name):
        # 类方法,至少包含一个cls参数
        cls.name = name  # 调用后,为该创建一个静态字段,并赋值,该类的所有对象都有值相同的name
        print("类方法")

    @staticmethod
    def static_func():
        # 静态方法,没有默认参数
        print("静态方法")

>>> f = Foo()
>>> f.custom_func("aaa")    # 调用普通方法  对象名.方法名
普通方法
>>> Foo.class_func("bbb")   # 调用类方法 类名.方法名
类方法
>>> Foo.static_func()  # 调用静态方法 类名.方法名
静态方法

普通方法的实现中可以直接使用对象的普通字段self.name
类方法中可以直接使用静态字段cls.FooName
静态方法中不能直接使用普通字段,静态字段需要通过 类名.静态字段名 进行调用

注意:静态字段在哪儿调用都是类名.静态字段名

属性

Python中的属性其实是普通方法的变种。就是普通方法+装饰器,实现和字段同样的调用效果(即是调用时不加括号)本质:就是使得普通方法的调用换为一种简洁的调用方式,(主要原因是@property装饰器或者是property类可以创建只读属性)

对于属性,有以下两个知识点:基本使用,两种定义方式

  1. 属性的基本使用
class Foo:
    def func(self):
        pass

    @property      # 内置装饰器,用来创建只读属性,使得可以像访问字段一样来获取一个函数的返回值
    def prop(self):
        print("属性")

f = Foo()

# 调用函数
f.func()

# 调用属性         
f.prop

访问属性时可以制造出和访问字段完全相同的假象,它拥有字段的简洁性,又拥有方法的多功能性

属性的定义有两种方式:

  • 装饰器:在方法上应用装饰器

  • 静态字段:在类中定义值为property对象的静态字段

我们知道Python中的类有经典类和新式类之分,如果类继承自object,那么该类是新式类,新式类的属性比经典类更丰富。但是现在都已经使用python3了,而python3中默认类都继承自object,所以python3中全是新式类。(不知所云)

  • 装饰器方式:普通方法加上@property装饰器

class Goods:
    @property
    def price(self):
        print("@property")

    @price.setter
    def price(self, val):
        print("@price.setter: ", val)

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

obj = Goods()
obj.price            # 自动执行@property修饰的price方法,并获取方法的返回值
obj.price = 100  # 自动执行@price.setter修饰的price方法,并将100赋值给方法的参数
del obj.price      # 自动执行@price.deleter修饰的price方法
 
# 属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
# 本质是换了更加简洁的调用方式,制造假象
  •  静态字段方式,使用property创建静态字段

property是一个类,在builtins.py文件中,初始化函数:def __init__(self, fget=None, fset=None, fdel=None, doc=None),有四个参数

  • 第一个参数fget是方法名,调用 对象.属性 时自动触发执行方法

  • 第二个参数fset是方法名,调用 对象.属性 = XXX 时自动触发执行方法

  • 第三个参数fdel是方法名,调用 del 对象.属性 时自动触发执行方法

  • 第四个参数doc是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

class Foo:
    def __init__(self):
        self.price = 10

    def get_price(self):
        return self.price

    # set函数必须有两个参数
    def set_price(self, value):
        self.price = value

    def del_price(self):
        del self.price

    PRICE = property(get_price, set_price, del_price, "description Price")

f = Foo()
print(f.PRICE)    # 自动调用get_price
f.PRICE = 20     # 自动调用set_price
del f.PRICE        # 自动调用del_price

在python的类中,没有真正的私有化,不管是方法还是属性,为了编程的需要,约定加了下划线 _ 的属性和方法不属于API,不应该在类的外面访问,也不会被from M import * 导入。类中的方法和字段通过下划线来区分其作用域:

  • xx:公有变量,所有对象都可以访问;

  • _xx:单前置下划线,私有化属性和方法,for 包名 import * 禁止导入,类对象和子类可以访问,使用对象._变量名调用

  • __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问,该类的私有变量;

  • __xx__:双前后下划线,用于定义类的特殊属性/模法方法,例如:__init__,__str__等,无法在类的外部直接被import,但是部分可以通过类名.__xx__实现调用;

  • xx_:单后置下划线,用于避免与python关键字的冲突。

Python 类中,凡是以双下划线 "__" 开头和结尾命名的成员(属性和方法),都被称为类的特殊成员(特殊属性和特殊方法)。Python 类中的特殊成员,其特殊性类似 C++ 类的 private 私有成员,即不能在类的外部直接调用,但允许借助类中的普通方法调用甚至修改它们。如果需要,还可以对类的特殊方法进行重写,从而实现一些特殊的功能。

重载运算符含义
__new__创建类,在 __init__ 之前创建对象
__init__类的构造函数,其功能是创建类对象时做初始化工作。
__del__ 析构函数,其功能是销毁对象时进行回收资源的操作
__dict__获取l类或者对象的所有成员,调用方法:类名/对象名.__dict__
__add__加法运算符 +,当类对象 X 做例如 X+Y 或者 X+=Y 等操作,内部会调用此方法。但如果类中对 __iadd__ 方法进行了重载,则类对象 X 在做 X+=Y 类似操作时,会优先选择调用 __iadd__ 方法。
__radd__当类对象 X 做类似 Y+X 的运算时,会调用此方法。
__iadd__重载 += 运算符,也就是说,当类对象 X 做类似 X+=Y 的操作时,会调用此方法。
__or__“或”运算符 |,如果没有重载 __ior__,则在类似 X|Y、X|=Y 这样的语句中,“或”符号生效
__repr__,__str__在打印print/打印或者str转换时,输出该方法的返回值。
__call__函数调用,使用对象X(*args, **kwargs) ,就调用__call__()
__getattr__点号运算,用来获取类属性
__setattr__属性赋值语句,类似于 X.any=value
__delattr__删除属性,类似于 del X.any
__getattribute__获取属性,类似于 X.any
__getitem__索引运算,类似于 X[key],X[i:j],在dataset类中该方法负责实现读取样本逻辑
__setitem__索引赋值语句,类似于 X[key], X[i:j]=sequence
__delitem__ 索引和分片删除
__get__, __set__, __delete__描述符属性,类似于 X.attr,X.attr=value,del X.attr
__len__ 计算长度,类似于 len(X)
__lt__,__gt__,__le__,__ge__,__eq__,__ne__ 比较,分别对应于 <、>、<=、>=、=、!= 运算符。
__iter__,__next__迭代环境下,生成迭代器与取下一条,类似于 I=iter(X) 和 next()
__contains__成员关系测试,类似于 item in X
__index__ 整数值,类似于 hex(X),bin(X),oct(X)
__enter__,__exit__在对类对象执行类似 with obj as var 的操作之前,会先调用 __enter__ 方法,其结果会传给 var;在最终结束该操作之前,会调用 __exit__ 方法(常用于做一些清理、扫尾的工作)
__module__,__class__表示当前操作的对象在那个模块(即.py文件)/属于哪个类

注意__init__与__call__的区别:两个都是实现类时的关键方法,__init__的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class  Foo:
    def  __init__(self):
        pass

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

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

额外讲点:

super() 函数是用于调用父类(超类)的一个方法。使用super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的序表。

class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super().add(x)
b = B()
b.add(2)  # 3

Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx

super(FooChild,self) 首先找到 FooChild 的父类(就是类 FooParent),然后把类 FooChild 的对象转换为类 FooParent 的对象

关于类方法中的cls

在python的类方法中,默认使用的第一个参数是cls,而在实例方法中,一般使用self作为第一个参数。

(1)比较一般类方法中的self和cls的区别:一般来说,使用某个类的方法,需要先将类实例化,赋予一个对象才可以调用类中的方法,但是如果使用了@staticmethod 或@classmethod,就可以不用实例化,直接类名.方法名()来调用。

(2)在classmethod中可以调用类中定义的其他方法、类的属性,但staticmethod只能通过类名.属性调用类的属性,但无法通过在该函数内部调用类名.常规方法

(3)关于cls(),其实这就是类本身,比如这里的cls()=A,如果cls()里面有参数,那么这个参数就是构造函数init(self,parameter1,parameter2)中的参数1,2,同时还是表示类本身。

 python中的cls到底指的是什么,与self有什么区别? - 知乎

答疑《python的CLS》 - 云+社区 - 腾讯云 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值