栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

Python数据模型和内置函数之间有什么关系?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python数据模型和内置函数之间有什么关系?

Python数据模型和内置函数之间有什么关系?

  • 内建函数和运算符使用基础数据模型方法或属性。
  • 内建函数和运算符的行为更加优雅,并且通常更向前兼容。
  • 数据模型的特殊方法是语义上非公共的接口。
  • 内置函数和语言运算符专门用作通过特殊方法实现的行为的用户界面。

因此,在数据模型的特殊方法和属性上,应尽可能使用内置函数和运算符。

内部语义上的API比公共接口更可能更改。尽管Python实际上并没有考虑任何“私有”并公开内部信息,但这并不意味着滥用该访问权是一个好主意。这样做存在以下风险:

  • 在升级Python可执行文件或切换到其他Python实现(例如PyPy,IronPython或Jython或某些其他无法预料的实现)时,您可能会发现更多重大更改。
  • 您的同事可能对您的语言技能和尽责程度考虑不佳,并认为这是一种代码嗅觉,使您和您的其余代码受到更严格的审查。
  • 内置函数易于拦截行为。使用特殊方法直接限制了Python用于自省和调试的功能。

深入

内置函数和运算符调用特殊方法并在Python数据模型中使用特殊属性。它们是隐藏对象内部的可读且可维护的贴面。通常,用户应使用语言提供的内置函数和运算符,而不是调用特殊方法或直接使用特殊属性。

与更原始的数据模型特殊方法相比,内置函数和运算符还可以具有回退或更优雅的行为。例如:

  • next(obj, default)
    允许您提供默认值,而不是
    StopIteration
    在迭代器用尽时提高而不是提高
    obj.__next__()
  • str(obj)
    回退到
    obj.__repr__()
    when
    obj.__str__()
    不可用的时间-而
    obj.__str__()
    直接调用会引发属性错误。
  • obj != other``not obj == other
    在没有时回退到Python 3中
    __ne__
    -调用
    obj.__ne__(other)
    不会利用这一点。

(如果有必要或需要,还可以在模块的全局范围或

builtins
模块上轻松覆盖内置函数,以进一步自定义行为。)

将内建函数和运算符映射到数据模型

这是内建函数和操作符到它们使用或返回的特殊方法和属性的映射,并带有注释-
请注意,通常的规则是内建函数通常映射到同名的特殊方法,但这不一致,不足以保证在下面提供此地图:

builtins/     special methods/operators  -> datamodel    NOTES (fb == fallback)repr(obj)     obj.__repr__()          provides fb behavior for strstr(obj)      obj.__str__()fb to __repr__ if no __str__bytes(obj)    obj.__bytes__()         Python 3 onlyunipre(obj)  obj.__unipre__()       Python 2 onlyformat(obj)   obj.__format__()        format spec optional.hash(obj)     obj.__hash__()bool(obj)     obj.__bool__()          Python 3, fb to __len__bool(obj)     obj.__nonzero__()       Python 2, fb to __len__dir(obj)      obj.__dir__()vars(obj)     obj.__dict__ does not include __slots__type(obj)     obj.__class__type actually bypasses __class__ -     overriding __class__ will not affect typehelp(obj)     obj.__doc__  help uses more than just __doc__len(obj)      obj.__len__()provides fb behavior for booliter(obj)     obj.__iter__()          fb to __getitem__ w/ indexes from 0 onnext(obj)     obj.__next__()          Python 3next(obj)     obj.next()   Python 2reversed(obj) obj.__reversed__()      fb to __len__ and __getitem__other in obj  obj.__contains__(other) fb to __iter__ then __getitem__obj == other  obj.__eq__(other)obj != other  obj.__ne__(other)       fb to not obj.__eq__(other) in Python 3obj < other   obj.__lt__(other)       get >, >=, <= with @functools.total_orderingcomplex(obj)  obj.__complex__()int(obj)      obj.__int__()float(obj)    obj.__float__()round(obj)    obj.__round__()abs(obj)      obj.__abs__()

如果未实现,则该

operator
模块具有
length_hint
通过相应的特殊方法实现的后备广告
__len__

length_hint(obj)  obj.__length_hint__()

虚线查询

虚线查找是上下文相关的。在没有特殊方法实现的情况下,首先在类层次结构中查找数据描述符(例如属性和插槽),然后在实例中

__dict__
查找(例如变量),然后在类层次结构中查找非数据描述符(例如方法)。特殊方法实现以下行为:

obj.attr      obj.__getattr__('attr')       provides fb if dotted lookup failsobj.attr      obj.__getattribute__('attr')  preempts dotted lookupobj.attr = _  obj.__setattr__('attr', _)    preempts dotted lookupdel obj.attr  obj.__delattr__('attr')       preempts dotted lookup

描述符

描述符有点先进-可以跳过这些条目并稍后再返回-
回顾描述符实例在类层次结构中(如方法,插槽和属性)。数据描述符实现

__set__
__delete__

obj.attr        descriptor.__get__(obj, type(obj)) obj.attr = val  descriptor.__set__(obj, val)del obj.attr    descriptor.__delete__(obj)

实例化(定义)该类时,

__set_name__
如果有任何描述符将其属性名称告知描述符,则将调用以下描述符方法。(这是Python
3.6中的新功能。)
cls
type(obj)
上面相同,
'attr'
代表属性名称:

class cls:    @descriptor_type    def attr(self): pass # -> descriptor.__set_name__(cls, 'attr')

项目(下标符号)

下标符号也与上下文相关:

obj[name]         -> obj.__getitem__(name)obj[name] = item  -> obj.__setitem__(name, item)del obj[name]     -> obj.__delitem__(name)

如果找不到键

dict
__missing__
则调用的子类的特殊情况
__getitem__

obj[name]         -> obj.__missing__(name)

经营者

还有一些特殊的

+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |
操作员方法,例如:

obj + other   ->  obj.__add__(other), fallback to other.__radd__(obj)obj | other   ->  obj.__or__(other), fallback to other.__ror__(obj)

以及用于扩展分配的就地运算符

+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=
,例如:

obj += other  ->  obj.__iadd__(other)obj |= other  ->  obj.__ior__(other)

(如果未定义这些就地运营商,Python的回落到,例如,用于

obj += other
obj = obj + other

一元运算:

+obj          ->  obj.__pos__()-obj          ->  obj.__neg__()~obj          ->  obj.__invert__()

内容管理员

上下文管理器定义了

__enter__
,在进入代码块时会调用,(它的返回值通常是self,以别名
as
),并且
__exit__
保证在离开代码块时会调用,并带有异常信息。

with obj as enters_return_value: #->  enters_return_value = obj.__enter__()    raise Exception('message')#->  obj.__exit__(Exception, #->    Exception('message'), #->    traceback_object)

如果

__exit__
获取异常,然后返回一个假值,它将在离开方法时重新引发。

如果没有异常,则改为

__exit__
获取
None
这三个参数,并且返回值无意义:

with obj:#->  obj.__enter__()    pass         #->  obj.__exit__(None, None, None)

一些元类特殊方法

类似地,类可以具有支持抽象基类的特殊方法(来自其元类):

isinstance(obj, cls) -> cls.__instancecheck__(obj)issubclass(sub, cls) -> cls.__subclasscheck__(sub)

一个重要的收获是,尽管Python 2和3之间的内建函数如

next
bool
不变,但底层实现名称 却在 变化。

因此,使用内置函数还可以提供更多的向前兼容性。

我什么时候应该使用特殊名称?

在Python中,以下划线开头的名称在语义上是用户的非公共名称。下划线是创作者说的“放手,不要碰”的方式。

这不仅是文化上的,而且在Python对API的处理中也是如此。当程序包

__init__.py
用于
import*
从子程序包提供API时,如果子程序包不提供
__all__
,则它会排除以下划线开头的名称。子包的内容
__name__
也将被排除在外。

IDE自动补全工具在考虑以下划线开头的名称是非公开的名称时会混合使用。然而,我非常感谢没有看到

__init__
__new__
__repr__
__str__
__eq__
,等。(也没有任何用户的创建的非公共接口)当我输入一个对象和一个周期的名称。

因此,我断言:

特殊的“笨拙”方法不是公共接口的一部分。 避免直接使用它们。

那么什么时候使用它们呢?

主要用例是实现自己的自定义对象或内置对象的子类时。

仅在绝对必要时尝试使用它们。这里有些例子:

__name__
在函数或类上使用特殊属性

装饰一个函数时,通常会得到一个包装函数,以隐藏有关该函数的有用信息。我们将使用

@wraps(fn)
装饰器来确保不会丢失该信息,但是如果我们需要函数的名称,则需要
__name__
直接使用属性:

from functools import wrapsdef decorate(fn):     @wraps(fn)    def decorated(*args, **kwargs):        print('calling fn,', fn.__name__) # exception to the rule        return fn(*args, **kwargs)    return decorated

类似地,当我需要一个方法中的对象类的名称时(例如,用于a

__repr__
),我将执行以下操作:

def get_class_name(self):    return type(self).__name__          # ^          # ^- must use __name__, no builtin e.g. name()          # use type, not .__class__

使用特殊属性编写自定义类或子类内置函数

当我们想要定义自定义行为时,必须使用数据模型名称。

这是有道理的,因为我们是实现者,所以这些属性不是我们专有的。

class Foo(object):    # required to here to implement == for instances:    def __eq__(self, other):   # but we still use == for the values:        return self.value == other.value    # required to here to implement != for instances:    def __ne__(self, other): # docs recommend for Python 2.        # use the higher level of abstraction here:        return not self == other

但是,即使在这种情况下,我们也不要使用

self.value.__eq__(other.value)
notself.__eq__(other)
(请参阅此处的答案以证明后者可能导致意外行为。)相反,我们应该使用更高级别的抽象。

我们需要使用特殊方法名称的另一点是当我们在孩子的实现中并想委托给父方法时。例如:

class NoisyFoo(Foo):    def __eq__(self, other):        print('checking for equality')        # required here to call the parent's method        return super(NoisyFoo, self).__eq__(other)

结论

特殊方法允许用户为对象内部实现接口。

尽可能使用内置的函数和运算符。仅在没有书面公开API的地方使用特殊方法。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/634383.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号