在阅读有关Stack
Overflow的Python答案时,我继续看到有人告诉用户直接使用数据模型的特殊方法或属性。
然后,我看到矛盾的建议(有时是来自我本人)说不要这样做,而是直接使用内置函数和运算符。
这是为什么?Python数据模型和内置函数的特殊“
dunder”方法与属性之间是什么关系?
我什么时候应该使用特殊名称?
因此,在数据模型的特殊方法和属性上,应尽可能使用内置函数和运算符。
内部语义上的API比公共接口更可能更改。尽管Python实际上并没有考虑任何“私有”并公开内部信息,但这并不意味着滥用该访问权是一个好主意。这样做存在以下风险:
内置函数和运算符调用特殊方法并在Python数据模型中使用特殊属性。它们是隐藏对象内部的可读且可维护的贴面。通常,用户应使用语言提供的内置函数和运算符,而不是调用特殊方法或直接使用特殊属性。
与更原始的数据模型特殊方法相比,内置函数和运算符还可以具有回退或更优雅的行为。例如:
next(obj, default)
允许您提供默认值,而不是StopIteration
在迭代器用尽时提高而不是提高obj.__next__()
。str(obj)
回退到obj.__repr__()
whenobj.__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 str
str(obj) obj.__str__() fb to __repr__ if no __str__
bytes(obj) obj.__bytes__() Python 3 only
unicode(obj) obj.__unicode__() Python 2 only
format(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 type
help(obj) obj.__doc__ help uses more than just __doc__
len(obj) obj.__len__() provides fb behavior for bool
iter(obj) obj.__iter__() fb to __getitem__ w/ indexes from 0 on
next(obj) obj.__next__() Python 3
next(obj) obj.next() Python 2
reversed(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 3
obj < other obj.__lt__(other) get >, >=, <= with @functools.total_ordering
complex(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 fails
obj.attr obj.__getattribute__('attr') preempts dotted lookup
obj.attr = _ obj.__setattr__('attr', _) preempts dotted lookup
del 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 wraps
def 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)
或not self.__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的地方使用特殊方法。
问题内容: 我对python级别的函数和常规函数(用定义)之间的差异感到好奇。(我知道对程序员有什么区别,以及何时使用每个程序员。) 如我们所见-python 知道 这是一个函数,并且是一个常规函数。这是为什么?它们 与python有 什么区别? 问题答案: 它们是同一类型,因此它们的处理方式相同: Python还知道将其定义为lambda函数,并将其设置为函数名称: 换句话说,它影响了该函数将获
问题内容: 我一直在阅读iBooks中的快速编程指南。有人可以向我解释函数和闭包之间的区别是什么。只是它没有名称并且可以在表达式中使用? 问题答案: 函数实际上只是命名为闭包。以下至少在概念上是等效的: 在使用声明方法的情况下,这变得有些复杂,例如,关于自动插入公共命名参数等,添加了一些有趣的糖,例如,变为`func myMethod(foo:Int, #bar:Int, 但是,即使方法只是闭包的
问题内容: OpenAI的强化学习的REINFORCE和actor-critic示例具有以下代码: 加强: 演员评论家: 一种正在使用,另一种正在使用。 据我所知,文档没有对它们之间进行任何明确的区分。 我很高兴知道这些功能之间的区别。 问题答案: 沿着 新的维度 连接张量序列。 在给 定维度上 连接给定序列张量的序列。 因此,如果和具有形状(3,4),则将具有形状(6,4),并将具有形状(2,3
问题内容: 我有以下功能 这段代码: 我的问题可能有点难以理解,所以请忍受:是什么使该代码段与常规调用完全区分开,或者是什么使该代码段需要引用函数变量而不是常规调用?() 我怎么知道应该在哪里引用该函数,以及什么时候该真正调用它? 问题答案: 好吧,该属性期望对函数的引用,以便在单击元素时执行该函数。通常是: 要么 (但是,当然,最好使用和) 请注意,它们都是如何引用函数而不是调用。 当某些东西需
本文向大家介绍纯函数和函数式编程有什么关系?相关面试题,主要包含被问及纯函数和函数式编程有什么关系?时的应答技巧和注意事项,需要的朋友参考一下 要实现函数式编程,我们所封装的方法应该是抽象的,应该是和外部状态无关系的,也就需要是纯函数的,这样才能保证抽象的方法可复用而且输出结果只决定于输入参数。
问题内容: Python模块和Python包之间有什么区别? 问题答案: 模块是单个文件(一个或多个文件),可在一个导入下导入并使用。例如 包是目录中提供包层次结构的模块的集合。