最初学习的编程语言是C,后来接触了C++,但是C++的特色就是类,当时学得朦朦胧胧,再后来接触python,学习python类的时候感觉似曾相识,与C++异曲同工,今天就好好整理一下。
文章目录
1. 术语预览
- 类的属性(Class Atrribute)
- 实例(instance)
- 访问类属性(attribute references)
- 方法(Method)/实例方法(Instance Method)
self
参数(就是被创建的实例本身)- “魔法方法”(是方法,不过会魔法,莫名的产生,如
__inti__()
等)。
1.1 私有属性和“受保护的”属性
-
私有属性
什么是私有?属于类的属性,甚至在类外无法访问查看。
怎么表示?属性前加下划线: _属性名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!
这里举了一个可口可乐的例子,我们对于可口可乐的常识,可口可乐有相应的配方,同时可口可乐可以被买被喝。
- 什么是属性,实例方法
我理解的属性是一种形容词,可以描述类的特征,比如你向别人介绍你时,你可以说你的身高体重,身高体重就是人这一类的属性。
在此例中formula
和self.local_logo
都为属性,只是self.local_logo
多了一个self
,以self
为前缀的变量都可供类中的所有方法使用。
实例方法为类的一些动作,方法前加实例二字,只是说类中的方法是供实例来调用的。本例中创建了两个方法,分别为def buy(self, money):
买可乐和def drink(self,how_much):
喝可乐。 - 怎样创建实例、访问属性和调用方法
创建实例:ice_coke = CocaCola('可口可乐')
,需要注意的是其传入的参数要根据__init__(self, logo_name)
方法的形式而定。
访问属性:ice_coke.formula
句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。
调用方法:ice_coke.buy(3.5)
和ice_coke.drink('a sip')
- 创建多个实例
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__
- 当你print打印一个对象的时候,触发
-
__repr__(self)
:repr
是str
的备胎- 有
__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 属性的位置
对比上例中的formula
和self.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_list
和sum
每一类都是相同的,因此出现了这样的执行结果:
编译器把之前的运行结果保存了,并没有重新定义类,因此这样写鲁棒性比较低,因此__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