zl程序教程

您现在的位置是:首页 >  Python

当前栏目

通过内置对象理解 Python(七)

2023-03-31 10:30:03 时间

通过内置对象理解 Python(七)

hasattr, getattr, setattr and delattr :属性助手

我们知道,在字典中,可以通过键访问键值对的值:

>>> dictionary = {'property': 42}
>>> dictionary['property']
42

而在对象上,它是通过“ . ”操作符访问到属性的值:

>>> class C:
...     prop = 42
...
>>> C.prop
42

你甚至可以设置和删除对象的属性:

>>> C.prop = 84
>>> C.prop
84
>>> del C.prop

将字典的键值对和对象及其属性比较,两者具有很高的相似性,但字典要灵活得多,例如,可以检查字典中是否存在一个键(对应于对象的属性):

>>> d = {}
>>> 'prop' in d
False
>>> d['prop'] = 'exists'
>>> 'prop' in d
True

对于实例的属性而言,类似的操作可以通过 try ... except 语句完成:

>>> class X:
...    pass
...
>>> x = X()
>>> try:
...     print(x.prop)
>>> except AttributeError:
...     print("prop doesn't exist.")
prop doesn't exist.

但是,这并不是常用的,更提倡使用 hasattr 函数判断对象是否含有某个属性。

>>> class X:
...    pass
...
>>> x = X()
>>> hasattr(x, 'prop')
False
>>> x.prop = 'exists'
>>> hasattr(x, 'prop')
True

下面的操作,很显然不成立:

>>> class X:
...     value = 42
...
>>> x = X()
>>> attr_name = 'value'
>>> x.attr_name
AttributeError: 'X' object has no attribute 'attr_name'

但是,如果想让它成为现实,可以这么做:

>>> class X:
...     value = 42
...
>>> x = X()
>>> getattr(x, 'value')
42
>>> attr_name = 'value'
>>> getattr(x, attr_name)
42  # It works!

这里使用了 getattr 函数,它能接受一个字符串为属性,并设置属性值。

setattrdelattr 也都有类似的功能:它们接受字符串的属性名称,并相应地设置或删除属性值。

>>> class X:
...     value = 42
...
>>> x = X()
>>> setattr(x, 'value', 84)
>>> x.value
84
>>> delattr(x, 'value')  # deletes the attribute completety
>>> hasattr(x, 'value')
False  # `value` no longer exists on the object.

下面试着用其中的一个函数来构建一些有意义的东西:

class api:
    """A dummy API."""
    def send(item):
        print(f'Uploaded {item!r}!')

def upload_data(item):
    """Uploads the provided value to our database."""
    if hasattr(item, 'get_value'):
        data = item.get_value()
        api.send(data)
    else:
        api.send(item)

这里的 upload_data 函数检查实参是否有 get_value 方法,如果有,则通过此方法读取数据。符合此类要求的对象(作为实参)是:

>>> import json
>>> class DataCollector:
...     def __init__(self):
...         self.items = []
...     def add_item(self, item):
...         self.items.append(item)
...     def get_value(self):
...         return json.dumps(self.items)
...
>>> upload_data('some text')
Uploaded 'some text'!
>>> collector = DataCollector()
>>> collector.add_item(42)
>>> collector.add_item(1000)
>>> upload_data(collector)
Uploaded '[42, 1000]'!

super :用于继承

super 是 Python 引用父类的方式,例如,为了使用父类的方法而使用 super

以下面的类为例,它的作用是实现两项相加。

class Sum:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def perform(self):
        return self.x + self.y

这个类的应用非常简单:

>>> s = Sum(2, 3)
>>> s.perform()
5

现在又创建了类 DoubleSum ,并且这个类继承了 Sum 类。在 DoubleSum 类中,有与父类相同的方法 perform ,但是它的返回值是两项和的 2 倍。这时候如果不断算写重复代码,而是依然要首先调用父类中的 perform 方法计算两项和,就可以用 super 方法实现:

class DoubleSum(Sum):
    def perform(self):
        parent_sum = super().perform()
        return 2 * parent_sum

这样做,不需要重复已经定义过的东西,比如 __init__ 方法,也不用重复写求和逻辑,只需要在继承父类的基础上调用父类方法即可。

>>> d = DoubleSum(3, 5)
>>> d.perform()
16

super 函数也不只用在类中,其他地方也可以使用,比如:

>>> super(int)
<super: <class 'int'>, NULL>
>>> super(int, int)
<super: <class 'int'>, <int object>>
>>> super(int, bool)
<super: <class 'int'>, <bool object>>

但说实话,我不明白这些有什么用。如果你知道,请在评论中告诉我,感谢。

property, classmethod and staticmethod :方法的装饰器

这三个是对类中方法的三个装饰器:

property:

当你想要在类中对属性通过 gettersetter 进行读写时,就要使用装饰器 @property 。在此情形下,可以通过 gettersetter 对读写过程给予干涉。

通过 property 装饰器,将属性转换为一组方法来实现的:一个方法在访问属性时运行,另一个方法尝试更改属性值时运行。

看一个例子,我们试图确保一个学生的 marks 属性总是被设置为正数,因为学习成绩不能是负数:

class Student:
    def __init__(self):
        self._marks = 0

    @property
    def marks(self):
        return self._marks

    @marks.setter
    def marks(self, new_value):
        # Doing validation
        if new_value < 0:
            raise ValueError('marks cannot be negative')

        # before actually setting the value.
        self._marks = new_value

运行这段代码:

>>> student = Student()
>>> student.marks
0
>>> student.marks = 85
>>> student.marks
85
>>> student.marks = -10
ValueError: marks cannot be negative

classmethod:

装饰器 @classmethod 用在方法上,使其成为类方法。这样一来,它就获得对类对象的引用,而不是对实例 (self)的引用。

比如,在类里面创建一个返回类名称的方法:

>>> class C:
...     @classmethod
...     def class_name(cls):
...         return cls.__name__
...
>>> x = C()
>>> x.class_name
'C'

staticmethod:

装饰器 @staticmethod 用于将方法转换为静态方法:相当于位于类中的函数,独立于任何类或对象属性。使用 @staticmethod 后的方法就不必在参数列表中的第一个位置使用 self 参数。

We could make one that does some data validation for example:

下面的应用中是利用 @staticmethod 进行数据验证:

class API:
    @staticmethod
    def is_valid_title(title_text):
        """Checks whether the string can be used as a blog title."""
        return title_text.istitle() and len(title_text) < 60

这些内置函数是使用一个非常高级的主题 descriptors(描述符)创建的。坦率地说,描述符是一个非常高级的话题,如果在这里试图涵盖它,没有任何用处,因为它只是和已告知的内容有所关联。

list, tuple, dict, set and frozenset :容器

Python 中的“容器”指的是一类数据结构,在这类数据结构中可以保存任意数量的成员。

Python 有5种基本容器类型:

list :有序的索引容器。每个元素都有一个特定的索引。列表是可变的,即:可以在任何时候添加或删除成员。

>>> my_list = [10, 20, 30]  # Creates a list with 3 items
>>> my_list[0]              # Indexes start with zero
10
>>> my_list[1]              # Indexes increase one by one
20
>>> my_list.append(40)      # Mutable: can add values
>>> my_list
[10, 20, 30, 40]
>>> my_list[0] = 50         # Can also reassign indexes
>>> my_list
[50, 20, 30, 40]

tuple :像列表一样有序和,但有一个关键区别: 它们是不可变的,这意味着一旦创建了元组对象,就不能添加或删除其中的成员。

>>> some_tuple = (1, 2, 3)
>>> some_tuple[0]              # Indexable
1
>>> some_tuple.append(4)       # But NOT mutable
AttributeError: ...
>>> some_tuple[0] = 5          # Cannot reassign an index as well
TypeError: ...

dict:以键值对为成员。

>>> flower_colors = {'roses': 'red', 'violets': 'blue'}
>>> flower_colors['violets']               # Use keys to access value
'blue'
>>> flower_colors['violets'] = 'purple'    # Mutable
>>> flower_colors
{'roses': 'red', 'violets': 'purple'}
>>> flower_colors['daffodil'] = 'yellow'   # Can also add new values
>>> flower_colors
{'roses': 'red', 'violets': 'purple', 'daffodil': 'yellow'}

set :是由无序的、唯一的成员组成。

>>> forest = ['cedar', 'bamboo', 'cedar', 'cedar', 'cedar', 'oak', 'bamboo']
>>> tree_types = set(forest)
>>> tree_types
{'bamboo', 'oak', 'cedar'}      # Only unique items
>>> 'oak' in tree_types
True
>>> tree_types.remove('oak')    # Sets are also mutable
>>> tree_types
{'bamboo', 'cedar'}

frozenset 与集合相同,但它是不可变的。

>>> forest = ['cedar', 'bamboo', 'cedar', 'cedar', 'cedar', 'oak', 'bamboo']
>>> tree_types = frozenset(forest)
>>> tree_types
frozenset({'bamboo', 'oak', 'cedar'})
>>> 'cedar' in tree_types
True
>>> tree_types.add('mahogany')           # CANNOT modify
AttributeError: ...

内置的 listtupledict 也可以用来创建这些数据结构的空实例:

>>> x = list()
>>> x
[]
>>> y = dict()
>>> y
{}