Что делает
blockquote>**
(двойная звезда) и*
(звезда) для параметровОни позволяют определять функции для принятия и для пользователей проходить любое число аргументов, positional (
*
) и ключевое слово (**
).Определяющие функции
*args
допускают любое количество необязательных позиционных аргументов (параметров), который будет присвоен кортежу с именемargs
.
**kwargs
допускает любое количество необязательных аргументов (параметров) ключевого слова, которые будут находиться в имени dictkwargs
.Вы можете (и должны) выбрать любое подходящее имя , но если целью является аргумент неспецифической семантики,
args
иkwargs
являются стандартными именами.Расширение, Передача любого количества аргументов
Вы также могут использовать
*args
и**kwargs
для передачи параметров из списков (или любого итеративного) и dicts (или любого сопоставления) соответственно.Функция, получающая параметры, не должна знать, что они расширяются.
Например, xrange Python 2 явно не ожидает
*args
, но поскольку в качестве аргументов он принимает 3 целых числа:>>> x = xrange(3) # create our *args - an iterable of 3 integers >>> xrange(*x) # expand here xrange(0, 2, 2)
В качестве другого примера мы можем использовать расширение dict в
str.format
:>>> foo = 'FOO' >>> bar = 'BAR' >>> 'this is foo, {foo} and bar, {bar}'.format(**locals()) 'this is foo, FOO and bar, BAR'
Новое в Python 3: Определение функций только с ключевыми аргументами
После [f0] ключевого слова можно использовать только - например, здесь
kwarg2
должен быть задан как аргумент ключевого слова - не позиционно:def foo(arg, kwarg=None, *args, kwarg2=None, **kwargs): return arg, kwarg, args, kwarg2, kwargs
Использование:
>>> foo(1,2,3,4,5,kwarg2='kwarg2', bar='bar', baz='baz') (1, 2, (3, 4, 5), 'kwarg2', {'bar': 'bar', 'baz': 'baz'})
Также можно использовать
*
def foo(arg, kwarg=None, *, kwarg2=None, **kwargs): return arg, kwarg, kwarg2, kwargs
Здесь
kwarg2
снова должен быть явно именованным аргументом ключевого слова:>>> foo(1,2,kwarg2='kwarg2', foo='foo', bar='bar') (1, 2, 'kwarg2', {'foo': 'foo', 'bar': 'bar'})
И мы больше не можем принимать неограниченные позиционные аргументы, потому что у нас нет
*args*
:>>> foo(1,2,3,4,5, kwarg2='kwarg2', foo='foo', bar='bar') Traceback (most recent call last): File "
", line 1, in TypeError: foo() takes from 1 to 2 positional arguments but 5 positional arguments (and 1 keyword-only argument) were given Опять же, здесь мы требуем, чтобы
kwarg
указывался по имени, а не по положению :def bar(*, kwarg=None): return kwarg
В этом примере мы видим, что если мы попытаемся передать
kwarg
позиционно, мы получим ошибку:>>> bar('kwarg') Traceback (most recent call last): File "
", line 1, in TypeError: bar() takes 0 positional arguments but 1 was given We должен явно передать параметр
kwarg
в качестве аргумента ключевого слова.>>> bar(kwarg='kwarg') 'kwarg'
Совместимые с Python демки
*args
(обычно называемые «star-args») и**kwargs
(звезды могут подразумеваться, говоря «kwargs», но быть явным с «двойными звездами») являются общими идиомами Python для использования нот*
и**
. Эти конкретные имена переменных не требуются (например, вы могли бы использовать*foos
и**bars
), но отклонение от конвенции может вызвать возмущение у ваших коллег-программистов Python.Обычно мы используем их, когда мы не знаем, что получит наша функция, или сколько аргументов мы можем передавать, а иногда даже при наименовании каждой переменной отдельно получилось бы очень грязное и избыточное (но это это случай, когда обычно явный лучше, чем неявный).
Пример 1
Следующая функция описывает, как их можно использовать и демонстрирует поведение. Обратите внимание, что именованный аргумент
b
будет потребляться вторым аргументом position before:def foo(a, b=10, *args, **kwargs): ''' this function takes required argument a, not required keyword argument b and any number of unknown positional arguments and keyword arguments after ''' print('a is a required argument, and its value is {0}'.format(a)) print('b not required, its default value is 10, actual value: {0}'.format(b)) # we can inspect the unknown arguments we were passed: # - args: print('args is of type {0} and length {1}'.format(type(args), len(args))) for arg in args: print('unknown arg: {0}'.format(arg)) # - kwargs: print('kwargs is of type {0} and length {1}'.format(type(kwargs), len(kwargs))) for kw, arg in kwargs.items(): print('unknown kwarg - kw: {0}, arg: {1}'.format(kw, arg)) # But we don't have to know anything about them # to pass them to other functions. print('Args or kwargs can be passed without knowing what they are.') # max can take two or more positional args: max(a, b, c...) print('e.g. max(a, b, *args) \n{0}'.format( max(a, b, *args))) kweg = 'dict({0})'.format( # named args same as unknown kwargs ', '.join('{k}={v}'.format(k=k, v=v) for k, v in sorted(kwargs.items()))) print('e.g. dict(**kwargs) (same as {kweg}) returns: \n{0}'.format( dict(**kwargs), kweg=kweg))
Мы можем проверить онлайн-справку для подписи функции с помощью
help(foo)
, которая сообщает намfoo(a, b=10, *args, **kwargs)
Назовем эту функцию с помощью
foo(1, 2, 3, 4, e=5, f=6, g=7)
, который печатает:
a is a required argument, and its value is 1 b not required, its default value is 10, actual value: 2 args is of type
and length 2 unknown arg: 3 unknown arg: 4 kwargs is of type and length 3 unknown kwarg - kw: e, arg: 5 unknown kwarg - kw: g, arg: 7 unknown kwarg - kw: f, arg: 6 Args or kwargs can be passed without knowing what they are. e.g. max(a, b, *args) 4 e.g. dict(**kwargs) (same as dict(e=5, f=6, g=7)) returns: {'e': 5, 'g': 7, 'f': 6} Пример 2
Мы также можем назвать это с помощью другого функция, в которую мы просто предоставляем
a
:def bar(a): b, c, d, e, f = 2, 3, 4, 5, 6 # dumping every local variable into foo as a keyword argument # by expanding the locals dict: foo(**locals())
bar(100)
prints:a is a required argument, and its value is 100 b not required, its default value is 10, actual value: 2 args is of type
and length 0 kwargs is of type and length 4 unknown kwarg - kw: c, arg: 3 unknown kwarg - kw: e, arg: 5 unknown kwarg - kw: d, arg: 4 unknown kwarg - kw: f, arg: 6 Args or kwargs can be passed without knowing what they are. e.g. max(a, b, *args) 100 e.g. dict(**kwargs) (same as dict(c=3, d=4, e=5, f=6)) returns: {'c': 3, 'e': 5, 'd': 4, 'f': 6} Пример 3: практическое использование в декораторах
Хорошо, так что, возможно, мы еще не видим утилиту. Итак, представьте, что у вас есть несколько функций с избыточным кодом до и / или после дифференцирующего кода. Следующие именованные функции являются просто псевдокодом для иллюстративных целей.
def foo(a, b, c, d=0, e=100): # imagine this is much more code than a simple function call preprocess() differentiating_process_foo(a,b,c,d,e) # imagine this is much more code than a simple function call postprocess() def bar(a, b, c=None, d=0, e=100, f=None): preprocess() differentiating_process_bar(a,b,c,d,e,f) postprocess() def baz(a, b, c, d, e, f): ... and so on
Мы могли бы обрабатывать это по-другому, но мы можем, конечно, извлечь избыточность с помощью декоратора, и поэтому наш приведенный ниже пример демонстрирует, как
*args
и**kwargs
могут быть очень полезными:def decorator(function): '''function to wrap other functions with a pre- and postprocess''' @functools.wraps(function) # applies module, name, and docstring to wrapper def wrapper(*args, **kwargs): # again, imagine this is complicated, but we only write it once! preprocess() function(*args, **kwargs) postprocess() return wrapper
И теперь каждая завернутая функция может быть написана гораздо более лаконично, поскольку мы учли избыточность:
@decorator def foo(a, b, c, d=0, e=100): differentiating_process_foo(a,b,c,d,e) @decorator def bar(a, b, c=None, d=0, e=100, f=None): differentiating_process_bar(a,b,c,d,e,f) @decorator def baz(a, b, c=None, d=0, e=100, f=None, g=None): differentiating_process_baz(a,b,c,d,e,f, g) @decorator def quux(a, b, c=None, d=0, e=100, f=None, g=None, h=None): differentiating_process_quux(a,b,c,d,e,f,g,h)
И, разглаживая наш код, который позволяет нам делать
*args
и**kwargs
, мы сокращаем строки кода, улучшаем читаемость и ремонтопригодность и имеем единственные канонические места для логики нашей программы. Если нам нужно изменить какую-либо часть этой структуры, у нас есть одно место, в которое можно внести каждое изменение.
Этот вопрос кажется таким же, как мой вопрос Как заполнить пустые пространства содержимым ниже изображения в android
Я нашел решение, используя библиотеку flowtext, найдите первый ответ это может помочь вам до сих пор
Я могу предложить более удобный конструктор для класса MyLeadingMarginSpan2
MyLeadingMarginSpan2(Context cc,int textSize,int height,int width) {
int pixelsInLine=(int) (textSize*cc.getResources().getDisplayMetrics().scaledDensity);
if (pixelsInLine>0 && height>0) {
this.lines=height/pixelsInLine;
} else {
this.lines=0;
}
this.margin=width;
}
text flow around image
для изображения, расположенного в правой части экрана? Ответ высоко оценен.
– Ramona
8 August 2017 в 13:30
Ответы Vorrtex и Ronen работают на меня, за исключением одной детали. После обтекания текста вокруг изображения был странный «отрицательный» край под изображением и на противоположной стороне. Я понял, что при установке диапазона на SpannableString я изменил
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
на
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, lines, 0);
, который остановил интервал после изображения. Может быть, не нужно во всех случаях, но думал, что я поделюсь.
Ответ vorrtex не сработал для меня, но я взял много от него и придумал свое решение. Вот он:
package ie.moses.keepitlocal.util;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.IntRange;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
import android.view.View;
import android.widget.TextView;
import ie.moses.keepitlocal.util.MeasurementUtils;
import ie.moses.keepitlocal.util.TextUtils;
import static com.google.common.base.Preconditions.checkArgument;
public class WrapViewSpan implements LeadingMarginSpan.LeadingMarginSpan2 {
private final Context _context;
private final int _lineCount;
private int _leadingMargin;
private int _padding;
public WrapViewSpan(View wrapeeView, TextView wrappingView) {
this(wrapeeView, wrappingView, 0);
}
/**
* @param padding Padding in DIP.
*/
public WrapViewSpan(View wrapeeView, TextView wrappingView, @IntRange(from = 0) int padding) {
_context = wrapeeView.getContext();
setPadding(padding);
int wrapeeHeight = wrapeeView.getHeight();
float lineHeight = TextUtils.getLineHeight(wrappingView);
int lineCnt = 0;
float linesHeight = 0F;
while ((linesHeight += lineHeight) <= wrapeeHeight) {
lineCnt++;
}
_lineCount = lineCnt;
_leadingMargin = wrapeeView.getWidth();
}
public void setPadding(@IntRange(from = 0) int paddingDp) {
checkArgument(paddingDp >= 0, "padding cannot be negative");
_padding = (int) MeasurementUtils.dpiToPixels(_context, paddingDp);
}
@Override
public int getLeadingMarginLineCount() {
return _lineCount;
}
@Override
public int getLeadingMargin(boolean first) {
if (first) {
return _leadingMargin + _padding;
} else {
return _padding;
}
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline,
int bottom, CharSequence text, int start, int end,
boolean first, Layout layout) {
}
}
и в моем фактическом классе, где используется диапазон:
ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
headerViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
String descriptionText = _descriptionView.getText().toString();
SpannableString spannableDescriptionText = new SpannableString(descriptionText);
LeadingMarginSpan wrapHeaderSpan = new WrapViewSpan(_headerView, _descriptionView, 12);
spannableDescriptionText.setSpan(
wrapHeaderSpan,
0,
spannableDescriptionText.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
_descriptionView.setText(spannableDescriptionText);
ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
headerViewTreeObserver.removeOnGlobalLayoutListener(this);
}
});
Мне нужен глобальный приемник макета, чтобы получить правильные значения для getWidth()
и getHeight()
.
Вот результат:
В настоящее время вы можете использовать библиотеку: https://github.com/deano2390/FlowTextView . Например:
<uk.co.deanwild.flowtextview.FlowTextView
android:id="@+id/ftv"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:padding="10dip"
android:src="@drawable/android"/>
</uk.co.deanwild.flowtextview.FlowTextView>
Теперь это возможно, но только для телефонов с версией выше или равной 2.2 с использованием интерфейса android.text.style.LeadingMarginSpan.LeadingMarginSpan2
, доступного в API 8.
Вот статья , но не на английском языке, но вы можете загрузить исходный код примера непосредственно из здесь .
Если вы хотите, чтобы ваше приложение совместимо со старыми устройствами, вы можете отобразить разная компоновка без плавающего текста. Вот пример:
Макет (по умолчанию для более старых версий будет изменен программно для более новых версий)
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/thumbnail_view"
android:src="@drawable/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView android:id="@+id/message_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/thumbnail_view"
android:textSize="18sp"
android:text="@string/text" />
</RelativeLayout>
Вспомогательный класс
class FlowTextHelper {
private static boolean mNewClassAvailable;
static {
if (Integer.parseInt(Build.VERSION.SDK) >= 8) { // Froyo 2.2, API level 8
mNewClassAvailable = true;
}
}
public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display){
// There is nothing I can do for older versions, so just return
if(!mNewClassAvailable) return;
// Get height and width of the image and height of the text line
thumbnailView.measure(display.getWidth(), display.getHeight());
int height = thumbnailView.getMeasuredHeight();
int width = thumbnailView.getMeasuredWidth();
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int)FloatMath.ceil(height / textLineHeight);
//For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
SpannableString ss = new SpannableString(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageView.setText(ss);
// Align the text with the image by removing the rule that the text is to the right of the image
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
int[]rules = params.getRules();
rules[RelativeLayout.RIGHT_OF] = 0;
}
}
Класс myLeadingMarginSpan2 (обновлен для поддержки API 21)
public class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
private int margin;
private int lines;
private boolean wasDrawCalled = false;
private int drawLineCount = 0;
public MyLeadingMarginSpan2(int lines, int margin) {
this.margin = margin;
this.lines = lines;
}
@Override
public int getLeadingMargin(boolean first) {
boolean isFirstMargin = first;
// a different algorithm for api 21+
if (Build.VERSION.SDK_INT >= 21) {
this.drawLineCount = this.wasDrawCalled ? this.drawLineCount + 1 : 0;
this.wasDrawCalled = false;
isFirstMargin = this.drawLineCount <= this.lines;
}
return isFirstMargin ? this.margin : 0;
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
this.wasDrawCalled = true;
}
@Override
public int getLeadingMarginLineCount() {
return this.lines;
}
}
Пример использования
ImageView thumbnailView = (ImageView) findViewById(R.id.thumbnail_view);
TextView messageView = (TextView) findViewById(R.id.message_view);
String text = getString(R.string.text);
Display display = getWindowManager().getDefaultDisplay();
FlowTextHelper.tryFlowText(text, thumbnailView, messageView, display);
Так выглядит приложение на устройстве Android 2.2: [/g2]
И это для устройства Android 2.1:
[/g3]
Html.fromHtml
не будет работать с каким-либо html, он поддерживает только простой html с несколькими тегами.
– vorrtex
14 December 2014 в 08:39
«Но я не уверен, что он подразумевает, выполнив мою собственную версию TextView?»
Он означает, что вы можете расширить класс android.widget.TextView (или Canvas или какой-либо другой визуализируемой поверхности) и реализовать свою собственную переопределяющую версию, которая позволяет встроенным изображениям с текстом, текущим вокруг них.
Это может быть довольно много работы в зависимости от того, насколько вы это делаете.
Это улучшение для FlowTextHelper (из ответа vorrtex). Я добавил возможность добавить дополнительное дополнение между текстом и изображением и улучшить расчет строки, чтобы также учитывать отступы. Наслаждайтесь!
public class FlowTextHelper {
private static boolean mNewClassAvailable;
/* class initialization fails when this throws an exception */
static {
try {
Class.forName("android.text.style.LeadingMarginSpan$LeadingMarginSpan2");
mNewClassAvailable = true;
} catch (Exception ex) {
mNewClassAvailable = false;
}
}
public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display, int addPadding){
// There is nothing I can do for older versions, so just return
if(!mNewClassAvailable) return;
// Get height and width of the image and height of the text line
thumbnailView.measure(display.getWidth(), display.getHeight());
int height = thumbnailView.getMeasuredHeight();
int width = thumbnailView.getMeasuredWidth() + addPadding;
messageView.measure(width, height); //to allow getTotalPaddingTop
int padding = messageView.getTotalPaddingTop();
float textLineHeight = messageView.getPaint().getTextSize();
// Set the span according to the number of lines and width of the image
int lines = (int)Math.round((height - padding) / textLineHeight);
SpannableString ss = new SpannableString(text);
//For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
messageView.setText(ss);
// Align the text with the image by removing the rule that the text is to the right of the image
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
int[]rules = params.getRules();
rules[RelativeLayout.RIGHT_OF] = 0;
}
}