Fluent系列3

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fluent系列3相关的知识,希望对你有一定的参考价值。

参考技术A

标签(空格分隔): Python

Python variables are like reference variables in Java, so it’s better to think of them as labels attached to objects .

Example 8-2 proves that the righthand side of an assignment happens first. Example 8-2. Variables are assigned to objects only after the objects are created

Example 8-3. charles and lewis refer to the same object

Example 8-4. alex and charles compare equal, but alex is not charles

Every object has an identity, a type and a value. An object’s identity never changes once it has been created; you may think of it as the object’s address in memory. The is operator compares the identity of two objects; the id() function returns an integer representing its identity.

By far, the most common case is checking whether a variable is bound to None. This is the recommended way to do it:
x is None
And the proper way to write its negation is:
x is not None
The is operator is faster than ==, because it cannot be overloaded.

In contrast, a == b is syntactic sugar for a.__eq__(b) .

If the referenced items are mutable, they may change even if the tuple itself does not.

Example 8-5. t1 and t2 initially compare equal, but changing a mutable item inside tuple t1 makes it different.

The easiest way to copy a list (or most built-in mutable collections) is to use the builtin constructor for the type itself.

For lists and other mutable sequences, the shortcut l2 = l1[:] also makes a copy.

However, using the constructor or [:] produces a shallow copy (i.e., the outermost container is duplicated, but the copy is filled with references to the same items held by the original container).

I highly recommend watching the interactive animation for Example 8-6 at the Online Python Tutor .

Example 8-6. Making a shallow copy of a list containing another list; copy and paste this code to see it animated at the Online Python Tutor

The copy module provides the deepcopy and copy functions that return deep and shallow copies of arbitrary objects.

Example 8-8. Bus picks up and drops off passengers

Example 8-9. Effects of using copy versus deepcopy

Note that making deep copies is not a simple matter in the general case. Objects may have cyclic references that would cause a naïve algorithm to enter an infinite loop. The deepcopy function remembers the objects already copied to handle cyclic references gracefully.

Example 8-10. Cyclic references: b refers to a, and then is appended to a; deepcopy still manages to copy a

You can control the behavior of both copy and deepcopy by implementing the __copy__() and __deepcopy__() special methods.

The only mode of parameter passing in Python is call by sharing .

Example 8-11. A function may change any mutable object it receives.

Example 8-12. A simple class to illustrate the danger of a mutable default.

Example 8-13. Buses haunted by ghost passengers

The problem is that Bus instances that don’t get an initial passenger list end up sharing the same passenger list among themselves.

Strange things happen only when a HauntedBus starts empty, because then self.passengers becomes an alias for the default value of the passengers parameter.

The last bus example in this chapter shows how a TwilightBus breaks expectations by sharing its passenger list with its clients.

Example 8-15. A simple class to show the perils of mutating received arguments.

Example 8-14. Passengers disappear when dropped by a TwilightBus

The problem here is that the bus is aliasing the list that is passed to the constructor. Instead, it should keep its own passenger list .

The del statement deletes names, not objects. An object may be garbage collected as result of a del command, but only if the variable deleted holds the last reference to the object, or if the object becomes unreachable.

Every object-oriented language has at least one standard way of getting a string representation from any object . Python has two:

As you know, we implement the special methods __repr__ and __str__ to support repr() and str() .

There are two additional special methods to support alternative representations of objects: __bytes__ and __format__ . The __bytes__ method is analogous to __str__ : it’s called by bytes() to get the object represented as a byte sequence. Regarding __format__ , both the built-in function format() and the str.format() method call it to get string displays of objects using special formatting codes.

Example 9-2. vector2d_v0.py: methods so far are all special methods

Because we can export a Vector2d as bytes, naturally we need a method that imports a Vector2d from a binary sequence.

classmethod changes the way the method is called, so it receives the class itself as the first argument, instead of an instance. By convention, the first parameter of a class method should be named cls (but Python doesn’t care how it’s named).

In contrast, the staticmethod decorator changes a method so that it receives no special first argument. In essence, a static method is just like a plain function that happens to
live in a class body, instead of being defined at the module level.

Example 9-4. Comparing behaviors of classmethod and staticmethod.

The classmethod decorator is clearly useful, but I’ve never seen a compelling use case for staticmethod. If you want to define a function that does not interact with the class, just define it in the module.

The format() built-in function and the str.format() method delegate the actual formatting to each type by calling their .__format__(format_spec) method.

The format_spec is a formatting specifier, which is either:

The notation used in the formatting specifier is called the Format Specification Mini-Language .

If a class has no __format__ , the method inherited from object returns str(my_object). However, if you pass a format specifier, object.__format__ raises TypeError:

We will fix that by implementing our own format mini-language. To generate polar coordinates we already have the __abs__ method for the magnitude, and we’ll code a simple angle method using the math.atan2() function to get the angle.

With that, we can enhance our __format__ to produce polar coordinates.

As defined, so far our Vector2d instances are unhashable, so we can’t put them in a set.

To make a Vector2d hashable, we must implement __hash__ (__eq__ is also required, and we already have it). We also need to make vector instances immutable .

Right now, anyone can do v1.x = 7 and there is nothing in the code to suggest that changing a Vector2d is forbidden. This is the behavior we want:

Example 9-7. vector2d_v3.py: only the changes needed to make Vector2d immutable are shown here.

Now that our vectors are reasonably immutable, we can implement the __hash__ method. It should return an int and ideally take into account the hashes of the object attributes that are also used in the __eq__ method, because objects that compare equal should have the same hash.

With the addition of the **__hash__ method, we now have hashable vectors:

Our strategy to implement Vector will be to use composition, not inheritance. We’ll store the components in an array of floats, and will implement the methods needed for our Vector to behave like an immutable flat sequence.

Example 10-2. vector_v1.py: derived from vector2d_v1.py

The way I used reprlib.repr deserves some elaboration. That function produces safe representations of large or recursive structures by limiting the length of the output string and marking the cut with \'...\'.

Because of its role in debugging, calling repr() on an object should never raise an exception .

Example 10-1. Tests of Vector.__init__ and Vector.__repr__.

You don’t need to inherit from any special class to create a fully functional sequence type in Python ; you just need to implement the methods that fulfill the sequence protocol .

In the context of object-oriented programming, a protocol is an informal interface , defined only in documentation and not in code.

For example, the sequence protocol in Python entails just the __len__ and __getitem__ methods. All that
matters is that it provides the necessary methods.

Example 10-3. Code from Example 1-1, reproduced here for convenience.

The FrenchDeck class in Example 10-3 takes advantage of many Python facilities because it implements the sequence protocol , even if that is not declared anywhere in the code. Any experienced Python coder will look at it and understand that it is a sequence , even if it subclasses object. We say it is a sequence because it behaves like one, and that is what matters .

Because protocols are informal and unenforced, you can often get away with implementing just part of a protocol , if you know the specific context where a class will be used. For example, to support iteration, only __getitem__ is required; there is no need to provide __len__.

With these additions, all of these operations now work:

As you can see, even slicing is supported—but not very well. It would be better if a slice of a Vector was also a Vector instance and not a array . In the case of Vector, a lot of functionality is lost when slicing produces plain arrays.

Example 10-4. Checking out the behavior of __getitem__ and slices.

Example 10-5. Inspecting the attributes of the slice class.

Calling dir(slice) reveals an indices attribute, which turns out to be a very interesting but little-known method. Here is what help(slice.indices) reveals:

This method produces “normalized” tuples of nonnegative start, stop, and stride integers adjusted to fit within the bounds of a sequence of the given length.

Example 10-6. Part of vector_v2.py: __len__ and __getitem__ methods added to Vector class from vector_v1.py.

Excessive use of isinstance may be a sign of bad OO design , but handling slices in __getitem__ is a justified use case.

Example 10-7. Tests of enhanced Vector.getitem from Example 10-6.

In the evolution from Vector2d to Vector, we lost the ability to access vector components by name (e.g., v.x, v.y).

In Vector2d, we provided read-only access to x and y using the @property decorator . We could write four properties in Vector, but it would be tedious . The __getattr__ special method provides a better way .

The __getattr__ method is invoked by the interpreter when attribute lookup fails. In simple terms, given the expression my_obj.x, Python checks if the my_obj instance has an attribute named x; if not, the search goes to the class (my_obj.__class__), and then up the inheritance graph. If the x attribute is not found, then the __getattr__ method defined in the class of my_obj is called with self and the name of the attribute as a string (e.g., \'x\').

Example 10-8. Part of vector_v3.py: __getattr__ method added to Vector class from vector_v2.py.

Example 10-9. Inappropriate behavior: assigning to v.x raises no error, but introduces an inconsistency

To do that, we’ll implement __setattr__ as listed in Example 10-10.

Example 10-10. Part of vector_v3.py: __setattr__ method in Vector class.

The super() function provides a way to access methods of superclasses dynamically , a necessity in a dynamic language supporting multiple inheritance like Python. It’s used to delegate some task from a method in a subclass to a suitable method in a superclass.

Even without supporting writing to the Vector components, here is an important takeaway from this example: very often when you implement __getattr__ you need to code __setattr__ as well, to avoid inconsistent behavior in your objects .

If we wanted to allow changing components, we could implement __setitem__ to enable v[0] = 1.1 and/or __setattr__ to make v.x = 1.1 work. But Vector will remain immutable because we want to make it hashable in the coming section.

Once more we get to implement a __hash__ method. Together with the existing __eq__, this will make Vector instances hashable.

The reduce is not as popular as before, but computing the hash of all vector components is a perfect job for it.

When you call reduce(fn, lst), fn will be applied to the first pair of elements — fn(lst[0], lst[1]) — producing a first result, r1. Then fn is applied to r1 and the next element—fn(r1, lst[2])—producing a second result, r2. Now fn(r2, lst[3]) is called to produce r3 … and so on until the last element, when a single result, rN, is returned.

Example 10-12. Part of vector_v4.py: two imports and __hash__ method added to Vector class from vector_v3.py.

When using reduce, it’s good practice to provide the third argument , reduce(function, iterable, initializer), to prevent this exception: TypeError : reduce() of empty sequence with no initial value (excellent message: explains the problem and how to fix it). The initializer is the value returned if the sequence is empty and is used as the first argument in the reducing loop, so it should be the identity value of the operation .

The mapping step produces one hash for each component, and the reduce step aggregates all hashes with the xor operator. Using map instead of a genexp makes the mapping step even more visible:

In Python 3, map is lazy : it creates a generator that yields the results on demand, thus saving memory—just like the generator expression.

The zip built-in makes it easy to iterate in parallel over two or more iterables by returning tuples that you can unpack into variables, one for each item in the parallel inputs.

Example 10-15. The zip built-in at work

The enumerate built-in is another generator function often used in for loops to avoid manual handling of index variables.

The __format__ method of Vector will resemble that of Vector2d, but instead of providing a custom display in polar coordinates.

An abstract class represents an interface.

From the dynamic protocols that are the hallmark of duck typing to abstract base classes (ABCs) that make interfaces explicit and verify implementations for conformance.

An interface seen as a set of methods to fulfill a role is what Smalltalkers called a procotol , and the term spread to other dynamic language communities. Protocols are independent of inheritance. A class may implement several protocols, enabling its instances to fulfill several roles.

Protocols are interfaces , but because they are informal—defined only by documentation and conventions—protocols cannot be enforced like formal interfaces can. A protocol may be partially implemented in a particular class, and that’s OK.

Now, take a look at the Foo class. It does not inherit from abc.Se quence, and it only implements one method of the sequence protocol: __getitem__ (__len__ is missing).

Example 11-3. Partial sequence protocol implementation with __getitem__: enough for item access, iteration, and the in operator.

Given the importance of the sequence protocol, in the absence __iter__ and __contains__ Python still manages to make iteration and the in operator work by invoking __getitem__ .

The standard random.shuffle function is used like this:

However, if we try to shuffle a FrenchDeck instance, we get an exception. Example 11-5. random.shuffle cannot handle FrenchDeck.

The problem is that shuffle operates by swapping items inside the collection, and FrenchDeck only implements the immutable sequence protocol. Mutable sequences must also provide a __setitem__ method.

Because Python is dynamic, we can fix this at runtime, even at the interactive console.

Example 11-6. Monkey patching FrenchDeck to make it mutable and compatible with random.shuffle.

This is an example of monkey patching : changing a class or module at runtime, without touching the source code . Monkey patching is powerful, but the code that does the actual patching is very tightly coupled with the program to be patched, often handling private and undocumented parts.

Example 11-6 highlights that protocols are dynamic: random.shuffle doesn’t care what type of argument it gets, it only needs the object to implement part of the mutable sequence protocol .

Alex Martelli explains in a guest essay why ABCs were a great addition to Python .

Alex makes the point that inheriting from an ABC is more than implementing the required methods. In addition, the use of isinstance and issubclass becomes more acceptable to test
against ABCs.

However, even with ABCs, you should beware that excessive use of isinstance checks may be a code smell—a symptom of bad OO design . It’s usually not OK to have a chain of if/elif/elif with insinstance checks performing different actions depending on the type of an object.

For example, in several classes in this book, when I needed to take a sequence of items and process them as a list, instead of requiring a list argument by type checking, I simply took the argument and immediately built a list from it: that way I can accept any iterable, and if the argument is not iterable, the call will fail soon enough with a very clear message.

Example 11-7. Duck typing to handle a string or an iterable of strings.

Example 11-8. frenchdeck2.py: FrenchDeck2, a subclass of collections.MutableSequence.

To use ABCs well, you need to know what’s available.

Let’s review the clusters in Figure 11-3:

The numbers package defines the so-called “ numerical tower ” (i.e., this linear hierarchy of ABCs), where Number is the topmost superclass, Complex is its immediate subclass, and so on, down to Integral :

So if you need to check for an integer, use
isinstance(x, numbers.Integral)
to accept int, bool (which subclasses int) or other integer types that may be provided by external libraries that register their types with the numbers ABCs. And to satisfy your check, you or the users of your API may always register any compatible type as a virtual subclass of numbers.Integral.

Beazley and Jones’s Python Cookbook, 3rd Edition (O’Reilly) has a section about defining an ABC.

以上是关于Fluent系列3的主要内容,如果未能解决你的问题,请参考以下文章

EF 学习系列二 数据库表的创建和表关系配置(Fluent APIData Annotations约定)

用FLUENT对换热管进行流动与传热模拟之后,怎样得到传热系数、场协同角等一系列的数值

Fluent多文件处理 批量处理

EF Code First Fluent API指定外键属性

Kubernetes官方java客户端之八:fluent style

收集kubernetes控制台日志及元数据(fluent-bit+es+kibana搭建)