本篇博客为你带来 python 类中的小技巧,学会就能提高效率。
魔法方法 __str__
和 __repr__
这两个方法需要对比学习,因为其功能十分类似。
class Student(object):
def __init__(self, name):
self.name = name
# 使用 print 打印类
s = Student("橡皮擦")
print(s) # <__main__.Student object at 0x0000000000547710>
复制代码
上述代码使用 print
打印类,发现直接输出了类对象的内存地址。如果在类中增加魔法方法 __str__
,可以实现定制化的输出。
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return "这是一个学生类,你传递的姓名是:" + self.name
# 使用 print 打印类
s = Student("橡皮擦")
print(s) # 这是一个学生类,你传递的姓名是:橡皮擦
复制代码
__repr__
与 __str__
实现的效果基本一致,也是在某种情况下将对象转换为字符串。
__repr__
出现的场景可以通过下述步骤测试(忽略异常,实例化时忘记传递参数)。
在控制台中直接调用 s
对象,即可查阅到 __repr__
方法。如果希望手动控制 __str__
与 __repr__
方法,可以通过 str()
与 repr()
函数来实现。一般行业中使用 __repr__
方法,实现对开发人员有意义的字符串进行输出。
如果在类中不使用 __str__
方法,仅使用了 __repr__
方法,那程序在运行时,会自动调用 __repr__
方法。
浅复制与深复制
深浅复制其实都是 python 克隆对象中衍生出来的概念,浅复制只复制对象的第一层,深复制复制整个对象树,概念不容易区分,直接查看代码即可。浅复制
my_list1 = [[1, 2, 3], ["a", "b", "c"]]
my_list2 = list(my_list1)
print(my_list2) # 浅复制 ,输出 [[1, 2, 3], ['a', 'b', 'c']]
my_list1.append([4, 5, 6]) # 给 my_list1 追加元素
print(my_list2) # my_list2 没有受到影响 , 输出 [[1, 2, 3], ['a', 'b', 'c']]
# 但由于浅复制仅复制了第一层,如果修改 my_list1 中的元素
my_list1[0].append(666)
print(my_list2) # my_list2 中的第一项受到了影响,输出 [[1, 2, 3, 666], ['a', 'b', 'c']]
复制代码
如果进行深复制,那两个对象会完全独立,深复制使用 copy
模块的 deepcopy()
实现。
from copy import deepcopy
my_list1 = [[1, 2, 3], ["a", "b", "c"]]
my_list2 = deepcopy(my_list1)
print(my_list2) # 深复制 ,输出 [[1, 2, 3], ['a', 'b', 'c']]
my_list1.append([4, 5, 6]) # 给 my_list1 追加元素
print(my_list2) # my_list2 没有受到影响 , 输出 [[1, 2, 3], ['a', 'b', 'c']]
my_list1[0].append(666) # 深复制仅复制整个对象数,如果修改 my_list1 中的元素
print(my_list2) # my_list2 不会受到影响,输出 [[1, 2, 3], ['a', 'b', 'c']]
复制代码
copy 模块中的 copy 方法是浅复制。
使用 namedtuple 定义类
namedtuple
与普通的元组一样,是不可变数据类型,一般称作具有名称的元组,它其中的元素可以通过唯一的标志符访问,不使用整数索引,也可以用它定义类,具体实现如下:
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
复制代码
其中 namedtuple
函数的第一个参数表示 新创建类的名称,第二个参数是 类中的属性名称,可以用列表,也可以用字符串,但不同属性之间需要用空格隔开。
使用 Student
创建一个对象,代码如下所示:
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
s1 = Student("橡皮擦", 18)
print(s1.name)
print(s1.age)
print(s1) # Student(name='橡皮擦', age=18),可以看到其自动重写了 `__str__` 方法
print(s1.__doc__) # Student(name, age)
复制代码
由于元组是不可变的,所以对象初始化之后,不可以在进行修改。
s1.name = "擦姐" # 报错:AttributeError: can't set attribute
复制代码
namedtuple 内部是由 python 类进行实现的,所以其创建的类可以被继承。
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
class MidStudent(Student):
def run(self):
print(self.name,"gogogo")
s2 = MidStudent("橡皮擦", 18)
print(s2)
s2.run()
复制代码
namedtuple 类具有的特殊属性和方法_fields:获取类字段:
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
class MidStudent(Student):
def run(self):
print(self.name,"gogogo")
s2 = MidStudent("橡皮擦", 18)
print(s2._fields) # 输出 ('name', 'age')
复制代码
_asdict:将 namedtuple
对象以字典形式返回:
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
class MidStudent(Student):
def run(self):
print(self.name,"gogogo")
s2 = MidStudent("橡皮擦", 18)
print(s2._asdict()) # OrderedDict([('name', '橡皮擦'), ('age', 18)])
复制代码
_replace:替换元组中的一些属性值,并返回一个浅复制对象
from collections import namedtuple
Student = namedtuple('Student', ["name", "age"])
class MidStudent(Student):
def run(self):
print(self.name,"gogogo")
s2 = MidStudent("橡皮擦", 18)
print(s2._replace(name="擦姐")) # MidStudent(name='擦姐', age=18)
复制代码
类变量与实例变量,类方法与实例方法
类变量与实例变量这两个概念需要对比着进行学习,先说概念:
下面演示一下二者出现的位置。
class Student():
school_name = "实验小学" # 类变量
def __init__(self, name):
self.name = name # 实例变量
def run(self):
print(self.name, "在跑步")
s1 = Student("橡皮擦")
s1.age = 18 # 实例变量
复制代码
上述代码在两个位置使用了 实例变量,在一个位置使用了 类变量,如果想要访问上述变量,使用下述代码:
print(s1.name, s1.age) # 访问实例变量
print(s1.school_name) # 访问类变量
print(Student.school_name) # 访问类变量
复制代码
使用对象实例或者类名都可以访问到类变量,但是不能通过类名访问实例变量:
# 错误的演示
print(Student.name) # 异常
复制代码
接下来假设 s2 小明转学了,那代码进行下述修改。
s1 = Student("橡皮擦")
s2 = Student("小明")
s2.school_name = "科技小学" # 小明转学
print(s1.school_name) # 橡皮擦的学校没有变
print(Student.school_name) # 类变量也没有变
复制代码
此时问题出现了,通过修改 s2
对象的 school_name
,将其进行了重新赋值操作,但是并没有影响到 Student
类的类变量,这与刚才提及的,修改类变量会影响到所有实例 产生了矛盾,原因是,s2.school_name
表示的创建一个 实例变量,只是该实例变量恰好覆盖了类变量。
如果希望小学改名,需要编写如下代码:
class Student():
school_name = "实验小学" # 类变量
def __init__(self, name):
self.name = name # 实例变量
def run(self):
print(self.name, "在跑步")
s1 = Student("橡皮擦")
s2 = Student("小明")
Student.school_name = "科技小学"
print(s1.school_name)
print(s2.school_name)
复制代码
在实际编码过程中,经常会出现创建一个 实例变量,因为与类变量同名的原因,导致覆盖类变量的场景,需要特别注意下。
类方法与实例方法,在增加静态方法首先创建一个类,这个类包含上述 3 种方法。
class Student(object):
# 普通方法,实例方法
def func(self):
print("我是实例方法")
@classmethod
def cls_func(cls):
print("我是类方法")
@staticmethod
def sta_func():
print("我是静态方法")
复制代码
在编码过程中,最常出现的就是实例方法,该方法必须具备一个 self
参数,用于表示实例对象,如果希望访问类,可以用 self.__class__
实现对类内部状态的修改。
使用装饰器 @classmethod
,可以将一个普通方法转换为类方法,类方法不需要 self
参数,而需要 cls
参数用于指向类自己。
静态方法需要使用装饰器 @staticmethod
进行修饰,它不需要设置 self
和 cls
,但可以设置任意其它参数。
普通的实例方法被调用时,使用如下代码:
s = Student()
s.func() # 调用普通方法
复制代码
上述写法其实也是 python 提供的语法糖,python 自动将对象名 s
替换到了 func
方法的参数 self
位置,如果不使用语法糖,使用下述代码进行实例方法的调用。
s = Student()
Student.func(s) # 给 Student 类的 func 方法传递参数 s
复制代码
类方法的调用,需要使用类名.方法名()
静态方法的调用,可以使用类名.方法名(),也可以使用对象名.方法名()
s = Student()
s.sta_func()
Student.sta_func()
复制代码
但需要注意的是,静态方法既不能访问实例对象,也不能访问类,它仅仅是属于某个类的名称空间。
评论