1、类和实例
在 Python 中,定义类是通过class
关键字:
# 类的定义:方式一
class Student(Object):
pass
# 类的定义:方式二
class Student():
pass
class
后面紧接着是类名
,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类(也可以省略不写)。
定义好了Student
类,就可以根据Student
类创建出Student
的实例,创建实例是通过类名+()
实现的:
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>
可以看到,变量bart
指向的就是一个Student
的实例,后面的0x10a67a590
是内存地址,每个object
的地址都不一样,而Student
本身则是一个类。
2、属性和方法
类可以有属性和方法。
属性
属性又可以分为类数据属性、实例数据属性、特殊的类属性。
首先看一个类的示例:
# coding=utf-8
# 类中的属性分为类属性和实例属性
class Person(object):
tall = 180 # 类属性
def __init__(self, name, age, weight):
# 以下 3 个为实例属性
self.name = name
self.age = age
self.weight = weight
def infoma(self):
print('%s is %s weights %s' % (self.name, self.age, self.weight))
person = Person('bruce', 25, 60)
person.infoma()
在上面的Person
类中,tall
、name
、age
、weight
都被称为类的数据属性,但是它们又分为 类数据属性
和 实例数据属性
。
(1)类数据属性
类数据属性
属于类本身,可以通过类名进行访问/修改:
Person.tall = 178
print("person tall : " % Person.tall)
在类定义之后,可以通过 类名
动态添加 类数据属性
,新增的类数据属性
也被类和所有实例共有:
Person.habbies = ['reading', 'swimming', 'running']
print("Person.habbies: " % Person.habbies)
(2)实例数据属性
实例数据属性
只能通过实例来访问。
Bruce = Person("Bruce", 25,60)
print ("%s is %d years old" %(Bruce.name, Bruce.age))
在实例生成后,还可以动态添加实例数据属性
,但是这些实例数据属性
只属于该实例
对于类属性和实例属性,可以总结为:
- 类属性属于类本身,可以通过类名进行访问/修改
- 类属性也可以被类的所有实例访问/修改
- 在类定义之后,可以通过类名动态添加类数据属性,新增的类数据属性也被类和所有实例共有
- 实例数据属性只能通过实例访问
- 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
注意事项(重要):虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦
(3)特殊的类属性
__ name__: 类的名字(字符串)
__ doc __ : 类的文档字符串
__ bases __: 类的所有父类组成的元组
__ dict __: 类的属性组成的字典
__ module __: 类所属的模块
__ class __: 类对象的类型
示例:
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
# 类中的属性分为类数据属性和实例数据属性
class Person(object):
"""类的特殊属性介绍"""
tall = 180 # 类数据属性
def __init__(self, name, age, weight):
# 以下 3 个为实例数据属性
self.name = name
self.age = age
self.weight = weight
def infoma(self):
print('%s is %s weights %s' % (self.name, self.age, self.weight))
print(Person.__name__) # Person
print(Person.__doc__) # 类的特殊属性介绍
print(Person.__bases__) # (<class 'object'>,)
print(Person.__dict__)
# {
# '__module__': '__main__',
# '__doc__': '类的特殊属性介绍',
# 'tall': 180,
# '__init__': <function Person.__init__ at 0x10ca928c8>,
# 'infoma': <function Person.infoma at 0x10ca92950>,
# '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
print(Person.__module__) # __main__
print(Person.__class__) # <class 'type'>
(4)获取对象信息
type()
方法和types
模块
用于判断对象的类型。基本类型都可以使用type()
函数判断:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
如果变量指向一个函数或者类,也可以用type()
函数判断:
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(A)
<class '__main__.Animal'>
type()
函数返回的对应的class
类型。如果要判断一个对象是否是一个函数,则需要借助types
模块:
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
isinstance()
方法
对于class
的继承关系来说,使用type()
就很不方便。此时可以使用isinstance()
方法。例如有如下继承关系:
object -> Animal -> Dog -> Husky
那么,isinstance()
就可以告诉我们,一个对象是否是某种类型:
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
能用type()
方法的地方也可以用isinstance()
方法进行判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
dir()
方法
如果要获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list
,比如,获得一个str对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
(5)属性隐藏
类数据属性属于类本身,被所有该类的实例共享;并且,通过实例可以去访问/修改类属性。但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现属性”隐藏”的情况。
# -*- coding: utf-8 -*-
"""
Description:属性隐藏
一般情况下,类数据属性只推荐使用类名.属性名来访问,不推荐使用 实例.类数据数据属性来访问,因为后一种方式可能会出现属性隐藏
的现象
"""
# 类中的属性分为类数据属性和实例数据属性
class Person(object):
"""属性隐藏"""
tall = 180 # 类数据属性
hobbies = []
def __init__(self, name, age, weight):
# 以下 3 个为实例数据属性
self.name = name
self.age = age
self.weight = weight
def infoma(self):
print('%s is %s weights %s' % (self.name, self.age, self.weight))
Bruce = Person("Bruce", 25, 180)
print("person.tall is Bruce.tall: ", Person.tall is Bruce.tall)
Bruce.tall = 185 # 通过实例对类数据属性进行重新赋值或者修改
print("person.tall is Bruce.tall: ", Person.tall is Bruce.tall)
print(Person.__dict__)
print(Bruce.__dict__)
del Bruce.tall # 再次删除实例的赋值
print("person.tallis Bruce.tall: ", Person.tall is Bruce.tall) # person.tall is Bruce.tall为True
# person.tall is Bruce.tall: True
# person.tall is Bruce.tall: False
# {
# '__module__': '__main__',
# '__doc__': '属性隐藏',
# 'tall': 180,
# 'hobbies': [],
# '__init__': <function Person.__init__ at 0x10d4d38c8>,
# 'infoma': <function Person.infoma at 0x10d4d3950>,
# '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180, 'tall': 185}
# person.tallis Bruce.tall: True
print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)
Bruce.hobbies = ["C#", "Python"]
print("person.hobbies is Bruce.hobbies : ", Person.hobbies is Bruce.hobbies)
print(Person.__dict__)
print(Bruce.__dict__)
del Bruce.hobbies
print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)
# person.hobbies is Bruce.hobbies: True
# person.hobbies is Bruce.hobbies : False
# {
# '__module__': '__main__',
# '__doc__': '属性隐藏',
# 'tall': 180,
# 'hobbies': [],
# '__init__': <function Person.__init__ at 0x10d3108c8>,
# 'infoma': <function Person.infoma at 0x10d310950>,
# '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180, 'hobbies': ['C#', 'Python']}
# person.hobbies is Bruce.hobbies: True
Bruce.hobbies.append("CSS") #内存地址没有新指向,在原来基础上扩展
print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)
print(Person.__dict__)
print(Bruce.__dict__)
# person.hobbies is Bruce.hobbies: True
# {
# '__module__': '__main__',
# '__doc__': '属性隐藏',
# 'tall': 180,
# 'hobbies': ['CSS'],
# '__init__': <function Person.__init__ at 0x1081ed8c8>,
# 'infoma': <function Person.infoma at 0x1081ed950>,
# '__dict__': <attribute '__dict__' of 'Person' objects>,
# '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180}
对于不可变类型的类属性,隐藏属性可以总结为:
- 对于不可变类型的类属性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.hobbies,可以通过实例Bruce进行访问,并且”person.hobbies is Bruce. hobbies”
- 当通过实例赋值hobbies 属性的时候,都将为实例Bruce新建一个 hobbies-> [‘c#’,‘python’] 实例属性,这时,”person.hobbies is not Bruce. hobbies”
- 当通过”del Bruce. hobbies”语句删除实例的hobbies属性后,再次成为”person. hobbies is Bruce.books”
- 当通过实例修改hobbies属性的时候,将修改Bruce. hobbies指向的内存地址(即person.hobbies),此时,”person.hobbies is not Bruce. hobbies”
ps:虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦
方法
(1)专有方法
类的专有方法有:
# 和实例创建、运行相关的方法
__init__ 构造函数,在生成对象时调用
__new__ 创建实例
__call__ 函数调用
__del__ 析构函数,释放对象时使用
# 和字符串有关的方法
__str__ 打印转换
__repr__ 打印,转换
__formate__
__bytes__
# 和运算有关的方法
__add__ 加运算
__sub__ 减运算
__mul__ 乘运算
__div__ 除运算
__mod__ 求余运算
__pow__ 称方
__eq__ 相等
__ge__ 大于等于
__gt__ 大于
__le__ 小于等于
__lt__ 小于
__ne__ 不等于
__len__ 获得长度
__cmp__ 比较运算
__bool__
# 和实例属性有关的方法
__getattribute__
__getattr__
__setattr__
__delattr__
__getitem__
__setitem__
__delitem__
__missing__
和实例创建、运行相关的特殊方法:__init__
、__new__
和__call__
方法
类名加括号调用了__init__
方法类创建一个实例对象。这一过程分成了两步:
-
第一步:类调用
__new__
来创建实例对象; -
第二步:
__new__
调用__init__
来初始化实例对象。
# -*- coding: utf-8 -*-
"""
Description:和实例创建、运行相关的专有方法
"""
# class A:
# count = 0
#
# def __init__(self):
# print("__init__ has called. 2")
#
# def __new__(cls):
# print("__new__ has called. 1")
# return object.__new__(cls)
#
# def __call__(cls):
# print("__call__ has called. 3")
#
#
# a = A()
# 通过调用方法的调用顺序也能看出:__init__和__new__方法执行的先后顺序。
# __new__ has called. 1
# __init__ has called. 2
# a()
# __call__ has called. 3
class Obj(object):
def __init__(self, name, price):
self.name = name
self.__price = price
def __say(self):
print("{}, {}.".format(self.name, self.__price))
def __call__(self, discount):
self.__price = discount * self.__price
self.__say()
# 类名加括号,先调用__new__方法创建实例,创建的实例在调用__init__方法进行初始化。
apple = Obj("apple", 10.0)
# 实例对象后加括号:调用__call__方法,作为程序的入口。
apple(0.8)
类的__new__()
方法很少通过用户代码定义。如果定义了它,它通常是用原型__new__(cls, *args, **kwargs)
编写的。其中args
和kwargs
与传递给__init__()
的参数相同。
__new__()
始终是一个类方法,接受类对象作为第一个参数。尽管__new__()
会创建一个实例,但它不会自动地调用__init__()
。
实例对象加括号会调用__call__
方法,一般作为程序的入口,并且可以在这一步修改实例属性或调用实例方法。
和实例属性有关的专有方法:__getattribute__
、__getattr__
、__setattr__
、__delattr__
、__getitem__
、__setitem__
、__delitem__
、__missing__
方法
__getattribute__
、__getattr__
方法
- 首先执行
__getattribute__
方法,去调用object
基类的__getattribute__
来查询__dict__
里的键值对。如果存在则直接返回,相当于执行了member.__dict__.get(obj)
,如果不存在则调用__getattr__
方法。 __getattr__
方法会在实例内存空间中尽可能的搜索该属性值,如果搜索到则直接返回,搜索不到会抛出AttributeError
。
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
class MemberCounter:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattribute__(self, obj):
print("__getattribute__ is called.")
# 调用Object 基类的__getattribute__方法来查询__dict__中的键值对。
return object.__getattribute__(self, obj)
def __getattr__(self, obj):
print("__getattr__ is called.")
return "{} Not assgined.".format(obj)
# 创建示例对象,并进行初始化
member = MemberCounter("An", 24)
# 访问示例对象的属性
print(member.name)
"""
首先调用__getattribute__方法,去调用object基类的__getattribute__来查询__dict__
里的键值对。如果存在则直接返回,相当于执行了member.__dict__.get(obj),
__getattribute__ is called.
An
"""
print("\n------------------\n")
print(member.gender)
"""
首先调用__getattribute__方法,去调用object基类的__getattribute__来查询__dict__
里的键值对,如果不存在则调用__getattr__方法。
__getattribute__ is called.
__getattr__ is called.
调用__getattr__方法会在实例内存空间中尽可能的搜索该属性值,如果搜索到则直接返回,
搜索不到会抛出AttributeError。
gender Not assgined.
"""
注意下面两段代码的区别:__getattribute__
的return
只是一个硬编码的字符串而不是属性值,__getattr__
的return
则是实例的属性值。因此,如果有需求,一般会在__getattr__
里写需求代码。
class MemberCounter:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattribute__(self, obj):
if obj == "gender":
return "male"
else:
return object.__getattribute__(self, obj)
member = MemberCounter("An", 24)
print(member.gender)
member.gender = "female"
print(member.gender)
print(member.__dict__)
"""
male
male
{'name': 'An', 'age': 24, 'gender': 'female'}
"""
class MemberCounter:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self, obj):
if obj == "gender":
return "male"
else:
raise AttributeError
member = MemberCounter("An", 24)
print(member.gender)
member.gender = "female"
print(member.gender)
print(member.__dict__)
"""
male
female
{'name': 'An', 'age': 24, 'gender': 'female'}
"""
__setattr__
和__delattr__
方法
在__init__
时,会调用__setattr__
初始化实例属性,在__dict__
中添加键值对。
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
class MemberCounter:
def __init__(self, name, age):
self.name = name
self.age = age
def __setattr__(self, old, new):
print("{} = {} has been created.".format(old, new))
object.__setattr__(self, old, new)
def __delattr__(self, key):
print("{} is deleted.".format(key))
del self.__dict__[key]
def __getattr__(self, item):
print("__getattr__ 方法被调用。")
return self.__dict__[item] # object.__getattr__(self, item)
# 创建并初始化实例对象
member = MemberCounter("An", 24)
# __init__方法中调用__setattr__方法,对实例属性进行赋值
# name = An has been created.
# age = 24 has been created.
# 访问实例的字典
print(member.__dict__)
# {'name': 'An', 'age': 24}
# 删除某个实例属性:调用 delattr 方法
del member.age
# age is deleted.
print(member.__dict__)
# {'name': 'An'}
在使用self.name = name
和del member.age
时,实际上调用了__setattr__
和__delattr__
。这两个函数没有类似__getattribute__
的使用规则。即无论何时给属性赋值,都会调用 __setattr__()
方法;无论何时删除一个属性,都将调用 __delattr__()
方法。
__getitem__
、__setitem__
、__delitem__
和__missing__
方法
通过操作实例的__dict__
属性来获取、设置和删除实例的属性值
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
class A:
def __init__(self, *args):
self.name, self.age = args
def __getitem__(self, key):
print("__getitem__ has called.")
if key in self.__dict__:
return self.__dict__.get(key, ValueError)
else:
self.__missing__(key)
return self.__dict__.get(key, ValueError)
def __setitem__(self, key, value):
print("__setitem__ has called.")
self.__dict__.update({key: value})
def __delitem__(self, key):
print("__delitem__ has called.")
del self.__dict__[key]
def __missing__(self, nonexistent_key):
print("__missing__ has called")
self.__dict__[nonexistent_key] = 'male'
# 创建并初始化实例
a = A("Li", 27)
print(a.__dict__)
# {'name': 'Li', 'age': 27}
# 通过键来获取值:__getitem__方法被调用
a["name"]
print(a.__dict__)
# __getitem__ has called.
# {'name': 'Li', 'age': 27}
# 通过键来设置值:__setitem__方法被调用
a["name"] = "Alex"
print(a.__dict__)
# __setitem__ has called.
# {'name': 'Alex', 'age': 27}
# 删除一个键值对:__delitem__方法被调用
del a["age"]
print(a.__dict__)
# __delitem__ has called.
# {'name': 'Alex'}
print(a['gender'])
# male
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCDMdg9I-1573040226033)(./pic/类的特殊方法 2.jpeg)]
跟运算有关的专有方法 __eq__(==)
、__ge__(>=)
、__gt__(>)
、__le__ (<=)
、__lt__ (<)
、__ne__ (!=)
、__bool__
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
class A:
def __init__(self, number):
self.number = number
def __gt__(self, obj):
print("__gt__ has called.")
return self.number > obj.number
def __ge__(self, obj):
print("__ge__ has called.") # 既然执行了这个函数,你可以尽情的在return之前写想写的功能
return self.number >= obj.number
a = A(23)
b = A(45) # 这里A就相当于int
c = A(45)
print(a > b) # 当你写23 > 45时,实际等同于 a = int(23), b = int(45); a > b
print(b >= c)
"""
__gt__ has called.
False
__ge__ has called.
True
"""
和字符串相关的方法:__str__
、__repr__
、__formate__
和__bytes__
方法
# -*- coding: utf-8 -*-
"""
Description:类的特殊属性
"""
class Cls(object):
def __init__(self):
self.name = "alex"
self.age = 27
self.gender = "female"
def __str__(self):
print("__str__ has called.")
return "Str: my name is {}, {}.".format(self.name, self.age)
def __format__(self, special):
print("__format__ has called.")
if special == "":
return self.__str__() # 也可以写成str(self)
for _, v in self.__dict__.items(): # 实际上是{"name": "alex", "age": 27}
special = special.replace("%s", str(v), 1)
return special
观察x下面两段代码的打印结果:
cls = Cls()
str1 = "{:%s, %s, %s}".format(cls)
print(str1)
"""
__format__ has called.
alex, 27, female
"""
cls = Cls()
str2 = format(cls, "my name is %s, %s, %s.")
print(str2)
"""
__format__ has called.
my name is alex, 27, female.
"""
以上两端代码本质都是一样的,即format和一个字符串匹配,也就是__format__
函数传递了special
参数(字符串)。当format
函数没有和字符串匹配,也就是__format__
函数没有传入参数special
时,会调用__str__
函数。
__str__
和__repr__
的区别和联系
当__str__
存在时,print(obj)
和str(obj)
实际调用了__str__
方法;当__str__
不存在时,就会调用__repr__
。但是当定义了__repr__
时,obj
本身会返回一个"合法"的字符串表达式,而不再返回一个描述符<'__main__.A object at XXX>
,当然这个描述符也可以被__str__
修改,只是会被__repr__
覆盖。
class A:
def __str__(self):
print("__str__ has called.")
return self.__class__.__name__ + "__str__"
a = A()
a # <__main__.A at 0x110a764e0>
print(a)
# __str__ has called.
# A__str__
str(a)
# __str__ has called.
# A__str__
class A:
def __repr__(self):
print("__repr__ has called.")
return self.__class__.__name__ + "__repr__"
a = A()
a
#__repr__ has called.
#A__repr__
print(a)
#__repr__ has called.
#A__repr__
str(a)
#__repr__ has called.
#A__repr__
class A:
def __str__(self):
print("__str__ has called.")
return self.__class__.__name__ + "__str__"
def __repr__(self):
print("__repr__ has called.")
return self.__class__.__name__ + "__repr__"
a = A()
a
#__repr__ has called.
#A__repr__
print(a)
#__str__ has called.
#A__str__
str(a)
#__str__ has called.
#'A__str__'
通常__str__()
和__repr__()
代码都是一样的,所以会这么写:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
(2)自定义方法
在类中定义方法时,有三种类型的方法可供供选择:
实例方法
通过def
定义,需要至少传入一个参数,即self
,这样定义的方法必须通过一个类的实例取访问,类似于C++
中通过对象去访问。
类方法
这种类方法的特点是可以通过类名去调用,但是也必须传递一个参数:cls
。即class
,表示可以通过类名直接调用。
静态方法
静态的类方法,类似C++
的静态函数,特点是参数可以为空,支持类名和对象两种调用方式。
class A(object):
# 实例方法
def foo1(self):
print("hello, ", self)
# 静态方法
@staticmethod
def foo2():
print("hello, ")
# 类方法
@classmethod
def foo3(cls):
print("hello, ", cls)
a = A()
a.foo1()
# 最常见的调用方式
A.foo1(a)
# 与第一种调用方式相同
A.foo2()
a.foo2()
# 静态方法可以通过类名和对象调用
A.foo3()
print(A)
# 类方法,支持通过类名调用