python 浅浅盘一盘“类”

最初学习的编程语言是C,后来接触了C++,但是C++的特色就是类,当时学得朦朦胧胧,再后来接触python,学习python类的时候感觉似曾相识,与C++异曲同工,今天就好好整理一下。

1. 术语预览

  1. 类的属性(Class Atrribute)
  2. 实例(instance)
  3. 访问类属性(attribute references)
  4. 方法(Method)/实例方法(Instance Method)
  5. self参数(就是被创建的实例本身)
  6. “魔法方法”(是方法,不过会魔法,莫名的产生,如__inti__()等)。

1.1 私有属性和“受保护的”属性

python 私有属性和受保护属性

  • 私有属性
    什么是私有?属于类的属性,甚至在类外无法访问查看。
    怎么表示?属性前加下划线: _属性名

    class A:
    def __init__(self, x, y):
        self.x = x
        self.__y = y
    >>> a = A(1,2)
    >>> a.x
    output: 1
    >>> a.__y
    output: 报错
    >>> a._A__y
    output: 2
    
  • 受保护属性
    什么是私有?类外可以访问但是不能修改。

    没什么原理,就是约定. python程序员约定, 不会在类外部使用该属性,编译器不会对该属性做任何处理。

    就是编译器不会管你,但是是大家规定好的,对受保护属性进行修改是一种“合理但有病”的行为。

    class A:
    def __init__(self, x, y, z):
        self.x = x
        self._y = y
        self.__z = z
    
    a = A(2,3,4)
    print(a.x)
    print(a._y)
    
    

2. 例子与分析

python3.8

class CocaCola:
    """
    一个可口可乐类,初始化时被贴上中文标签
    可口可乐可以被买
    被喝
    """
    formula = ['caffeine', 'sugar', 'water', 'soda']  # formula称为类的属性(Class Atrribute)

    def __init__(self, logo_name):  # __init__(self)称为魔法方法
        self.local_logo = logo_name  # self.local_logo称为类的属性(Class Atrribute)

    def buy(self, money):  # def buy(self, money)称为方法(Method)或实例方法(Instance Method),供实例使用
        print('Cost {} yuan.'.format(money))
        # print('Cost ' + str(money) + ' yuan')  # 同样效果

    def drink(self,how_much):  # def drink(self,how_much)称为方法
        if how_much == 'a sip':
            print(self.local_logo + ' Cool~')
        elif how_much == 'whole bottle':
            print(self.local_logo + ' Headache!')


ice_coke = CocaCola('可口可乐')  # 创建一个冰可乐的实例
print(ice_coke.formula)  # ['caffeine', 'sugar', 'water', 'soda']  # 访问属性
ice_coke.buy(3.5)  # Cost 3.5 yuan.
ice_coke.drink('a sip')  # 可口可乐 Cool~

ice_coke2 = CocaCola('CocaCola')  # 可口可乐 Cool~
ice_coke2.drink('whole bottle')  # CocaCola Headache!

这里举了一个可口可乐的例子,我们对于可口可乐的常识,可口可乐有相应的配方,同时可口可乐可以被买被喝。

  1. 什么是属性,实例方法
    我理解的属性是一种形容词,可以描述类的特征,比如你向别人介绍你时,你可以说你的身高体重,身高体重就是人这一类的属性。
    在此例中formulaself.local_logo都为属性,只是self.local_logo多了一个self,以self为前缀的变量都可供类中的所有方法使用。
    实例方法为类的一些动作,方法前加实例二字,只是说类中的方法是供实例来调用的。本例中创建了两个方法,分别为 def buy(self, money):买可乐和def drink(self,how_much): 喝可乐。
  2. 怎样创建实例、访问属性和调用方法
    创建实例ice_coke = CocaCola('可口可乐'),需要注意的是其传入的参数要根据__init__(self, logo_name)方法的形式而定。
    访问属性ice_coke.formula句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。
    调用方法:ice_coke.buy(3.5)ice_coke.drink('a sip')
  3. 创建多个实例
    ice_coke2 = CocaCola('CocaCola')

3. 魔法方法

魔法方法为python事先就定义好了魔法方法的运行规则,在创建删除类时能够被自动调用的方法,最大的特征就是开头和末尾各有两个下划线,如def __init__(self, logo_name):,还有好多魔法方法,在以后遇到了再一点点列出。
魔法方法让我感觉有点像C++中运算符的重载,让类实现普通数据结构的一些功能,甚至超过原本的这些功能。

魔法方法说明
__init__(self)初始化,设置对象属性的一些初始值
__new__(self)创建对象,先于__init__
__iter__(self)当类加入该魔法方法后,类实现的实例对象实现循环后最先触发该魔法方法,用于初始化,此后的循环不会触发该魔法方法。比如c语言中的for(int i = 0; i < 5; i++),相当于int i = 0
__next__(self)相当于 i < 5; i++
__len__(self)len(实例)返回实例的长度
__del__对象被从内存中销毁前,会被 自动 调用
__str__返回对象的描述信息,print 函数输出使用
__call__详解Python的__call__()方法

关于魔法方法超棒的链接:
python 魔术方法(四)非常用方法与运算符重载方法大合集
Python运算符重载详解及实例代码

3.1 基本的魔法方法

  • __init__(self[, ...]) 构造器,当一个实例被创建的时候调用的初始化方法。

    # __init__()新增实例属性
    class CocaCola():
        formula = ['caffeine','sugar','water','soda']
    
        def __init__(self):
            self.local_logo = 'kekekele'
    
        def drink(self):    # HERE
            print('Energy!')
    
            
    coke = CocaCola()
    print(coke.local_logo)
    
    # __init__()可以有自己的参数
    class CocaCola:
        formula = ['caffeine','sugar','water','soda']
        def __init__(self,logo_name):
        	self.local_logo = logo_name
        def drink(self):
        	print('Energy!')
    coke = CocaCola('kekekele')
    coke.local_logo
    
  • __new__(cls[, ...]) 在一个对象实例化的时候所调用的第一个方法,在调用__init__初始化前,先调用__new__

    • __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由 Python 解释器自动提供,后面的参数直接传递给__init__
    • __new__对当前类进行了实例化,并将实例返回,传给__init__self。但是,执行了__new__,并不一定会进入__init__,只有__new__返回了,当前类cls的实例,当前类的__init__才会进入。
    • __new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行,将没有__init__被调用
    class A(object):
        def __init__(self, value):
            print("into A __init__")
            self.value = value
    
        def __new__(cls, *args, **kwargs):
            print("into A __new__")
            print(cls)
            return object.__new__(cls)
    
    
    class B(A):
        def __init__(self, value):
            print("into B __init__")
            self.value = value
    
        def __new__(cls, *args, **kwargs):
            print("into B __new__")
            print(cls)
            return super().__new__(cls, *args, **kwargs)
    
    
    b = B(10)
    
    # 结果:
    # into B __new__
    # <class '__main__.B'>
    # into A __new__
    # <class '__main__.B'>
    # into B __init__
    
    class A(object):
        def __init__(self, value):
            print("into A __init__")
            self.value = value
    
        def __new__(cls, *args, **kwargs):
            print("into A __new__")
            print(cls)
            return object.__new__(cls)
    
    
    class B(A):
        def __init__(self, value):
            print("into B __init__")
            self.value = value
    
        def __new__(cls, *args, **kwargs):
            print("into B __new__")
            print(cls)
            return super().__new__(A, *args, **kwargs)  # 改动了cls变为A
    
    
    b = B(10)
    
    # 结果:
    # into B __new__
    # <class '__main__.B'>
    # into A __new__
    # <class '__main__.A'>
    
    
  • __del__(self) 析构器,当一个对象将要被系统回收之时调用的方法。

    class C(object):
        def __init__(self):
            print('into C __init__')
    
        def __del__(self):
            print('into C __del__')
    
    
    c1 = C()
    # into C __init__
    c2 = c1
    c3 = c2
    del c3
    del c2
    del c1
    # into C __del__
    
    
  • __str__(self):

    • 当你print打印一个对象的时候,触发__str__
    • 当你使用%s格式化的时候,触发__str__
    • str强转数据类型的时候,触发__str__
  • __repr__(self)

    • reprstr的备胎
    • __str__的时候执行__str__,没有实现__str__的时候,执行__repr__
    • repr(obj)内置函数对应的结果是__repr__的返回值
    • 当你使用%r格式化的时候 触发__repr__
    class Cat:
        """定义一个猫类"""
    
        def __init__(self, new_name, new_age):
            """在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
            self.name = new_name
            self.age = new_age
    
        def __str__(self):
            """返回一个对象的描述信息"""
            return "名字是:%s , 年龄是:%d" % (self.name, self.age)
            
        def __repr__(self):
            """返回一个对象的描述信息"""
            return "Cat:(%s,%d)" % (self.name, self.age)
    
        def eat(self):
            print("%s在吃鱼...." % self.name)
    
        def drink(self):
            print("%s在喝可乐..." % self.name)
    
        def introduce(self):
            print("名字是:%s, 年龄是:%d" % (self.name, self.age))
    
    
    # 创建了一个对象
    tom = Cat("汤姆", 30)
    print(tom)  # 名字是:汤姆 , 年龄是:30  __str__
    print(str(tom)) # 名字是:汤姆 , 年龄是:30  __str__
    print(repr(tom))  # Cat:(汤姆,30)  __repr__
    tom.eat()  # 汤姆在吃鱼....
    tom.introduce()  # 名字是:汤姆, 年龄是:30
    

    __str__(self) 的返回结果可读性强。也就是说,__str__ 的意义是得到便于人们阅读的信息,就像下面的 ‘2019-10-11’ 一样。

    __repr__(self) 的返回结果应更准确。怎么说,__repr__ 存在的目的在于调试,便于开发者使用。

  • __iter__(self)

    class Vector2d:
    	def __init__(self, x, y):
    		self.x = float(x) 
    		self.y = float(y)
    	def __iter__(self):
    		return (i for i in (self.x, self.y))
    		
    v1 = Vector2d(3, 4)
    x, y = v1  # 调用__iter__
    

3.2 算术运算符魔法方法

  • __add__(self, other)定义加法的行为:+
  • __sub__(self, other)定义减法的行为:-
  • __mul__(self, other)定义乘法的行为:*
  • __truediv__(self, other)定义真除法的行为:/
  • __floordiv__(self, other)定义整数除法的行为://
  • __mod__(self, other) 定义取模算法的行为:%
  • __divmod__(self, other)定义当被 divmod() 调用时的行为
  • divmod(a, b)把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)
  • __pow__(self, other[, module])定义当被 power() 调用或 ** 运算时的行为
  • __lshift__(self, other)定义按位左移位的行为:<<
  • __rshift__(self, other)定义按位右移位的行为:>>
  • __and__(self, other)定义按位与操作的行为:&
  • __xor__(self, other)定义按位异或操作的行为:^
  • __or__(self, other)定义按位或操作的行为:|

4. 补充

4.1 属性的位置

对比上例中的formulaself.local_logo,首先是两个属性在类中的位置不一样,但在类中都是可以调用这两个属性的。二是在__init__下的属性在前面加了self。我最初的理解是在__init__外的属性是默认的,每个类都相同的,如人体的组成成分(碳基生物),在__init__内的则是因类而异的,如人的身高体重,但是在乐扣做了一题:剑指 Offer II 041. 滑动窗口的平均值时出现了以下问题。
代码:

class MovingAverage:
    window_list = []
    sum = 0
    def __init__(self, size: int):
        """
        Initialize your data structure here.
        """
        self.size = size
        
    def next(self, val: int) -> float:
        ans = 0.0
        self.sum += val
        self.window_list.append(val)
        if len(self.window_list) > self.size:
            self.sum -= self.window_list[0]       
            self.window_list.pop(0)  
        print(self.sum)
        print(self.window_list)
        return self.sum / len(self.window_list)

我的理解为window_listsum每一类都是相同的,因此出现了这样的执行结果:
在这里插入图片描述
编译器把之前的运行结果保存了,并没有重新定义类,因此这样写鲁棒性比较低,因此__init__外的属性应该为不会修改的量,因此将上方代码修改:

class MovingAverage:
    
    def __init__(self, size: int):
        """
        Initialize your data structure here.
        """
        self.size = size
        self.window_list = []
        self.sum = 0
        
    def next(self, val: int) -> float:
    	# ...
    	

就可以啦。

4.2 c++的构造函数和__init__

做算法的时候用C++,而做课题的时候用python,在语法上python更简单,而容器什么的又对C++比较熟悉。
C++的构造函数是用来对参数进行初始化的,所以需要先对参数进行定义。
而python在类中__init__中既能初始化又能定义。
比如力扣232. 用栈实现队列初始两个栈的定义:
C++版本:

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    /** Initialize your data structure here. */
    MyQueue() {

    }
    ...
    

Python版本:

class MyQueue:

    def __init__(self):
        """
        in主要负责push,out主要负责pop
        """
        self.stack_in = []
        self.stack_out = []
    ...
    

参考资料

《编程小白的第一本python入门书》
《Python编程 从入门到实践》Eric Matthes

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙橙小狸猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值