第十八章:面相对象进阶以及内容补充
第十六章:面相对象进阶以及内容补充
本章内容知识点:
- 迭代器
- 生成器
- 装饰器
- 实例方法
- 静态方法
- 类方法
- @property 装饰器
- 集合类型
迭代器
迭代器对象要求支持迭代器协议的对象,在Python中,支持迭代器协议就是实现对象的__iter__()和__next__()方法。其中__iter__()方法返回迭代器对象本身;__next__()方法返回容器的下一个元素,在结尾时引发StopIteration异常。
list_data = [1, 2]
li = iter(list_data)
print(li)
print(li.__next__())
print(li.__next__())
print(li.__next__())
自定义迭代器
了解了迭代器协议之后,就可以自定义迭代器了。
下面例子中实现了一个MyRange的类型,这个类型中实现了__iter__()方法,通过这个方法返回对象本身作为迭代器对象;同时,实现了__next__()方法用来获取容器中的下一个元素,当没有可访问元素后,就抛出StopIteration异常。
class MyRange(object):
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
生成器
在Python中,使用生成器可以很方便的支持迭代器协议。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。
也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。
def my_range(n):
i = 0
while i < n:
yield i
i += 1
my_range = my_range(3)
print(my_range)
print(next(my_range))
# print([i for i in my_range])
在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。
其实,生成器函数返回生成器的迭代器。 "生成器的迭代器"这个术语通常被称作"生成器"。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__()。如同迭代器一样,我们可以使用__next__()函数来获取下一个值。
下面就仔细看看生成器是怎么工作的。从上面的例子也可以看到,生成器函数跟普通的函数是有很大差别的。结合上面的例子我们加入一些打印信息,进一步看看生成器的执行流程:
def my_range(n):
print('开始迭代...')
i = 0
while i < n:
print('迭代中...')
yield i
i += 1
print('迭代结束...')
my_range = my_range(3)
# print(my_range)
print(next(my_range))
print(next(my_range))
print(next(my_range))
通过结果可以看到:
- 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。
- 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止
- next()方法的返回值就是yield语句处的参数(yielded value)
- 当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常
生成器表达式
在开始介绍生成器表达式之前,先看看我们比较熟悉的列表解析( List comprehensions),列表解析一般都是下面的形式。
[expr for iter_var in iterable if cond_expr]
迭代iterable里所有内容,每一次迭代后,把iterable里满足cond_expr条件的内容放到iter_var中,再在表达式expr中应该iter_var的内容,最后用表达式的计算值生成一个列表。
例如,生成一个list来保护50以内的所以奇数:
[i for i in range(50) if i % 2]
生成器表达式是在python2.4中引入的,当序列过长, 而每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析。生成器表达式的语法和列表解析一样,只不过生成器表达式是被()括起来的,而不是[],如下:
(expr for iter_var in iterable if cond_expr)
生成器表达式并不是创建一个列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目"产生"(yield)出来。 生成器表达式使用了"惰性计算"(lazy evaluation),只有在检索时才被赋值(evaluated),所以在列表比较长的情况下使用内存上更有效。
gen = (i for i in range(10000) if i % 2)
print("__iter__" in dir(gen))
print("__next__" in dir(gen))
# 使用sum求和之后会导致再次迭代所获取的值为空
print(sum(gen))
print([i for i in gen])
生成器中的send() 与 close() 方法
生成器中还有两个很重要的方法:send()和close()。
-
send(value):
从前面了解到,next()方法可以恢复生成器状态并继续执行,其实send()是除next()外另一个恢复生成器的方法。
Python 2.5中,yield语句变成了yield表达式,也就是说yield可以有一个值,而这个值就是send()方法的参数,所以send(None)和next()是等效的。同样,next()和send()的返回值都是yield语句处的参数(yielded value)
关于send()方法需要注意的是:调用send传入非None值前,生成器必须处于挂起状态,否则将抛出异常。也就是说,第一次调用时,要使用next()语句或send(None),因为没有yield语句来接收这个值。
-
close():
这个方法用于关闭生成器,对关闭的生成器后再次调用next或send将抛出StopIteration异常。
def my_range(n):
i = 0
while i < n:
val = yield i
print('val is: ', val)
i += 1
my_range = my_range(5)
print(my_range.__next__())
print(my_range.__next__())
print(my_range.send('hello'))
my_range.close()
print(my_range.send('world'))
总结:
- 生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义__iter__()和__next__()方法。
- 生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。
装饰器
装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。
为什么需要装饰器
我们假设你的程序实现了say_hello()
和say_goodbye()
两个函数。
def say_hello():
print("hello!")
def say_goodbye():
print("hello!") # 此处应打印goodbye
if __name__ == '__main__':
say_hello()
say_goodbye()
假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。
[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!
实现方式:
def say_hello():
print ("[DEBUG]: enter say_hello()")
print ("hello!")
def say_goodbye():
print ("[DEBUG]: enter say_goodbye()")
print ("hello!")
if __name__ == '__main__':
say_hello()
say_goodbye()
对上述代码进行优化:
def debug():
import inspect
caller_name = inspect.stack()[1][3]
print("[DEBUG]: enter {}()".format(caller_name))
def say_hello():
debug()
print("hello!")
def say_goodbye():
debug()
print("goodbye!")
if __name__ == '__main__':
say_hello()
say_goodbye()
上述代码需要在每个业务函数里都要调用一下debug()
函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
如何实现一个装饰器
在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper
def say_hello():
print("hello!")
say_hello = debug(say_hello)
say_hello()
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper
@debug
def say_hello():
print("hello!")
say_hello()
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper
接受和原函数一样的参数,比如:
def debug(func):
def wrapper(something): # 指定一毛一样的参数
print("[DEBUG]: enter {}()".format(func.__name__))
return func(something)
return wrapper # 返回包装过函数
@debug
def say(something):
print("hello {}!".format(something))
say('顾安')
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了。
def debug(func):
def wrapper(*args, **kwargs):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper # 返回
@debug
def say(something):
print("hello {}!".format(something))
say('顾安')
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@logging(level='INFO')
def say(something):
print("say {}!".format(something))
# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do(something):
print("do {}...".format(something))
if __name__ == '__main__':
say('hello')
do("my work")
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG')
,它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
class Test:
def __call__(self):
print('call me!')
t = Test()
t() # call me
像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class Logging:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[DEBUG]: enter function {func}()".format(
func=self.func.__name__))
return self.func(*args, **kwargs)
@Logging
def say(something):
print("say {}!".format(something))
say('木木')
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__
方法是就需要接收一个函数并返回一个函数。
class Logging:
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接收函数
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
func(*args, **kwargs)
return wrapper # 返回函数
@Logging(level='INFO')
def say(something):
print("say {}!".format(something))
say('木木')
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。
@property
什么是property属性?
一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。
class Foo:
def func(self):
pass
# 定义property属性
@property
def prop(self):
pass
foo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用property属性
class Goods:
@property
def money(self):
return 100
goods = Goods()
print(goods.money)
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
- 调用时,无需括号
简单的实例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
class Pager:
def __init__(self, current_page):
# 用户当前请求的页码(第一页、第二页...)
self.current_page = current_page
# 每页默认显示10条数据
self.per_items = 10
@property
def start(self):
val = (self.current_page - 1) * self.per_items
return val
@property
def end(self):
val = self.current_page * self.per_items
return val
# ############### 调用 ###############
p = Pager(1)
print(p.start) # 就是起始值,即:m
print(p.end) # 就是结束值,即:n
- Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
- 装饰器 即:在方法上应用装饰器
- 类属性 即:在类中定义值为property对象的类属性
装饰器方式
在类的实例方法上应用@property装饰器
Python中的类有经典类
和新式类
,新式类
的属性比经典类
的属性丰富。( 如果类继object,那么该类是新式类 )
经典类,具有一种@property装饰器:
class Goods:
@property
def price(self):
return 100
obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)
新式类,具有三种@property装饰器
class Goods(object):
@property
def price(self):
print('@property')
@price.setter
def price(self, value):
print('@price.setter')
@price.deleter
def price(self):
print('@price.deleter')
obj = Goods()
print(obj.price) # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
注意:
- 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
print(obj.price) # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
# 当前属性被删除之后再获取则报错
# print(obj.price)
类属性方式,创建值为property对象的类属性
- 当使用类属性的方式创建property属性时,
经典类
和新式类
无区别
class Goods:
def get_price(self):
return 100
price = property(get_price)
obj = Goods()
result = obj.price # 自动调用get_price方法,并获取方法的返回值
print(result)
property方法中有个四个参数
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
class Foo(object):
def get_bar(self):
print("getter...")
return 'a...'
def set_bar(self, value):
"""必须两个参数"""
print("setter:", value)
return 'set value' + value
def del_bar(self):
print("deleter...")
return 'b...'
BAR = property(get_bar, set_bar, del_bar, "description...")
obj = Foo()
print(obj.BAR) # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "c" # 自动调用第二个参数中定义的方法:set_bar方法,并将“c”当作参数传入
desc = Foo.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR # 自动调用第三个参数中定义的方法:del_bar方法
由于类属性方式
创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
def get_price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
def set_price(self, value):
self.original_price = value
def del_price(self):
del self.original_price
PRICE = property(get_price, set_price, del_price, '价格属性描述...')
obj = Goods()
print(obj.PRICE) # 获取商品价格
obj.PRICE = 200 # 修改商品原价
print(obj.PRICE)
del obj.PRICE # 删除商品原价
Django框架中应用了property属性(了解)
WEB框架 Django 的视图中 request.POST 就是使用的类属性的方式创建的属性
class WSGIRequest(http.HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
path_info = get_path_info(environ)
if not path_info:
# Sometimes PATH_INFO exists, but is empty (e.g. accessing
# the SCRIPT_NAME URL without a trailing slash). We really need to
# operate as if they'd requested '/'. Not amazingly nice to force
# the path like this, but should be harmless.
path_info = '/'
self.environ = environ
self.path_info = path_info
self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
self.META = environ
self.META['PATH_INFO'] = path_info
self.META['SCRIPT_NAME'] = script_name
self.method = environ['REQUEST_METHOD'].upper()
_, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
if 'charset' in content_params:
try:
codecs.lookup(content_params['charset'])
except LookupError:
pass
else:
self.encoding = content_params['charset']
self._post_parse_error = False
try:
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
self.resolver_match = None
def _get_scheme(self):
return self.environ.get('wsgi.url_scheme')
def _get_request(self):
warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or '
'`request.POST` instead.', RemovedInDjango19Warning, 2)
if not hasattr(self, '_request'):
self._request = datastructures.MergeDict(self.POST, self.GET)
return self._request
@cached_property
def GET(self):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')
return http.QueryDict(raw_query_string, encoding=self._encoding)
# ############### 看这里看这里 ###############
def _get_post(self):
if not hasattr(self, '_post'):
self._load_post_and_files()
return self._post
# ############### 看这里看这里 ###############
def _set_post(self, post):
self._post = post
@cached_property
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
return http.parse_cookie(raw_cookie)
def _get_files(self):
if not hasattr(self, '_files'):
self._load_post_and_files()
return self._files
# ############### 看这里看这里 ###############
POST = property(_get_post, _set_post)
FILES = property(_get_files)
REQUEST = property(_get_request)
@property - 应用
- 私有属性添加getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def get_money(self):
return self.__money
def set_money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money = Money()
print(money.get_money())
money.set_money(10)
print(money.get_money())
- 使用property升级getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def get_money(self):
return self.__money
def set_money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
# 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
money = property(get_money, set_money)
money_obj = Money()
money_obj.money = 100 # 调用setMoney方法
print(money_obj.money) # 调用getMoney方法
- 使用property取代getter和setter方法
- 重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):
def __init__(self):
self.__money = 0
# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
@property
def money(self):
return self.__money
# 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
@money.setter
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money_obj = Money()
money_obj.money = 100
print(money_obj.money)
类中的实例方法、静态方法、类方法
实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
类方法
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类和实例对象都可以调用。
静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类和实例对象都可以调用。
类方法
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
如下场景:
假设我有一个学生类和一个班级类,想要实现的功能为:
- 执行班级人数增加的操作、获得班级的总人数;
- 学生类继承自班级类,每实例化一个学生,班级人数都能增加;
- 最后,我想定义一些学生,获得班级中的总人数。
思考:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。
class ClassTest(object):
__num = 0
@classmethod
def addNum(cls):
cls.__num += 1
@classmethod
def getNum(cls):
return cls.__num
# 这里我用到魔术方法__new__,主要是为了在创建实例的时候调用累加方法。
def __new__(self):
ClassTest.addNum()
class Student(ClassTest):
def __init__(self):
self.name = ''
a = Student()
b = Student()
c = Student()
print(ClassTest.getNum())
静态方法
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
譬如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数。
import time
class TimeTest(object):
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = second
@staticmethod
def showTime():
return time.strftime("%H:%M:%S", time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)
如上,使用了静态方法(函数),然而方法体中并没使用实例的属性和方法(但可以通过类名调用类属性和类方法)。若要获得当前时间的字符串时,并不一定需要实例化对象,此时对于静态方法而言,所在类更像是一种名称空间。其实,我们也可以在类外面写一个同样的函数来做这些事,但是这样做就打乱了逻辑关系,也会导致以后代码维护困难。
set 集合类型
set 和 dict 类似,也是一组 key 的集合,但是不存储 value. 由于 key 不重复,所以,在 set 中, 没有重复的 key 集合是可变类型
# 第一种方式创建 set 类型
>>> set1 = {1, 3, 6, 'z', 'a', 'b'}
>>> print(type(set1), set1)
<class 'set'> {1, 3, 6, 'z', 'a', 'b'}
# 第二种方式创建 set 类型
>>> set2 = set(['z', 'a', 'b', 3, 6, 1])
>>> print(type(set2), set2)
<class 'set'> {1, 3, 6, 'z', 'a', 'b'}
# 第三种方式创建 set 类型
>>> set3 = set('hello')
>>> print(type(set3), set3)
<class 'set'> {'o', 'e', 'l', 'h'}
set 内置函数
- add():新增一个元素到集合
set1 = {'a', 'z', 'b', 4, 6, 1}
set1.add(8)
set1.add('hello')
print(set1)
# 执行结果:
# {'b', 1, 'a', 4, 6, 8, 'hello', 'z'}
- clear():清空所有集合元素
set1 = {'a', 'z', 'b', 4, 6, 1}
set1.clear()
print(set1)
- copy():拷贝整个集合并赋值给变量
set1 = {'a', 'z', 'b', 4, 6, 1}
set2 =set1.copy()
print(set2)
- pop():随机删除集合中一个元素,可以通过变量来获取删除的元素
set1 = {'a', 'z', 'b', 4, 6, 1}
ys = set1.pop()
print('set1集合:', set1)
print('删除的元素:', ys)
- remove(self, *args, **kwargs):删除集合中指定的元素,如果该集合内没有该元素就报错
set1 = {'a', 'z', 'b', 4, 6, 1}
set1.remove('a')
print(set1)
set1.remove('x')
print(set1)
- discard(self, *args, **kwargs):删除集合中指定的元素,如果该集合内没有该元素也不会报错
set1 = {'a', 'z', 'b', 4, 6, 1}
set1.discard('a')
print(set1)
set1.discard('y')
print(set1)
pop() 、remove() 、 discard() 三个集合删除函数比较:
- pop() 随机删除集合中一个元素
- remove() 删除集合中指定的元素,如果集合中没有指定的元素,程序报错!
- discard() 删除集合中指定的元素,如果集合中没有指定的元素,程序正常运行。
集合的交并差运算
intersection & :交集; union | :并集合; difference - : 差集
set1 = {'a', 'b', 'x', 'y'}
set2 = {'i', 'j', 'b', 'a'}
# 交集
print(set1 & set2)
print(set1.intersection(set2))
# 执行结果:
# {'a', 'b'}
# {'a', 'b'}
# 并集
print(set1 | set2)
print(set1.union(set2))
# 执行结果:
# {'y', 'j', 'a', 'b', 'x', 'i'}
# {'y', 'j', 'a', 'b', 'x', 'i'}
# 差集
print(set1 - set2)
print(set1.difference(set2))
print(set2 - set1)
print(set2.difference(set1))
# 执行结果:
# {'y', 'x'}
# {'y', 'x'}
# {'j', 'i'}
# {'j', 'i'}
- difference_update():求差集,并赋值给源集合
set1 = {'a', 'b', 'x', 'y'}
set2 = {'i', 'j', 'b', 'a'}
set1.difference_update(set2)
print(set1)
# 执行结果:
# {'y', 'x'}
- intersection_update():求交集,并赋值给源集合
set1 = {'a', 'b', 'x', 'y'}
set2 = {'i', 'j', 'b', 'a'}
set1.intersection_update(set2)
print(set1)
# 执行结果:
# {'b', 'a'}
- symmetric_difference():和 ^ 符号效果一样,求交叉补集
set1 = {'a', 'b', 'x', 'y'}
set2 = {'i', 'j', 'b', 'a'}
print('symmetric_difference:', set1.symmetric_difference(set2))
print('^:', set1 ^ set2)
# 执行结果:
# symmetric_difference: {'x', 'i', 'y', 'j'}
# ^: {'x', 'i', 'y', 'j'}
- symmetric_difference_update():求交叉补集并赋值给源集合
set1 = {'a', 'b', 'x', 'y'}
set2 = {'i', 'j', 'b', 'a'}
set1.symmetric_difference_update(set2)
print(set1)
# 执行结果:
# {'y', 'i', 'j', 'x'}
- update():更新集合,参数为可迭代对象
set1 = {'a', 'b', 'x', 'y'}
set1.update(('hello', 'world'))
print(set1)
# 执行结果:
# {'hello', 'world', 'b', 'a', 'y', 'x'}
add() 和 update() 比较:
- add(): 只能添加一个元素到集合
- update(): 可以添加多个元素到集合,参数为 iterable
- 使用 frozenset 定义不可变集合
s = frozenset('hello')
print(s)
# 执行结果:
# frozenset({'h', 'e', 'o', 'l'})
使用 frozenset 定义的集合,没有 add 或者 pop 等方法。